1. 程式人生 > >利用 tcpcopy 引流做模擬在線測試

利用 tcpcopy 引流做模擬在線測試

一次 modprobe pos () ive 線上 拷貝 分享圖片 套接字

一、工具介紹

Tcpcopy是一個分布式在線壓力測試工具,可以將線上流量拷貝到測試機器,實時的模擬線上環境,達到在程序不上線的情況下實時承擔線上流量的效果,盡早發現bug,增加上線信心。

Tcpcopy是由網易技術部於2011年9月開源的一個項目,現在已經更新到0.4版本。

與傳統的壓力測試工具(如:abench)相比,tcpcopy的最大優勢在於其實時及真實性,除了少量的丟包,完全拷貝線上流量到測試機器,真實的模擬線上流量的變化規律。

二、Tcpcopy的原理

1.流程

現在以nginx作為前端說明tcpcopy的原理:

技術分享圖片

上圖中左邊是線上前端機,右邊是測試前端機。線上前端機開啟tcpcopy客戶端(tcpcopy進程),測試前端機開啟tcpcopy服務端(interception進程),且兩臺機器上都啟動了nginx服務。

Tcpcopy拷貝一次流量訪問的步驟如下:

① 一個訪問到達線上前端機;

② socket包在ip層被拷貝了一份傳給tcpcopy進程;

③ tcpcopy修改包的目的及源地址,發給測試前端機;

④ 拷貝的包到達測試前端機;

⑤ 測試前端機的nginx處理訪問,並返回結果;

⑥ 返回結果在ip層被截獲、丟棄,由intercpetion拷貝返回結果的ip header返回;

⑦ 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,發送:

struct receiver_msg_st msg;
... 
memset(&msg,0,sizeof(struct receiver_msg_st)); 
memcpy((void *) &(msg.ip_header),ip_header,sizeof(struct iphdr)); 
memcpy((void *) &(msg.tcp_header),tcp_header,sizeof(struct tcphdr));
...
send(sock,(const void *)msg,sizeof(struct receiver_msg_st),0);

interception向內核發送IPQM_VERDICT消息報告裁決結果:

struct nlmsghdr* nl_header=(struct nlmsghdr*)buffer; 
struct ipq_verdict_msg *ver_data = NULL; 
struct sockaddr_nl addr;
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這個包,則設為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));

內核接收到這個包後將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拷貝線上流量作模擬壓測已成為我們日常開發上線流程中的一項內容。技術分享圖片

利用 tcpcopy 引流做模擬在線測試