测试工具tcpcopy


一、Tcpcopy的介绍

可测试性问题?
上线前,满怀信心;上线后,系统崩溃
性能问题?
到底nginx比apache快多少?
可用性问题?
服务器崩溃了,会不会发生雪崩?
可修改问题?
程序更新后,心惊胆战?

TCPCopy七大功能:

1)分布式压力测试工具,利用在线数据,可以测试系统能够承受的压力大小(远比ab压力测试工具真实地多),也可以提前发现一些bug
2)普通上线测试,可以发现新系统是否稳定,提前发现上线过程中会出现的诸多问题,让开发者有信心上线
3)对比试验,同样请求,针对不同或不同版本程序,以及不同的内核参数,可以做性能对比等试验
4)流量放大功能,可以利用多种手段构造无限在线压力,满足中小网站压力测试要求
5)利用TCPCopy转发传统压力测试工具发出的请求,可以增加网络延迟,使其压力测试更加真实
6)热备份
7)实战演习(架构师必备)

特点

1)实时
2)效果真实
3)低负载,不影响在线
4)操作简单
5)分布式

总结:
如果你对上线没有信心,如果你的单元测试不够充分,如果你对新系统不够有把握,如果你对未来的请求压力无法预测,如果你想对比诸如apache和nginx的性能,如果你想放大在线流量,TCPCopy可以帮助你解决上述难题。

二、Tcpcopy的原理

tcpcopy的基本原理就是:在在线服务器内抓在线数据包,想尽办法修改在线数据包并发送给测试服务器,以达到欺骗测试服务器的目的

下面以nginx作为前端说明tcpcopy的原理:

上图中左边是线上前端机,右边是测试前端机。线上前端机开启tcpcopy客户端(tcpcopy进程),测试前端机开启tcpcopy服务端(interception进程),且两台机器上都启动了nginx服务。
Tcpcopy拷贝一次流量访问的步骤如下:

① 一个访问到达线上前端机;
② socket包在ip层被拷贝了一份传给tcpcopy进程;
③ tcpcopy修改包的目的及源地址,发给测试前端机;
④ 拷贝的包到达测试前端机;
⑤ 测试前端机的nginx处理访问,并返回结果;
⑥ 返回结果在ip层被截获、丢弃,由intercpetion拷贝返回结果的ip header返回;
⑦ ip header被发送给线上前端机的tcpcopy进程。

这里需要注意第六点,测试服务器的响应信息最终会被drop掉,并不会返回给客户端,返回的只是header.这样可以不增加出口带宽费用,节省测试成本,并且不会对客户端tcp/ip造成干扰。
而且因为使用了netlink,所以可以在响应包丢弃之前,截获响应包头信息。

三、TCPCopy的使用:

1.基本操作:
下载地址: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的日志确认。

2.TCPCopy 分布式架构
为了充分挖掘在线流量资源,我们可以复制多台在线流量到同一台测试服务器上去, 这
样可以演绎在分布式攻击情况下,系统能否承受此压力。

分布式压力测试的时候,同等压力情况下,连接数越少,性能越高,理由如下:
因为TCPCopy 是基于session 的,每一个session 由客户端IP 地址、客户端端口号、服
务器IP 地址和服务器应用端口号决定, 因此如果出现大量session, 必然要在TCPCopy server端保存大量的路由信息供响应包头返回使用,这会显著降低TCPCopy server 的性能。

3.制造多重压力。
1)在在线服务器上执行多个tcpcopy实例,但命令稍微不同,在原有命令基础上会增加-f参数
比如你想复制3倍的在线流量或者逐步增加流量,你可以执行如下命令

./tcpcopy 本地ip地址1[:本地ip地址2:…]  本地port  远程ip地址 远程port
./tcpcopy 本地ip地址1[:本地ip地址2:…]  本地port  远程ip地址 远程port -f 1
./tcpcopy 本地ip地址1[:本地ip地址2:…]  本地port  远程ip地址 远程port -f 2

2)同一个tcpcopy实例内复制多份流量

./tcpcopy 本地ip地址1[:本地ip地址2:…]  本地port  远程ip地址 远程port -n xx
其中-n后面的xx值代表复制的份数

四.TCPCopy的总结

1.tcpCopy的参数优化: 
要让TCPCopy 更好地工作,需要在各个方面进行优化,除了影响TCPCopy 的地方外,
我们还可以进行如下方面的优化。

1)TCPCopy server 的-s 参数,如果session 过多,压力也很大,请修改该参数为更高的值(默认为65536),建议设置65536 的倍数
2)TCPCopy client 的-t 参数,默认为60s,如果后端普遍响应很慢,建议设置与之匹配的值
3)TCPCopy client 的-m 参数(此参数只有在内核版本>=2.6.32 才有效) ,由于默认所占内存最大512M,如果内存需要超过此值,请设置合理的值
4)response MTU 越大越好,因为response MTU 越大,一般相应的response packets 数量越少,这样能显著降低TCPCopy 所占的CPU,性能得到提升

