写点什么

转转测试环境治理高效能实践

  • 2023-01-04
    北京
  • 本文字数:6909 字

    阅读完需:约 23 分钟

转转测试环境治理历经 3 个版本的迭代,环境搭建耗时及资源占用大幅度下降,在此过程中积累了丰富的实践经验。本文将从测试环境的需求及背景出发,介绍转转测试环境治理各个版本的原理、技术、优缺点,毫无保留地将转转的实践经验分享给各位读者。

1. 背景及需求

1.1 系统架构的发展

很久很久以前,在并发量较低时系统大多采用单体架构,由单个 web 服务直接连接数据库,由 nginx 在多个 web 服务节点间做负载均衡。



随着系统并发量的提高,单体架构无法满足性能需求,需要对单体服务进行拆分,于是来到了微服务架构。微服务架构与单体架构显著的不同在于链路更长更复杂了。


1.2 测试环境的需求

测试环境与线上环境的典型不同在于线上环境各节点一般情况下代码是一致的,各节点是对等的,请求到达任意节点业务逻辑都是一致的;而测试环境一般是多分支并行开发,每个节点的逻辑是不一致的,既然要测试本次所开发的功能,那就要求请求能精准地到达所部署的节点。


在单体架构下,该需求是很容易实现的,通过采用修改 nginx 配置,在 upstream 中仅包含目标测试节点可以很容易实现对目标节点的精准测试,或者不使用 nginx 直接通过 IP+port 的形式进行调用也同样可以实现请求精准到达目标节点。如下图所示的 A'及 A''属于两个不同的需求,分别通过固定 nginx upstream 及 IP+port 的形式实现了请求的精准控制。



而在微服务架构下,该需求实现起来就没那么简单了。如下图所示,链路上包括服务 A、B、C,图中 A'、B'、C'属于同一个需求,而 A''、B''、C''属于另一个需求。采用在单体架构下的解决方案只能控制请求精准地到达 A'或者 A'',无法实现请求精准地到达 B'、C'及 B''、C''。


2. 传统的测试环境解决方案-物理隔离

为了实现请求精准地到达被测服务节点,传统的做法是提供多套完全互相隔离的测试环境,每套测试环境包含全量的服务及注册中心、MQ broker 等。每个需求分配一套测试环境,只需将被测服务部署至该环境内即可实现请求精准地到达被测节点,这种做法也称作物理隔离。



物理隔离在公司服务数量较少时(20 个服务以下)称得上是完美的解决方案,非常简单可靠。但是随着服务数量的增长,物理隔离的缺点逐渐显露——资源浪费太严重。假设整个系统有 100 个服务,而每次需求仅修改其中的 1-3 个服务,资源浪费率高达 97%-99%。而且服务数量的增长,往往也意味着公司业务的发展壮大,同时进行中的需求数量也在增长,需要提供更多的测试环境,资源消耗巨大。

3. 转转测试环境 V1-改进的物理隔离

转转公司传统的测试环境解决方案属于改进型的物理隔离。

3.1 稳定环境

在转转有一套稳定环境包含全量的服务并且与线上代码一致。在测试环境不使用注册中心,而是为每一个服务分配唯一的域名并通过修改机器 host 映射的方式手动控制服务发现。默认情况下,如果服务 A 部署在稳定环境的 192.168.1.1 上,那么所有测试环境机器的 host 文件中都存在一项配置192.168.1.1 A.zhuaninc.com

3.2 动态环境

每次需求所申请的环境称为动态环境,为一台 kvm 虚拟机。假设某动态环境的 IP 为192.168.2.1,在该动态环境部署服务 A 时,同时将该机器的 host 文件中192.168.1.1 A.zhuaninc.com(假设稳定环境服务 A 的 IP 为 192.168.1.1)映射,修改为127.0.0.1 A.zhuaninc.com


如下图所示的动态环境192.168.4.1,其中 A'及 E'即为本次需求所修改的服务。



