草庐IT

得物社区 Golang 灰度环境探索和实践

无风 2023-03-28 原文

1、背景

灰度发布可以在服务正式上线前,提前用小流量对新功能进行验证,提前发现问题,避免故障影响所有用户,对业务稳定性非常有价值。

得物社区后端技术栈以 golang 为主,本文记录了社区后端在灰度环境建设过程中遇到的挑战,以及对应的探索和实践。

名词解释

  • 小得物:得物内部小流量灰度环境。
  • ARK:得物内部配置中心。
  • DLB:得物内部负载均衡中间件。
  • DMQ:得物内部消息中间件。
  • DRPC:golang 后端 RPC 系统。
本文对涉及内部敏感信息部分做了打码和脱敏处理,敬请理解。

2、小得物灰度引流架构优化

2.1    小得物 V1

跟 Java 网关对接注册中心不同,社区 HTTP 是依赖容器 Service 和 Ingress。

对社区来说,因为只有 C 端有外部流量的应用才有部署小得物的价值,所以希望:

  • 小得物可以只部署部分应用。
  • 未部署小得物的 HTTP 入口应用,HTTP 流量导向生产。
  • 未部署小得物的 gRPC 下游应用,gRPC 流量导向生产。
gRPC 流量比较简单,通过 RPC 系统流量路由功能即可实现,这个在后面流量路由部分会介绍。

要实现小得物环境只部署部分应用,正确路由流量而不报错,需要网关层、RPC 等调用层感知集群内后端服务有没有部署。

Ingress 这层,其实相当于接了 k8s 的注册中心,它是可以感知到集群是否有可用 upstream。但开源配置无法支持这个需求,二开比较复杂,这个也不在社区的控制范围内。

这个时期社区应用正在进行容器新老集群迁移,在容器 Ingress 之前加了一层 DLB(可以简单理解为 Nginx),通过 location 来区分应用是否部署新集群,以及新老集群流量灰度。

于是参考生产环境在小得物 Ingress 之前加了一层 DLB,通过 location 和 upstream 配置实现流量有生产应用兜底。虽然依赖人工配置,但中间件都是现成的,而且这部分配置变化频率较低,只有应用上下架时需要修改。

年中时社区第一批约 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   依赖队列自动生成

每个版本版本 owner 都需要整理版本清单,标记出应用的依赖关系,最后手动导入到发布平台,生产依赖梯队。

组内大佬觉得这些工作可以自动化完成,便写了一个代码静态分析工具来解决:

  • 对版本分支、线上分支分别进行静态扫描。
  • 使用 go 标准库的 parser 包将其解析 为 AST 语法树,根据查找 proto client 桩代码包引用生成单应用 RPC 调用依赖图。
  • 将两个版本依赖图进行 diff,找出版本变化部分。
  • 将版本所有应用的依赖图进行关联,最终生成版本依赖图。
可以有人会问,为什么不基于 trace 来做?原因新功能可能没有流量,或是有些路径执行不到,trace 数据需要线上流量跑一段时间才能完整。而通过静态分析,源码中没有秘密,只要是写在代码中的依赖都能覆盖到。这套静态分析工具还可以实现循环调用分析,RPC 圈复杂度分析,帮助开发进行微服务治理。

同时与发布平台打通,发布时触发静态分析,自动生成发布依赖状态图。以前都是版本 owner 手动画这个图,在办公沟通群众同步。通过自动化手段,大幅提高了效率和用户体验。

流程图:

效果图:

3.2   批量发布、梯度引流、灰度分析

在发布平台和稳定生产小得物团队的帮助下,社区小得物发布使用了新的批量发布流程。

发布时同时支持同时发布 ARK 配置,版本变更在发布平台内完成闭环。不必喊应用 owner 去 ARK 修改配置,再人工确认后,再发布程序代码。

在前文提到的小得物 V2 架构中,灰度流量在社区小得物 DLB 中控制。因此在小得物发布过程中,可以直接通过 openAPI 将小得物流量摘除。没有了流量,就可以无视应用间依赖,直接批量将所有应用并发部署,大幅提高小得物环境部署效率。

同时摘流后,再通过 API 将流量梯度拉升,从 0% 缓慢提升至 5%,每次引流都会触发稳定生产 SOS 事件中心的自动巡检,根据配置的巡检规则,计算出得分,展示与七天平均值偏差较大的异常点,帮助版本 owner 提前发现灰度问题。

效果图:

4、全链路灰度

4.1  RPC 调用路由

RPC 路由这个功能,大多数据 RPC 调用系统都有。社区目前的 RPC 是基于 grpc-go 扩展实现的,很多人都说 grpc 没有服务治理功能,但实际上 grpc 有着良好的扩展性和丰富的生态。得物 go 框架基于 grpc-go 只用了千余行代码即可实现拥有服务发现、多注册中心、多服务名、地址路由、自定义 interceptor 等完备功能的 RPC 调用系统。

在 grpc resolver 扩展点,在服务发现阶段根据规则过滤调用不包含 xdw 元数据的地址,即可实现服务路由功能。

