[转载]流量录像和流量回放

相关文章

   基于testng的自动化测试框架,自动化集成测试框架,自动本地集成测试框架,自动化单测框架_个人渣记录仅为自己搜索用的博客-CSDN博客_集成测试框架

本文转载自滴滴php流量拦截和回放的原理文章 

其他方案  

流量录制与回放技术实践 田小波 调研了一些平台

公司名称面向语言描述
阿里doom全套管理后台,淘宝内部核心业务系统接入使用在阿里云上可使用https://help.aliyun.com/document_detail/151244.htmlhttps://help.aliyun.com/document_detail/151244.html
阿里

  sandbox-jvm-repeater 

java

无管理后台

支持native aop ,例如System.currentTimeMillis的mock

支持系统参数的录制和回放?https://github.com/alibaba/jvm-sandbox/pull/306?spm=a1ziqju.22887754.0.0.65563eddeodnhe

好未来柯南

用户提供流量回放全流程解决方案 Based on the recording and playback capability

流量回放的常规校验方式基本上是以流量结果的 DIFF 为主,但大量的流量噪声(时间戳,自增数据...)一直影响结果的准确性,柯南平台在回放中基于配置的 jsonSchema 做第一层校验,再结合自研的降噪比对服务进行流量 DIFF 的第二层校验,从而保障了结果校验的准确性,大大提升了流量回放结果的可信度。https://zhuanlan.zhihu.com/p/360067771icon-default.png?t=M85Bhttps://zhuanlan.zhihu.com/p/360067771

滴滴Sharingango流量录制实现原理,https://github.com/didi/sharingan/wiki/%E6%B5%81%E9%87%8F%E5%BD%95%E5%88%B6%E5%AE%9E%E7%8E%B0%E5%8E%9F%E7%90%86https://github.com/didi/sharingan/wiki/%E6%B5%81%E9%87%8F%E5%BD%95%E5%88%B6%E5%AE%9E%E7%8E%B0%E5%8E%9F%E7%90%86
滴滴rdebugphp 

基于libc的拦截录制,比较底层.

具备跨语言.

支持时间的录制和回放.

支持系统参数的录制和回放?

diffyTwitter

类似流量仿真,并不是录制和回放.

diffy的原理是作为代理,截取请求并发送至不同代码的服务实例,通过对比响应结果来发现每次迭代代码中可能存在的问题。

diffy能够通过一定的方式,清除这类噪音,保证diff结果不被影响https://www.sohu.com/a/250860553_216613 所以才会有组装diffy和goreplay的平台
所以才会有组装goreplay的平台,我们开发了kudiffy平台用于统一管理diffy和goreplay服务,并且不用手动安装下载,无接入成本。https://zhuanlan.zhihu.com/p/110426445

tcpcopy网易

录制依赖tcpdump,回放依赖tcpcopy和intercept 

全套工具GitHub地址

无currentTimeMillis等mock

流量复制方案对比:Tcpcopy vs Goreplay

如何使用tcpcopy离线回放TCP流量

goreplay仅支持http的复制和回放
ares去哪儿

拦截点的选取

流量录制必然要在某个点上对流量进行拦截并镜像。这里有几个选择。

在进入到 tcp/ip 协议栈的时候线程id已经丢失了。所以只有两个选项

  • libc 拦截。因为 send/recv 的拦截是在业务进程内执行的,所以调用的call stack是在同一个线程上。可以直接调用获取‘当前线程id“。
  • syscall 拦截
    • ptrace,走的是中断,太慢
    • syscall jmp table 补丁,需要改内核
    • syscall eBPF 需要新版本的内核

所以对于使用了 libc 的语言来说,LD_PRELOAD 拦截 libc 是最合适的拦截点。对于 Go 语言来说,没有使用 libc,所以拦截是通过修改语言标准库来实现的。需要用定制版的 Go 编译器对源代码进行编译,但是也不需要修改业务源代码。

本质上,这个问题就是一个书同文,车同轨的问题。理论上如果所有的代码都用同一种语言实现,那么我们修改这个语言就行。如果所有的代码是用同一种框架实现,我们修改这个框架就行。因为我们现在只能保证所有的代码用一套类似的 syscall(无论是linux还是mac),所以只能在 syscall 层面做公共的拦截。否则就需要给每一种语言,每一种框架写一套拦截代码。什么时候能够实现书同文,车同轨?我们先需要有一个能够焚书坑儒的秦始皇。

时间的拦截和回放

原理就是用 libc 钩子拦截了和时间相关的系统调用。

最初使用的是 https://github.com/wolfcw/libfaketime

