Rhino 字节跳动全链路压测的实践( 二 )


 
3.2 压测隔离压测隔离中需要解决的压测流量隔离,以及压测数据的隔离 。
压测流量隔离,主要是通过构建压测环境来解决,如线下压测环境,或泳道化/Set 化建设,将压测流量与线上流程完全隔离 。优点是压测流量与线上流量完全隔离,不会影响到线上用户 。缺点:机器资源及维护成本高,且压测结果需要经过一定的换算,才能得线上容量,结果准确性存在一定的问题 。目前公司内压测都是在线上集群上完成的,线上泳道化正在建设中 。
压测数据隔离,主要是通过对压测流量进行染色,让线上服务能识别哪些是压测流量,哪些是正常流量,然后对压测流量进行特殊处理,以达到数据隔离的目的 。目前 Rhino 平台整体压测隔离框架如图 。

Rhino 字节跳动全链路压测的实践

文章插图
 
压测标记压测标记就是最常见的压测流量染色的方式 。
  • 对于 RPC 协议,会在请求的头部中增加一个 Key:Value 的字段作为压测标记 。
  • 对于 HTTP 和其他协议,会在请求头,自动注入一个 Stress 标记(Key-Value)。
  • 压测标记 Key:Value,其中 key 是固定的 Stress_Tag 值,但是每个压测任务都有唯一的 Stress_Value 值,主要用于解决压测数据冲突,以及性能问题定位 。
压测标记透传目前公司内各个基础组件、存储组件,以及 RPC 框架都已经支持了压测标记的透传 。其原理是将压测标记的 KV 值存入 Context 中,然后在所有下游请求中都带上该 Context,下游服务可以根据 Context 中压测标记完成对压测流量的处理 。在实际业务中,代码改造也非常简单,只需要透传 Context 即可 。
Golang 服务: 将压测标记写入 Context 中 。
Python 服务:利用 threading.local()存储线程 Context 。
JAVA 服务:利用 ThreadLocal 存储线程 Context 。
压测开关为了解决线上压测安全问题,我们还引入了压测开关组件 。
  • 每个服务每个集群,都有一个压测开关 。只有打开压测开关时,压测流量才能流入到服务内,否则就会被底层微服务框架直接拒绝,业务层无感知 。
  • 在每个 IDC 区域,都会有一个全局的压测总开关 。只有打开了这个全局压测开关,压测流量才被允许在这个 IDC 内流转 。
  • 当线上出现压测问题,除了从源头关闭压测流量以外,关闭目标服务的压测开关,也能立即阻断压测流量 。
压测数据隔离线上压测中,最复杂的问题就是压测链路中涉及到写操作,如何避免污染线上数据,并且能保证压测请求保持和线上相同的请求路径 。业界有很多解决方案,常见的有影子表,影子库,以及数据偏移,如图[7] 。
Rhino 平台针对不同存储,有不同的解决方案:
  • MySQL、MongoDB:影子表 。SDK 判断是否是压测流量,若是则根据配置映射至新表名 。配置策略有两种,一是读写影子表,二是读线上表、写影子表 。
  • redis:Redis Key 加上 Stress 前缀 。如 Stress_Tag=Valuex,那么读写 Redis 的 Key=Valuex_Key 。这样可以解决多个压测任务数据冲突的问题 。压测结束后,只需要对 Prefix=Valuex 做清除或过期操作即可 。
  • MQ:对于消息队列,Rhino 平台有两种策略 。一是直接丢弃,然后针对消息队列的性能,单独进行压测;二是在 Header 中透传压测标记,Consumer 根据压测标记和业务需求,再做特殊处理 。默认走丢弃策略,业务方可根据需求进行配置 。
  • 其他存储,如 ES,ClickHouse 等,都有压测集群 。压测时,会将压测请求打到指定的压测集群中 。

Rhino 字节跳动全链路压测的实践

文章插图
 
服务压测改造在压测之前,需要对服务进行压测验证 。对于不满足压测要求(即压测数据隔离)的服务,需要进行压测改造 。
  1. 压测验证:对于存储服务,在不打开压测开关的前提下,通过压测请求,发送读写操作都是会被拒绝 。如果没有拒绝,说明在操作存储服务时,没有带上压测 Context,需要进行改造 。
  2. 压测改造:压测改造是线上全链路压测推进中非常关键,而又非常困难的一个环节 。对于已经上线的服务,压测改造还极有可能会引入新的 BUG,所以经常推动起来比较困难 。因此为了解决这些问题,Rhino 平台有以下几个解决方案:
a. 尽量减少代码改动,并给出完整的指导手册及代码示例,减少 RD 的工作量,降低代码错误的可能性
b. 提供简单便捷的线上线下 HTTP&RPC 的压测请求 Debug 工具,方便代码改动的验证


推荐阅读