传统线下压力测试,难模拟真实流量,尤其正常流量混杂着各色异常流量

线下压得好好的系统,上线后可能某天突然雪崩,说好能支撑 5 倍流量的系统重构,也许流量一翻倍就彻底挂了

ab,JMeter, LoadRunner, httperf 都没能解决如何正确模拟生产环境的流量

TcpCopy

c语言编写,使用复杂,依赖于 assistant server的intercept截获应答

思路

应用层面复制,如基于服务器的请求复制,实现相对简单,但

  1. 请求复制从应用层出发,穿透整个协议栈,挤占应用资源,比如宝贵的连接资源
  2. 测试跟实际应用耦合在一起
  3. 难支撑压力大的请求复制
  4. 难控制网络延迟

基于底层数据包的请求复制,无需穿透整个协议栈

从链路层抓请求包,从数据链路层发包,路程最短 IP层抓请求包,IP层发出去,路程一般

只要不走TCP,对在线的影响就会小得多。是 TCPCopy基本思路

演化

  • 传统架构 rawsocket+iptable+netlink
  • 新架构的 pacp+route

资源需求

  • Online Server(OS):部署 TCPCopy,从数据链路层(pcap 接口)抓请求数据包,发包从IP层
  • Test Server(TS):新架构把 intercept 工作从 TS 中 offload 出来。指定默认路由到辅助server,确保响应包路由到AS,而不是回包给 Online Server
  • Assistant Server(AS):在数据链路层(pcap)截获到(测试机)的响应包,抽取出有用的信息,再返回给相应的 OS 上的 tcpcopy 进程,完成一次请求的复制

AS上设置, net.ipv4.ip_forward = 0

拓扑

数据流转顺序

生产环境和镜像环境数据传输流程图

  1. TCPcopy 从数据链路层 copy 端口请求,然后更改目的 ip 和目的端口
  2. 将修改过的数据包传送给数据链路层,并且保持 tcp 连接请求
  3. 通过数据链路层从 online server 发送到 test server
  4. 在数据链路层解封装后到达 nginx 响应的服务端口
  5. 等用户请求的数据返回结果后,回包走数据链路层
  6. 通过数据链路层将返回的结果从 test server 发送到 assistant server。注:test server 只有一条默认路由指向 assistant server
  7. 数据到达 assistant server 后被 intercept 进程截获
  8. 过滤相关信息将请求状态发送给 online server 的 tcpcopy,关闭 tcp 连接

普通场景

nameip
client10.10.1.245
线上server10.10.1.240
测试server10.10.1.241
辅助server10.10.1.242

线上、辅助server 安装tcpcopy

yum -y install libpcap-devel

./configure --enable-advanced --enable-pcap --enable-dlinject(支持链路层pcap发包)

启动辅助服务器(10.10.2.242)tcpcopy server

/usr/local/bin/intercept -i eth1 -F 'tcp and src host 10.10.2.241 and src port 80' -d

复制线上流量到测试机 -s指定辅助服务器(intercept serve),-i 指定抓包网卡

/usr/local/bin/tcpcopy -x 10.10.2.240:80-10.10.2.241:80 -s 10.10.2.242 -i eth1 -d

测试服务器加路由:

route add -host 10.10.1.245 gw 10.10.2.242

客户端(10.10.1.245)的请求走辅助服务器(10.10.2.242),外网应用,测试服务器也可默认路由设成辅助服务器

route del default gw 原外网网关IP
route add -net 0.0.0.0/0 gw xxx

复制N倍流量到测试机

第一种:

/usr/local/bin/tcpcopy -x 10.10.2.240:80-10.10.2.241:80 -s 10.10.2.242 -i eth1 -d -n 3

第二种:

/usr/local/bin/tcpcopy -x 10.10.2.240:80-10.10.2.241:80 -s 10.10.2.242 -i eth1 -d
/usr/local/bin/tcpcopy -x 10.10.2.240:80-10.10.2.241:80 -s 10.10.2.242 -i eth1 -d -f 1
/usr/local/bin/tcpcopy -x 10.10.2.240:80-10.10.2.241:80 -s 10.10.2.242 -i eth1 -d -f 2

