Curve 是雲原生計算基金會 (CNCF) Sandbox 專案,是網易數帆發起開源的高效能、易運維、雲原生的分散式儲存系統。
為了讓大家更容易使用以及瞭解 Curve,我們期望接下來通過系列應用實踐文章,以專題的形式向大家展示 Curve。
本篇文章是Curve塊儲存應用實踐的第一篇,該系列文章包括:
tgt 是一個開源 iSCSI 伺服器,詳情請見 tgt githu[1]。我們在開發 Curve 塊裝置伺服器時,想讓更多的系統能夠使用 Curve 塊裝置,而不僅僅是 Linux 系統,iSCSI 協定是一個廣泛使用的塊裝置協定,我們想修改 tgt 以便讓 Curve 提供 iSCSI 服務。
為tgt提供了存取 Curve 的驅動,詳見部署網路高效能版本tgt[2] , 檔案裡有操作步驟,這樣使用者就可以在任何支援 iSCSI 的作業系統上使用 Curve 塊裝置儲存,例如Windows。
Curve 在初步使用 tgt 時也遇到一些問題:
我們觀察到原版 tgt 使用單一主執行緒 epoll 來處理 iSCSI 命令,還包括管理平面的 unix domian socket 也在這個主執行緒裡。
在10 Gbit/s 網路上甚至更快的網路上,單執行緒(也即單cpu)處理 iSCSI 命令的速度已經跟不上需求了,一個執行緒對付多個target的情況下,多個iSCSI Initiator的請求速度稍微高一點,這個單執行緒的cpu使用率就100%忙碌。
所以本文的重點就是介紹tgt的效能優化。同時社群使用者使用過程中還遇到了nebd服務的單點和效能問題,社群使用者對此也進行了優化,詳情可參考創雲融達基於 Curve 的智慧稅務場景實踐。
1. 使用多個執行緒做 epoll
實現多個event loop執行緒,每個執行緒負責一定數量的socket connection上的iSCSI命令處理。這樣就能發揮多cpu的處理能力。
2. 為每個 target 建立一個 epoll 執行緒
為了避免多個target共用一個epoll時依然可能出現超過單個cpu處理能力的問題,我們為每一個 target設定了一個epoll執行緒。target epoll的cpu使用由OS負責排程,這樣在各target上可以 實現公平的cpu使用。當然如果網路速度再快,依然會出現單個epoll執行緒處理不過來一個iSCSI target上的請求,但是目前這個方案依然是我們能做的最好方案。
3. 管理平面
管理平面保持了與原始tgt的相容性。從命令列使用方面來說,沒有任何區別,沒有任何修改。管理平面在程式的主執行緒上提供服務,主執行緒也是一個epoll loop執行緒,這與原始的tgt沒有區別,它負責target,lun,login/logout,discover,session, connection等的管理。當Intiator連線到iSCSI 伺服器時,總是先被管理平面執行緒所服務,如果該connection最後需要建立session去存取某個target,那麼該connection會被遷移到對應的target的epoll執行緒上去。
4. 資料結構的鎖
為每一個target提供一個mutex,當target epoll執行緒在執行時,這把鎖是被該執行緒鎖住的,這樣該執行緒可以任意結束一個sesssion或connection,當執行緒進入epoll_wait時,這把鎖是釋放了的,epoll_wait返回時又會鎖住這把鎖。我們修改了相關程式碼,讓這個epoll執行緒不用遍歷target list,只存取它服務的target相關結構,這樣我們不需要target列表鎖。管理面也會增加、刪除一個session或者connection時,也需要鎖住這把target鎖。所以管理面和target epoll執行緒使用這個mutex來互斥,這樣就可以安全地存取對應target上的session和connection了。
5. connection 建立 session
當login_finish成功時,login_finish有時候會建立session(如果沒有session存在)。login_finish在connection結構的欄位migrate_to裡設定目標iSCSItarget。
6. 什麼時候做 connection 遷移
當呼叫返回到iscsi_tcp_event_handler時,因為login_finish設定了migrate_to目標target,iscsi_tcp_event_handler就鎖住目標iscsi target結構,並把該connection的fd插入到目標target的evloop 裡面,完成遷移。
7. 設定 pthread name
設定各target event loop的執行緒在top中的名為tgt/n, n為target id,這樣容易用top之類的工具觀察哪一個target佔用的cpu高。
8. 舉個例子
假如MGMT要刪除一個target,下面的程式碼說明了流程:
/* called by mgmt */ tgtadm_err tgt_target_destroy(int lld_no, int tid, int force) { struct target *target; struct acl_entry *acl, *tmp; struct iqn_acl_entry *iqn_acl, *tmp1; struct scsi_lu *lu; tgtadm_err adm_err; eprintf("target destroy\n"); /* * 這裡因為控制面是單執行緒的,而且SCSI IO執行緒不會刪除target, * 所以我們找target的時候並不需要鎖 */ target = target_lookup(tid); if (!target) return TGTADM_NO_TARGET; /* * 這裡要鎖住target,因為我們要刪除資料結構,所以不能和iscsi io * 執行緒一起共用,必須在scsi 執行緒釋放了鎖時進行 */ target_lock(target); if (!force && !list_empty(&target->it_nexus_list)) { eprintf("target %d still has it nexus\n", tid); target_unlock(target); return TGTADM_TARGET_ACTIVE; } … /* 以上步驟刪除了所有資源 ,可以釋放鎖了 */ target_unlock(target); if (target->evloop != main_evloop) { /* 通知target上的evloop停止,並等待evloop 執行緒退出 */ tgt_event_stop(target->evloop); if (target->ev_td != 0) pthread_join(target->ev_td, NULL); /* 下面把evloop的資源刪除乾淨 */ work_timer_stop(target->evloop); lld_fini_evloop(target->evloop); tgt_destroy_evloop(target->evloop); }
我們為tgt設定了3塊盤,一塊 Curve 塊儲存卷,兩塊本地盤
<target iqn.2019-04.com.example:curve.img01> backing-store cbd:pool//iscsi_test_ bs-type curve </target> <target iqn.2019-04.com.example:local.img01> backing-store /dev/sde </target><target iqn.2019-04.com.example:local.img02> backing-store /dev/sdc </target>
使用本機登入iscsi iscsiadm --mode node --portal 127.0.0.1:3260 --login
為fio設定存取這些 iSCSI 的塊裝置,使用:
[global] rw=randread direct=1 iodepth=128 ioengine=aio bsrange=16k-16k runtime=60 group_reporting [disk01] filename=/dev/sdx [disk02] filename=/dev/sdy size=10G [disk03] filename=/dev/sdz size=10G
測試結果如下:
下面是未經優化的fio成績,IOPS 38.8K
下面是經過多執行緒優化的fio成績,IOPS 60.9K
參考[1]:https://github.com/fujita/tgt
參考[2]:https://github.com/opencurve/...