分布式TCP压力测试工具 tcpcopy-有感兴趣的没?

分布式TCP压力测试工具 tcpcopy-有感兴趣的没?
发布于:2012-10-22 17:53:23来自【群】QTP                     QTP

一、工具介绍

Tcpcopy 是一个分布式在线压力测试工具,可以将线上流量拷贝到测试机器,实时的模拟线上环境,达到在程序不上线的情况下实时承担线上流量的效果(测致友GANGGANG的),尽早发现 bug,增加上线信心。

Tcpcopy 是由网易技术部于 2011 年 9 月开源的一个项目,现在已经更新到0.4版本。

与传统的压力测试工具(如:abench)相比,tcpcopy 的最大优势在于其实时及真实性,除了少量的丢包,完全拷贝线上流量到测试机器,真实的模拟线上流量的变化规律。

二、Tcpcopy 的原理

1.流程

现在以 nginx 作为前端说明 tcpcopy 的原理

      

       

上图中左边是线上前端机,右边是测试前端机。线上前端机开启 tcpcopy 客户端(tcpcopy 进程),测试前端机开启 tcpcopy 服务端(interception 进程),且两台机器上都启动了 nginx 服务。

Tcpcopy 拷贝一次流量访问的步骤如下:

  1. 一个访问到达线上前端机;
  2. socket 包在 ip 层被拷贝了一份传给 tcpcopy 进程;
  3. tcpcopy 修改包的目的及源地址,发给测试前端机;
  4. 拷贝的包到达测试前端机;
  5. 测试前端机的 nginx 处理访问,并返回结果;
  6. 返回结果在 ip 层被截获、丢弃,由 intercpetion 拷贝返回结果的 ip header 返回;
  7. ip header 被发送给线上前端机的 tcpcopy 进程。

1.代码分析

1) 首先,在链路层或者 IP 层,在把包交到上一层之前,系统会检查有没进程创建了 socket (AF_PACKET,SOCK_DGRAM,…)或 socket (AF_INET,SOCK_RAW,…)等类型的套接字(即原始套接字 sock_raw),如果有,这个包就会被复制一份并发送到这个 socket 的缓冲区。tcpcopy 就是通过这种方式来复制访问流量的。上述的两种抓包方式,前者工作在数据链路层,后者工作在 IP 层。在 tcpcopy 中不同版本所使用的抓包函数不同,在0.3版本中是:

int sock = socket (AF_PACKET,SOCK_RAW,htons (ETH_P_IP));

而在0.4版本中,用的是:

int sock = socket (AF_INET,SOCK_RAW,IPPROTO_TCP);

以上两个函数分别工作在链路层和 IP 层,前者会把进来和出去的包都抓取到,后者只抓取到进来的包。

2) Tcpcopy 在发送拷贝的数据包的时候,使用了如下 socket:

sock = socket (AF_INET, SOCK_RAW,IPPROTO_RAW);

并对这个 socket 设置了 IP_HDRINCL:

setsockopt (sock, IPPROTO_IP, IP_HDRINCL, &n, sizeof (n));

因此网络层不会再增加 ip header. 发送之前更改了包的目的 ip 和端口:

tcp_header->dest = remote_port;

ip_header->daddr = remote_ip;

最后调用 sendto 函数发送包到测试前端机:

send_len = sendto (sock,(char *) ip_header,tot_len,0,

(struct sockaddr *)&toaddr,sizeof (toaddr));

3) 在测试前端机上加载了 ip_queue 模块,并设置 iptables 规则:

iptables -I OUTPUT -p tcp –sport 80 -j QUEUE

复制的访问流量到达测试前端机上的 nginx,nginx 处理并返回结果,这个结果包在 IP 层会被前面所设置的 iptables 规则匹配发往目标(target)QUEUE。而 QUEUE 是由 ip_queue 模块实现。下一步这个匹配包就会被内核经过 netlink socket 发往用户空间的程序(在这是 tcpcopy 的服务端 interception 进程)。