2.tcpCopy的优点:
TCPCopy 主要用来解决TCP 层及其以上(如http 协议)的流量实时复制问题,主要用于server 的流量实时回放领域。总体来说,TCPCopy 有如下优势:

1)TCPCopy 能够对server 进行回放,不仅可以离线回放,还可以实时回放(推荐)
2)TCPCopy 实时复制在线请求包给测试服务器,并不需要大量IO 操作,因此不影响在线IO
3)TCPCopy 在大部分场合下,所占内存和CPU 都很小,相对于当今互联网服务器,几乎可以忽略不计
4)TCPCopy 复制转发过去的数据包,继承了在线数据包的特征,如高延迟特征,使其跟在线环境一样
5)TCPCopy 使用非常简单
6)TCPCopy 成本低,在同一网段复制在线流量,可以做到零成本

3.TCPCopy 缺点

1)过多路由条目信息造成TCPCopy server 性能瓶颈
2)TCPCopy 的性能跟所处理的包的多少关系很大
3)无法保持不同连接的请求的有序性
4)过分依赖于内核,需系统参数调优才能有好效果
5)一般情况下,不能测试网络底层协议性能瓶颈(不过可通过设置代理测试网络底层协议)
6)TCPCopy 会消耗一定的带宽,主要在转发在线请求数据包方面

4.影响TCPCopy 的因素
可能影响到TCPCopy 的地方有如下几个:
1)Raw socket input 接口

抓包利用了raw input socket 接口,由于系统参数的原因,系统内核并不一定能把所有你关心的包都给你,压力越大,一般丢得越多,所以这方面系统参数要设置好。

2) Raw socket output 接口

发包利用了raw socket output 接口,系统内核并不一定能够成功发送所有包,有时候会遇到各种各样的问题。例如,当所抓的包的大小超过发送MTU 的值,在0.5 以下版本,就会发送不出去,所以最好采用0.5 版本及其以上版本。

3) 去往测试机器的路上

发包以后,路途可能不会一帆风顺,由于源IP 地址还是客户端的IP 地址,可能会造成中间设备认为不合法,将发送的包给drop 掉。这时候,在测试机器利用tcpdump 来抓包,将不会抓到任何复制转发过来的数据包。 这时候你可以在同一网段试验一下, 如果同网段成功而跨网段不成功,那么跨网段转发出去的包应该被中途drop 掉了。需要注意的是,如果跨网段请求复制不出去,你可以先复制请求到本网段的代理,通过代理再把请求传递给跨网段的测试机器。

4) 测试系统OS

在某些情况下,比如在极端情况下,如果你利用压力测试工具来发请求,也许压力测试工具已经测试完了,而转发的数据包中的第一个syn 包还没有到达测试机器,这样就造成原先串行的请求变成了并行请求,造成大量syn 涌向测试服务器,这样就可能被测试机器误报为syn flood 攻击,一旦syn 包被丢弃,那么相应的请求都会被drop 掉。针对这种情况,要优化系统参数,才能有好的效果,或者你可以采用一些长连接的压力测试工具来发请求,比如httpsender.

5) 后端应用

并不是每个请求过去, 后端应用都能够及时处理或者都能处理, 有可能触发了后端应用的bug, 导致请求迟迟没有响应; 也有可能上层协议仅仅处理socket buffer 中的第一个请求,这些情况都会导致请求大量丢失。

6)Netlink socket 接口

假设后端响应都顺利,压力比较大的时候,ip queue 模块传递响应包给netlink socket 的时候,也会遇到丢包现象,这时候可以通过cat /proc/net/ip_queue 命令,查看ip queue 运行情况:
i) 如果Queue dropped 的数值不断增大,则需要修改ip_queue_maxlen 参数:
echo 4096 > /proc/sys/net/ipv4/ip_queue_maxlen
ii) 如果Netlink dropped 的数值不断增大,修改rmem_max 和wmem_max 参数:
sysctl -w net.core.rmem_max=16777216
sysctl -w net.core.wmem_max=16777216

五、tcpcopy应用实例

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

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

sudo ./tcpcopy ipA 80 ipB 80 >/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://www.wjthink.org/

本文链接地址: http://www.wjthink.org/?p=313

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
/******************************************************************************** * * * G-TcpClient:基于完成端口的Tcp客户端通讯模块(IOCP TcpClient) * * * * Copyright &copy; 2009-2010 GuestCode 代码客(卢益贵) * * 版权所有 侵权必究 * * * * QQ:48092788 E-Mail:[email protected] 源码博客: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、付费专栏及课程。

余额充值