初始化过程如下:


  1. 申请动态环境192.168.4.1,申请完成后该机器的 host 文件中包含全量的服务域名映射,默认映射到对应的稳定环境所在的 IP,该例中仅展示了 F 服务的域名映射为192.168.5.1 F.zhuaninc.com(假设稳定环境服务 F 的 IP 为192.168.5.1)。

  2. 部署服务 E',同时将127.0.0.1写入 host 文件。

  3. 部署服务 D,同时将127.0.0.1写入 host 文件。

  4. 部署服务 C,同时将127.0.0.1写入 host 文件。

  5. 部署服务 B,同时将127.0.0.1写入 host 文件。

  6. 部署服务 A',同时将127.0.0.1写入 host 文件。

  7. 部署 Entry。

  8. 部署 nginx,同时将服务 A 的 upstream 修改为仅包含127.0.0.1


如此以来,就像焊接管道一样,从服务 E 至 nginx 倒序焊接出一条没有分叉的管道。测试时只需要通过 host 映射将被测域名映射到该 IP,即可实现请求精准地到达 A'及 E',E'以后的链路(从 F 开始的链路)则使用稳定环境。


对于 MQ,则通过在动态环境 topic 添加 IP 前缀的方式实现与稳定环境的物理隔离,如下图所示不同的 topic 在 broker 上存在着不同的队列,稳定环境的消息和动态环境的消息不能互通。



如在测试过程中发现需要修改更多的服务,例如需要修改服务 F,则在将 F 服务部署在该动态环境的同时,需要重启服务 E',因为 E'已经与稳定环境的 F 建了 tcp 连接,修改 F 服务的域名映射并不会导致 E'与 F 之间重新建立 tcp 连接,所以需要重启 E'。如 F 服务的调用方不仅有 E',则都需要重启。

3.3 优缺点

该解决方案近似于物理隔离,但较物理隔离有所改进,改进的地方在于动态环境并没有包含全量的服务,动态环境仅包含了从 nginx 开始至最后一个被测的服务,而使用稳定环境充当被测链路的尾巴。

3.3.1 优点

  • <font color="#00dd00">隔离性强,近似于物理隔离。</font>

  • <font color="#00dd00">链路简单,流量封闭在同一台机器上。</font>

3.3.2 缺点

  • <font color="#dd0000">需要部署从 nginx 开始至最后一个被测的服务,存在未修改服务部署在动态环境中的情况,造成资源浪费。</font>

  • <font color="#dd0000">由于部署过程依赖服务的调用关系,导致部署效率低下,极端情况下需要数天调试测试环境。</font>

  • <font color="#dd0000">host 管理复杂。</font>

  • <font color="#dd0000">topic 添加 IP 前缀易出错,代码与环境相关。</font>

  • <font color="#dd0000">单台机器内存有限,链路过长无法满足。</font>

4. 转转测试环境 V2-基于自动 IP 标签的流量路由

随着转转公司业务的飞速发展,服务数量迅速增加,每个动态环境部署的服务数量增至 30-60 个,搭建测试环境的成本越来越高。为了解决该问题,转转架构部、运维部、工程效率部推出了流量路由解决方案。该解决方案在此前的公众号文章转转测试环境的服务治理实践中已有详细讲解,本文仅做简要介绍。


该版本的流量路由称为基于自动 IP 标签的流量路由,之所以选择 IP 为标签是因为基于现有使用习惯发现所有被测服务部署在同一台虚拟机上,IP 地址一样,并且 IP 易获取,用户无感知;“自动”则体现在无需用户对服务及流量进行打标,打标是完全自动化进行的。


基于自动 IP 标签的流量路由将测试环境的搭建时间从数小时-数天减少至 30 分钟-1 小时,每环境部署的服务数量从 30-60 个服务下降至个位数,并且完全兼容没有流量路由时的使用习惯。但是仍然存在申请虚拟机耗时长,kvm 内存无法扩容的问题。

5. 转转测试环境 V3-基于手动标签的流量路由

基于自动 IP 标签的流量路由解决了转转测试环境存在的大部分问题,使测试环境的搭建时间从数小时-数天下降至 30 分钟-1 小时,但是仍然存在申请环境耗时长,kvm 内存无法扩容的问题,本着精益求精,用户至上的原则,我们开发了基于手动标签的流量路由。

5.1 docker 化

