上一篇介绍了,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 注解方式使用
如何喜欢能够关注分享本公众号。