基于springcloud实现的灰度发布

其他参考:

https://github.com/charlesvhe/spring-cloud-practice
https://github.com/SpringCloud/spring-cloud-gray
https://github.com/saleson/fm-cloud
https://github.com/JeromeLiuLly/springcloud-gray

springcloud-gray

基于springcloud实现的灰度发布

架构设计和界面

架构模型

基于springcloud实现的灰度发布_第1张图片 基于springcloud实现的灰度发布_第2张图片

平台化操作

基于springcloud实现的灰度发布_第3张图片

项目结构

gray-config-server 配置中心

端口:6007,方便起见直接读取配置文件,生产环境可以读取git。先启动配置中心,所有服务的配置(包括注册中心的地址)均从配置中心读取。

gray-xxx-service 服务消费者

调用服务提供者和服务提供者,验证是否进入灰度服务。

gray-core 框架核心包

核心jar包,所有微服务均引用该包,用于负载自定义策略规则,是实现灰度发布的核心架包。

gray-service-registry-center 注册中心

端口:6006,用于统筹各个注册服务。

gray-api-gateway 网关

端口:4002,拉取灰度策略,进行请求的把标签操作。

应用场景

灰度发布

通过灰度版本的控制,实现符合灰度策略的对象,优先进入灰度服务进行体验。

异构服务的共存

例如,根据不同的策略,有根据不同的渠道、地域、门店、品牌等,优先使用不同的服务。例如,广州地域的用户,仅能使用基于广州部署的微服务。

同等级服务的调用

例如,业务场景,根据不同的渠道和来源进行下单。微信的下单,仅能调用微信的order-service服务;官网下单,仅能调用官网的order--service下单; 通过这样的方式,上层业务无须调用何种具体服务统一底层进行负载调用,实现业务的解耦和服务的可插拔配置;

实现思路

根据标签的控制,我们当然放到之前写的Ribbon的** CustomMetadataRule 中,每个实例配置的不同规则也是跟之前一样放到注册中心的metadata中,关键是标签数据如何传过来。自定义规则[ CustomMetadataRule ]的实现思路里面有答案,请求都通过 gray-api-gateway 进来,因此我们可以在zuul里面给请求打标签,基于用户,IP或其他看你的需求,然后将标签信息放入Thystrix。hystrix的原理,为了做到故障隔离,hystrix启用了自己的线程。另外使用sleuth方案,他的链路跟踪就能够将spam传递下去,翻翻sleuth源码,找找其他资料,发现可以使用 HystrixRequestVariableDefault ,这里不建议直接使用 HystrixConcurrencyStrategy ,会和sleuth的strategy冲突。代码参见 CoreHeaderInterceptor**。现在可以测试zuul里面的rule,看能否拿到标签内容了。

这里还不是终点,解决了zuul的路由,服务A调服务B这里的路由怎么处理呢?zuul算出来的标签如何往后面依次传递下去呢,我们还是抄sleuth:把标签放入header,服务A调服务B时,将服务A header里面的标签放到服务B的header里,依次传递下去。这里的关键点就是:内部的微服务在接收到发来的请求时(gateway-->A,A-->B都是这种情况)。

总结一下:zuul依据用户或IP等计算标签,并将标签放入header里向后传递,后续的微服务通过拦截器,将header里的标签放入RestTemplate请求的header里继续向后接力传递。将灰度标识放入( HystrixRequestVariableDefault),使Ribbon Rule可以使用。

代码分析实现流程

自定义的规则,该处可以实现针对不同的策略,使用不同的负载机制[ 轮询、随机、权重随机]

publicclassCustomMetadataRuleextendsZoneAvoidanceRule{//检测灰度开关是否启动privateHttpResultcheckGraySwitch() {Stringurl="http://10.200.102.136:6015/eureka/apps/switch";HttpResultresult=newHttpResult();
		result.statusCode=500;try{
			result=HttpClient.get(url,null);
		}catch(Exceptione1) {
			e1.printStackTrace();
		}returnresult;
	}@OverridepublicServerchoose(Objectkey) {//获取是否存在存活的服务可调用ListserverList=this.getPredicate().getEligibleServers(this.getLoadBalancer().getAllServers());//获取不到服务if(CollectionUtils.isEmpty(serverList)) {returnnull;
		}//获取灰度开关是否启动HttpResultresult=checkGraySwitch();//灰度开关被设置成关闭状态,默认走空metadata或者是特定标识是正常的服务,轮询访问BooleanisOpen=Boolean.parseBoolean(JSONObject.parseObject(result.content).getString("errorMsg"));if(result.statusCode==200&&!isOpen) {
			isOpen=true;returnRoundRobinRuleBySelf.getInstance().choose(this.getLoadBalancer(), key,isOpen);
		}//灰度发布启动状态,未被设置成灰度对象,默认走空metadata或者是特定标识是正常的服务,轮询访问if(StringUtils.isEmpty(CoreHeaderInterceptor.label.get())) {
			isOpen=false;returnRoundRobinRuleBySelf.getInstance().choose(this.getLoadBalancer(), key,isOpen);
		}//灰度发布启动状态,被设置成灰度对象,走空特定标识的服务,轮询访问returnRoundRobinRuleBySelf.getInstance().choose(this.getLoadBalancer(), key,!isOpen);
	}
}

feignClient 调用flag位透传的问题

publicclassCoreFeignRequestInterceptorimplementsRequestInterceptor{privatestaticfinalLoggerlogger=LoggerFactory.getLogger(CoreHttpRequestInterceptor.class);@Overridepublicvoidapply(RequestTemplatetemplate) {Stringheader=StringUtils.collectionToDelimitedString(CoreHeaderInterceptor.label.get(),CoreHeaderInterceptor.HEADER_LABEL_SPLIT);Stringtag=CoreHeaderInterceptor.tag.get();
		template.header(CoreHeaderInterceptor.HEADER_LABEL, header).header(CoreHeaderInterceptor.HEADER_TAG, tag);
		logger.info("label:"+header+"tag :"+tag);
	}

}

