当前位置: 首页 > 科技观察

微服务灰度发布应该是这样设计的

时间:2023-03-18 14:55:35 科技观察

如果实际生产中有需求变化,在线服务将不会直接更新。最常见的方式是切掉一小部分在线流量进行体验测试。测试无问题后,即可全面上线。这样做的好处也是非常明显的。一旦出现BUG,可以保证大部分客户端的正常使用。要实现这种平滑过渡,需要使用本文介绍的全链路灰度发布。什么是灰度发布?灰度发布(也称为金丝雀发布)是指一种可以在黑白之间平滑过渡的发布方式。可以对其进行A/B测试,即让部分用户继续使用产品特性A,部分用户开始使用产品特性B,如果用户对B没有异议,则逐步扩大范围,全部迁移用户来B来。灰度发布可以保证整个系统的稳定性,可以在灰度初期发现问题并进行调整,保证其影响。为什么是全链路灰度发布?Chen在上一篇文章中介绍了网关灰度发布的实现,只实现了网关路由转发的灰度发布,如下图:如上图所示,网关灰度发布实现网关通过灰度标签服务B(grayscaleservice)路由到文章,文章服务B通过openFeign内部调用评论服务,默认无法实现灰度标签grayTag的透传,所以文章服务B最后调用评论服务A,而不是评论服务B。全链路灰度发布需要实现的是:网关将部分流量通过灰度标签转发给文章服务B。文章服务B可以实现grayTag的透传,最终调用评论服务B,经过以上分析,全链路灰度度发布需要实现两点:网关路由和转发,内部通过openFeign实现灰度发布服务调用实现灰度发布(透明灰标grayTag)。网关层灰度路由转发本文将使用Ribbon+SpringCloudGateway改造负载均衡策略,实现灰度发布。实现思路如下:在网关的全局过滤器中,根据业务规则给流量打上灰度标记,将灰度标记放入请求头中,传给下游服务修改Ribbon负载均衡策略,并根据流量标记从注册中心获取灰度服务请求路由转发第一个问题:根据什么条件标记灰度标记?这个需要根据实际业务需要,比如用户所在的区域,使用的客户端类型,流量的随机拦截...这里我会直接使用一个标签grayTag,只要携带这个参数即可客户端请求头,设置为true则执行灰度发布逻辑。在请求头中携带:grayTag=true第二个问题:为什么在请求头中加一个灰色标签,传递给下游服务?这一步非常关键。将灰度标记透传给下游服务是关键。将灰度标记放在请求头中,下游服务只需要从请求头中获取灰度标记就可以知道是否是灰度发布。这与命令卡中继原理有关。第三个问题:灰度标签如何请求隔离?SpringMVC中每个请求都会启动一个线程进行处理,所以灰色标记可以放在ThreadLocal中进行线程隔离。第四个问题:如何知道注册中心的哪个服务是灰度服务?Nacos支持在服务中配置一些元数据,可以在元数据中配置灰度标签,这样可以区分哪些是灰度服务,哪些是普通服务。第五个问题:具体业务如何灰度发布?比如我的《Spring Cloud Alibaba实战》中涉及的一个调用链接如下:需求:现在灰度发布的只有文章服务和评论服务,其他服务仍然使用在线运行服务。这时调用关系就变成了下图:我们知道在网关路由中配置了很多服务,如何只对文章服务进行灰度发布呢?很简单:只需要??让自定义的Ribbon灰度发布规则只对文章服务生效即可。这里涉及到Ribbon中的一个注解:@RibbonClients,你只需要在value属性中指定需要生效的服务名称,那么此时网关中的配置如下:@RibbonClients(value={//Only灰度文章服务Publish@RibbonClient(value="article-server",configuration=GrayRuleConfig.class)})@SpringBootApplicationpublicclassGatewayApplication{}@RibbonClient可以指定多个,这个注解有如下两个属性:value:名字specifiedservice,registeredcenterconfiguration中配置的服务名称:自定义负载均衡策略,这里是Grayscale发布的策略@RibbonClients,它有一个属性defaultConfiguration,一旦使用了这个属性,Grayscale发布的策略就会应用到所有网关路由中配置的服务生效。第六个问题:说了这么多,怎么实现呢?首先,需要在网关中定义一个全局过滤器。伪代码如下:publicclassGlobalGrayFilterimplementsGlobalFilter{@OverridepublicMonofilter(ServerWebExchangeexchange,GatewayFilterChainchain){//①解析请求头,看是否有灰度释放请求头信息,如果存在,它会被放在ThreadLocalHttpHeadersheaders=exchange.getRequest().getHeaders();如果(headers.containsKey(GrayConstant.GRAY_HEADER)){Stringgray=headers.getFirst(GrayConstant.GRAY_HEADER);if(StrUtil.equals(gray,GrayConstant.GRAY_VALUE)){//②设置灰色标签GrayRequestContextHolder.setGrayTag(true);}}//③把灰色标签放在请求头ServerHttpRequesttokenRequest=exchange.getRequest().mutate()//把灰色标签传过去.header(GrayConstant.GRAY_HEADER,GrayRequestContextHolder.getGrayTag().toString())。建造();ServerWebExchangebuild=exchange.mutate().request(tokenRequest).build();返回chain.filter(build);}}①处的代码:来自请求头获取客户端传过来的灰度标签(这里可以根据业务需要更改),判断是否是灰度发布时的代码②:GrayRequestContextHolder是一个自定义ThreadLocal实现的线程隔离工具,用于存储灰度标签③处的代码:在请求头中放置灰度标记传递给下游微服务,这里是一个带token的逻辑注意:这个全局过滤器必须放在OAuth2.0认证过滤器之前,并且应该调整优先级high-globalfilter已经打上了灰度标记,放在了GrayRequestContextHolder中。接下来只需要修改Ribbon的负载均衡策略去注册中心选择灰度服务即可。创建GrayRule,代码如下:/***灰度发布规则*/publicclassGrayRuleextendsZoneAvoidanceRule{@OverridepublicvoidinitWithNiwsConfig(IClientConfigclientConfig){}@OverridepublicServerchoose(Objectkey){try{//从ThreadLocal获取灰度标签booleangrayTag=GrayRequestContextHolder.getGrayTag().get();//获取所有可用服务ListserverList=this.getLoadBalancer().getReachableServers();//灰度发布服务ListgrayServerList=newArrayList<>();//普通服务ListnormalServerList=newArrayList<>();for(Serverserver:serverList){NacosServernacosServer=(NacosServer)server;//从nacos中获取匹配到的元素ant.GRAY_VALUE)){grayServerList.add(server);}else{normalServerList.add(server);}}//如果标记为灰度发布,则调用灰度发布服务if(grayTag){returnoriginChoose(grayServerList,key);}else{returnoriginChoose(normalServerList,key);}}finally{//清除灰色标记GrayRequestContextHolder.remove();}}privateServeroriginChoose(ListnoMetaServerList,Objectkey){Optionalserver=getPredicate().chooseRoundRobinAfterFiltering(noMetaServerList,key);如果(server.isPresent()){返回server.get();}else{返回空值;}}}逻辑很简单,如下:获取灰度标识从Nacos注册中心获取灰度服务和普通服务,根据灰度标识判断。如果发布灰度,则选择特定的灰度服务进行转发。定义一个配置类,注入改造后的灰度策略GrayRule,如下:/***加载灰度部署的规则配置类*注意:这个类一定不能被SpringBoot扫描到IOC容器中,一旦扫描到就会生效对于所有服务*/public类GrayRuleConfig{@BeanpublicGrayRulegrayRule(){returnnewGrayRule();}}注意:这个GrayRuleConfig不能扫描到IOC容器中,一旦扫描到里面,就会全局生效,因为不仅网关需要使用这个灰度发布策略,所有涉及OpenFeign调用的微服务如果需要都需要使用为了配置灰度发布,所以这里Chen定义了一个publicgray-starter。经过以上步骤,网关的灰度发布就配置好了。这时候只需要通过@RibbonClients指定灰度发布哪个服务即可。OpenFeign透传灰度标签上面在介绍网关的灰度发布配置时,将灰度标签(grayTag=true)放在了请求头中,所以下游服务中只需要在from添加灰度标签即可请求标头。度数标记被取出,然后存储在GrayRequestContextHolder上下文中。这样下游服务中的GrayRule就可以从GrayRequestContextHolder中获取灰度标记,从注册中心获取灰度服务进行调用。问题来了:如何去除请求头中的灰度标记?在介绍OAuth2.0相关知识的时候,有一篇文章:实战!openFeign是如何实现全链接JWTtoken信息不丢失的呢?介绍了tokenrelay的解决方案,使用openFeign的请求拦截器配置请求头信息。如上图:openFeign在调用时并没有使用原来的Request,而是在内部创建了一个新的Request,复制了请求的URL和请求参数的一些信息,但是没有复制请求头,所以openFeign调用会丢失请求中的头信息。但是你可以通过实现RequestInterceptor来复制原来的请求头,代码如下:>headers=getHeaders(httpServletRequest);for(Map.Entryentry:headers.entrySet()){//②设置请求头为新的Requesttemplate.header(entry.getKey(),entry.getValue());}}/***获取原始请求头*/privateMapgetHeaders(HttpServletRequestrequest){Mapmap=newLinkedHashMap<>();Enumerationenumeration=request.getHeaderNames();if(enumeration!=null){while(enumeration.hasMoreElements()){Stringkey=enumeration.nextElement();字符串值=request.getHeader(钥匙);//将灰度标记的请求头传递给下一个服务;}}①①处代码:从请求头中获取灰度published标签,并设置在GrayRequestContextHolder上下文中②此处代码:将此请求头设置为新的Request,继续传递给下游服务。其实RequestInterceptor的配置已经完成了。对于灰度发布策略,只需要复用网关的GrayRule。注意:@RibbonClients注解也需要去标注文章服务调用的哪些服务需要灰度发布。代码如下:@RibbonClients(value={//指定为评论服务开启灰度部署@RibbonClient(value="comments",configuration=GrayRuleConfig.class)})publicclassArticleApplication{}如何做灰度在Nacos中为服务做标记其实很简单,分为两种:1.在配置文件中指定,如下:spring:cloud:nacos:discovery:metadata:##灰度标签grayTag:true2.动态指定Nacos中灰度标签配置完成后,客户端请求时,只需要携带请求头grayTag=true即可调用灰度服务。综上所述,微服务中的全链路灰度发布方案其实很简单。最重要的是灰度标记。整体流程如下:网关通过全局过滤器实现灰度标记,并将灰度标记放在请求头中传递给下游服务。网关通过自定义负载均衡策略从注册中心获取灰度服务并转发。调用openFeign时,需要从请求头中获取灰色标记,放入上下文中。openFeign的调用也是基于自定义的负载均衡策略从注册中心获取灰度服务并进行调用。