Discovery基于apollo自定义规则蓝绿测试
系统架构
系统配置
app-gateway配置
pom
<dependency>
<groupId>com.nepxion</groupId>
<artifactId>discovery-plugin-register-center-starter-eureka</artifactId>
</dependency>
<dependency>
<groupId>com.nepxion</groupId>
<artifactId>discovery-plugin-config-center-starter-apollo</artifactId>
</dependency>
<dependency>
<groupId>com.nepxion</groupId>
<artifactId>discovery-plugin-admin-center-starter</artifactId>
</dependency>
<dependency>
<groupId>com.nepxion</groupId>
<artifactId>discovery-plugin-strategy-starter-gateway</artifactId>
</dependency>
apollo
为了不对本地有影响,创建集群gray,连接本地eureka,新增如下配置
server.port = 8905
spring.application.name = app-gateway
spring.profiles.active = test
spring.cloud.gateway.routes[1].id = app-infra
spring.cloud.gateway.routes[1].uri = lb://app-infra
spring.cloud.gateway.routes[1].predicates[0] = Path=/app/infra/**
spring.cloud.gateway.routes[1].predicates[1].name = RequestBodyCachePredicateFactory
spring.cloud.gateway.routes[1].predicates[1].args.inClass = #{T(String)}
spring.cloud.gateway.routes[1].predicates[1].args.predicate = #{@configPredicate}
spring.cloud.gateway.routes[1].filters[0] = StripPrefix=1
##中台
spring.cloud.gateway.routes[2].id = app-middle
spring.cloud.gateway.routes[2].uri = lb://app-middle
spring.cloud.gateway.routes[2].predicates[0] = Path=/app/middle/**
spring.cloud.gateway.routes[2].predicates[1].name = RequestBodyCachePredicateFactory
spring.cloud.gateway.routes[2].predicates[1].args.inClass = #{T(String)}
spring.cloud.gateway.routes[2].predicates[1].args.predicate = #{@configPredicate}
spring.cloud.gateway.routes[2].filters[0] = StripPrefix=1
eureka.instance.prefer-ip-address = true
eureka.client.service-url.defaultZone = http://127.0.0.1:8761/eureka
#服务默认注册状态
eureka.instance.initialStatus = UP
#客户端从注册中心拉取实例信息的频率,单位为s
eureka.client.registryFetchIntervalSeconds = 2
#ribbon刷新本地缓存的频率,单位ms
ribbon.ServerListRefreshInterval = 2000
spring.codec.max-in-memory-size = 2MB
##### 灰度相关配置
###灰度发布测试
spring.cloud.discovery.reactive.enabled = false
spring.cloud.gateway.discovery.locator.enabled = true
spring.cloud.gateway.discovery.locator.lowerCaseServiceId = true
### 元数据组
eureka.instance.metadataMap.group = app-group
## 调试日志
spring.application.strategy.monitor.enabled = true
spring.application.strategy.rest.intercept.debug.enabled = true
spring.application.strategy.logger.debug.enabled = true
app-middle配置
pom
<dependency>
<groupId>com.nepxion</groupId>
<artifactId>discovery-plugin-register-center-starter-eureka</artifactId>
</dependency>
<dependency>
<groupId>com.nepxion</groupId>
<artifactId>discovery-plugin-config-center-starter-apollo</artifactId>
</dependency>
<dependency>
<groupId>com.nepxion</groupId>
<artifactId>discovery-plugin-strategy-starter-service</artifactId>
</dependency>
apollo
灰度实例
创建gray集群,复制基础信息,并添加如下配置
spring.application.name = app-middle
server.port = 9906
logging.level.root = info
spring.profiles.active = test
### 灰度配置
spring.application.strategy.monitor.enabled = true
spring.application.strategy.rest.intercept.debug.enabled = true
spring.application.strategy.logger.debug.enabled = true
spring.application.strategy.version.prefer.enabled = true
eureka.instance.metadataMap.group = app-group
eureka.instance.metadataMap.version = 1.1
eureka.instance.metadataMap.region = gray
eureka.instance.metadataMap.env = gray
eureka.client.serviceUrl.defaultZone = http://127.0.0.1:8761/eureka
# eureka注册优先使用ip地址
eureka.instance.prefer-ip-address = true
## 心跳间隔(默认30秒)
eureka.instance.leaseRenewalIntervalInSeconds = 5
# 定时刷新本地缓存时间
eureka.client.registryFetchIntervalSeconds = 5
# ribbon缓存时间
ribbon.ServerListRefreshInterval = 2000
# 连接超时时间(默认1秒)
feign.client.config.default.connectTimeout = 10000
# 读取超时时间(默认1秒)
feign.client.config.default.readTimeout = 12000
正常实例
创建normal集群,复制基础信息,并添加如下配置
spring.application.name = app-middle
server.port = 8906
logging.level.root = info
spring.profiles.active = test
##灰度配置
spring.application.strategy.monitor.enabled = true
spring.application.strategy.rest.intercept.debug.enabled = true
spring.application.strategy.logger.debug.enabled = true
spring.application.strategy.version.prefer.enabled = true
eureka.instance.metadataMap.group = app-group
eureka.instance.metadataMap.version = 1.0
eureka.instance.metadataMap.region = test
eureka.instance.metadataMap.env = test
eureka.client.serviceUrl.defaultZone = http://127.0.0.1:8761/eureka
# eureka注册优先使用ip地址
eureka.instance.prefer-ip-address = true
## 心跳间隔(默认30秒)
eureka.instance.leaseRenewalIntervalInSeconds = 5
# 定时刷新本地缓存时间
eureka.client.registryFetchIntervalSeconds = 5
# ribbon缓存时间
ribbon.ServerListRefreshInterval = 2000
# 连接超时时间(默认1秒)
feign.client.config.default.connectTimeout = 10000
# 读取超时时间(默认1秒)
feign.client.config.default.readTimeout = 12000
app-infra配置
pom
<dependency>
<groupId>com.nepxion</groupId>
<artifactId>discovery-plugin-register-center-starter-eureka</artifactId>
</dependency>
<dependency>
<groupId>com.nepxion</groupId>
<artifactId>discovery-plugin-config-center-starter-apollo</artifactId>
</dependency>
<dependency>
<groupId>com.nepxion</groupId>
<artifactId>discovery-plugin-strategy-starter-service</artifactId>
</dependency>
apollo
灰度实例
创建gray集群,复制基础信息,并添加如下配置
spring.application.name = app-infra
server.port = 9901
logging.level.root = info
spring.profiles.active = test
##灰度发布测试
spring.application.strategy.monitor.enabled = true
spring.application.strategy.rest.intercept.debug.enabled = true
spring.application.strategy.logger.debug.enabled = true
eureka.instance.metadataMap.group = app-group
eureka.instance.metadataMap.version = 1.1
eureka.instance.metadataMap.region = gray
eureka.instance.metadataMap.env = gray
eureka.client.serviceUrl.defaultZone = http://127.0.0.1:8761/eureka
# eureka注册优先使用ip地址
eureka.instance.prefer-ip-address = true
## 心跳间隔(默认30秒)
eureka.instance.leaseRenewalIntervalInSeconds = 5
# 定时刷新本地缓存时间
eureka.client.registryFetchIntervalSeconds = 5
# ribbon缓存时间
ribbon.ServerListRefreshInterval = 2000
# 连接超时时间(默认1秒)
feign.client.config.default.connectTimeout = 10000
# 读取超时时间(默认1秒)
feign.client.config.default.readTimeout = 12000
正常实例
创建normal集群,复制基础信息,并添加如下配置
spring.application.name = app-infra
server.port = 8901
logging.level.root = info
spring.profiles.active = test
#灰度发布调试
spring.application.strategy.monitor.enabled = true
spring.application.strategy.rest.intercept.debug.enabled = true
spring.application.strategy.logger.debug.enabled = true
eureka.instance.metadataMap.group = app-group
eureka.instance.metadataMap.version = 1.0
eureka.instance.metadataMap.region = test
eureka.instance.metadataMap.env = test
eureka.client.serviceUrl.defaultZone = http://127.0.0.1:8761/eureka
# eureka注册优先使用ip地址
eureka.instance.prefer-ip-address = true
## 心跳间隔(默认30秒)
eureka.instance.leaseRenewalIntervalInSeconds = 5
# 定时刷新本地缓存时间
eureka.client.registryFetchIntervalSeconds = 5
# ribbon缓存时间
ribbon.ServerListRefreshInterval = 2000
# 连接超时时间(默认1秒)
feign.client.config.default.connectTimeout = 10000
# 读取超时时间(默认1秒)
feign.client.config.default.readTimeout = 12000
app-gateway自定义灰度路由规则配置
在app-gateway中编写如下代码
package cn.example.app.gateway.gray;
import com.nepxion.discovery.plugin.strategy.gateway.filter.DefaultGatewayStrategyRouteFilter;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
/**
* 适用于A/B Testing或者更根据某业务参数决定蓝绿灰度路由路径。可以结合配置中心分别配置A/B两条路径,可以动态改变并通 知// 当Header中传来的用户为张三,执行一条路由路径;为李四,执行另一条路由路径
*/
@Slf4j
public class MyGatewayStrategyRouteFilter extends DefaultGatewayStrategyRouteFilter {
//兜底路由
private static final String DEFAULT_A_ROUTE_VERSION = "{\"app-middle\":\"1.0\", \"app-infra\":\"1.0\"}";
@Value("${blue.route.version:" + DEFAULT_A_ROUTE_VERSION + "}")
private String blueRouteVersion;
@Value("${green.route.version:" + DEFAULT_A_ROUTE_VERSION + "}")
private String greenRouteVersion;
@Override
public String getRouteVersion() {
String version = strategyContextHolder.getHeader("APP-VERSION");
log.info("自定义灰度规则 version:{}", version);
if (version.equals("v1.0")) {
log.info("执行全链路路由:{}", blueRouteVersion);
return blueRouteVersion;
}
if (version.equals("v1.1")) {
log.info("执行全链路路由:{}", greenRouteVersion);
return greenRouteVersion;
}
return DEFAULT_A_ROUTE_VERSION;
}
}
在配置类里@Bean方式进行过滤类创建,覆盖框架内置的过滤类
package cn.example.app.gateway;
import cn.example.app.gateway.gray.MyGatewayStrategyRouteFilter;
import com.ctrip.framework.apollo.spring.annotation.EnableApolloConfig;
import com.nepxion.discovery.plugin.strategy.gateway.filter.GatewayStrategyRouteFilter;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.Bean;
@EnableApolloConfig
@SpringBootApplication
public class AppGatewayApplication {
public static void main(String[] args) {
SpringApplication.run(AppGatewayApplication.class, args);
}
//自定义网关
@Bean
public GatewayStrategyRouteFilter serviceStrategyRouteFilter() {
return new MyGatewayStrategyRouteFilter();
}
}
在app-gateway的apollo中增加如下配置
##蓝路由
blue.route.version = {"app-middle":"1.0", "app-infra":"1.0"}
##绿路由
green.route.version = {"app-middle":"1.1", "app-infra":"1.1"}
编写测试接口
app-infra
@RestController
@RequestMapping("/test/")
@Slf4j
public class DeployTestController {
@Autowired
private PluginAdapter pluginAdapter;
public String getPluginInfo(String value) {
return pluginAdapter.getPluginInfo(value);
}
/**
*
*此接口返回当前服务的元数据信息包括:版本、端口、区域、环境等
**/
@GetMapping(path = "/rest/{value}")
public String rest(@PathVariable(value = "value") String value) {
value = getPluginInfo(value);
log.info("调用路径:{}", value);
return value;
}
}
编写feign client,并deploy
package cn.example.infra.client;
@FeignClient("app-infra")
@RequestMapping("/test/")
public interface InfraTestClient {
@GetMapping(path = "/rest/{value}")
String rest(@PathVariable(value = "value") String value);
}
app-middle
配置pom,并引入InfraTestClient调用rest方法
@RestController
@RequestMapping("/middle/test")
@Slf4j
public class TestController {
@Autowired
private InfraTestClient infraTestClient;
@Autowired
private PluginAdapter pluginAdapter;
public String getPluginInfo(String value) {
return pluginAdapter.getPluginInfo(value);
}
/**
*
*此接口返回当前服务的元数据信息包括:版本、端口、区域、环境等
**/
@GetMapping(path = "/rest/{value}")
public String rest(@PathVariable(value = "value") String value) {
value = getPluginInfo(value);
value =infraTestClient.rest(value);
log.info("调用路径:{}", value);
return value;
}
}
测试
分别启动如下服务
接口测试
##绿色请求
curl -X GET "http://127.0.0.1:8905/app/middle/test/rest/1" -H "accept: */*" -H "APP-VERSION: v1.1"
## 蓝色请求
curl -X GET "http://127.0.0.1:8905/app/middle/test/rest/1" -H "accept: */*" -H "APP-VERSION: v1.0"
##兜底请求
curl -X GET "http://127.0.0.1:8905/app/middle/test/rest/1" -H "accept: */*" -H "APP-VERSION: v3.0"
在调用脚本请求之前,再看一眼apollo配置的规则
##蓝色请求
blue.route.version = {"app-middle":"1.0", "app-infra":"1.0"}
## 绿色请求
green.route.version = {"app-middle":"1.1", "app-infra":"1.1"}
测试结果
绿色请求,期望返回结果包含:v1.1
蓝色请求,期望返回结果包含:v1.0
兜底请求测试,期望返回结果包含:v1.0
到此整个实例完整结束。
QA
-
目前的配置方式,如果不通过网关调用,比如app-infra调用app-middle,这种配置是不是就不支持了?
答:是的,如果服务内部之间调用这种就没法支持了,后续章节会给出对应的解决方案。
-
如果A部门的服务没有集成discovery调用B部门的服务app-infra,而app-infra正好有2个版本,那他是不是都有机会命中调用?这样是不是不合理?
答:是的,都有机会命中,B部门应该提供稳定的app-infra供A部门的服务调用,后续章节会给出对应的解决方案。