当前位置:网站首页>基于 GateWay 和 Nacos 实现微服务架构灰度发布方案
基于 GateWay 和 Nacos 实现微服务架构灰度发布方案
2022-06-11 14:30:00 【小毕超】
一、灰度发布
灰度发布(又名金丝雀发布)是指在黑与白之间,能够平滑过渡的一种发布方式。在其上可以进行A/B testing,即让一部分用户继续用产品特性A,一部分用户开始用产品特性B,如果用户对B没有什么反对意见,那么逐步扩大范围,把所有用户都迁移到B上面来。灰度发布可以保证整体系统的稳定,在初始灰度的时候就可以发现、调整问题,以保证其影响度。
灰度发布开始到结束期间的这一段时间,称为灰度期。灰度发布能及早获得用户的意见反馈,完善产品功能,提升产品质量,让用户参与产品测试,加强与用户互动,降低产品升级所影响的用户范围。
下面基于 GateWay 和 Nacos 实现微服务架构灰度发布方案,首先对生产的服务和灰度环境的服务统一注册到 Nacos 中,但是版本不同,比如生产环境版本为 1.0 ,灰度环境版本为 2.0 ,请求经过网关后,判断携带的用户是否为灰度用户,如果是将请求转发至 2.0 的服务中,否则转发到 1.0 的服务中。
二、开始实施
首先搭建两个web服务模拟生产和灰度环境,分别注册到nacos 中,注意这里服务ID 要一致:
生产环境配置:
spring:
application:
name: web
cloud:
nacos:
discovery:
server-addr: 127.0.0.1:8848
metadata:
version: 1.0 # 指定版本号
灰度环境配置:
spring:
application:
name: web
cloud:
nacos:
discovery:
server-addr: 127.0.0.1:8848
metadata:
version: 2.0 # 指定版本号
启动两个服务后,可以在nacos 中查看详情:


下面为了模拟两个服务的差异性,创建相同的接口,不同的返回:
@RestController
public class TestController {
@GetMapping("/getTest")
public String getTest(){
return "当前处于-生产环境!";
}
}
@RestController
public class TestController {
@GetMapping("/getTest")
public String getTest(){
return "当前处于-灰度环境!";
}
}
下面开始搭建 GateWay 网关,同样需要注册到 nacos 中,但是和以前不同的是,这里我们要实现一个负载均衡器,在负载均衡器中判断是否使用哪个版本的服务,这里为了演示效果,在nacos 中新建一个配置文件,将灰度用户配置在这个配置文件中,在项目中应该从 db 或 noSQL 中进行获取。

