在微服务里面,我们一般编写完成之后都是统一打包测试的,但是我们真实的Toc环境中,这里我们上线的每一个服务都可能涉及到使用灰度测试/发布来逐渐迁移线上的流量,因此这里的话我们介绍下在微服务中如何做灰度测试/发布来进行微服务的实施。
这里的演示项目还是使用前面文章《Spring Cloud微服务项目模板系列(五)添加文件上传模块》里面的演示项目,这里我们准备实施的示例部署图如下:
我们首先演示下的场景如下:
这里的order-service我们需要使用灰度发布,因此我们部署一个正常的,同时再部署一个灰度的,用户访问的时候,如果在header里面添加了grayTag这个标志,我们就让他访问order-service-b, 如果没有在header里面添加graTag的话,我们就让他访问order-service-b
所以基于上诉情况,我们的操作步骤如下:
一、在order-service中的nacos配置元数据
线上环节所有的服务部署肯定是统一的,因此这里我们如果需要做灰度测试/发布的话,我们需要给对应的服务标记一下,此时我们这里的order-service-a服务是不需要标记的,只需要标记order-service-b服务。整体的nacos配置如下:
order-service-a服务的nacos配置如下:
spring: cloud: nacos: discovery: namespace: e058c047-2fc2-4291-8328-d8634fd9502f server-addr: 192.168.31.218:8848 metadata: grayTag: false
order-service-b服务的nacos配置如下:
spring: cloud: nacos: discovery: namespace: e058c047-2fc2-4291-8328-d8634fd9502f server-addr: 192.168.31.218:8848 metadata: grayTag: true
当我们这个grayTag值为true的时候,就代表这个服务需要进行灰度发布,此时服务启动之后,我们能看到这里的服务有对应的元数据。
可以看到nacos上有对应的标志。
二、在gateway中做tag的识别
这里我们所有的服务都是通过spring cloud gateway进行访问后端服务的,那么所以我们肯定是需要springcloud gateway来对request进行识别,如果request header里面带有grayTag=true的话,则我们让他访问nacos上带有grayTag=true的服务,所以这里其实我们主要做的就是控制Ribbon的负载均衡,所以这里我们肯定是需要编写规则的,流程如下:
1、spring cloud gateway 接受到用户的request 2、spring cloud gateway 从用户的request请求里面判断是否有grayTag=true的标志 3、spring cloud gateway适配新的robbion负载均衡的策略,进入到策略方法执行方法里面 4、策略方法从nacos中获取order-service的server list列表。 5、测试方法遍历从nacos中获取到的server list列表,获取里面的元数据grayTag=true,把grayTag=true的list放入新的serverlist1上,把grayTag=false的list放入新的serverlist2上 6、测试方法获取request中header头里面的grayTag=true是否为true,如果是true的话,ribbon调用选用serverlist1。如果是false的话,ribbon调用选用serverlist2。 7、后端对应的order-service就接受到对应的灰度的请求了。
所以我们完整的实例如下:
1)首先在spring cloud gateway中配置下对应的order-service为自定义的ribbon策略,这里我们在application启动类里面配置即可,例如:
package org.shop.gateway; import org.shop.common.config.GrayRuleConfig; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration; import org.springframework.cloud.client.discovery.EnableDiscoveryClient; import org.springframework.cloud.netflix.ribbon.RibbonClient; import org.springframework.cloud.netflix.ribbon.RibbonClients; import org.springframework.context.annotation.ComponentScan; import org.springframework.context.annotation.FilterType; @RibbonClients(value = { //只对订单服务进行灰度发布 @RibbonClient(value = "order-service6",configuration = GrayRuleConfig.class) }) @SpringBootApplication(exclude = {DataSourceAutoConfiguration.class}) @EnableDiscoveryClient @ComponentScan(basePackages = {"org.shop.*"},excludeFilters = { @ComponentScan.Filter(type = FilterType.REGEX, pattern = "org.shop.common.http.exception.*") }) public class GatewayApplication { public static void main(String[] args) { SpringApplication.run(GatewayApplication.class, args); } }
这里的核心其实就是
@RibbonClients(value = { //只对订单服务进行灰度发布 @RibbonClient(value = "order-service6",configuration = GrayRuleConfig.class) })
里面指定了需要灰度的服务,也配置了自定义的规则GrayRuleConfig
2)编写ribbon的自定义规则GrayRuleConfig,这里主要注入一个规则类即可,因为我们一般项目里面规则是规则,配置是配置,所以这个GrayRuleConfig类的完整代码示例如下:
package org.shop.common.config; import org.shop.common.rule.GrayRule; import org.springframework.context.annotation.Bean; public class GrayRuleConfig { @Bean public GrayRule grayRule(){ return new GrayRule(); } }
备注:
1、这里的GrayRuleConfig我们没有添加spring的扫描,这里是不需要扫描的,切记。
3)编写规则GrayRule类,这里我们主要做以下几个事情:
1、从nacos中获取对应服务的serverlist列表。 2、遍历serverlist,把他分为灰度的serverlist1和不需要灰度的serverlist2 3、从request里面获取到header里面的grayTag的值 4、根据3获取到的值选择使用哪个serverlist
规则类GrayRule的完整代码如下:
package org.shop.common.rule; import java.util.ArrayList; import java.util.List; import org.shop.common.http.handler.GrayRequestContextHolder; import org.shop.common.utils.ConmonConstants; import com.alibaba.cloud.nacos.ribbon.NacosServer; import com.alibaba.fastjson.JSON; import com.google.common.base.Optional; import com.netflix.client.config.IClientConfig; import com.netflix.loadbalancer.Server; import com.netflix.loadbalancer.ZoneAvoidanceRule; import lombok.extern.slf4j.Slf4j; /** * 灰度发布的规则 */ @Slf4j public class GrayRule extends ZoneAvoidanceRule { @Override public void initWithNiwsConfig(IClientConfig clientConfig) { } @Override public Server choose(Object key) { try { //在request里面获取是否需要灰度发布 boolean grayTag = GrayRequestContextHolder.getGrayTag().get(); //获取所有可用服务 List<Server> serverList = this.getLoadBalancer().getReachableServers(); //灰度发布的服务 List<Server> grayServerList = new ArrayList<>(); //正常的服务 List<Server> normalServerList = new ArrayList<>(); for(Server server : serverList) { NacosServer nacosServer = (NacosServer) server; //从nacos中获取元素剧进行匹配 if(nacosServer.getMetadata().containsKey(ConmonConstants.GRAY_HEADER) && nacosServer.getMetadata().get(ConmonConstants.GRAY_HEADER).equals(ConmonConstants.GRAY_VALUE)) { grayServerList.add(server); } else { normalServerList.add(server); } } System.out.println("灰度的list: {}"+JSON.toJSONString(grayServerList)); System.out.println("正常的list: {}"+JSON.toJSONString(normalServerList)); //如果被标记为灰度发布,则调用灰度发布的服务 if(grayTag) { log.info("准备走灰度服务: {} ",JSON.toJSONString(grayServerList)); return originChoose(grayServerList,key); } else { log.info("不走灰度服务: {} ",JSON.toJSONString(normalServerList)); return originChoose(normalServerList,key); } } finally { //清除灰度标记 GrayRequestContextHolder.remove(); } } private Server originChoose(List<Server> noMetaServerList, Object key) { Optional<Server> server = getPredicate().chooseRoundRobinAfterFiltering(noMetaServerList, key); if (server.isPresent()) { return server.get(); } else { return null; } } }
备注:
1、这里的话,我们拿不到request,因此这里的话,我们使用threadlocal来管理request,这里的GrayRequestContextHolder源码如下:
package org.shop.common.http.handler; /** * 这里使用 * @author Administrator * */ public class GrayRequestContextHolder { private static final ThreadLocal<Boolean> grayTag = new ThreadLocal<Boolean>(); public static void setGrayTag(boolean grayTag) { GrayRequestContextHolder.grayTag.set(grayTag); } public static ThreadLocal<Boolean> getGrayTag() { if(GrayRequestContextHolder.grayTag.get() == null) { GrayRequestContextHolder.grayTag.set(false); } return GrayRequestContextHolder.grayTag; } public static void remove() { GrayRequestContextHolder.grayTag.remove(); } }
那么这里肯定有疑问了,这里是直接从threadlocal里面获取的,那什么时候会放进去呢?其实这里主要是在filter里面放进来,所以这里需要在spring cloud gateway里面创建一个filer,名称为GlobalGrayFilter,这个类的主要作用是:
1、从request中解析grayTag的值 2、把是否灰度设置到thread local里面去。
这里的GlobalGrayFilter的完整代码如下:
package org.shop.gateway.filter; import org.shop.common.http.handler.GrayRequestContextHolder; import org.shop.common.utils.ConmonConstants; import org.springframework.cloud.gateway.filter.GatewayFilterChain; import org.springframework.cloud.gateway.filter.GlobalFilter; import org.springframework.core.Ordered; import org.springframework.http.HttpHeaders; import org.springframework.http.server.reactive.ServerHttpRequest; import org.springframework.stereotype.Component; import org.springframework.web.server.ServerWebExchange; import com.alibaba.fastjson.JSON; import cn.hutool.core.util.StrUtil; import lombok.extern.slf4j.Slf4j; import reactor.core.publisher.Mono; /** * 网关中的灰度发布全局过滤器 * @author Administrator * */ @Component @Slf4j public class GlobalGrayFilter implements GlobalFilter , Ordered{ @Override public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) { log.info("进入到了灰度的filter上"); // ① 解析请求头,查看是否存在灰度发布的请求头信息,如果存在则将其放置在ThreadLocal中 HttpHeaders headers = exchange.getRequest().getHeaders(); if (headers.containsKey(ConmonConstants.GRAY_HEADER)) { String gray = headers.getFirst(ConmonConstants.GRAY_HEADER); if (StrUtil.equals(gray, ConmonConstants.GRAY_VALUE)) { // ②设置灰度标记 GrayRequestContextHolder.setGrayTag(true); } } // ③ 将灰度标记放入请求头中 ServerHttpRequest tokenRequest = exchange.getRequest().mutate() // 将灰度标记传递过去 .header(ConmonConstants.GRAY_HEADER, GrayRequestContextHolder.getGrayTag().get().toString()).build(); HttpHeaders headers2 = tokenRequest.getHeaders(); log.info("网关灰度设置之后,获取到的request是:{}",JSON.toJSONString(headers2)); ServerWebExchange build = exchange.mutate().request(tokenRequest).build(); return chain.filter(build); } @Override public int getOrder() { return 0; } }
三、测试
以上我们就完成了spring cloud 微服务体系里面基于nacos的灰度发布和测试,这里我们测试一下。这里我们只启动两个服务,一个是spring cloud gateway和order-service。启动后可以看到都注册到了nacos上
这里我们启动了一个灰度的order-service6,那么我们的测试预期结果应该是:
我们如果在request里面添加grayTag=true的话那是可以正常访问的,如果没有添加或者添加为false,则是访问不到的。
这里我们测试访问的url是:
127.0.0.1:8060/order/prod/1
可以看到带有graTag的头,他就会走到灰度的serverlist里面,请求是成功的。我们再看看日志,看看对应的serverlist
和结果是一样的,因为我们只启动了一个orderService,同时配置了灰度。我们再请求下,不添加graTag的头
可以看到请求不到对应的服务,是完全没问题的,此时我们的灰度测试/发布案例就完成了。最后按照惯例,附上本案例的源码,登录后即可下载。
还没有评论,来说两句吧...