HttpRequest 调用flag位透传的问题

publicclassCoreHttpRequestInterceptorimplementsClientHttpRequestInterceptor{privatestaticfinalLoggerlogger=LoggerFactory.getLogger(CoreHttpRequestInterceptor.class);@OverridepublicClientHttpResponseintercept(HttpRequestrequest,byte[]body,ClientHttpRequestExecutionexecution)throwsIOException{HttpRequestWrapperrequestWrapper=newHttpRequestWrapper(request);Stringheader=StringUtils.collectionToDelimitedString(CoreHeaderInterceptor.label.get(),CoreHeaderInterceptor.HEADER_LABEL_SPLIT);Stringtag=CoreHeaderInterceptor.tag.get();
        
        logger.info("label:"+header+"tag :"+tag);HttpHeadersheaders=requestWrapper.getHeaders();
        headers.add(CoreHeaderInterceptor.HEADER_LABEL, header);
        headers.add(CoreHeaderInterceptor.HEADER_TAG, tag);returnexecution.execute(requestWrapper, body);
    }
}

配置生效

@[email protected]sWebMvcConfigurerAdapter{@BeanpublicDefaultPropertiesFactorydefaultPropertiesFactory() {returnnewDefaultPropertiesFactory();
	}@[email protected]() {RestTemplaterestTemplate=newRestTemplate();
		restTemplate.getInterceptors().add(newCoreHttpRequestInterceptor());returnrestTemplate;
	}//用于配置feignClient透传生效@BeanpublicFeign.BuilderfeignBuilder() {returnFeign.builder().requestInterceptor(newCoreFeignRequestInterceptor());
	}@OverridepublicvoidaddInterceptors(InterceptorRegistryregistry) {
		registry.addInterceptor(newCoreHeaderInterceptor());
	}
}

测试

测试/验证流程说明:

第一步,测试灰度发布总开关是否生效

简易形式:

1.先启动 order服务;

1.1 未标识灰度服务时,前端每一次访问都是随机的情况 访问url: http://127.0.0.1:4002/order/inner/order/getOrderInfoListByUserName?userName=liulianyuan 基于springcloud实现的灰度发布_第4张图片1.2 分别启动灰度服务和正常服务。一开始启动的时候是无差别的。我们可以在 metadata.html 进行动态配置,指定某个服务是灰度的。 以下,设置了order-service2 是灰度服务。 访问url: http://127.0.0.1:4002/order/inner/order/getOrderInfoListByUserName?userName=liulianyuan 基于springcloud实现的灰度发布_第5张图片 基于springcloud实现的灰度发布_第6张图片 基于springcloud实现的灰度发布_第7张图片 基于springcloud实现的灰度发布_第8张图片

第二步,测试灰度发布的正常行为

方式1------(灰度用户): 前端 ==> 网关 ==> 正常服务 ==> 灰度服务

1.启动 order服务 和 user服务;

1.1 user-service是正常服务,标识user-service2为灰度服务;order-service 均是正常服务。 1.2 启动所有服务。一开始启动的时候是无差别的。我们可以在 metadata.html 进行动态配置,指定某个服务是灰度的。

请求url: http://127.0.0.1:4002/order/test?userName=liulianyuan 基于springcloud实现的灰度发布_第9张图片 基于springcloud实现的灰度发布_第10张图片该处是随机访问正常的order-service服务的。多试几次,就可以看见order-service和order-service2的出现。【该处如果需要做成轮需,需要改代码】

方式2------(灰度用户): 前端 ==> 网关 ==> 灰度服务 ==> 正常服务

1.启动 order服务 和 user服务;

1.1 user-service是正常服务,标识user-service2为灰度服务;order-service 均是正常服务。 1.2 启动所有服务。一开始启动的时候是无差别的。我们可以在 metadata.html 进行动态配置,指定某个服务是灰度的。 基于springcloud实现的灰度发布_第11张图片请求url: http://127.0.0.1:4002/user/getOrderInfo?userName=liulianyuan 基于springcloud实现的灰度发布_第12张图片 基于springcloud实现的灰度发布_第13张图片该处是随机访问正常的order-service服务的。多试几次,就可以看见order-service和order-service2的出现。【该处如果需要做成轮需,需要改代码】

方式3------(灰度用户): 前端 ==> 网关 ==> 灰度服务 ==> 灰度服务

1.启动 order服务 和 user服务;

1.1 user-service是正常服务,标识user-service2为灰度服务;order-service 均是正常服务。 1.2 启动所有服务。一开始启动的时候是无差别的。我们可以在 metadata.html 进行动态配置,指定某个服务是灰度的。 基于springcloud实现的灰度发布_第14张图片请求url: http://127.0.0.1:4002/order/test?userName=liulianyuan

方式4------(正常用户): 前端 ==> 网关 ==> 正常服务 ==> 正常服务

1.启动 order服务 和 user服务;

1.1 user-service是正常服务,标识user-service2为灰度服务;order-service 均是正常服务。 1.2 启动所有服务。一开始启动的时候是无差别的。我们可以在 metadata.html 进行动态配置,指定某个服务是灰度的。 基于springcloud实现的灰度发布_第15张图片请求url: http://127.0.0.1:4002/order/test?userName=lly 基于springcloud实现的灰度发布_第16张图片

你可能感兴趣的:(基于springcloud实现的灰度发布)