基于springcloud实现的灰度发布

标签: | 发表时间:2020-02-16 21:43 | 作者:
出处:https://github.com

其他参考:


springcloud-gray

基于springcloud实现的灰度发布

架构设计和界面

架构模型

灰度发布架构设计图 灰度发布数据流图

平台化操作

平台化界面图

项目结构

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) {//获取是否存在存活的服务可调用List<Server>serverList=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);
    }
}

配置生效

@Configuration@EnableWebMvcpublicclassCoreAutoConfigurationextendsWebMvcConfigurerAdapter{@BeanpublicDefaultPropertiesFactorydefaultPropertiesFactory() {returnnewDefaultPropertiesFactory();
	}@LoadBalanced@BeanpublicRestTemplaterestTemplate() {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 图一1.2 分别启动灰度服务和正常服务。一开始启动的时候是无差别的。我们可以在 metadata.html 进行动态配置,指定某个服务是灰度的。 以下,设置了order-service2 是灰度服务。 访问url: http://127.0.0.1:4002/order/inner/order/getOrderInfoListByUserName?userName=liulianyuan 图二 图二 图二 图二

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

方式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 图二 图二该处是随机访问正常的order-service服务的。多试几次,就可以看见order-service和order-service2的出现。【该处如果需要做成轮需,需要改代码】

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

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

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

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

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 图二

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

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=lly 图二

相关 [springcloud 灰度] 推荐:

基于springcloud实现的灰度发布

- -
基于springcloud实现的灰度发布. gray-config-server 配置中心. 端口:6007,方便起见直接读取配置文件,生产环境可以读取git. 先启动配置中心,所有服务的配置(包括注册中心的地址)均从配置中心读取. gray-xxx-service 服务消费者. 调用服务提供者和服务提供者,验证是否进入灰度服务.

SpringCloud灰度发布实践(附源码) - 微服务实践 - SegmentFault 思否

- -
在平时的业务开发过程中,后端服务与服务之间的调用往往通过. resttemplate两种方式. 但是我们在调用服务的时候往往只需要写服务名就可以做到路由到具体的服务,这其中的原理相比大家都知道是. ribbon组件帮我们做了负载均衡的功能. 灰度的核心就是路由,如果我们能够重写ribbon默认的负载均衡算法是不是就意味着我们能够控制服务的转发呢.

SpringCloud项目接入Jaeger(下) - 掘金

- -
spring-cloud-sleuth这个组件时,会面临两个问题. 首先是日志中无法显示traceId和spanId这些链路信息,其次是不能在用. spring-cloud-sleuth所提供的方式进行链路传值. spring-cloud-sleuth是将traceId等链路信息保存在. slf4j的MDC(Mapped Diagnostic Contexts)中,然后通过%X{traceId}这种方式将traceId提取出来,比如打印到控制台的默认格式是:.

Springcloud + RocketMQ 解决分布式事务

- - 掘金架构
分布式事务有哪些实现方式. 随着互联网时代的高速发展,分布式成了大型系统的标配,这是时代发展的选择. 大型分布式系统不是每个公司和开发人员都能够涉及的领域,因为大型系统后面都 隐藏着众多代名词:复杂,昂贵,高科技,人才云集,大战略. 大部分领头互联网公司甚至依托自己的分布式经验逐步建立自己的体系,并使用这套体系搭建自己的平台对内,甚至对外提供服务, 就像现在众多的云平台提供的服务,甚至有些大战略提出促进发展:大中台小前台、大炮台支援单兵作战等等.

SpringCloud Gateway与k8s_zhangjunli的博客-CSDN博客

- -
接下来的内容由以下几部分组成:. 什么是SpringCloud Gateway. SpringCloud Gateway实战参考. kubernetes上的SpringCloud Gateway. 开发k8sgatewaydemo. 什么是SpringCloud Gateway. SpringCloud Gateway是SpringCloud技术栈下的网关服务框架,在基于SpringCloud的微服务环境中,外部请求会到达SpringCloud Gateway应用,该应用对请求做转发、过滤、鉴权、熔断等前置操作,一个典型的请求响应流程如下所示:.

将应用从 SpringCloud 迁移到 k8s - Rason's Blog

- -
最近花了几天时间看了一下 k8s 和 istio 的文档,对容器化运维以及服务网格有了基础的了解. 俗话说读万卷书不如行万里路,于是先尝试用 minikube 练一下手,将现有了一个 Spring Cloud 项目迁移到 k8s 上来. 粗略地整理了一个整个流程,主要有以下几个改动点:. 安装 kubectl 和 minikube.

SpringCloud基础教程(五)-配置中心热生效和高可用

- - 掘金后端
 我的博客: 兰陵笑笑生,欢迎浏览博客.  上一章 SpringCloud基础教程(四)-配置中心入门当中,我们在对Eureka的有了基本的基础认识之上,深入的了解Eureka高可用集群和其他的生产环境中用到的一些配置. 本章将开始了解分布式环境下的配置中心.  在实际的项目运行中,我们会根据实际需求修改配置内容,那么有没有一种方式,能够在不启动服务组件的情况向让配置文件动态的生效呢,Spring Cloud Conifg中提供了一种方式了.

SpringCloud实战十三:Gateway之 Spring Cloud Gateway 动态路由_zhuyu19911016520-CSDN博客

- -
前面分别对 Spring Cloud Zuul 与 Spring Cloud Gateway 进行了简单的说明,它门是API网关,API网关负责服务请求路由、组合及协议转换,客户端的所有请求都首先经过API网关,然后由它将匹配的请求路由到合适的微服务,是系统流量的入口,在实际生产环境中为了保证高可靠和高可用,尽量避免重启,如果有新的服务要上线时,可以通过动态路由配置功能上线.

微服务SpringCloud之GateWay熔断、限流、重试 - 社会主义接班人 - 博客园

- -
纯洁的微笑的Spring Cloud系列博客终于学完了,也对Spring Cloud有了初步的了解. StripPrefix Filter 是一个请求路径截取的功能,我们可以利用这个功能来做特殊业务的转发. StripPrefix是当请求路径匹配到/name/**会将包含name和后边的字符串接去掉转发, StripPrefix=2就代表截取路径的个数,当访问.

k8s部署springcloud架构的一些心得体会_浅抒流年的博客-CSDN博客

- -
最近在研究k8s,顺便将公司springcloud架构改造了一下,以更好适应用k8s来部署. 期间遇到了一些问题,自己想办法解决了. 提供者和消费者向eureka注册时的问题. 大家都知道,springcloud架构是有一个注册中心的,无论是服务提供者还是服务消费者都要注册到该注册中心上. 在上一篇博文中,已经介绍过如何把eureka部署到k8s上.