为了解决申请环境耗时长,kvm 内存无法扩容的问题,我们决定将服务部署在 docker 容器内,无需提前申请资源,理论上无内存限制。但是 docker 化以后,基于 IP 为标签的流量路由显然无法工作了,因为不同的服务部署在不同的 docker pod 内,IP 也是不一样的。

5.2 服务及流量打标

此时就需要手动为服务及流量指定标签,我们称为基于手动打标的流量路由。为服务打标是自动化完成的,在环境平台申请环境时指定标签,在该环境内部署服务时,由环境平台自动添加 jvm 参数-Dtag=xxx。而为流量打标则通过 http header 进行,在发起请求时添加 header tag=xxx。





并非所有请求都是通过 http 发起的,有些由进程内部(如定时任务)直接发起的调用则自动携带当前节点的标签。

5.3 目标形态

基于手动打标的流量路由目标形态如下图所示,标签为yyy的动态环境,本次需求修改了服务 B 及服务 D,只只需要在该动态环境内部署 B 和 D,真正实现修改什么就部署什么。


5.4 RPC 调用实现

5.4.1 服务注册、发现及调用

以下图服务 A 调用服务 B 为例,服务 B 有 3 个节点,稳定环境的 B,动态环境的 B'(标签为yyy)及 B''(标签为xxx)。B'和 B''在启动时会将标签参数注册到注册中心,服务 A 在启动时会从注册中心发现 B、B'、B'',同时获取它们的标签参数。



以红色的链路为例,流量标签为zzz,A 在调用时发现并没有动态环境的服务 B 拥有标签zzz,于是调用稳定环境的 B 节点。


橙色链路的标签为xxx,A 在调用时发现 B''的标签为xxx,且为动态环境,则调用 B''。

5.4.2 标签的传递

转转使用的 RPC 为自研 RPC 框架,在调用时除了传递请求方法及参数等信息之外,还可以通过 attachement 功能传递额外的参数,而路由标签就是通过该功能在 RPC 调用时实现传递。

5.5 MQ 消息实现

5.5.1 消费原理

若想实现消息可以在动态环境与稳定环境之间路由,通过不同的的 topic 前缀实现动态环境和稳定环境的物理隔离显然已经行不通。此时的解决方案为动态环境和稳定环境拥有相同的 topic,但是不同的消费 group,不同的消费 group 就对应不同的消费 offset。动态环境的 group 添加${tag}前缀,而稳定环境的 group 添加test_前缀,添加前缀的过程由 MQ 客户端自动完成,对用户透明。