netlink socket 是内核与用户进程之间的一种通信机制,是网络应用程序与内核通信的最常用的接口,可以用来配置网络的各个方面(比如包的过滤)。

interception 用如下方式创建 netlink socket:

int sock = socket (AF_NETLINK,SOCK_RAW,NETLINK_FIREWALL);

NETLINK_FIREWALL 协议有三种消息类型:IPQM_MODE,IPQM_PACKET,IPQM_VERDICT.

内核通过一个 IPQM_PACKET 消息将刚才截获的返回结果包发送到 interception,interception 给内核发送一个 IPQM_VERDICT 消息告诉内核对这个包的裁决结果(DROP,ACCEPT,etc.)。tcpcopy 通过这样的办法将测试前端机上 nginx 返回的结果截获丢弃,并由 interception 返回一个 ip header.相应代码实现如下:

拷贝结果包的 ip header,发送:

此处略去C++代码......

内核接收到这个包后将 packet_id 这个包 drop 或 accept。在后文中可以看到从0.4版本开始的 tcpcopy 利用这个特点保留了一个允许访问的 ip 列表,因为默认情况下访问测试前端机上 nginx 服务所得到的结果会在 ip 层被 drop 掉,造成在 80 端口上无法访问 nginx。有了这个允许 ip 列表,即使是刷了 iptables 规则、起了 interception 进程,在某些机器上也是可以正常访问测试前端机上的 nginx 服务的。

三、操作方法

下载地址:http://tcpcopy.googlecode.com/files/tcpcopy-0.3.3.tar.gz,下载 tcpcopy 源码包后解压,执行常规的./configure;make;make install 三部曲即可。

假如有两台机器:

机器A:线上前端机,ip:61.135.xxx.1;

机器B:测试前端机,ip:61.135.xxx.2;

两台机器上都起了 nginx 服务,操作者在两台机器上都需有 sudo 权限。

操作步骤:

1.  在B依次执行,

1) 加载 ip_queue 模块,modprobe ip_queue;

2) 配置 iptables 规则,sudo iptables -t filter -I OUTPUT -p tcp –sport 80 -j QUEUE;

3) 启动 tcpcopy 服务端,sudo ./interception & ;

2.  在A上执行,

启动 tcpcopy 客户端,sudo ./tcpcopy 61.135.xxx.1 80 61.135.xxx.2 80 &;

如果在A上看到“I am booted”,则表示操作成功,tcpcopy 已经开始工作,可以查看一下机器B上 nginx 的日志确认。

四、高级用法

1.  级联

设有线上前端机一台命名A,测试前端机若干B,C,D,……利用 tcpcopy 可以将A上的访问流量拷贝到B,B拷贝到C,C拷贝到D,……这样就将一份流量放大了多倍,可以用来测试引擎的极限承受能力。

2.  同一 tcpcopy 实例内多重复制

从0.4版开始,tcpcopy 支持在同一个客户端实例复制多份请求到同一个服务端,启动的方式如下(比如要复制 2 份,使用-n这个选项来控制要复制的份数),

sudo ./tcpcopy 61.135.xxx.1 80 61.135.xxx.2 80;

sudo ./tcpcopy 61.135.xxx.1 80 61.135.xxx.2 80 -n 1;

sudo ./tcpcopy 61.135.xxx.1 80 61.135.xxx.2 80 -n 2;

3.  服务端允许访问 ip 列表

由于配置了 iptables 规则,使用 tcp 协议且源端口号为 80 的包都会被匹配放到目标 QUEUE 去,进而被 drop 掉,因此这个时候测试前端机上的 nginx 服务是不可访问的。从0.4版本开始,可以指定一个允许访问 ip 列表,在列表中的机器上是可以访问测试前端机上的 nginx 服务的。假如要添加 61.135.xxx.3,61.135.xxx.4到允许 ip 列表,启动 interception 时使用如下方式:

sudo ./interception 61.135.xxx.3:61.135.xxx.4;

五、tcpcopy 在一淘的应用

