上一篇介紹了,ribbon的組件。本篇要本身寫一個灰度方案。其實就是一個很簡單的思惟擴散。前端
前端header請求攜帶version字段。路由服務根據version去須要對應版本的服務集合,進行或輪詢或hash或權重的負載。請求路由到服務上,若是還要調用下游服務,也按照version規則去路由下游服務器。前端未攜帶版本按照後端服務最高version版本進行路由。git
分析若是本身動手寫一個灰度方案。須要考慮的因素有幾點?github
解決方案:spring
來寫一下網關層的實現。
gateway負載規則有一個攔截器segmentfault
建立負載規則的類信息GrayscaleProperties後端
public class GrayscaleProperties implements Serializable { private String version; private String serverName; private String serverGroup; private String active; private double weight = 1.0D; }
由於gateway的特殊性LoadBalancerClientFilter過濾器主要解析lb:// 爲前綴的路由規則,在經過LoadBalancerClient#choose(String) 方法獲取到須要的服務實例,從而實現負載均衡。在這裏咱們要寫本身的負載均衡就須要從新須要重寫LoadBalancerClientFilter 過濾器
LoadBalancerClientFilter 介紹:次過濾器做用在url以lb開頭的路由,而後利用loadBalancer來獲取服務實例,構造目標requestUrl,設置到GATEWAY_REQUEST_URL_ATTR屬性中,供NettyRoutingFilter使用。服務器
GatewayLoadBalancerClientAutoConfiguration 在初始化會檢測@ConditionalOnBean(LoadBalancerClient.class) 是否存在,若是存在就會加載LoadBalancerClientFilter負載過濾器負載均衡
如下是源碼dom
@Override public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) { URI url = exchange.getAttribute(GATEWAY_REQUEST_URL_ATTR); String schemePrefix = exchange.getAttribute(GATEWAY_SCHEME_PREFIX_ATTR); //判斷url 前綴 如不是lb開頭的就進行下一個過濾器 if (url == null || (!"lb".equals(url.getScheme()) && !"lb".equals(schemePrefix))) { return chain.filter(exchange); } //根據網關的原始網址。替換exchange url爲 http://IP:PORT/path 路徑的url //preserve the original url addOriginalRequestUrl(exchange, url); log.trace("LoadBalancerClientFilter url before: " + url); // 這裏呢會進行調用真正的負載均衡 final ServiceInstance instance = choose(exchange); if (instance == null) { String msg = "Unable to find instance for " + url.getHost(); if(properties.isUse404()) { throw new FourOFourNotFoundException(msg); } throw new NotFoundException(msg); } URI uri = exchange.getRequest().getURI(); // if the `lb:<scheme>` mechanism was used, use `<scheme>` as the default, // if the loadbalancer doesn't provide one. String overrideScheme = instance.isSecure() ? "https" : "http"; if (schemePrefix != null) { overrideScheme = url.getScheme(); } URI requestUrl = loadBalancer.reconstructURI(new DelegatingServiceInstance(instance, overrideScheme), uri); log.trace("LoadBalancerClientFilter url chosen: " + requestUrl); exchange.getAttributes().put(GATEWAY_REQUEST_URL_ATTR, requestUrl); return chain.filter(exchange); } 。。。。 // 由於注入了ribbon 會使用ribbon 進行負載均衡規則進行負載 protected Server getServer(ILoadBalancer loadBalancer, Object hint) { return loadBalancer == null ? null : loadBalancer.chooseServer(hint != null ? hint : "default"); }
若是單單定製了 IRule 的實現類 Server choose(Object key) 方法裏面的 key值就是一個默認值。就不知道轉發到那個服務。因此要進行重寫LoadBalancerClientFilter 這個類的 protected ServiceInstance choose(ServerWebExchange exchange) 進行key的賦值操做ide
public class GatewayLoadBalancerClientFilter extends LoadBalancerClientFilter { public GatewayLoadBalancerClientFilter(LoadBalancerClient loadBalancer, LoadBalancerProperties properties) { super(loadBalancer, properties); } @Override protected ServiceInstance choose(ServerWebExchange exchange) { if (this.loadBalancer instanceof RibbonLoadBalancerClient) { RibbonLoadBalancerClient client = (RibbonLoadBalancerClient) this.loadBalancer; HttpHeaders headers = exchange.getRequest().getHeaders(); String version = headers.getFirst( GrayscaleConstant.GRAYSCALE_VERSION ); String serviceId = ((URI) exchange.getAttribute(GATEWAY_REQUEST_URL_ATTR)).getHost(); GrayscaleProperties build = GrayscaleProperties.builder().version( version ).serverName( serviceId ).build(); //這裏使用服務ID 和 version 作爲選擇服務實例的key //TODO 這裏也能夠根據實際業務狀況作本身的對象封裝 return client.choose(serviceId,build); } return super.choose(exchange); } }
自定義gateway灰度負載規則
@Slf4j public class GrayscaleLoadBalancerRule extends AbstractLoadBalancerRule { @Autowired private NacosDiscoveryProperties nacosDiscoveryProperties; @Override public void initWithNiwsConfig(IClientConfig iClientConfig) { //留空 } /** * gateway 特殊性。須要設置key值內容知道你要轉發的服務名稱 key已經在filter內設置了key值。 * @param key * @return */ @Override public Server choose(Object key) { try { GrayscaleProperties grayscale = (GrayscaleProperties) key; String version = grayscale.getVersion(); String clusterName = this.nacosDiscoveryProperties.getClusterName(); NamingService namingService = this.nacosDiscoveryProperties.namingServiceInstance(); List<Instance> instances = namingService.selectInstances(grayscale.getServerName(), true); if (CollectionUtils.isEmpty(instances)) { log.warn("no instance in service {}", grayscale.getServerName()); return null; } else { List<Instance> instancesToChoose = buildVersion(instances,version); //進行cluster-name分組篩選 // TODO 思考若是cluster-name 節點所有掛掉。是否是能夠請求其餘的分組的服務?能夠根據狀況在定製一份規則出來 if (StringUtils.isNotBlank(clusterName)) { List<Instance> sameClusterInstances = (List)instancesToChoose.stream().filter((instancex) -> { return Objects.equals(clusterName, instancex.getClusterName()); }).collect(Collectors.toList()); if (!CollectionUtils.isEmpty(sameClusterInstances)) { instancesToChoose = sameClusterInstances; } else { log.warn("A cross-cluster call occurs,name = {}, clusterName = {}, instance = {}", new Object[]{grayscale.getServerName(), clusterName, instances}); } } //按nacos權重獲取。這個是NacosRule的代碼copy 過來 沒有本身實現權重隨機。這個權重是nacos控制檯服務的權重設置 // 若是業務上有本身特殊的業務。能夠本身定製規則,黑白名單,用戶是不是灰度用戶,測試帳號。等等一些自定義設置 Instance instance = ExtendBalancer.getHostByRandomWeight2(instancesToChoose); return new NacosServer(instance); } } catch (Exception var9) { log.warn("NacosRule error", var9); return null; } } }
以上就是gateway的定製負載規則。
啓動三個cloud-discovery-client服務
對應版本一、二、3
而後postman進行接口請求 http://localhost:9000/client/client/user/service/save header 裏面添加 version 字段。分別請求對應的版本服務。
gateway 路由所有請求到了對應版本的路由服務上。
其實和gateway 原理同樣,只不過少了gateway 攔截器這一層。
建立本身的AbstractGrayscalLoadBalancerRule 繼承AbstractLoadBalancerRule 抽象類,這個抽象類封裝了一些咱們須要用到的方法。
/** * @Author: xlr * @Date: Created in 1:03 PM 2019/11/24 */ @Slf4j @Data public abstract class AbstractGrayscalLoadBalancerRule extends AbstractLoadBalancerRule { /** * asc 正序 反之desc 倒敘 */ protected boolean asc = true; /** * 篩選想要的值 * @param instances * @param version * @return */ protected List <Instance> buildVersion(List<Instance> instances,String version){ //進行按版本分組排序 Map<String,List<Instance>> versionMap = getInstanceByScreen(instances); if(versionMap.isEmpty()){ log.warn("no instance in service {}", version); } //若是version 未傳值使用最低版本服務 if(StringUtils.isBlank( version )){ if(isAsc()){ version = getFirst( versionMap.keySet() ); }else { version = getLast( versionMap.keySet() ); } } List <Instance> instanceList = versionMap.get( version ); return instanceList; } /** * 根據version 組裝一個map key value 對應 version List<Instance> * @param instances * @return */ protected Map<String,List<Instance>> getInstanceByScreen(List<Instance> instances){ Map<String,List<Instance>> versionMap = new HashMap<>( instances.size() ); instances.stream().forEach( instance -> { String version = instance.getMetadata().get( GrayscaleConstant.GRAYSCALE_VERSION ); List <Instance> versions = versionMap.get( version ); if(versions == null){ versions = new ArrayList<>( ); } versions.add( instance ); versionMap.put( version,versions ); } ); return versionMap; } /** * 獲取第一個值 * @param keys * @return */ protected String getFirst(Set<String> keys){ List <String> list = sortVersion( keys ); return list.get( 0 ); } /** * 獲取最後一個值 * @param keys * @return */ protected String getLast(Set <String> keys){ List <String> list = sortVersion( keys ); return list.get( list.size()-1 ); } /** * 根據版本排序 * @param keys * @return */ protected List<String > sortVersion(Set <String> keys){ List<String > list = new ArrayList <>( keys ); Collections.sort(list); return list; } }
建立實現類GrayscaleLoadBalancerRule 繼承本身定義的抽象類AbstractGrayscalLoadBalancerRule
/** * fegin 負載均衡。在獲取到咱們想設置的對象以後,咱們還能夠設置 服務、用戶、角色等各個維度的黑白名單,限制、轉發、等策略,具體的使用場景還得須要結合工做中的實際使用場景。 * 這裏只是提供一個簡單的思路。但願看到這個註釋的人。可以有觸類旁通的能力,定製本身的規則。 * @Author: xlr * @Date: Created in 12:19 PM 2019/11/24 */ @Slf4j public class GrayscaleLoadBalancerRule extends AbstractGrayscalLoadBalancerRule { @Autowired private NacosDiscoveryProperties nacosDiscoveryProperties; @Override public void initWithNiwsConfig(IClientConfig iClientConfig) { //留空 } /** * gateway 特殊性。須要設置key值內容知道你要轉發的服務名稱。 * @param key * @return */ @Override public Server choose(Object key) { log.info("GrayscaleLoadBalancerRule 執行 choose方法 ,參數 key: {}",key); try { String clusterName = this.nacosDiscoveryProperties.getClusterName(); DynamicServerListLoadBalancer loadBalancer = (DynamicServerListLoadBalancer)this.getLoadBalancer(); String name = loadBalancer.getName(); NamingService namingService = this.nacosDiscoveryProperties.namingServiceInstance(); List<Instance> instances = namingService.selectInstances(name, true); if (CollectionUtils.isEmpty(instances)) { log.warn("no instance in service {}", name); return null; } else { List<Instance> instancesToChoose = null; String version = (String) ThreadLocalUtils.getKey( GrayscaleConstant.GRAYSCALE_VERSION ); List <Instance> instanceList = buildVersion( instances,version ); if (StringUtils.isNotBlank(clusterName)) { List<Instance> sameClusterInstances = (List)instanceList.stream().filter((instancex) -> { return Objects.equals(clusterName, instancex.getClusterName()); }).collect(Collectors.toList()); if (!CollectionUtils.isEmpty(sameClusterInstances)) { instancesToChoose = sameClusterInstances; } else { log.warn("A cross-cluster call occurs,name = {}, clusterName = {}, instance = {}", new Object[]{name, clusterName, instanceList}); } } Instance instance = ExtendBalancer.getHostByRandomWeight2(instancesToChoose); return new NacosServer(instance); } } catch (Exception var9) { log.warn("NacosRule error", var9); return null; } } }
分別在client、server的啓動類上,聲明自定義的IRule
@Bean IRule rule(){ return new GrayscaleLoadBalancerRule(); }
在啓動三個server服務進行負載均衡。繼續的測試效果。就不在貼圖了。有興趣的小夥伴們能夠本身嘗試寫一下。
這裏在多說一點,注意bean對象父子上下文。若是有沒接觸過這個的能夠度娘一下這個知識點。
企業定製路由規則,在根據gateway提供的謂詞、斷言、過濾器這幾個要素組合, 定製企業本身想要的路由規則。到此時這樣gateway纔是企業真正想要的路由功能。
摘自參考 spring cloud 官方文檔
往期地址 spring cloud alibaba 地址
Spring Cloud Alibaba (nacos 註冊中心搭建)
Spring Cloud Alibaba 使用nacos 註冊中心
Spring Cloud Alibaba nacos 配置中心使用
Spring Cloud alibaba網關 sentinel zuul 四 限流熔斷
Spring Cloud gateway 網關服務二 斷言、過濾器
Spring Cloud gateway 三 自定義過濾器GatewayFilter
Spring Cloud gateway 五 Sentinel整合
Spring Cloud gateway 六 Sentinel nacos存儲動態刷新
Spring Cloud gateway 七 Sentinel 註解方式使用
如何喜歡能夠關注分享本公衆號。