在 drpc pickers 配置项中配置注册中心元数据表达式 env == "xdw" ,优先路由至小得物节点,在下游服务未部署小得物时兜底至生产节点,保证可用性。

同时为了解决业务应用 RPC 服务名、注册中心地址、路由规则等配置维护困难、且不统一的痛点,我们做了点微创新,参考 Istio 做了一个中心配置下发,懒加载的功能。

在所有应用中都相同的注册中心地址、服务名配置维护在控制中心配置中。server 会查找与 target 同名的 service 作为服务名注册,client 根据 target 名来查找服务名,只有被客户端桩代码实际调用的服务才会被 watch。

应用配置只需要引用 drpc 控制中心配置地址即可,pickers 路由规则可以统一下发到所有服务。而像超时等个性化配置应用端可以覆盖远端,框架会将其做合并处理。

控制中心远端配置:

metadata:
env: xdw
registries:
# grpc 协议
nacos-grpc:
type: nacos-grpc
url: http://xdw.xxx.com:80
priority: 11
# http 协议
nacos-http:
type: nacos-http
url: http://xdw.xxx.com:80
priority: 11
# 备用注册中心
nacos-bak:
type: nacos-grpc
url: http://bak.xxx.com:80
priority: 11
# java 服务
java-nacos:
type: java-dubbo
url: http://java.xxx.com:80
priority: 11
multi-nacos:
type: ref
refs: # 多注册中心引用
- nacos-grpc
- nacos-bak
direct:
type: direct
priority: 15
client:
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: https://ark.xxx.com?ns=XDW&cf=drpc.yaml
client:
targetMap:
sns-aaa:
# 超时配置
methodTimeout:
AaaService/FooMethod: 100
sns-bbb:
methodTimeout:
BbbService/BarMethod: 50

4.2  MQ 消息路由

社区小得物与生产环境公用一套 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 隔离

事务消息比较特殊,主要体现在 trans producer 有一个回查逻辑。trans producer 不光会向 server 发消息,还会接受 server 发送的回查消息。

查看了一下 DMQ 的 Java 源码,发现 Boroker 回查时是通过消息 properties 中的 group 来查找在线 producer。那么跟 consumer 类似,给 trans producer 配上 group ,给小得物 group 加上环境前缀即可实现事务回查隔离。用于 trans producer 的  group 只是一个标识,甚至不需要在 DMQ 后台申请。

5、总结

目前社区已经通过小得物灰度环境的运营取得一些收益:

  • 在业务稳定性上,能在正式上线前发现了一些测试、预发环境难以发现的问题,缩小影响范围,减少上线出问题后匆忙排查、紧急回滚的紧张时刻,降低了系统风险。
  • 在开发效率上,通过摘流批量发布、依赖梯队自动生成、发布流程编排等手段,大大降低了版本发布人力和时间成本。以前版本十来个应用发布,需要多个开发介入,前后依赖等待、观察,耗费较大人力,生产发布需要 4 个小时以上;现在由一个版本 owner 负责,在小得物验收通过后,一键发布至生产环境,小得物加生产在 2 个小时内能搞定。几乎解放了 0.5 天的时间,开发可以把这个时间投入到下个版本的技术方案设计上去。
但社区灰度环境只解决了部分问题,还有很多技术难点、体验优化、流程规范待完善,例如:

  • 和前端同学合作,打通中后台、H5 页面前后端灰度链路。
  • 涉及外部业务域、数据同步中间件等场景的 MQ 消息和灰度流量闭环。
  • 扩大灰度窗口期,下探“深水区”,优化QA验证和产品走查流程。
  • 优化开发用户体验,降低小得物环境维护成本。例如:抽出小得物、生产公共配置,只维护一份。
革命尚未成功,同志仍需努力!​