后来我们重写了 time_hook.cpp。回放的时候当拿到了剧本之后,根据回放所需的时间和当前时间,计算一个偏移量(负多少秒)。然后每次查询系统时间的时候,加上这个偏移量。

  • gettimeofday
  • clock_gettime
  • time
  • ftime

libc的拦截

对于 mac 和 linux 上的实现是不同的。使用了第三方的开源 https://github.com/ccurtsinger/interpose 实现了同一套代码。

录制只发生在 linux 上。所以 mac 版本的 libc 拦截仅仅用于流量回放。

因为这种拦截是非常通用的技术,看别人写的博客就好了

GitHub - ccurtsinger/interpose: Function interposition for Linux and Mac OS 的 INTERPOSE 宏看起来很美观。本质上就是拿 C++ 11 的高级语法做了一把源代码生成而已。如果不用这个库,拿 php 写一个 c 源代码生成,也是一样的效果。所谓现代 c++,就是各种语法上的奇技淫巧,做一些源代码生成的工作。

mac 上有一点需要注意是因为 10.11 开始 SIP 保护,对于 /usr/bin 下 binary 是无法拦截的(参见 https://github.com/wolfcw/libfaketime/blob/master/README.OSX)。好在 brew install 出来的 php 是在 /usr/local/bin 下,所以并不会受到这个限制。

outbound 请求拦截原理

回放的时候,outbound rpc 的拦截不仅仅是需要录制下来,还要在没有集成环境的情况下真实地给一个响应。

我们当然可以拦截所有的 syscall 虚拟一个 tcp 链接出来。但是这样的实现代价太高了。

直接的做法是真实地起了一个 tcp 服务器做为通用的 outbound server。然后所有的 outbound rpc 在做 connect 系统调用的时候把目标的 ip:port 给修改了。业务以为请求的是 a,实际 connect 到的是 b。达到 mock server 伪造的目的。

if envarg.IsReplaying() {
    countlog.Debug("event!gw4libc.redirect_connect_target",
        "origAddr", origAddr,
        "redirectTo", envarg.OutboundAddr())
    sockaddr_in_sin_addr_set(remoteAddr, ch.Ip2int(envarg.OutboundAddr().IP))
    sockaddr_in_sin_port_set(remoteAddr, ch.Htons(uint16(envarg.OutboundAddr().Port)))
}

本来 libc 拦截下来,在 Go 的代码里改一下 addr 对象的内容就可以。

因为 Go 对于 struct 里小写开头的 field 无法直接读写。所以,实际的实现是通过反射知道 struct field offset。然后通过 unsafe pointer 用指针运算绕过 Go 的可见性检查直接修改的。

这些 Go 和 C 打交道的小 trick 都封装到了ch(c helper 的意思)这个包里面。

请求和响应切分原理

Mingliang Tan edited this page on 15 Feb 2019 · 1 revision

分为以下几个问题

  • 录制的原理
  • 如何知道一个socket fd的性质
  • 怎么样切分inbound request/response(PHP自身处理的HTTP请求)
  • 怎么样切分outbound request/response(对外发起的redis/mysql/http/thrift等外部依赖的请求)

录制的原理

因为录制是在 syscall 层面直接录制的 7 层网络协议的包体。也就是把 send/recv 的函数参数里传递的 byte 数组给拷贝了一份。所以避免了 tcpdump 抓包需要进行 tcp 流重建的问题。

 对于系统中所有的 socket 的 send 和 recv 我们都会拦截。但是不同的 socket 代表了不同的方向。所以需要解答第二个问题

如何知道一个socket的性质

socket 如果是 accept 进来的,那么它代表了 inbound 方向。recv 是 inbound request,send 是 inbound response。如果 socket 不是 accept 进来的,则代表了 outbound 方向。recv 是 outbound response,send 是 outbound request(注意 request response 正常是相反的)。

因为需要知道这点区分,所以我们不仅仅需要拦截 send recv 还需要拦截 accept。而且更复杂的问题是 accept 和 send recv 未必发生在同一个线程。对于从别的线程里创建的 socket fd,需要能够被发现。解决方法是维护了一个全局的 socket fd 的 map。如果一个线程发现了一个新的 fd,首先去这个全局 map 里找找看。

Inbound Request 切分的原理

参见:通过网络抓包度量 RPC 的响应时间 - 知乎

简单来说是两点

先利用线程内是串行执行的特性,把并发的流变成了串行的流。 在一个线程内,利用 request/response 的时序就可以把请求和响应切分开来。无需进行7层协议的解析。总是 请求 - 响应 - 再请求 这样的一个节奏。 所以实现就是当 recv 的时候,发现已经 send 了,则结束上一个 session,开始下一个session。这样即便是在 fastcgi_finish_request 之后发生的 call outbound 也可以被录制到 session 里

Outbound Request 切分的原理

同上

a 小部件

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值