复制 20% 流量

 /usr/local/bin/tcpcopy -x 10.10.2.240:80-10.10.2.241:80 -s 10.10.2.242 -i eth1 -d -r 20

lvs场景

nameip
线上server10.10.2.100 外网IP:x.x.x.x
测试server110.10.2.241
测试server210.10.2.242
辅助sever10.10.2.246
LVS 调度10.10.2.240
VIP10.10.2.245

说明:

1、240 LB,241、242 realserver

2、realserver 部署线上应用,修改默认网关为辅助server

route del default gw 10.10.2.1
route add default gw 10.10.2.246

要保证lvs可以正常监控到realserver,就是LB和realserver网络正常

3、辅助server在上面运行intercept

/usr/local/bin/intercept -i eth1 -F 'tcp  and src port 80' -d

4、线上server运行tcpcopy

/usr/local/bin/tcpcopy -x 80-10.10.2.245:80 -s 10.10.2.246:36524 -i eth0 -d

复制eth0数据包到 vip 245,测试应用端口号80,intercept机器ip 246,端口36524

要复制流量很大,辅助server可扩展,两台辅助server 246、247,线上server的tcpcopy

/usr/local/bin/tcpcopy -x 80-10.10.2.245:80 -s 10.10.2.246:36524,10.10.2.247:36524 -i eth0 -d

cloud VPC环境

线上连接数量不多(非短连接应用),可用tcpcopy -c参数设置成tcpcopy所在机器的ip地址,就无需在测试服务器端设置路由,在tcpcopy所在机器设置iptables,干掉测试服务器回来的响应

aws可用此测试MySQL应用

云平台环境,用传统架构,如何编译

tcpcopy方式不变,intercept改变:

./configure --traditional

等同1.0以下版本默认方式

intercept将采用iptables的方式来获取响应包,并干掉响应包

内核<3.5,默认采用IP Queue

modprobe ip_queue # if not running
iptables -I OUTPUT -p tcp --s端口 -j QUEUE
./intercept

Or

内核>=3.5,默认采用NFQueue

iptables -I OUTPUT -p tcp --s端口 -j NFQUEUE
./intercept

Tips

  • 性能

想用好 tcpcopy,需要熟悉系统知识,包括如何高效率抓包,如何定位系统瓶颈,如何部署测试应用系统,如何抓包分析

建议连接数量非常多的时候,应关闭 内核模块 ip_conntrack

tcpcopy,默认从 ip 层发包,会被 ip_conntrack 干涉,可 –enable-dlinject 发包,避开ip层的ip_conntrack,或者对线上应用系统端口设置 NOTRACK,为性能考虑,是一种好的运维习惯

如果没有报“ip_conntrack: table full, dropping packet”,一般无需去操心ip_conntrack

  • 不提取 7 层信息

按域名区分拷贝流量,省得把不在本次压测范围内的工程打挂

但 tcpcopy 的原理是在 ip 层拷贝,不提取 7 层的信息,就是说,Nginx server上部署 TCPCopy,只能是将所有流量拷贝到镜像环境的 Nginx 上。反正没有配置对应的 server,或者 server 停掉,这种处理不了的流量就丢弃掉

  • pcap_inject发包两点好处

1) 避开在线机器在IP层的防火墙设置

tcpcopy进程复制过来的包源IP还是线上客户端的IP,在线机器上可能会被直接扔掉(比如只允许real server 源ip发回包源?),不能改变防火墙设置的情况下,pacp_inject直接从数据链路层发包,可以避开IP层复杂的防火墙设置

2) 提高30%的性能

但是pcap_inject发包需要知道数据包从在线机器发出下一跳的网卡地址,需要使用tcpdump工具抓包获取

