一、工具介紹
Tcpcopy是一個分布式在線壓力測試工具,可以將線上流量拷貝到測試機(jī)器,實(shí)時的模擬線上環(huán)境,達(dá)到在程序不上線的情況下實(shí)時承擔(dān)線上流量的效果,盡早發(fā)現(xiàn)bug,增加上線信心。
Tcpcopy是由網(wǎng)易技術(shù)部于2011年9月開源的一個項目,現(xiàn)在已經(jīng)更新到0.4版本。
與傳統(tǒng)的壓力測試工具(如:abench)相比,tcpcopy的最大優(yōu)勢在于其實(shí)時及真實(shí)性,除了少量的丟包,完全拷貝線上流量到測試機(jī)器,真實(shí)的模擬線上流量的變化規(guī)律。
二、Tcpcopy的原理
1.流程
現(xiàn)在以nginx作為前端說明tcpcopy的原理:
上圖中左邊是線上前端機(jī),右邊是測試前端機(jī)。線上前端機(jī)開啟tcpcopy客戶端(tcpcopy進(jìn)程),測試前端機(jī)開啟tcpcopy服務(wù)端(interception進(jìn)程),且兩臺機(jī)器上都啟動了nginx服務(wù)。
Tcpcopy拷貝一次流量訪問的步驟如下:
① 一個訪問到達(dá)線上前端機(jī);
② socket包在ip層被拷貝了一份傳給tcpcopy進(jìn)程;
③ tcpcopy修改包的目的及源地址,發(fā)給測試前端機(jī);
④ 拷貝的包到達(dá)測試前端機(jī);
⑤ 測試前端機(jī)的nginx處理訪問,并返回結(jié)果;
⑥ 返回結(jié)果在ip層被截獲、丟棄,由intercpetion拷貝返回結(jié)果的ip header返回;
⑦ ip header被發(fā)送給線上前端機(jī)的tcpcopy進(jìn)程。
1.代碼分析
1) 首先,在鏈路層或者IP層,在把包交到上一層之前,系統(tǒng)會檢查有沒進(jìn)程創(chuàng)建了socket(AF_PACKET,SOCK_DGRAM,…) 或socket(AF_INET,SOCK_RAW,…)等類型的套接字(即原始套接字sock_raw),如果有,這個包就會被復(fù)制一份并發(fā)送到這個 socket的緩沖區(qū)。tcpcopy就是通過這種方式來復(fù)制訪問流量的。上述的兩種抓包方式,前者工作在數(shù)據(jù)鏈路層,后者工作在IP層。在 tcpcopy中不同版本所使用的抓包函數(shù)不同,在0.3版本中是:
int sock = socket(AF_PACKET,SOCK_RAW,htons(ETH_P_IP));
而在0.4版本中,用的是:
int sock = socket(AF_INET,SOCK_RAW,IPPROTO_TCP);
以上兩個函數(shù)分別工作在鏈路層和IP層,前者會把進(jìn)來和出去的包都抓取到,后者只 抓取到進(jìn)來的包。
2) Tcpcopy在發(fā)送拷貝的數(shù)據(jù)包的時候,使用了如下socket:
sock = socket(AF_INET, SOCK_RAW,IPPROTO_RAW);
并對這個socket設(shè)置了IP_HDRINCL:
setsockopt(sock, IPPROTO_IP, IP_HDRINCL, &n, sizeof(n));
因此網(wǎng)絡(luò)層不會再增加ip header. 發(fā)送之前更改了包的目的ip和端口:
tcp_header->dest = remote_port;
ip_header->daddr = remote_ip;
最后調(diào)用sendto函數(shù)發(fā)送包到測試前端機(jī):
send_len = sendto(sock,(char *)ip_header,tot_len,0,
(struct sockaddr *)&toaddr,sizeof(toaddr));
3) 在測試前端機(jī)上加載了ip_queue模塊,并設(shè)置iptables規(guī)則:
iptables -I OUTPUT -p tcp –sport 80 -j QUEUE
復(fù)制的訪問流量到達(dá)測試前端機(jī)上的nginx,nginx處理并返回結(jié)果,這個結(jié)果包在IP層會被前面所設(shè)置的iptables規(guī)則匹配發(fā)往目標(biāo)(target)QUEUE。而QUEUE是由ip_queue模塊實(shí)現(xiàn)。下一步這個匹配包就會被內(nèi)核經(jīng)過netlink socket發(fā)往用戶空間的程序(在這是tcpcopy的服務(wù)端interception進(jìn)程)。
netlink socket是內(nèi)核與用戶進(jìn)程之間的一種通信機(jī)制,是網(wǎng)絡(luò)應(yīng)用程序與內(nèi)核通信的最常用的接口,可以用來配置網(wǎng)絡(luò)的各個方面(比如包的過濾)。
interception用如下方式創(chuàng)建netlink socket:
int sock = socket(AF_NETLINK,SOCK_RAW,NETLINK_FIREWALL);
NETLINK_FIREWALL協(xié)議有三種消息類型:IPQM_MODE,IPQM_PACKET,IPQM_VERDICT.
內(nèi)核通過一個IPQM_PACKET消息將剛才截獲的返回結(jié)果包發(fā)送到interception,interception給內(nèi)核發(fā)送一個 IPQM_VERDICT消息告訴內(nèi)核對這個包的裁決結(jié)果(DROP,ACCEPT,etc.)。tcpcopy通過這樣的辦法將測試前端機(jī)上nginx 返回的結(jié)果截獲丟棄,并由interception返回一個ip header.相應(yīng)代碼實(shí)現(xiàn)如下:
拷貝結(jié)果包的ip header,發(fā)送:
structreceiver_msg_stmsg;... memset(&msg,0,sizeof(structreceiver_msg_st)); memcpy((void *) &(msg.ip_header),ip_header,sizeof(structiphdr)); memcpy((void *) &(msg.tcp_header),tcp_header,sizeof(structtcphdr));...send(sock,(constvoid *)msg,sizeof(structreceiver_msg_st),0);
interception向內(nèi)核發(fā)送IPQM_VERDICT消息報告裁決結(jié)果:
struct nlmsghdr* nl_header=(struct nlmsghdr*)buffer; struct ipq_verdict_msg *ver_data = NULL; struct sockaddr_nladdr;nl_header->nlmsg_type=IPQM_VERDICT;nl_header->nlmsg_len=NLMSG_LENGTH(sizeof(struct ipq_verdict_msg));nl_header->nlmsg_flags=(NLM_F_REQUEST);nl_header->nlmsg_pid=getpid();nl_header->nlmsg_seq=seq++;ver_data=(struct ipq_verdict_msg *)NLMSG_DATA(nl_header);ver_data->value=NF_DROP; /*如果要accept這個包,則設(shè)為NF_ACCEPT)*/ver_data->id=packet_id; memset(&addr,0,sizeof(addr));addr.nl_family = AF_NETLINK;addr.nl_pid =0;addr.nl_groups =0;sendto(firewall_sock,(void *)nl_header,nl_header->nlmsg_len,0,(struct sockaddr *)&addr,sizeof(struct sockaddr_nl));
內(nèi)核接收到這個包后將packet_id這個包drop或accept。在后文中可以看到從0.4版本開始的tcpcopy利用這個特點(diǎn)保留了一個允許訪問的ip列表,因?yàn)槟J(rèn)情況下訪問測試前端機(jī)上nginx服務(wù)所得到的結(jié)果會在ip層被drop掉,造成在80端口上無法訪問nginx。有了這個允許ip列表,即使是刷了iptables規(guī)則、起了interception進(jìn)程,在某些機(jī)器上也是可以正常訪問測試前端機(jī)上的nginx服務(wù)的。
三、操作方法
下載地址:http://tcpcopy.googlecode.com/files/tcpcopy-0.3.3.tar.gz,下載tcpcopy源碼包后解壓,執(zhí)行常規(guī)的./configure;make;make install三部曲即可。
假如有兩臺機(jī)器:
機(jī)器A:線上前端機(jī),ip:61.135.xxx.1;
機(jī)器B:測試前端機(jī),ip:61.135.xxx.2;
兩臺機(jī)器上都起了nginx服務(wù),操作者在兩臺機(jī)器上都需有sudo權(quán)限。
操作步驟:
1. 在B依次執(zhí)行,
1) 加載ip_queue模塊,modprobe ip_queue;
2) 配置iptables規(guī)則,sudo iptables -t filter -I OUTPUT -p tcp –sport 80 -j QUEUE;
3) 啟動tcpcopy服務(wù)端,sudo ./interception & ;
2. 在A上執(zhí)行,
啟動tcpcopy客戶端,sudo ./tcpcopy 61.135.xxx.1 80 61.135.xxx.2 80 &;
如果在A上看到“I am booted”,則表示操作成功,tcpcopy已經(jīng)開始工作,可以查看一下機(jī)器B上nginx的日志確認(rèn)。
四、高級用法
1. 級聯(lián)
設(shè)有線上前端機(jī)一臺命名A,測試前端機(jī)若干B,C,D,……利用tcpcopy可以將A上的訪問流量拷貝到B,B拷貝到C,C拷貝到D,……這樣就將一份流量放大了多倍,可以用來測試引擎的極限承受能力。
2. 同一tcpcopy實(shí)例內(nèi)多重復(fù)制
從0.4版開始,tcpcopy支持在同一個客戶端實(shí)例復(fù)制多份請求到同一個服務(wù)端,啟動的方式如下(比如要復(fù)制2份,使用-n這個選項來控制要復(fù)制的份數(shù)),
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. 服務(wù)端允許訪問ip列表
由于配置了iptables規(guī)則,使用tcp協(xié)議且源端口號為80的包都會被匹配放到目標(biāo)QUEUE去,進(jìn)而被drop掉,因此這個時候測試前端機(jī)上的nginx服務(wù)是不可訪問的。從0.4版本開始,可以指定一個允許訪問ip列表,在列表中的機(jī)器上是可以訪問測試前端機(jī)上的nginx服務(wù)的。假如要添加61.135.xxx.3,61.135.xxx.4到允許ip列表,啟動interception時使用如下方式:
sudo ./interception 61.135.xxx.3:61.135.xxx.4;
五、tcpcopy在一淘的應(yīng)用
一淘引擎在今年2月份時有一次重大的更新,在上線之前,利用tcpcopy把所有前端機(jī)的流量拷貝到新的demo前端機(jī)上,進(jìn)行在線模擬實(shí)驗(yàn)。引流示例如下圖:
所有線上前端機(jī)都開啟tcpcopy客戶端,由于一直報”Message too long”(這是由于packets長度超過1500造成,每分鐘差不多有50個)刷屏,所以將stderror重定向,
sudo ./tcpcopy ipA 80 ipB 80 2>/dev/null &
在測試前端機(jī)上開啟tcpcopy服務(wù)端程序interception,并設(shè)置iptables規(guī)則。
壓了大約有一個星期,期間觀察qps,load等各項指標(biāo)是否正常。新引擎單個集群一天的平均qps大約是110,峰值大約240。實(shí)驗(yàn)結(jié)果顯示的包丟失率大約是(1822213-1797242)/1822213=1.37%. 后來進(jìn)一步將多個線上前端機(jī)的流量引到一個測試前端,測試新引擎的單集群極限服務(wù)能力,qps能達(dá)到1000以上, latency大約40ms,達(dá)到了上線要求。
Tcpcopy客戶端和服務(wù)端本身占用的資源較少,不影響在線服務(wù)。
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%,物理內(nèi)存占用分別是77m和38m.
由于幾乎完全模擬了線上環(huán)境,我們對于新引擎上線更有信心,最終上線圓滿成功,實(shí)現(xiàn)平穩(wěn)過渡。現(xiàn)在利用tcpcopy拷貝線上流量作模擬壓測已成為我們?nèi)粘i_發(fā)上線流程中的一項內(nèi)容。
