測試環境
DPVS: Inspur NF5270M3, E5-2630 2.60GHz 24 cores, memory 96G
Real Server: 6 臺核數不同的機器,148 個 redis-server
Bench Client: 8 臺核數不同的機器,24000 個連接 ping
官方推薦用 f-stack nginx 壓測,還要讓 real server 跑 dpdk 太麻煩。使用 redis ping 模擬小包,平均 74 bytes, 使用 set 指定 1000 bytes value 來模擬大包。
測試配置及腳本
啟動 redis-server 實例,一定要 taskset 綁核
taskset -c 8 ./redis-server ./redis6380.conf
taskset -c 9 ./redis-server ./redis6381.conf
taskset -c 10 ./redis-server ./redis6382.conf
......
測試機啟動 redis-benchmark pipeline 壓測 ping
#!/bin/sh
cpus=`lscpu | grep '^CPU(s)' | awk '{print $2}'`
for i in `seq 8 $cpus`
do
nohup taskset -c $i ./redis-benchmark -h 10.20.23.241 -p 6379 -c 100 -n 100000000 -t ping -P 10 -l&
done
統計InPkts OutPkts
ipvsadm -ln --stats
ipvsadm --zero
dpip link show dpdk0 -s
dpvs配置: two-arm, full-nat, 8 lcores,nic intel 82599ES
性能問題排查
模擬大包時,two-arm 雙 10G 網卡很快打滿,符合預期。關鍵轉發能力要看小包,也就是 pps (packets per second),發現只有 250w/pps, 遠低于官方數據最理想的 1500w
打開 dpvs debug 模式,可以看到增加了 -g ,并且優化級別是 0
# cat src/Makefile | grep -i debug
DEBUG := 1 # enable for debug
ifeq ($(DEBUG),)
CFLAGS += -g -O0 -D DEBUG
重新壓測,并使用 perf 抓取性能數據
perf record -g -p `pidof dpvs` #約 30 秒后 ctrl+c
perf report -i ./perf.data
由于 dpvs 是多核程序,所以看 lcore-slave-3 即可,一步步觀察,
INET_HOOK
占了 9.85% cpu, 里面的 dp_vs_in
占了 4.21%,rte_rwlock_read_lock/unlock
相關的占了合計 5.1%. 再深入 dp_vs_in
函數,發現 xmit_inbound
xmit_outbound
tcp_conn_lookup
占用較高,具體到底層函數都是 tcp header checksum 計算相關函數,比較符合預期。那值得懷疑的就是 rte_rwlock_read_lock/unlock
,貼上源碼:
int INET_HOOK(int af, unsigned int hook, struct rte_mbuf *mbuf,
struct netif_port *in, struct netif_port *out,
int (*okfn)(struct rte_mbuf *mbuf))
{
struct list_head *hook_list;
struct inet_hook_ops *ops;
struct inet_hook_state state;
int verdict = INET_ACCEPT;
state.hook = hook;
hook_list = af_inet_hooks(af, hook);
rte_rwlock_read_lock(af_inet_hook_lock(af));
ops = list_entry(hook_list, struct inet_hook_ops, list);
if (!list_empty(hook_list)) {
verdict = INET_ACCEPT;
list_for_each_entry_continue(ops, hook_list, list) {
repeat:
verdict = ops->hook(ops->priv, mbuf, &state);
if (verdict != INET_ACCEPT) {
if (verdict == INET_REPEAT)
goto repeat;
break;
}
}
}
rte_rwlock_read_unlock(af_inet_hook_lock(af));
if (verdict == INET_ACCEPT || verdict == INET_STOP) {
return okfn(mbuf);
} else if (verdict == INET_DROP) {
rte_pktmbuf_free(mbuf);
return EDPVS_DROP;
} else { /* INET_STOLEN */
return EDPVS_OK;
}
}
static inline rte_rwlock_t *af_inet_hook_lock(int af)
{
assert(af == AF_INET || af == AF_INET6);
if (af == AF_INET)
return &inet_hook_lock;
else
return &inet6_hook_lock;
}
鎖的內容是 af_inet_hook_lock(af)
, 再看實現,居然鎖的是一個全局 lock !!! dpdk 程序最忌多個核之間共享數據,特別是鎖競爭。查看 git 提交紀錄,由 cc5369c1a3bd4fa7bd838c62fc1cb8797db61b4e
ipv6/ipvs: ipvs core support ipv6 引入的問題。仔細閱讀代碼,這個鎖只在初始化期有用,運行期完全可以忽略,何況還是個讀鎖,也就是 INET_HOOK
根本用不到,果斷注釋掉重新壓測。
關掉后再次壓測,上面的 perf 圖里,沒有了鎖爭用問題,占 cpu 比例最高的是
dp_vs_in
,集中在 tcp header checksum,這部分沒有辦法,本身就是 cpu 消耗型的。符合預期。
NETIF: Fail to send 1 packets on dpdk1 tx3
NETIF: Fail to send 7 packets on dpdk1 tx2
NETIF: Fail to send 4 packets on dpdk1 tx2
NETIF: Fail to send 2 packets on dpdk1 tx1
NETIF: Fail to send 1 packets on dpdk1 tx1
如果遇到丟包,也就是 imiss 問題,需要調大 dpvs.conf 隊列的 descriptor_number 值,默認的可能有點小,大并發時丟包。
最終壓測數據
關掉 DEBUG 模式,77 bytes 小包打到 700w pps, 兩個萬兆網卡打到 8.5G,網卡先于 dpvs 達到瓶頸,性能比較平穩沒有抖動。以后有機會測網卡 bonding 的數據。
鎖的問題和 iqiyi 研發溝通了一下,他們之前的壓測數據是引入 ipv6 之前的,后續會再內部進行壓測優化性能。多核編程鎖還是永恒的話題,一個讀鎖也會有這么大影響,看來有機會得研究下底層鎖的實現。