利用tcpcopy引流做模擬線上測試
本文轉自 http://www.searchtb.com/2012/05/using-tcpcopy-to-simulate-traffic.html
一、工具介紹
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,傳送:
1 2 3 4 5 6 7 8 9 10 11 12 13 |
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訊息報告裁決結果:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 |
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服務的。
三、操作方法
假如有兩臺機器:
機器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拷貝線上流量作模擬壓測已成為我們日常開發上線流程中的一項內容。
六、附錄