你居然只知道蓝绿发布?今天教你全链路灰度~
大家好,我叫大鸡腿,大家可以关注下我,会持续更新技术文章还有人生感悟,感谢~Q:谈到灰度发布,你会想起什么?🙋♂️:在我上家公司其实只有小型的灰度发布,可能会有人跳起来喊:那你们怎么发布服务的?通过k8s的滚动发布来实现无缝上线,如果上线失败呢,通过旧release分支构回去。这其实是灰度里头的流量比负载。灰度发布(Gray release)是指在黑与白之间,能够平滑过渡的一种发布方式。大白话
大家好,我叫大鸡腿,大家可以关注下我,会持续更新技术文章还有人生感悟,感谢~
前言
Q:谈到灰度发布,你会想起什么?
🙋♂️:在我上家公司其实只有小型的灰度发布,可能会有人跳起来喊:那你们怎么发布服务的?
通过k8s的滚动发布来实现无缝上线,如果上线失败呢,通过旧release分支构回去。这其实是灰度里头的流量比负载。
谈谈我认知里的灰度发布
- 灰度发布是什么?
灰度发布(Gray release)是指在黑与白之间,能够平滑过渡的一种发布方式。大白话,就是在新旧版本里面可以丝滑的切换,一看就是德芙吃多了😁
- 灰度有哪些形式?
灰度方案 | 长什么样 | 优缺点 |
---|---|---|
蓝绿发布 | 有两套一毛一样的环境,在上线的时候,通过切换不同环境,来达到丝滑上线 | 优点:快捷。缺点:浪费资源 |
AB Test | 通过不同的角色或者用户来进入不同逻辑,进行验证 | 优点:粒度更小 |
金丝雀发布 | 通过流量比将流量负载到不同机器 | 缺点:灰度粒度比较粗 |
- 完善的灰度长什么样子?
把所有优点拿过,拿来主义。蓝绿+AB Test,归纳一下,其实就是流量比+tag。
蓝绿跟金丝雀,通过流量比来切换,AB Test通过标识来切换。
全链路灰度建设
全链路灰度是什么?
顾名思义,就是整条调用链路,你想访问哪就访问哪,指哪打哪。我们可以设想想,蓝绿发布是所有应用的灰度,一刀切。AB Test是部分用户,部分切。全链路发布,其实是需要细化到服务、接口级别灰度,而且贯穿整条链路~
全链路灰度怎么打造
全链路灰度由什么构成
-
网关流量染色
-
网关层负载均衡
-
rpc层负载均衡
-
上下文透传
-
数据隔离(看情况)
流量染色
流量染色,就是我们对符合条件的流量进行标记。那么它有几种方式呢?两种:主动染色、被动染色。
- 主动染色
比如说,只要你有100W就是土豪,每个人进来的时候进行搜查,你满足条件给你打上土豪tag。
- 被动染色
这个更好理解了,小明是老赖,我看见小明就知道他是老赖,不需要去翻他资料,对吧。
流量染色怎么实现?
其实很简单,首先我们需要一套规则体系,rule,然后在网关层进行筛选流量,进行打标。技术手段就是往gateway exchange 设置Attributes,然后在网关flux链路传递。
这里我们踩过的坑,为啥不用threadlocal去传递这个标识?
gateway采用flux异步非阻塞实现的,也就是说他们根本不在一个线程去处理。官方推荐就是塞入exchange里头。
网关层负载均衡
关注我的就知道,之前我写过类似的文章,大家可以阅读下。
原理是什么?
我们将染色的流量,路由到对应的服务。技术方案是:在网关流量染色之后,通过Nacos注册中心,改写负载均衡算法,进行路由到对应的服务。
这里的服务概念就比较模糊了,可以是特定版本的服务,比如说V1、V2。也可以是,服务起来之后打对应tag标签,这一类的服务是灰度标识,灰度流量走这边~
RPC层负载均衡
我们常见的RPC有哪些?Feign、RestTemplate、WebClient。
其中能负载的是那种根据serviceId来查询服务,如果你通过host、url那种来请求,拜拜了,干不了。(PS:其实也可以改写host,但是不够优雅)
怎么改写负载均衡呢?
在高版本,Feign已经不再依赖Ribbon实现负载了,通过loadBalance来实现负载均衡。
重写ReactorServiceInstanceLoadBalancer,实现Feign、RestTemplate负载均衡
Q:怎么实现服务级别的灰度呢?
🙋♂️:我们通过Nacos naminServer拿到不同环境namespace的服务列表,你想路由到哪里都行了。
我们可以参考NacosLoadBalancer
上下文透传
这个其实是APM的内容,比如说上面的Feign,怎么将这个染色的标识传到下一个节点呢?大部分中间件、框架都有拦截器,我们只需要把它打到对应的header头。
Feign
@Bean
public RequestInterceptor headerInterceptor() {
return template -> {
ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
if (null != attributes) {
HttpServletRequest request = attributes.getRequest();
Enumeration<String> headerNames = request.getHeaderNames();
if (headerNames != null) {
while (headerNames.hasMoreElements()) {
String name = headerNames.nextElement();
String values = request.getHeader(name);
// 跳过 content-length,防止报错Feign报错feign.RetryableException: too many bytes written executing
if (name.equals("content-length")) {
continue;
}
template.header(name, values);
}
}
}
};
}
RestTemplate
import org.springframework.http.HttpRequest;
import org.springframework.http.client.ClientHttpRequestExecution;
import org.springframework.http.client.ClientHttpRequestInterceptor;
import org.springframework.http.client.ClientHttpResponse;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import javax.servlet.http.HttpServletRequest;
import java.io.IOException;
import java.util.Enumeration;
@Component
public class RestTemplateInterceptor implements ClientHttpRequestInterceptor {
@Override
public ClientHttpResponse intercept(HttpRequest request, byte[] body, ClientHttpRequestExecution execution)
throws IOException {
ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
if (null != attributes) {
HttpServletRequest httpServletRequest = attributes.getRequest();
Enumeration<String> headerNames = httpServletRequest.getHeaderNames();
if (headerNames != null) {
while (headerNames.hasMoreElements()) {
String name = headerNames.nextElement();
String values = httpServletRequest.getHeader(name);
// 跳过 content-length,防止报错Feign报错feign.RetryableException: too many bytes written executing
if (name.equals("content-length")) {
continue;
}
request.getHeaders().add(name, values);
}
}
}
return execution.execute(request, body);
}
}
@LoadBalanced
@Bean
public RestTemplate getRestTemplate(RestTemplateInterceptor restTemplateInterceptor) {
RestTemplate restTemplate = new RestTemplate();
restTemplate.setInterceptors(List.of(restTemplateInterceptor));
return restTemplate;
}
当然这只是一部分场景,还有多线程,线程池、Hystrix等等场景。
数据隔离
这个看情况,如果做测试数据灰度的,需要做影子表、影子库进行隔离,重写数据库连接。
它山之石可以攻玉
我们来看阿里是怎么实现全链路灰度的,其实原理都差不多。
下一篇:多泳道建设
下一篇我会讲述多泳道建设,它采用的技术跟这个很像,大家敬请期待~
更多推荐
所有评论(0)