如下图所示服务 B 有稳定环境及动态环境(B')节点各一个,B'的标签为xxx。图中通过不同的颜色表示不同的标签,其中绿色表示没有标签。



B 节点在消费时,首先判断是否有和消息中标签对应的消费组注册到 broker 上,如果有则过滤掉,否则消费。其中消息 1、3、5、7 的标签和 B'匹配,消息 2 没有标签,而消息 4、6 的标签所对应的消费者没有注册到 broker 上,所以 B 节点消费了消息 2、4、6。


B'节点在消费时,只消费消息中标签和自身标签前缀一致的消息,即消息 1、3、5。

5.5.2 存在的问题

假如此时 B'下线了,我们发现消息 7 没有被消费者消费,该消息丢了。这是因为稳定环境消费组和动态环境消费组 offset 不一致导致的,稳定环境消费组 offset 大于动态环境消费组 offset,解决方案就是 B'下线时将其与稳定环境 offset 之间的消息重新投递。重新投递后假如 B'又上线了呢,就带来了消息的重复消费,但是我们认为重复消费是没有问题的,因为 mq 本身的消费语义就是至少一次,而不是仅仅一次重复消费幂等性应该由业务逻辑来保证


另一个问题是批量消费如何解决,mq 客户端有批量消费功能,一批消息所携带的标签可能是不一样的。对于这个问题,只需要将消息按标签分组后再执行消费逻辑即可。

5.5.3 标签的传递

转转使用的是 RocketMQ,并进行了二次开发,RocketMQ 提供了可扩展的 header 可以用来传递路由标签。

5.6 进程内标签的传递

跨进程的标签传递相对来讲比较容易解决,而进程内标签的传递难度更高。

5.6.1 通过方法参数传递

通过为每一个方法调用添加 tag 参数可以实现标签的的进程内传递,但是显然这不现实,需要全公司所有的代码配合改动。

5.6.2 通过 ThreadLocal 传递

jdk 内置有ThreadLocalInheritableThreadLocal可以实现标签的隐式传递,但是ThreadLocal无法实现new Thread及跨线程池传递,而InheritableThreadLocal可以实现new Thread传递却仍然无法实现跨线程池传递。虽然可以通过在跨线程池时对CallableRunnable进行包装实现跨线程池传递,但这仍然要求修改现有的业务代码,成本较高。

5.5.3 通过 TransmittableThreadLocal 传递

TransmittableThreadLocal是阿里巴巴开源的,通过 java agent 技术实现的可以跨线程/跨线程池传递的ThreadLocal,对用户透明,只需要在 jvm 启动参数中加入对应的 java agent 参数即可,最终我们采用了该方案。


5.6 上线收益

下图为转转公司动态环境平均部署服务数量曲线,在 2022 年 5 月之前平均每环境约部署 7-8 个服务,在 2022 年 5 月之后标签路由开始推广,至 2022 年 7 月每环境约部署 3-4 个服务。虽然从原理上看基于手动标签的流量路由每环境仅比基于自动 IP 标签的流量路由仅少部署一个服务(Entry),但是由于 kvm 无法扩容,所以在自动 IP 路由时代环境初始化时用户总是会预防性地多选择一些服务,以达到申请更大内存虚拟机的目的,而 docker 化之后,此种担忧不再存在,所以每环境部署的服务数量下降了约 4 个,动态环境总内存节省了约 65%。


测试环境搭建时间从 30 分钟-1 小时下降至 2 分钟-5 分钟,时间的节省主要来自无需提前申请 kvmdocker 资源隔离服务启动快无内存扩容担忧初始化服务数量少


5.7 辅助设施

docker 化虽然带来了效率的提升,资源占用的下降,但是也引入了一些问题。每次部署 IP 地址都会变化,导致远程登录及 debug 的成本增加。在 Http Header 中添加路由标签增加了测试同学的工作量。为了解决这些问题,我们又开发了对应的辅助设施来提高工作效率。

5.7.1 泛域名解析

使用 Http Header 传递标签,仍然需要配置 host 映射将域名映射至测试环境的 nginx,如192.168.1.1 app.zhuanzhuan.com,否则 dns 解析会将app.zhuanzhuan.com解析至线上 nginx。


是否可以免去 host 配置呢,答案是可以的。通过直接使用域名传递标签的形式来实现免去 host 配置,如app.zhuanzhuan.com直接使用域名传递标签写作app-${tag}.test.zhuanzhuan.com,该域名会直接解析至测试环境 nginx。在开发版 app 中内置了该功能,在 app 启动时输入环境标签即可实现域名的切换,无需配置 host 即可开始测试。


5.7.2 web shell

docker 化以后每次部署都是一个新的 docker pod,IP 地址也随之变化,如果需要登录查看日志,通过 xshell 等工具则需要在每次部署后使用新的 IP 重新登录。引入 web shell 功能只需要在环境平台页面中点击按钮即可通过 web shell 的方式直接登录,并且登录后的工作目录默认为该服务的日志目录。


5.7.3 debug 插件

同样因为 docker 化以后 IP 地址总是变化,测试环境的远程 debug 也变得更加不方便,每次需要更换新的 IP 进行连接。为了解决此问题,我们开发了 debug 插件,该插件会自动获取项目名作为服务名,需要 debug 时输入环境标签,插件会自动向环境平台发起请求,而环境平台则通过解析 jvm 参数的方式获取 debug 端口并向插件返回 IP 和 debug 端口,插件在收到 IP 和 debug 端口后自动连接。


5.8 优缺点

5.8.1 优点

  • <font color="#00dd00">更加节省资源,仅部署 X(修改的服务),不需要部署 nginx+Entry。</font>

  • <font color="#00dd00">申请环境速度快,秒级完成。</font>

  • <font color="#00dd00">搭建环境速度快,约 2-5 分钟。</font>

  • <font color="#00dd00">无内存限制。</font>

5.8.2 缺点

  • <font color="#dd0000">QA 有感知,需要在 HTTP Header 中添加标签。</font>

6. 分布式调用跟踪系统

以下图为例,在 D 调用 E'时出现问题,E'中未打出相应的业务日志,到底是 D 没有调用呢,还是流量路由存在问题没有路由到 E'呢。此时就需要分布式调用跟踪系统的辅助来排查问题。


6.1 原理

分布式调用跟踪系统的原理就是在链路中每个模块的入口和出口处进行埋点,并将埋点采集起来进行可视化展示。如下左图为调用链路,右图为采集到的埋点。



每一条链路有唯一的 Id 称为 TraceId,而每一个埋点称之为 span,每个 span 有唯一的 Id 称为 SpanId。

6.2 架构

转转分布式调用跟踪系统采用自研与开源结合的方式,如下图所示。其中 Radar 为转转自研分布式调用跟踪系统客户端,可与 MQ、SCF(转转 RPC 框架)、Servlet 进行整合,异步批量地将埋点上传到 Collector 服务,Collector 服务再将埋点写入 kafka。开源部分则使用 zipkin 实现,zipkin 具备自动从 kafka 消费埋点并存入 DB 的能力,而且自带 UI 界面可供查询。


6.3 TraceId 的获取

转转使用统一日志门面 slf4j,Radar 客户端自动将 TraceId、SpanId 存入 MDCContext 中,只需在日志配置文件中加入相应的占位符就可以将 TraceId、SpanId 打印至日志中,如下图所示。



在 Entry 层每次请求结束后将 TraceId 以 Http Header 的形式返回至前端,前端收到响应后可立即获取 TraceId 进行查询。


6.4 在路由关键节点采集流量标签和当前节点标签

如下图所示global.route.context.tag为流量标签,而global.route.instance.tag为当前节点标签,通过对比这两个标签是否匹配即可验证流量路由是否正确,本节开头所提到的问题也就迎刃而解。


7. 总结

转转测试环境治理共经历 3 个版本,物理隔离基于自动 IP 标签的流量路由基于手动标签的流量路由


  • 物理隔离:随着转转业务的发展,服务数量的增多,搭建测试环境极端情况下需要数天的时间,每环境平均部署服务数量高达 30-60 个

  • 基于自动 IP 标签的流量路由:每环境平均部署服务数量下降至 7-8 个,而环境搭建时间也下降至 30 分钟-1 小时

  • 基于手动标签的流量路由:每环境平均部署服务数量进一步下降至 3-4 个,搭建时间降至 2 分钟-5 分钟


流量路由带来效率提升及资源占用下降的同时,也引入了一些问题,如链路复杂性高,ip 地址变化等。为了更好地利用流量路由带来的便利,消除负面影响,就需要各种配套设施的辅助,如分布式调用跟踪、泛域名解析、web shell、自动连接 debug 插件等。


回头来看,流量路由减少了部署时间,降低了资源消耗,得到了业务线的一致好评。架构、运维与工程效率部门的同学排查问题的数量也大大减少。真真正正做到了降本增效,实实在在好项目。两个版本的流量路由分别获得转转公司优秀项目奖




关于作者


王建新,转转架构部服务治理负责人,主要负责服务治理、RPC 框架、分布式调用跟踪、监控系统等。爱技术、爱学习,欢迎联系交流。


转转研发中心及业界小伙伴们的技术学习交流平台,定期分享一线的实战经验及业界前沿的技术话题。关注公众号「转转技术」(综合性)、「大转转 FE」(专注于 FE)、「转转 QA」(专注于 QA),更多干货实践,欢迎交流分享~

用户头像

还未添加个人签名 2019-04-30 加入

转转研发中心及业界小伙伴们的技术学习交流平台,定期分享一线的实战经验及业界前沿的技术话题。 关注公众号「转转技术」,各种干货实践,欢迎交流分享~

评论

发布
暂无评论
转转测试环境治理高效能实践_架构_转转技术团队_InfoQ写作社区