網上檢視現有的一些實現gateway灰度釋出的部落格,一般都是使用過濾器、攔截器等,過於複雜,而且不夠靈活,索性自己研究一下gateway,發現可以透過 AbstractLoadBalancerRule 實現,下面是我實現的一套靈活一些的灰度釋出策略。
首先交代下環境:jdk8、SpringBoot 2.3.6.RELEASE、SpringCloud Hoxton.SR9、AlibabaCloud 2.2.3 RELEASE。
gateway的配置檔案如下(可以配置到nacos中):
# 服務灰度釋出配置server: gray: config: # 需要管理釋出的服務server-id,當前為 producer 服務 producer: # 需要灰度釋出的版本 version: 20210423 # 請求進入灰度服務的機率 0~1,為 0 表示不進入灰度服務,1 則完全灰度 rate: 0 spring: cloud: gateway: routes: - id: producer_server uri: lb://producer predicates: - Path=/producer/** filters: - StripPrefix=0
配置類:
package com.study.gateway.config; import com.alibaba.nacos.common.utils.MapUtils;import lombok.Data;import lombok.Setter;import org.apache.commons.lang3.StringUtils;import org.springframework.boot.context.properties.ConfigurationProperties;import org.springframework.stereotype.Component; import java.util.Map; /** * @author zhaochao * @date 2021/4/23 19:44 * @desc 灰度釋出服務配置 */@Component@ConfigurationProperties(prefix = "server.gray")@Setterpublic class ServerGrayProperty { private Integer maxTryTimes; private Map<String, Config> config; public Config getConfig(String name) { if (MapUtils.isEmpty(config)) { return null; } return config.get(name); } @Data public static class Config { private String version; private Double rate; } public Integer getMaxTryTimes() { return maxTryTimes == null || maxTryTimes <= 0 ? 10 : maxTryTimes; }}
實現灰度釋出的關鍵Configuration:
package com.study.gateway.config; import com.alibaba.cloud.nacos.ribbon.NacosServer;import com.alibaba.nacos.api.naming.pojo.Instance;import com.alibaba.nacos.common.utils.MapUtils;import com.netflix.client.config.IClientConfig;import com.netflix.loadbalancer.BaseLoadBalancer;import com.netflix.loadbalancer.ClientConfigEnabledRoundRobinRule;import com.netflix.loadbalancer.ILoadBalancer;import com.netflix.loadbalancer.Server;import lombok.extern.slf4j.Slf4j;import org.apache.commons.lang3.StringUtils;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.context.annotation.Configuration;import org.springframework.context.annotation.Scope;import org.springframework.util.CollectionUtils; import java.util.List;import java.util.Map;import java.util.Objects;import java.util.concurrent.atomic.LongAdder;import java.util.stream.Collectors; /** * @author zhaochao * @date 2021/4/24 10:26 * @desc 自定義負載均衡策略 服務灰度釋出配置 */@Slf4j@Scope("prototype")@Configurationpublic class ServerGrayRule extends ClientConfigEnabledRoundRobinRule { private static final LongAdder GRAY_TIMES = new LongAdder(); @Autowired private ServerGrayProperty serverGrayProperty; @Override public void initWithNiwsConfig(IClientConfig clientConfig) { } @Override public Server choose(Object o) { ILoadBalancer lb = this.getLoadBalancer(); if (!(lb instanceof BaseLoadBalancer)) { return super.choose(o); } Server server = null; int count = 0; int max = serverGrayProperty.getMaxTryTimes(); while (server == null && count++ < max) { if (Thread.interrupted()) { return null; } int allSize = ((BaseLoadBalancer) lb).getServerCount(false); if (allSize == 0) { return null; } // 灰度獲取服務 server = getServer((BaseLoadBalancer) lb, o); if (server == null) { Thread.yield(); continue; } if (server.isAlive() && server.isReadyToServe()) { return server; } server = null; Thread.yield(); } if (count >= max) { log.info("No available alive servers after " + max + " tries from load balancer: " + lb); } return server; } private Server getServer(BaseLoadBalancer lb, Object o) { String name = lb.getName(); ServerGrayProperty.Config config = serverGrayProperty.getConfig(name); if (Objects.isNull(config)) { return super.choose(o); } // 獲取指定的灰度版本 String targetVersion = config.getVersion(); if (StringUtils.isBlank(targetVersion)) { return super.choose(o); } // 判斷是否需要灰度 boolean needGray = isNeedGray(config.getRate()); // 不需要灰度 if (!needGray) { return super.choose(o); } // 需要灰度 List<Server> servers = lb.getAllServers(); List<Server> grayServers = servers.stream().filter(s -> { if (!(s instanceof NacosServer)) { return false; } Instance instance = ((NacosServer) s).getInstance(); if (instance == null) { return false; } Map<String, String> metadata = instance.getMetadata(); if (MapUtils.isEmpty(metadata)) { return false; } String version = metadata.get("version"); // 過濾指定的灰度版本 if (!Objects.equals(targetVersion, version)) { return false; } return true; }).collect(Collectors.toList()); if (CollectionUtils.isEmpty(grayServers)) { log.error("No gray server to return"); return null; } // 輪詢獲取灰度服務索引 int index = getIndex(grayServers.size()); return grayServers.get(index); } private int getIndex(int size) { if (size <= 1) { return 0; } GRAY_TIMES.increment(); return GRAY_TIMES.intValue() % size; } /** * 判斷當前是否需要返回灰度服務 * * @param rate * @return */ private boolean isNeedGray(Double rate) { // 未配置透過率或小於等於0,不需要灰度 不處理 if (Objects.isNull(rate) || rate <= 0) { return false; } // 完全灰度 if (Objects.equals(rate, 1.0)) { return true; } return Math.random() <= rate; } }
測試服務producer的配置:
# 服務的元資料spring.cloud.nacos.discovery.metadata.version=2021030V1
在nacos中修改gateway配置中的 version 和 rate 結合 producer服務的元資料,可以動態地實現灰度釋出
nacos
這樣就實現了灰度釋出。
可以繼續進行拓展,結合執行機制及nacos資料儲存結構等實現更為靈活的釋出功能,不僅僅是灰度而已!
最新評論