一淘引擎在今年 2 月份时有一次重大的更新,在上线之前,利用 tcpcopy 把所有前端机的流量拷贝到新的 demo 前端机上,进行在线模拟实验。引流示例如下图:

所有线上前端机都开启 tcpcopy 客户端,由于一直报”Message too long”(这是由于 packets 长度超过 1500 造成,每分钟差不多有 50 个)刷屏,所以将 stderror 重定向,

sudo ./tcpcopy ipA 80 ipB 80 2>/dev/null &

在测试前端机上开启 tcpcopy 服务端程序 interception,并设置 iptables 规则。

压了大约有一个星期,期间观察 qps,load 等各项指标是否正常。新引擎单个集群一天的平均 qps 大约是 110,峰值大约 240。实验结果显示的包丢失率大约是(1822213-1797242)/1822213=1.37%. 后来进一步将多个线上前端机的流量引到一个测 试前端,测试新引擎的单集群极限服务能力,qps 能达到 1000 以上, latency 大约 40ms,达到了上线要求。

Tcpcopy 客户端和服务端本身占用的资源较少,不影响在线服务。

13991 root      20   0  160m  77m  888 R  7.7  0.3  71:26.24 tcpcopy

7723 root      15   0 42592  38m  324 S  5.8  0.2  12:14.83 interception

%cpu 分别占7.7% 和5.8%,物理内存占用分别是 77m 和 38m.

由于几乎完全模拟了线上环境,我们对于新引擎上线更有信心,最终上线圆满成功,实现平稳过渡。现在利用 tcpcopy 拷贝线上流量作模拟压测已成为我们日常开发上线流程中的一项内容。

      

六、附录

项目主页:http://code.google.com/p/tcpcopy/

Sock_raw:http://sock-raw.org/papers/sock_raw

Netlink:http://smacked.org/docs/netlink.pdf

相关主题:http://blog.csdn.net/wangbin579/article/category/926096/1

代码 svn 地址:http://tcpcopy.googlecode.com/svn/trunk

感兴趣的同学可以一起预研一下,在我们公司的业务领域应该还是有其用武之地的~

