-
Notifications
You must be signed in to change notification settings - Fork 20
09. 使用支付服务为例实现灰度发布(金丝雀测试)(0.0.6)
0.0.5-SNAPSHOT中我们已经完成了支付服务,现在我们以它为例在0.0.6-SNAPSHOT中修改边缘服务实现灰度发布(金丝雀测试)。
以前矿工下矿洞前,会先放一只金丝雀进去试探氧气含量以及是否有有毒气体,金丝雀测试由此得名;生产环境中微服务都是多实例运行的,更新(发布)微服务,为避免风险,往往先更新1个实例,或一个很小的比例例如1%,承担很低的流量做验证,如果没有问题,则使用手工或自动的方式逐步更新剩余的实例,完成整体升级。
发布前:
发布中:
发布结束:
灰度发布需要边缘服务支持动态设置路由策略,常见的路由策略有:
- 基于匹配特定Header的策略
这个策略是通过匹配指定Header的值,决定转发微服务的版本;例如我们上线新版本后,用户界面提示是否尝试新版本,如果用户选择切换,则将特定的值写入请求的Header,此用户的所有后继请求都路由至新版本。这种方式给予了用户很大的灵活性和选择权,缺点是无法控制流量(因为完全要看大多数用户的选择)。
- 基于用户标识的策略
这个策略是通过匹配用户的标识例如ID,决定转发微服务的版本;例如我们上线新版本后,只将以Test开头的测试账号的请求路由至新版本。这种方式对客户端透明,也能够控制流量(限制了账号数),缺点是需要结合用户系统做一定的修改才能够支持,因为边缘服务无法直接获取用户信息。
- 基于流量控制的策略
这个策略是通过定制弹性伸缩(loadbalance)策略,决定转发微服务的版本;例如如果使用的是RoundRobin,则路由比例与版本实例数成正比,如果我们希望路由到新版本的流量为一个比较低的值例如1%但实例数比例远高于此值,则需要做定制。
- 基于匹配调用方IP的策略
这个策略是通过匹配调用发起方的IP,决定转发微服务的版本;例如在多地域多集群环境下,可以通过IP识别对方大致身份(例如所属省份、子系统等)。
我们先实现第一种策略。
我们改进微服务配置中与edge相关的部分,增加灰度发布策略:
edge:
routing-short-path:
user: user-service
payment: payment-service
dark-launch-rules:
#如果请求的User-Agent中包含vip,则指向0.0.2版本的payment-service(payment-service-canary)
payment-service: "{\"headerRules\":{\"User-Agent\":[{\"operator\":\"CONTAINS\",\"value\":\"vip\",\"version\":\"0.0.2\"}]},\"defaultVersion\":\"0.0.1\"}"
edge.dark-launch-rules.payment-service配置定义了payment-service对应的灰度规则,JSON格式,层次结构说明如下:
headerRules:Header匹配规则集合
{Header名}:要匹配的Header名,例如User-Agent
[匹配规则集合]:规则集合,每一个规则包含operator,value和version
operator:匹配操作符,支持START_WITH,END_WITH,CONTAINS和EQUALS
value:匹配的值
version:如果匹配,路由版本
defaultVersion:如果没有匹配任何规则,则默认版本(一般设置为老版本)
因此可以看出上面的payment-service微服务配置的灰度策略为:如果存在User-Agent头并且其中的内容包含vip这个字符串,则路由到0.0.2版本,否则默认路由到0.0.1版本。
通过策略配置决定路由版本:
private void onRequest(RoutingContext context) {
Map<String, String> pathParams = context.pathParams();
//从匹配的param0拿到{ServiceComb微服务Name}
final String service = pathParams.get("param0");
//从匹配的param1拿到{服务路径&参数}
String operationPath = "/" + pathParams.get("param1");
//还记得我们之前说的做出一点点改进吗?引入一个自定义配置edge.routing-short-path.{简称},映射微服务名;如果简称没有配置,那么就认为直接是微服务的名
final String serviceName = DynamicPropertyFactory.getInstance()
.getStringProperty("edge.routing-short-path." + service, service).get();
darkLaunchRules.computeIfAbsent(serviceName, s -> {
DynamicStringProperty property = DynamicPropertyFactory.getInstance()
.getStringProperty("edge.dark-launch-rules." + serviceName, "");
return parseRule(property.getValue());
});
//创建一个Edge转发
EdgeInvocation edgeInvocation = new EdgeInvocation();
//设定灰度版本策略
edgeInvocation.setVersionRule(darkLaunchRules.get(serviceName).matchVersion(context.request().headers().entries()));
edgeInvocation.init(serviceName, context, operationPath, httpServerFilters);
//处理Filter链并转发请求
loopExecuteEdgeFilterInChain(0, serviceName, operationPath, context, edgeInvocation);
}
private DarkLaunchRule parseRule(String config) {
try {
if (StringUtils.isNotEmpty(config)) {
return OBJ_MAPPER.readValue(config, DarkLaunchRule.class);
}
} catch (IOException e) {
LOGGER.error("parse rule failed", e);
}
return new DarkLaunchRule();
}
老版本的支付服务做风控,授信额度不能超过一百万,现在经济形势较好,信贷宽松,因此能尝试对vip用户将授信额度提升为两百万,为稳妥起见,采用灰度发布逐步升级。
为了能使两个版本的代码并存,我们将payment-service中的代码和配置全部拷贝进入新的微服务payment-service-canary(实际的开发并不需要这么做),并修改:
首先我们需要修改微服务的版本:
APPLICATION_ID: scaffold
service_description:
name: payment-service
#版本从0.0.1升级为0.0.2
version: 0.0.2
//信用额度
private static final double CREDIT_LIMIT = 2000000;
可以看到,相比0.0.1版本中的CREDIT_LIMIT,授信额度审查已经被放宽到两百万。
我们启动边缘服务、用户服务和老版本的支付服务,使用zhengyangyong账户登录系统,然后做一笔一百万的支付:
支付成功,现在我们再来做一笔金额为888的支付:
结果返回余额不足,因为老版本的支付服务只允许一百万额度,现在我们启动一个新版本的支付服务实例(模拟灰度发布),之后在Service Center UI中,我们已经可以看到新旧版本的支付系统并存:
再次请求:
仍然失败,我们在Header中增加User-Agent并设置值为vip:
返回成功,表明请求已经被路由至额度检查更宽松的0.0.2版本微服务实例。