1 前言

基于拦截器,实现一个基于 trpc 协议的流量录制、回放插件。 支持回放流量验证。

常用场景:

  • 服务重构后的流量验证,差异检测
  • 日常需求迭代,回放验证(上线前)

2 整体方案

实现两组拦截器:录制拦截器、回放拦截器

  1. 录制拦截器:负责记录服务接口+所有下游调用数据(req、rsp、err),序列化后上报,用于回放。
  2. 回放拦截器:负责下游调用的mock(不实际发起调用),服务接口的回包上报,用于diff。
  3. 回包 diff 能力:由 LogReplay 平台提供,拦截器插件只负责上报diff所需数据。

补充一点,如何保存切面数据?

方案一: 基于trpc包头 metadata 方式透传 切面数据。

  • 优点:完整性,录制的数据包含所有切面(直接可用于回放 + 下游mock)
  • 缺点:序列化后的切面数据总大小上限64KiB(2字节记录RequestHead长度)

方案二: 直接上报到LogReplay平台,基于traceID做关联(服务和切面数据)。

2.1 接口设计

接口设计

2.2 录制拦截器

接口设计

2.3 回放拦截器

接口设计

3 实现思路

阶段1:基础能力-录制

  • 1 支持 trpc 的染色录制能力(上报到录制平台)
  • 2 支持下游切面录制 (上报到录制平台)
  • 3 采用 replayContext,记录下游调用请求+回包(map + lock)
  • 4 所有下游数据,存入 trpc 请求 head 的 metadata 中,一并上报;

阶段2:支持切面回放

  • 1 支持回放,上报回放时产生的 rsp;
  • 2 回放时,支持对下游服务回报 mock(服务重构场景:流量验证);
  • 3 更多开关选项:
    • a) 比如录制时,是否录制切面;
    • b) 回放时,是否启用切面mock;

阶段3

  • 支持更多常见协议: http 协议等

4 更上一层楼

4.1 思考题:在一次服务请求中,调用同一个下游服务多次(spanName 都是相同的),回放时如何 mock ?

解法:

  1. 业务为每次RPC调用,构造唯一的spanName;
  2. 将spanName在发起client调用时,传递到 trpc-replay 拦截器中;
  3. trpc-replay 对外导出api.SpanNameFunc(ctx, req), 用户可以自定义实现,拿到spanName即可;

这里列举3种方案:

方案一:使用 ctx.Value() 来传递 trpc_replay_unique_name

  • 优点:实现简单,无并发问题;
  • 缺点:会在 ctx 链表中 增加一个节点;

方式二:借助于 trpc.Msg 来传递, 比如 client.WithMetaData()

  • 优点:实现简单,无并发问题;
  • 缺点:新增的 metaData kv 会通过trpc协议,透传给下游;

方式三(推荐):对 req 进行类型断言,从req 获取标识来区分;

  • 优点:实现简单,影响面小,只针对有多次请求的下游-做逻辑处理;
  • 缺点:无

实际案例: 使用 unionplus sdk 并发查询多个视图

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
func init() {
    // 使用 unionplus sdk 查询多个视图:可以将ViewName追加到spanName中,来保证唯一
    api.DefaultSpanNameFunc = func(ctx context.Context, req interface{}) string {
        spanName := api.GetSpanName(ctx, req)
        switch r := req.(type) {
        case *common.QueryReq:
            return spanName + r.GetViewName()
        default:
            return spanName
        }
    }
}

5 附录

5.1 回放mock效果

由于是mock返回(下游在 1ms 之内返回)

回放效果