Data ID: env-config.yaml
Group: DEFAULT_GROUP
env:
gray:
version: 2.0
users: abc,ii,ss,kk,bb,pp
pro:
version: 1.0
再增加一个 GateWay 路由的配置:
Data ID:gateway.yaml
Group: DEFAULT_GROUP
spring:
cloud:
gateway:
httpclient:
connect-timeout: 2000
response-timeout: 10s
routes:
- id: web
uri: lb://web/
order: 0
predicates:
- Path=/web/**
filters:
- StripPrefix=1 # 去除请求地址中的前缀
下面搭建 gateway 网关服务,注册到 nacos 中,并加载上面创建的配置文件:
spring:
application:
name: gateway
cloud:
nacos:
discovery:
server-addr: 127.0.0.1:8848
config:
server-addr: 127.0.0.1:8848
file-extension: yaml
refresh-enabled: true
extension-configs[0]:
data-id: env-config.yaml
group: DEFAULT_GROUP
refresh: true
启动后,查看下是否已经注册到 nacos 中了:
测试下是否可以负载转发:


已经实现了负载效果,但是还没有达到我们想要的效果,下面开始对 gateway 网关进行修改。
首先我们新建一个 EnvProperties 来接收 env-config.yaml 中的配置,注意一定要加 @RefreshScope 注解,这样才能修改配置后通知到相应的服务:
@Data
@Configuration
@RefreshScope
public class EnvProperties {
@Value("${env.pro.version}")
private String proVersion;
@Value("${env.gray.users}")
private List<String> grayUsers;
@Value("${env.gray.version}")
private String grayVersion;
}
在创建一个 ThreadLocal ,存储当前的版本信息,这里先记下来,后面就知道什么作用了:
public class GrayscaleThreadLocalEnvironment {
private static ThreadLocal<String> threadLocal = new ThreadLocal<String>();
public static void setCurrentEnvironment(String currentEnvironmentVsersion) {
threadLocal.set(currentEnvironmentVsersion);
}
public static String getCurrentEnvironment() {
return threadLocal.get();
}
}
下面创建 过滤器 对请求进行拦截,然后获取到用户的信息,这里就默认用户ID 在 header 中,key 为 userId,取到之后判断是否在 灰度用户列表中,如果存在就把当前的 ThreadLocal(就是上面声明的ThreadLocal ) 中存储灰度的版本号,,否则就为生产的版本号:
@Component
@RefreshScope
public class GrayscaleGlobalFilter implements GlobalFilter, Ordered {
@Autowired
EnvProperties envProperties;
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
ServerHttpRequest request = exchange.getRequest();
ServerHttpResponse response = exchange.getResponse();
HttpHeaders header = response.getHeaders();
header.add("Content-Type", "application/json; charset=UTF-8");
List<String> list = request.getHeaders().get("userId");
if (Objects.isNull(list) || list.isEmpty()) {
return resultErrorMsg(response," 缺少userId!");
}
String userId = list.get(0);
if (StringUtils.isBlank(userId)) {
return resultErrorMsg(response," 缺少userId!");
}
if (envProperties.getGrayUsers().contains(userId)) {
//指定灰度版本
GrayscaleThreadLocalEnvironment.setCurrentEnvironment(envProperties.getGrayVersion());
} else {
//指定生产版本
GrayscaleThreadLocalEnvironment.setCurrentEnvironment(envProperties.getProVersion());
}
return chain.filter(exchange.mutate().request(request).build());
}
public int getOrder() {
return -1;
}
private Mono<Void> resultErrorMsg(ServerHttpResponse response, String msg) {
JSONObject jsonObject = new JSONObject();
jsonObject.put("code", "403");
jsonObject.put("message", msg);
DataBuffer buffer = response.bufferFactory().wrap(jsonObject.toJSONString().getBytes());
return response.writeWith(Mono.just(buffer));
}
}
上面的过滤器已经标识出当前请求属于灰度还是生产,下面就需要我们重写Ribbon 负载均衡器,这里重写的 RoundRobinRule ,在 choose 方法中,根据当前 ThreadLocal 中的版本,便利服务中版本与之相等的服务,作为转发服务,为了防止服务获取失败,这里曾加了重试策略,重试 10 次还是失败,即放弃重试:
@Component
@Slf4j
public class EnvRoundRobinRule extends RoundRobinRule {
private AtomicInteger nextServerCyclicCounter;
public EnvRoundRobinRule() {
nextServerCyclicCounter = new AtomicInteger(0);
}
public Server choose(ILoadBalancer lb, Object key) {
if (lb == null) {
log.warn("no load balancer");
return null;
}
Server server = null;
int count = 0;
// 如果失败,重试 10 次
while (Objects.isNull(server) && count++ < 10) {
List<Server> reachableServers = lb.getReachableServers();
List<Server> allServers = lb.getAllServers();
int upCount = reachableServers.size();
int serverCount = allServers.size();
if ((upCount == 0) || (serverCount == 0)) {
log.warn("No up servers available from load balancer: " + lb);
return null;
}
List<NacosServer> filterServers = new ArrayList<>();
String currentEnvironmentVersion = GrayscaleThreadLocalEnvironment.getCurrentEnvironment();
for (Server serverInfo : reachableServers) {
NacosServer nacosServer = (NacosServer) serverInfo;
String version = nacosServer.getMetadata().get("version");
if (version.equals(currentEnvironmentVersion)) {
filterServers.add(nacosServer);
}
}
int filterServerCount = filterServers.size();
int nextServerIndex = incrementAndGetModulo(filterServerCount);
server = filterServers.get(nextServerIndex);
if (server == null) {
Thread.yield();
continue;
}
if (server.isAlive() && (server.isReadyToServe())) {
return (server);
}
server = null;
}
if (count >= 10) {
log.warn("No available alive servers after 10 tries from load balancer: "
+ lb);
}
return server;
}
private int incrementAndGetModulo(int modulo) {
for (; ; ) {
int current = nextServerCyclicCounter.get();
int next = (current + 1) % modulo;
if (nextServerCyclicCounter.compareAndSet(current, next))
return next;
}
}
}
到这 流程基本就已经结束了,下面在header 中增加 userId 为 abc,然后多访问几次,可以看到都被转发到了 灰度环境:
下面在header 中增加 userId 为 110,然后多访问几次,可以看到都被转发到了 生产环境:
边栏推荐
- Nexus configuration Yum repository for repository manager
- 2022 simulated 100 questions and simulated examination of quality officer municipal direction post skills (Quality Officer) examination
- Leetcode 1968. Construct an array whose elements are not equal to the average value of two adjacent elements (yes, finally solved)
- Leetcode 1963. 使字符串平衡的最小交换次数(学习)
- 2022-2028 near infrared (NIR) analyzer Market Status and future development trend in the world and China
- Lake Shore HR series sensors
- Top 10 bone conduction earphones in the list, and five easy-to-use bone conduction earphones are recommended
- Recommandation de la Bibliothèque open source de programmation
- Leetcode 1962. Remove stones to minimize the total amount (should be rounded up)
- Hamad application layout scheme 02 of hashicopy
猜你喜欢

Live800: several ways for intelligent customer service to improve customer experience

Webgl programming guide learning (0)

Telecommuting with cpolar (1)

树莓派获得网络安装系统功能,无需借助其他设备

Precision alignment adjustment platform

基于Qt开发实现的任务管理器

111. minimum depth of binary tree

Distributed file system and enterprise application -- elk enterprise log analysis system

非常值得学习的调度开源库推荐

深度剖析「圈组」关系系统设计 | 「圈组」技术系列文章
随机推荐
数据库“百亿蓝海”中,每位玩家都能找到一叶扁舟 | C位面对面
Riskscanner of multi Cloud Security compliance scanning platform
Ali, tell me about the application scenarios of message oriented middleware?
Distributed file system and enterprise application -- elk enterprise log analysis system
[team learning] task06:for, if, and while
树莓派知识大扫盲
【公开课预告】:MXPlayer OTT音视频转码实践和优化
Raspberry pie obtains the function of network installation system without the help of other devices
一些经典的嵌入式C面试题汇总
2021 年度 Go 开发者调查
浙江大学搞出了一款无人机,自动规避障碍,像鸟一样穿过树林,真正的蜂群来了...
Uniapp settings page Jump effect - navigateto switching effect - Global animationtype animation
使用cpolar远程办公(1)
【clickhouse专栏】新建库角色用户初始化
Nexus configuration Yum repository for repository manager
Nomad application layout scheme 04 of hashicopy (scaling and updating a job)
基于Qt开发实现的任务管理器
Hashicopy之nomad应用编排方案02
Current situation and future development trend of scaffold market in the world and China from 2022 to 2028
数据库优化