Curve 塊儲存應用實踐 -- iSCSI

2022-12-06 18:00:30

Curve 是雲原生計算基金會 (CNCF) Sandbox 專案,是網易數帆發起開源的高效能、易運維、雲原生的分散式儲存系統。

為了讓大家更容易使用以及瞭解 Curve,我們期望接下來通過系列應用實踐文章,以專題的形式向大家展示 Curve。

本篇文章是Curve塊儲存應用實踐的第一篇,該系列文章包括:

  • Curve塊儲存應用實踐一部曲之iSCSI
  • Curve塊儲存應用實踐二部曲之nbd
  • Curve塊儲存應用實踐三部曲之雲主機
  • Curve塊儲存應用實踐四部曲之雲原生資料庫
  • Curve塊儲存應用實踐五部曲之效能調優

iSCSI 及 tgt 簡介

tgt 是一個開源 iSCSI 伺服器,詳情請見 tgt githu[1]。我們在開發 Curve 塊裝置伺服器時,想讓更多的系統能夠使用 Curve 塊裝置,而不僅僅是 Linux 系統,iSCSI 協定是一個廣泛使用的塊裝置協定,我們想修改 tgt 以便讓 Curve 提供 iSCSI 服務。

Curve 塊儲存

為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 的智慧稅務場景實踐。

Curve 對 tgt 的效能優化實踐

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

<原創作者:徐逸鋒,Curve PMC>

參考[1]:https://github.com/fujita/tgt

參考[2]:https://github.com/opencurve/...

【點選瞭解更多網易技術】