/******************************************************************************** * * * G-TcpClient:基于完成端口Tcp客户端通讯模块(IOCP TcpClient) * * * * Copyright &copy; 2009-2010 GuestCode 代码客(卢益贵) * * 版权所有 侵权必究 * * * * QQ:48092788 E-Mail:48092788@qq.com 源码博客:http://blog.csdn.net/guestcode * * * * GSN:34674B4D-1F63-11D3-B64C-11C04F79498E * * * ********************************************************************************/ #pragma once extern "C" { //>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> 类型定义 >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> #ifndef _GTYPE #define _GTYPE typedef unsigned char* PGBUF; typedef void(__stdcall *PGFN_ON_CONNECTED)(unsigned int unPerHandle, unsigned char* pBuf, unsigned int unLen); typedef void(__stdcall *PGFN_ON_RECEIVED)(unsigned int unPerHandle, unsigned char* pBuf, unsigned int unLen); typedef void(__stdcall *PGFN_ON_SENDED)(unsigned int unPerHandle, unsigned int unSendID, unsigned int unLen); typedef void(__stdcall *PGFN_ON_DISCONNECTED)(unsigned int unPerHandle, unsigned int unFlag); typedef void(__stdcall *PGFN_ON_THREAD)(unsigned int unThreadContext, unsigned int unThreadHandle, unsigned int unThreadID, BOOL bIsBegin, unsigned int unFlag); /* typedef struct _CONNECTION { unsigned int unPerHandle; }CONNECTION, *PCONNECTION; typedef void(__stdcall *PGFN_ON_CONNECTED)(unsigned int unPerHandle, unsigned char* pBuf, unsigned int unLen); typedef void(__stdcall *PGFN_ON_RECEIVED)(PCONNECTION pConnection, unsigned char* pBuf, unsigned int unLen); typedef void(__stdcall *PGFN_ON_SENDED)(PCONNECTION pConnection, unsigned int unSendID, unsigned int unLen); typedef void(__stdcall *PGFN_ON_DISCONNECTED)(PCONNECTION pConnection, unsigned int unFlag); void __stdcall GTcpClt_OnThread(unsigned int unThreadContext, unsigned int unThreadHandle, unsigned int unThreadID, BOOL bIsBegin, unsigned int unFlag) { } void __stdcall GTcpClt_OnConnected(unsigned int unPerHandle, void* _NULL, unsigned int unNULL) { } void __stdcall GTcpClt_OnReceived(PCONNECTION pConnection, unsigned char* pBuf, unsigned int unLen) { } void __stdcall GTcpClt_OnSended(PCONNECTION pConnection, unsigned int unSendID, unsigned int unLen) { } void __stdcall GTcpClt_OnDisconnected(PCONNECTION pConnection, unsigned int unFlag) { } */ #define _USE_UNICODE 1 #ifndef _DLL //#define _DLL #endif #ifdef _DLL #define DllExport _declspec(dllexport) #else #define DllExport #endif #define VER_FLAG_WIDE_CHAR 0x01 #define VER_FLAG_BETA 0x02 #define VER_FLAG_ZERO_READ 0x04 #define VER_FLAG_TRIAL 0x08 #define VER_FLAG_DEBUG 0x10 #define HNDS_CONNECT 1 #define HNDS_CONNECTED 2 #define HNDS_DISCONNECT 3 #endif //<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< 类型定义 <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> 版本信息 >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> #if(_USE_UNICODE) DllExport wchar_t* __stdcall GTcpClt_GetVersionName(void); #else DllExport char* __stdcall GTcpClt_GetVersionName(void); #endif DllExport float __stdcall GTcpClt_GetVersionNumber(void); DllExport unsigned int __stdcall GTcpClt_GetVersionFlag(void); //<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< 版本信息 <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> 功能函数 >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> DllExport DWORDLONG __stdcall GTcpClt_GetPhyMemInfo(DWORDLONG* pdwTotal); #if(_USE_UNICODE) DllExport void __stdcall GTcpClt_WriteLog(wchar_t* pstrLog, unsigned int unCode = 0); DllExport void __stdcall GTcpClt_GetHostIP(wchar_t* pstrIP, unsigned int unLen, BOOL bIsInternetIP = FALSE); #else DllExport void __stdcall GTcpClt_WriteLog(char* pstrLog, unsigned int unCode = 0); DllExport void __stdcall GTcpClt_GetHostIP(char* pstrIP, unsigned int unLen, BOOL bIsInternetIP = FALSE); #endif //<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< 功能函数 <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<>>>>>>>>>>>>>>>>>>>>>>>>>>> PerIoData函数 >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> DllExport unsigned int __stdcall GTcpClt_GetGBufSize(void); DllExport unsigned int __stdcall GTcpClt_GetIoDataSize(void); DllExport unsigned int __stdcall GTcpClt_GetIoDataUse(void); DllExport unsigned int __stdcall GTcpClt_GetIoDataTotal(void); DllExport float __stdcall GTcpClt_GetIoDataUseRate(void); DllExport unsigned int __stdcall GTcpClt_GetIoDataUseMem(void); //<<<<<<<<<<<<<<<<<<<<<<<<<<<< PerIoData函数 <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<>>>>>>>>>>>>>>>>>>>>>>>>>>> PerHndData函数 >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> DllExport unsigned int __stdcall GTcpClt_GetHndDataUse(void); DllExport unsigned int __stdcall GTcpClt_GetHndDataTotal(void); DllExport unsigned int __stdcall GTcpClt_GetHndDataSize(void); DllExport float __stdcall GTcpClt_GetHndDataUseRate(void); DllExport unsigned int __stdcall GTcpClt_GetHndDataUseMem(void); //<<<<<<<<<<<<<<<<<<<<<<<<<<<< PerHndData函数 <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> 信息函数 >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> DllExport unsigned int __stdcall GTcpClt_GetThreadNumber(void); DllExport unsigned int __stdcall GTcpClt_GetPageSize(void); DllExport unsigned int __stdcall GTcpClt_GetBlockSize(void); DllExport unsigned int __stdcall GTcpClt_GetConnectCount(void); DllExport unsigned int __stdcall GTcpClt_GetThreadRunCount(unsigned int unThreadContext); DllExport unsigned int GTcpClt_GetState(unsigned int unPerHandle); #if(_USE_UNICODE) DllExport wchar_t* __stdcall GTcpClt_GetThreadName(unsigned int unThreadContext); DllExport BOOL __stdcall GTcpSock_GetPerHandleInfo(unsigned int unPerHandle, wchar_t* pstrIP, unsigned int unIPLen, wchar_t* pstrPort, unsigned int unPortLen); DllExport BOOL __stdcall GTcpSock_GetPerHandleName(unsigned int unPerHandle, wchar_t* pstrName, unsigned int unLen); #else DllExport char* __stdcall GTcpClt_GetThreadName(unsigned int unThreadContext); DllExport BOOL __stdcall GTcpSock_GetPerHandleInfo(unsigned int unPerHandle, char* pstrIP, unsigned int unIPLen, char* pstrPort, unsigned int unPortLen); DllExport BOOL __stdcall GTcpSock_GetPerHandleName(unsigned int unPerHandle, char* pstrName, unsigned int unLen); #endif DllExport unsigned int __stdcall GTcpClt_GetProcesserNumber(void); DllExport BOOL __stdcall GTcpClt_IsActive(); DllExport unsigned int __stdcall GTcpClt_GetUseMem(void); //<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< 信息函数 <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> 操作函数 >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> DllExport void* __stdcall GTcpClt_GetPerHandleOwner(unsigned int unPerHandle); DllExport BOOL __stdcall GTcpClt_SetPerHandleOwner(unsigned int unPerHandle, void* pOwner); DllExport PGBUF __stdcall GTcpClt_AllocGBuf(void); DllExport BOOL __stdcall GTcpClt_FreeGBuf(PGBUF pGBuf); DllExport unsigned int __stdcall GTcpClt_PostSendGBuf(unsigned int unPerHandle, PGBUF pGBuf, unsigned int unLen); DllExport unsigned int __stdcall GTcpClt_PostSendBuf(unsigned int unPerHandle, unsigned char* pBuf, unsigned int unLen); DllExport void __stdcall GTcpClt_PostBroadcast(unsigned char* pBuf, unsigned int unLen); //<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< 操作函数 <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> 主要函数 DllExport BOOL __stdcall GTcpClt_CloseConnect(unsigned int unPerHandle); #if(_USE_UNICODE) DllExport unsigned int __stdcall GTcpClt_OpenConnect( wchar_t* pstrRemoteIP, wchar_t* pstrRemotePort, wchar_t* pstrLocalIP, PGFN_ON_CONNECTED pfnOnConnected, PGFN_ON_RECEIVED pfnOnReceived, PGFN_ON_SENDED pfnOnSended, PGFN_ON_DISCONNECTED pfnOnDisconnected, void* pOwner = NULL); #else DllExport unsigned int __stdcall GTcpClt_OpenConnect( char* pstrRemoteIP, char* pstrRemotePort, char* pstrLocalIP, PGFN_ON_CONNECTED pfnOnConnected, PGFN_ON_RECEIVED pfnOnReceived, PGFN_ON_SENDED pfnOnSended, PGFN_ON_DISCONNECTED pfnOnDisconnected, void* pOwner = NULL); #endif DllExport BOOL __stdcall GTcpClt_Start(unsigned int unHeartbeatTime = 60, unsigned int unMaxNetDelayTime = 5, unsigned int unGuardThreadSleepTime = 2, PGFN_ON_THREAD pfnOnThread = NULL, unsigned int unHndDataInitNumber = 1000, unsigned int unIoDataInitNumber = 1500, unsigned int unProcesserThreadNumber = 0, unsigned int unWorkerThreadNumber = 0); DllExport void __stdcall GTcpClt_Stop(void); //<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< 主要函数 <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< } /* ... extern "C" */
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值