使用kmse实现服务间的灰度发布功能
通过一个简单的实例说明开发者如何通过kmse进行服务间的灰度发布功能。kmse的灰度发布功能可分为基于流量灰度和全链路灰度。
一、准备客户端和服务端两个demo
这里演示如何快速实践服务路由功能。假如现在有两个微服务 client 和 server,想实现 client 调用 server 时,通过灰度规则对服务间的请求流量做定向路由。
参考服务开发文档,下载server和client两个demo。
查看依赖,实践服务鉴权只需要依赖以下maven组件,调用端和被调用端都只需要如下依赖。
<dependency>
<groupId>com.ksyun.kmse</groupId>
<artifactId>spring-cloud-kmse-starter-route</artifactId>
<version>1.0-release</version>
</dependency>
准备测试的java代码,因为需要灰度功能,所以我们在controller加入特殊的逻辑用来体现灰度功能,server端提供服务的controller:
package com.cn.controller;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import javax.annotation.PostConstruct;
@RequestMapping({"/server"})
@RestController
@Configuration
public class ServerController {
private static final Logger log = LoggerFactory.getLogger(ServerController.class);
public ServerController() {
}
@GetMapping({"/{id}"})
public String account(@PathVariable("id") Integer id) {
System.out.println(appName);
System.out.println(instance);
return version + "#" + instance;
}
@Value("${spring.cloud.consul.discovery.tags:version=v1}")
private String tag;
private String version;
@Value("${spring.cloud.consul.discovery.instanceId:1}")
private String instance;
@GetMapping
public String routeServer() {
System.out.println(appName);
System.out.println(instance);
return version + "#" + instance;
}
@PostConstruct
private void parseVersion() {
System.out.println(String.format("configName={%s}", configName));
String[] split = tag.split(",");
for (String s : split) {
if (s.startsWith("version=")) {
version = s.substring(s.indexOf("version=") + 8);
break;
}
}
}
}
client端提供的远程调用client:
package com.cn.controller;
import com.cn.client.Client;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.HashMap;
import java.util.Map;
@RequestMapping({"/client"})
@RestController
public class ClientController {
private static final Logger log = LoggerFactory.getLogger(ClientController.class);
@Autowired
private Client client;
public ClientController() {
}
@GetMapping({"/{id}"})
public String account(@PathVariable("id") Integer id) {
log.info("调用client " + id);
Map<String, Integer> mapVersion = new HashMap<>(), mapInstance = new HashMap<>();
StringBuilder lbResult = new StringBuilder();
lbResult.append("\n");
for (int i = 0; i < id; i++) {
String result = client.getById(i);
String[] split = result.split("#");
String version = split[0];
String instance = split[1];
if (!mapVersion.containsKey(version)) { mapVersion.put(version, 1);
} else {
mapVersion.put(version, mapVersion.get(version) + 1);
}
if (!mapInstance.containsKey(instance)) {
mapInstance.put(instance, 1);
} else {
mapInstance.put(instance, mapInstance.get(instance) + 1);
}
lbResult.append(instance).append("\n");
}
log.info("版本统计 = {}", mapVersion.toString());
log.info("实例统计 = {}", mapInstance.toString());
log.info("负载均衡顺序 = {}", lbResult.toString());
return mapVersion.toString() + " " + mapInstance.toString() + " " + lbResult.toString();
}
}
至此两个测试应用的java代码准备完毕。
二、对基于流量的灰度进行测试
将server服务用maven(mvn package)命令打包,然后在本地启动consul作为注册中心。
使用如下两条命令分别运行server的v1和v2版本:
java -Dspring.cloud.consul.discovery.tags=namespace=ns1,version=v1 -Dserver.port=8081 -jar server.jar
java -Dspring.cloud.consul.discovery.tags=namespace=ns1,version=v2 -Dserver.port=8082 -jar server.jar
运行成功后,在consul-ui上看到如下的注册信息,可以看到分别有version不同的tag:
因为client是调用方,所以在client的resources文件夹中新增application.yaml文件,写入灰度配置。配置的意思是该应用访问server的流量,90%的流量发送到v2版本,10%的流量发送到v1版本。
ksyun:
cloud:
route:
rule:
client:
virtualService:
- route:
- destination:
host: server
subset: v1
weight: 10
- destination:
host: server
subset: v2
weight: 90
因为本地调试的特殊性,所以还需要加入一个命名空间的参数,然后运行client,访问测试接口:
http://127.0.0.1:8080/client/100
从请求返回体和日志都可以看到,v1、v2的流量确实是按照规则来分配的。
三、对全链路的灰度进行测试
1. 简单演示
这里采用uri来进行演示
ksyun:
cloud:
route:
rule:
mytest:
virtualService:
- match:
- uri:
endUser:
prefix: /client/10
route:
- destination:
host: server
subset: v1
这个配置的含义是"uri前缀等于/client/10的请求流量全部路由到v1版本"。 重启client后,再次调用测试接口。我们期望形如"/client/10"的请求全部打到v1版本。
http://127.0.0.1:8080/client/10
从请求返回体和日志都可以看到,流量全部路由到了v1版本,这就符合了我们的预期。
这里我们继续刚才的配置测试后缀拦截,将client的配置改为如下,这种类型的配置会将形如以下的请求流量都达到server的v2。
ksyun:
cloud:
route:
rule:
mytest:
virtualService:
- match:
- uri:
endUser:
suffix: /client/10
route:
- destination:
host: server
subset: v2
2. 更多的场景展示
由于参数类型,匹配规则,逻辑关系三者都能组合出众多的规则,所以下文例举了一些请求场景来作为参考,可以根据实际业务中的场景来做自定义扩展。
2.1 header前缀匹配
如果场景中有headers: name前缀匹配zhangsan 的请求,那么这种类型的配置会将形如以下的请求流量都路由到server的v1。
ksyun:
cloud:
route:
rule:
mytest:
virtualService:
- match:
- headers:
endUser:
prefix: "zhangsan"
param: name
route:
- destination:
host: server
subset: v1
2.2 uri正则匹配
如果场景中有queryParams:type正则匹配-?[1-9]*)$ 的请求,那么这种类型的配置会将形如以下的请求流量都路由到server的v1。
ksyun:
cloud:
route:
rule:
test:
virtualService:
- match:
- queryParams:
endUser:
regular: ^(-?[1-9]\d*)$
param: type
route:
- destination:
host: server
subset: v1
2.3 多规则匹配,"且"逻辑关系
如果场景中有headers:name=zhangsan, 且headers:age=20的请求,那么这种类型的配置会将形如以下的请求流量都路由到server的v1。
ksyun:
cloud:
route:
rule:
test:
virtualService:
- match:
- headers:
endUser:
exact: "20"
param: age
- headers:
endUser:
exact: zhangsan
param: name
route:
- destination:
host: server
subset: v1
2.4 多规则匹配,"或"逻辑关系
如果场景中有headers:age前缀匹配20, 或headers:name后缀匹配zhangsan 的请求,那么这种类型的配置会将形如以下的请求流量都路由到server的v1中。
ksyun:
cloud:
route:
rule:
test:
virtualService:
- match:
- headers:
endUser:
prefix: "20"
param: age
route:
- destination:
host: server
subset: v1
- match:
- headers:
endUser:
suffix: zhangsan
param: name
route:
- destination:
host: server
subset: v1
文档内容是否对您有帮助?
评价建议不能为空
非常感谢您的反馈,我们会继续努力做到更好!