利用tcpcopy引流做模擬在線測試
一、工具介紹
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 進程。 </ol>
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));
</blockquote>而在0.4版本中,用的是:
int sock = socket (AF_INET,SOCK_RAW,IPPROTO_TCP);
</blockquote>以上兩個函數分別工作在鏈路層和 IP 層,前者會把進來和出去的包都抓取到,后者只抓取到進來的包。
2) Tcpcopy 在發送拷貝的數據包的時候,使用了如下 socket:
sock = socket (AF_INET, SOCK_RAW,IPPROTO_RAW);
</blockquote>并對這個 socket 設置了 IP_HDRINCL:
setsockopt (sock, IPPROTO_IP, IP_HDRINCL, &n, sizeof (n));
</blockquote>因此網絡層不會再增加 ip header. 發送之前更改了包的目的 ip 和端口:
tcp_header->dest = remote_port;
ip_header->daddr = remote_ip;
</blockquote>最后調用 sendto 函數發送包到測試前端機:
send_len = sendto (sock,(char *) ip_header,tot_len,0,
(struct sockaddr *)&toaddr,sizeof (toaddr));
</blockquote>3) 在測試前端機上加載了 ip_queue 模塊,并設置 iptables 規則:
iptables -I OUTPUT -p tcp –sport 80 -j QUEUE
</blockquote>復制的訪問流量到達測試前端機上的 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.
</blockquote>內核通過一個 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 拷貝線上流量作模擬壓測已成為我們日常開發上線流程中的一項內容。
六、附錄
項目主頁: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;
來自: www.searchtb.com