Curve 塊存儲應用實踐 -- iSCSI

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/...

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

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章