tcpcopy -x 在线端口号@在线机器的出口网卡地址-测试机器的IP地址:测试机器的端口@下一跳网卡地址 -s 运行intercept的机器IP地址 -o 出口网卡设备 -i 抓包网卡设备

gor

Go语言编写,简单易用

现叫goreplay

支持流量放大和缩小、频率限制,正则表达式过滤流量,修改HTTP请求头,比如替换User-Agent, 或者增加某些HTTPHeader

将流量存入ES进行实时分析, –output-http-elasticsearch localhost:9200/gor

请求回放会模拟真实情况,如 request1 和 request2 间隔 3 秒,回放也会保留3秒间隔,准确呈现访问的负载状况

demo

  • 实时转发

线上系统8000 ,测试系统8001

gor --input-raw :8000 --output-http="http://localhost:8001"
  • 录制 回放 过滤
  gor --input-raw :8000 --output-file=requests.gor
  gor --input-file requests_0.gor --output-http="http://localhost:8001"

过滤

url过滤
gor --input-raw :8080 --output-http staging.com --http-allow-url /api
头过滤
gor --input-raw :8080 --output-http staging.com --http-allow-header api-version:^1.0\d
2倍
gor --input-file "requests.gor|200%" --output-http "staging.com"

频率控制

 gor --input-tcp :8080 --output-http "http://staging.com|1000%"
 缩小
 gor --input-raw :8080 --output-tcp "staging.com:8080|10%"

定义参数

$ gor --input-raw :80 --output-http 192.168.2.6:8080 --http-allow-method POST --http-set-header 'User-Agent: Gor' -output-http-workers=1 -http-allow-url test.php

复制两份到不同的测试服务

 gor --input-tcp :8080 --output-http "http://staging.com"  --output-http "http://dev.com"

loadbalance

gor --input-tcp :8080 --output-http "http://staging.com"  --output-http "http://dev.com" --split-output true

demo file server

gor file-server :8000

预发布系统压力测试

Api服务器上启动Gor Listener,复制80端口的流量,转发到预发布服务器的端口

 gor --input-raw :80 --output-tcp preview:80

预发布启动Gor,将请求转发到预发布服务器

gor --input-tcp preview:80 --output-http http://staging.com

也可以将listener和replayer合二为一

 gor --input-tcp preview:28020 --output-http http://staging.com

windows 编译

参考

https://stackoverflow.com/questions/38047858/compile-gopacket-on-windows-64bit

win 下编译要特别处理下,否则


go build -v github.com/buger/goreplay
github.com/buger/goreplay
# github.com/buger/goreplay
c:\go\pkg\tool\windows_amd64\link.exe: running gcc failed: exit status 1
C:/TDM-GCC-64/bin/../lib/gcc/x86_64-w64-mingw32/5.1.0/../../../../x86_64-w64-mingw32/bin/ld.exe: skipping incompatible C:/TDM-GCC-64/bin/../lib/gcc/x86_64-w64-mingw32/5.1.0/../../../../lib/libwpcap.a when searching for -lwpcap
C:/TDM-GCC-64/bin/../lib/gcc/x86_64-w64-mingw32/5.1.0/../../../../x86_64-w64-mingw32/bin/ld.exe: skipping incompatible C:/TDM-GCC-64/bin/../lib/gcc/x86_64-w64-mingw32/5.1.0/../../../../lib\libwpcap.a when searching for -lwpcap
C:/TDM-GCC-64/bin/../lib/gcc/x86_64-w64-mingw32/5.1.0/../../../../x86_64-w64-mingw32/bin/ld.exe: skipping incompatible C:/TDM-GCC-64/bin/../lib/gcc/x86_64-w64-mingw32/5.1.0/../../../libwpcap.a when searching for -lwpcap
C:/TDM-GCC-64/bin/../lib/gcc/x86_64-w64-mingw32/5.1.0/../../../../x86_64-w64-mingw32/bin/ld.exe: skipping incompatible C:/TDM-GCC-64/bin/../lib/gcc/x86_64-w64-mingw32/5.1.0/../../..\libwpcap.a when searching for -lwpcap
C:/TDM-GCC-64/bin/../lib/gcc/x86_64-w64-mingw32/5.1.0/../../../../x86_64-w64-mingw32/bin/ld.exe: skipping incompatible C:/TDM-GCC-64/bin/../lib/gcc/x86_64-w64-mingw32/5.1.0/../../../../lib/libwpcap.a when searching for -lwpcap
C:/TDM-GCC-64/bin/../lib/gcc/x86_64-w64-mingw32/5.1.0/../../../../x86_64-w64-mingw32/bin/ld.exe: skipping incompatible C:/TDM-GCC-64/bin/../lib/gcc/x86_64-w64-mingw32/5.1.0/../../../libwpcap.a when searching for -lwpcap
C:/TDM-GCC-64/bin/../lib/gcc/x86_64-w64-mingw32/5.1.0/../../../../x86_64-w64-mingw32/bin/ld.exe: cannot find -lwpcap