有关得物社区 Golang 灰度环境探索和实践的更多相关文章

  1. ruby-on-rails - 在 Rails 开发环境中为 .ogv 文件设置 Mime 类型 - 2

    我正在玩HTML5视频并且在ERB中有以下片段:mp4视频从在我的开发环境中运行的服务器很好地流式传输到chrome。然而firefox显示带有海报图像的视频播放器,但带有一个大X。问题似乎是mongrel不确定ogv扩展的mime类型,并且只返回text/plain,如curl所示:$curl-Ihttp://0.0.0.0:3000/pr6.ogvHTTP/1.1200OKConnection:closeDate:Mon,19Apr201012:33:50GMTLast-Modified:Sun,18Apr201012:46:07GMTContent-Type:text/plain

  2. Vscode+Cmake配置并运行opencv环境(Windows和Ubuntu大同小异) - 2

    之前在培训新生的时候,windows环境下配置opencv环境一直教的都是网上主流的vsstudio配置属性表,但是这个似乎对新生来说难度略高(虽然个人觉得完全是他们自己的问题),加之暑假之后对cmake实在是爱不释手,且这样配置确实十分简单(其实都不需要配置),故斗胆妄言vscode下配置CV之法。其实极为简单,图比较多所以很长。如果你看此文还配不好,你应该思考一下是不是自己的问题。闲话少说,直接开始。0.CMkae简介有的人到大二了都不知道cmake是什么,我不说是谁。CMake是一个开源免费并且跨平台的构建工具,可以用简单的语句来描述所有平台的编译过程。它能够根据当前所在平台输出对应的m

  3. ruby-on-rails - ruby gem如何在rails环境下工作 - 2

    我试图在rails中了解rubygems是如何变得可以自动使用的,而不是在使用required的文件中gem? 最佳答案 这是通过bundler/setup完成的:http://bundler.io/v1.3/bundler_setup.html.它在您的config/boot.rb文件中是必需的。简而言之,它首先将环境变量设置为指向您的Gemfile:ENV['BUNDLE_GEMFILE']||=File.expand_path('../../Gemfile',__FILE__)然后它通过要求bundler/setup将所有ge

  4. ruby - 扩展类和实例 - 2

    这个问题有两个部分。在RubyProgrammingLanguage一书中,有一个使用模块扩展字符串对象和类的示例(第8.1.1节)。第一个问题。为什么如果您使用新方法扩展类,然后创建该类的对象/实例,则无法访问该方法?irb(main):001:0>moduleGreeter;defciao;"Ciao!";end;end=>nilirb(main):002:0>String.extend(Greeter)=>Stringirb(main):003:0>String.ciao=>"Ciao!"irb(main):004:0>x="foobar"=>"foobar"irb(main):

  5. ruby-on-rails - 我需要一个真正的 UNIX RoR 开发环境 - 2

    从一开始,我就是一个Windows高手。我从MS-DOS开始。我安装了Windows2.1以及此后的所有Windows。现在,我家里有10台不同的Windows机器在运行,从Windows7Ultimate到各种版本的WindowsServer。我还没有完成Windows8,也不想去那里。我在服务器和各种软件方面都有UNIX经验,但它并不是我的首选环境。但是,我想我正在转换。我试图假装使用Cygwin和MSYS在Windows下运行UNIX。我的目的是搭建一个开发环境。两者都让我失望了。我花了比开发更多的时间来解决一系列技术问题。这是NotAcceptable。到目前为止,我的Ruby

  6. ruby-on-rails - lovdbyless VS 社区引擎……哪个最好? - 2

    随着ruby​​被引入为新的编程救世主,我想知道是否有人基于易用性、运行所需的资源、可用性和易定制性而有偏好。两者有更好的吗? 最佳答案 好吧,任何基于Rails的社交网络应用程序的比较都应该包括insoshi(http://portal.insoshi.com/)。话虽这么说,这三个都非常相似,区别在于实现细节。Lovd和Insoshi都是完整的Rails应用程序;它旨在供您将它们用作入门工具包,并使用您自己的自定义功能对其进行扩展。另一方面,CommunityEngine是一个Rails插件。这意味着您可以更轻松地向现有Rail

  7. ruby-on-rails - 如果特定语言环境中缺少翻译,如何配置 i18n 以使用 en 语言环境? - 2

    如果特定语言环境中缺少翻译,如何配置i18n以使用en语言环境翻译?当前已插入翻译缺失消息。我正在使用RoR3.1。 最佳答案 找到相似的question这里是答案:#application.rb#railswillfallbacktoconfig.i18n.default_localetranslationconfig.i18n.fallbacks=true#railswillfallbacktoen,nomatterwhatissetasconfig.i18n.default_localeconfig.i18n.fallback

  8. ruby-on-rails - 可移植 Ruby on Rails 环境 - 2

    我给自己买了一个新的8gigUSBkey,我正在寻找一个合适的解决方案来拥有一个可移植RoR环境来学习。我在谷歌上搜索了一下,发现了一些可能性,但我很想听听一些现实生活中的经历和意见。谢谢! 最佳答案 我喜欢InstantRails,非常容易使用,无需安装程序,也不会修改您的系统环境。 关于ruby-on-rails-可移植RubyonRails环境,我们在StackOverflow上找到一个类似的问题: https://stackoverflow.com/q

  9. ruby-on-rails - 如何通过 URL 更改语言环境? - 2

    在我的双语Rails4应用程序中,我有一个像这样的LocalesController:classLocalesController用户可以通过此表单更改其语言环境:deflocale_switcherform_tagurl_for(:controller=>'locales',:action=>'change_locale'),:method=>'get',:id=>'locale_switcher'doselect_tag'set_locale',options_for_select(LANGUAGES,I18n.locale.to_s)end这有效。但是,目前用户无法通过URL更改

  10. ruby - 从 FaSTLane 将环境变量传递给 shell 脚本 - 2

    我在跑Fastlane(适用于iOS的持续构建工具)以执行用于解密文件的自定义shell脚本。这是命令。sh"./decrypt.shENV['ENCRYPTION_P12']"我想不出将环境变量传递给该脚本的方法。显然,如果我将密码硬编码到脚本中,它就可以正常工作。sh"./decrypt.shmypwd"有什么建议吗? 最佳答案 从直接Shell中扩展假设这里的sh是一个faSTLane命令,它以给定的参数作为脚本文本调用shell命令:#asafastlanedirectivesh'./decrypt.sh"$ENCRYPTI

随机推荐