1
背景
灰度发布可以在服务正式上线前,提前用小流量对新功能进行验证,提前发现问题,避免故障影响所有用户,对业务稳定性非常有价值。
得物社区后端技术栈以 golang 为主,本文记录了社区后端在灰度环境建设过程中遇到的挑战,以及对应的探索和实践。
名词解释
小得物:得物内部小流量灰度环境。 ARK:得物内部配置中心。 DLB:得物内部负载均衡中间件。 DMQ:得物内部消息中间件。 DRPC:golang 后端 RPC 系统。
2
2.1 小得物 V1
小得物可以只部署部分应用。 未部署小得物的 HTTP 入口应用,HTTP 流量导向生产。 未部署小得物的 gRPC 下游应用,gRPC 流量导向生产。
要实现小得物环境只部署部分应用,正确路由流量而不报错,需要网关层、RPC 等调用层感知集群内后端服务有没有部署。
Ingress 这层,其实相当于接了 k8s 的注册中心,它是可以感知到集群是否有可用 upstream。但开源配置无法支持这个需求,二开比较复杂,这个也不在社区的控制范围内。
这个时期社区应用正在进行容器新老集群迁移,在容器 Ingress 之前加了一层 DLB(可以简单理解为 Nginx),通过 location 来区分应用是否部署新集群,以及新老集群流量灰度。
年中时社区第一批约 15 个 C 端应用上线小得物,同时对小得物环境的监控告警等基础设施进行了完善。
2.2 小得物 V2
V1 的核心问题在于引流机制是 DNS。DNS 的优势在于它是在客户端生效,是去中心化的。但也有很多缺点,比如控制维度单一,只有客户端 IP、地域。只依靠这个,灰度流量大小难以精准控制,想要的基于 UID、header 的灰度规则也没法实现。
想要做 UID 灰度引流,一般都是在入口网关上做。灰度配置可能经常需要开关、调整流量大小,如果配置错误或出现 bug,则影响所有流量。
因此想到一个折中的方案,从生产 DLB 根据 UID 引入 5% 灰度流量至小得物 DLB,小得物上再通过二次灰度规则控制流量大小在 0-5%。最大流量限定为 5%,生产只配置一次,后续开关、规则调整均在小得物 DLB 上进行。虽然多用了一个 DLB,但减少生产 DLB 配置变更频率,缩小了爆炸半径。
之前做新老集群迁移的生产 DLB,本来准备下掉,现在正好可以利用起来。对 DLB 进行了版本升级,配置好灰度规则后,就有了现在 V2 的架构。
架构升级后:
灰度流量可按 uid 规则引入,灰度用户流量总是进小得物,用户范围可控,规则清晰。 灰度流量入口与交易互不影响,流量大小可在 0-5% 范围内灵活调整。灰度流量规则是通过旁路控制,不在生产主链路 DLB 上进行,最大流量值限制为 5%,缩小爆炸半径。 xdw DLB 配置通过 openAPI 控制,且与发布平台打通,小得物新版本发布可 0-5% 梯度引流验证。 UID 规则外,添加 header 头引流规则,测试验证方便。App 可一键切换至小得物,由用户自由选择。验证小得物 api 时带上 header 头即可路由至小得物,再加上 trace 2.0 全面覆盖,方便定位流量路径。
# uid 路由规则
uidRoute:
start: 2000
end: 2500
# header 头路由规则
headerRoute:
X-Flow-Flag: xdw
3
3.1 依赖队列自动生成
对版本分支、线上分支分别进行静态扫描。 使用 go 标准库的 parser 包将其解析 为 AST 语法树,根据查找 proto client 桩代码包引用生成单应用 RPC 调用依赖图。 将两个版本依赖图进行 diff,找出版本变化部分。 将版本所有应用的依赖图进行关联,最终生成版本依赖图。
同时与发布平台打通,发布时触发静态分析,自动生成发布依赖状态图。以前都是版本 owner 手动画这个图,在办公沟通群众同步。通过自动化手段,大幅提高了效率和用户体验。
在前文提到的小得物 V2 架构中,灰度流量在社区小得物 DLB 中控制。因此在小得物发布过程中,可以直接通过 openAPI 将小得物流量摘除。没有了流量,就可以无视应用间依赖,直接批量将所有应用并发部署,大幅提高小得物环境部署效率。
同时摘流后,再通过 API 将流量梯度拉升,从 0% 缓慢提升至 5%,每次引流都会触发稳定生产 SOS 事件中心的自动巡检,根据配置的巡检规则,计算出得分,展示与七天平均值偏差较大的异常点,帮助版本 owner 提前发现灰度问题。
效果图:
4
RPC 路由这个功能,大多数据 RPC 调用系统都有。社区目前的 RPC 是基于 grpc-go 扩展实现的,很多人都说 grpc 没有服务治理功能,但实际上 grpc 有着良好的扩展性和丰富的生态。得物 go 框架基于 grpc-go 只用了千余行代码即可实现拥有服务发现、多注册中心、多服务名、地址路由、自定义 interceptor 等完备功能的 RPC 调用系统。
env == "xdw"
metadata: env: xdwregistries: # grpc 协议 nacos-grpc: type: nacos-grpc url: 协议 nacos-http: type: nacos-http url: 备用注册中心 nacos-bak: type: nacos-grpc url: 服务 java-nacos: type: java-dubbo url: 多注册中心引用 - nacos-grpc - nacos-bak direct: type: direct priority: 15client: requestTimeout: 700 pickers: - target: "*" desc: "优先使用小得物地址" match: tag: env == "xdw" - target: "*" desc: "兜底,无小得物地址时使用所有地址" match: tag: "*" targetMap: sns-aaa: services: # 多服务名 - registryName: muilti-nacos serviceName: sns-aaa - registryName: nacos-http serviceName: sns-aaa-http sns-bbb: services: - registryName: muilti-nacos serviceName: sns-bbb # java dubbo 服务 java-ccc: services: - registryName: java-nacos serviceName: "com.xxx.DubboTestGrpcServiceGrpc$ITestGrpcService:1.0:" # 直连地址 direct-ddd: services: - registryName: direct serviceName: ddd.xxx.com:8080
drpc:
remoteConfig:
type: ark
url:
client:
targetMap:
sns-aaa:
# 超时配置
methodTimeout:
AaaService/FooMethod: 100
sns-bbb:
methodTimeout:
BbbService/BarMethod: 50
社区小得物与生产环境公用一套 DB、 MQ 中间件。应用代码中 MQ producer、comsuer,HTTP、GRPC API 是在一个进程中。如果消息没有隔离逻辑,小得物打开消费,则会与生产节点成为同级消费者,消费生产消息。而小得物环境机器配置较低,消费速度慢会影响业务。
在没有 MQ 消息隔离前,采取一个笨办法,直接关闭小得物 MQ 消费。但这样小得物的消息是靠生产处理,在小得物有 MQ 相关新版本变更时,需要考虑新老兼容的问题。
随着社区阿里云 MQ 迁移 DMQ 进入收尾阶段,DMQ Go SDK 也趋于稳定,开始尝试使用程序化方案解决 MQ 灰度消费的问题。
最开始跟小得物团队了解了一下最初的方案,小得物和生产使用不同的 MQ 实例,这样就要求 producer、consumer 在小得物全量部署。对于跨业务域的 topic 需要消息同步机制。感觉复杂度过高,资源成本和维护成本都很高。
后面看到一篇 阿里云分享的 RocketMQ 灰度方案,其采用消息打标、group 隔离、SQL 属性过滤实现消息灰度,感觉这才是理想的方案。
这里说一下 tag 过滤和 SQL 过滤,tag 过滤大家比较常用,但一条消息只能有一个 tag,常被业务占用,且不能支持 != 这样的条件。而 SQL 过滤就灵活得多,可以使用消息 properties 自定义 kv 键值对,SQL 的 NOT、BETWEEN、IN 等关键词都可以使用。
找中间件团队沟通,他们表示 SQL 过滤性能较差,暂不支持。建议使用 Java 染色环境类似的方案,在客户端过滤。虽然客户端过滤,有很多无效的网络传输,但成本较低,只需要改造一下业务框架中 MQ SDK 即可,也能解决 MQ 灰度的问题。经过压测,小得物环境过滤生产环境高 QPS 生产的消息或是 group 积压的大量消息, 对应用不会造成较大的性能影响,于是采用了此方案。
4.2.1 消息消费 consumer 隔离
consumer 消费的隔离比较简单,MQ 的机制是不同 group 消息消费都是独立的,每个 group 都能收到topic 全量消息。
在业务框架中根据染色环境配置,增加不同的处理逻辑。
如果是染色环境(小得物):
producer 发送消息时,在消息 properties 中添加流量标 X-Flow-Flag=[prefix]。
consumer 启动时自动给配置的 group 添加 [prefix]。消费时过滤掉 properties 不包含流量标 X-Flow-Flag=[prefix] 的消息,直接 ack。
trafficRoute: colorEnv: xdw
producer 发送消息时,无特殊处理。 consumer 启动时使用配置中的 group。消费时过滤掉 properties 包含流量标 X-Flow-Flag=[prefix] 的消息,直接 ack。
trafficRoute: excludeEnvList: [xdw]
4.2.2 事务消息 producer 隔离
查看了一下 DMQ 的 Java 源码,发现 Boroker 回查时是通过消息 properties 中的 group 来查找在线 producer。那么跟 consumer 类似,给 trans producer 配上 group ,给小得物 group 加上环境前缀即可实现事务回查隔离。用于 trans producer 的 group 只是一个标识,甚至不需要在 DMQ 后台申请。
5
总结
在业务稳定性上,能在正式上线前发现了一些测试、预发环境难以发现的问题,缩小影响范围,减少上线出问题后匆忙排查、紧急回滚的紧张时刻,降低了系统风险。 在开发效率上,通过摘流批量发布、依赖梯队自动生成、发布流程编排等手段,大大降低了版本发布人力和时间成本。以前版本十来个应用发布,需要多个开发介入,前后依赖等待、观察,耗费较大人力,生产发布需要 4 个小时以上;现在由一个版本 owner 负责,在小得物验收通过后,一键发布至生产环境,小得物加生产在 2 个小时内能搞定。几乎解放了 0.5 天的时间,开发可以把这个时间投入到下个版本的技术方案设计上去。
和前端同学合作,打通中后台、H5 页面前后端灰度链路。 涉及外部业务域、数据同步中间件等场景的 MQ 消息和灰度流量闭环。 扩大灰度窗口期,下探“深水区”,优化QA验证和产品走查流程。 优化开发用户体验,降低小得物环境维护成本。例如:抽出小得物、生产公共配置,只维护一份。
*文/ 无风