tdm64-gcc

http://tdm-gcc.tdragon.net/download

Winpcap

https://www.winpcap.org/install/default.htm

Winpcap developer’s pack

https://www.winpcap.org/devel.htm

extract include* to TDM-GCC-64\include

C:\Windows\SysWOW64\wpcap.dll

C:\Windows\SysWOW64\Packet.dll

gendef wpcap.dll
gendef packet.dll
dlltool --as-flags=--64 -m i386:x86-64 -k --output-lib libwpcap.a --input-def wpcap.def
dlltool --as-flags=--64 -m i386:x86-64 -k --output-lib libpacket.a --input-def packet.def

copy libwpcap.a and libpacket.a to TDM-GCC-64\lib

nginx

post action


location / {
    proxy_pass http://prod.env:8080;
    post_action     @post_action; 
}

location @post_action {
    proxy_pass      http://dst_host:dst_port; 
}

ngx_http_mirror_module

Nginx 1.13.4 新增,对原始请求创建后台镜像,镜像子请求的输出会被忽略 比tcpcopy使用更方便

配置块可在 http, server, location

# original配置
location / {
    mirror /mirror;
    mirror /mirror2;
    mirror_request_body off; //是否镜像请求Body部分
    proxy_pass http://prod.env:8080;
}
# mirror配置  //mirror不会输出http返回内容
location /mirror {
    internal;       //指定此location只能被内部的请求调用,外部的调用请求会返回404
    proxy_pass http://test.env:8000$request_uri;   //指定上游Server的地址
    proxy_set_header X-Original-URI $request_uri;  //镜像流量的头部
}
location /mirror2 {
    internal;
    proxy_pass http://test2.env:8000$request_uri;
    proxy_set_header X-Original-URI $request_uri;
}

dubboCopy

Dubbo 技术栈,用TCPCopy将线上流量复制到线下测试环境,很多时候进行引流的时候,只希望复制部分服务的流量。比如想将下单服务的引入进来,但并不想将支付订单的流量也引入进来,这样可能需要搭建一个很复杂的环境

另外有时只想针对单个服务进行测试,只复制单个服务或符合条件的流量,可以更好的隔离其他服务对测试的影响

使用TCPCopy的原始结构

使用DubboCopy之后的结构

TCPCopy直接面对的是 dubboCopy proxy server,不直接跟线下测试服务器交互,使用者透明,降低了使用的复杂度

需要提供可供调用的接口在线上机器启动TCPCopy

DubboCopy可部署多台,可用类似负载均衡的方式发送不同的命令到不同的线上机器,将流量均衡的复制到各个DubboCopy

ref

tcpcopy架构漫谈 三代架构

TCPCopy使用总结

https://winway.github.io/2017/10/17/tcpcopy-introduce/

dubbocopy

http://www.aizhuanji.com/a/PxDnAqVY.html

美团点评酒店后台故障演练系统

https://tech.meituan.com/thrifycopy_and_faultdrill_system.html