說明:本篇中所有代碼分析,都是基於Jewel 10.2.9版本。本篇都是個人理解,其中有些理解或者解釋有不合理的,還請指正。
在Ceph的使用中,運行時調整參數值是個高頻的操作,使用起來簡單方便,最重要的是不用重啓服務即可生效。
如何使用
Ceph動態調整參數有兩種方式:
第一種:
|
第二種:
|
第二種還有兩個比較好用的地方:
-
單條命令可以改變所有的實例的某個參數值:
ceph tell <mon
/osd/mds
>.* injectargs
'--<參數名> <參數值>'
設置所有OSD的heartbeat超時時間:
ceph tell osd.* injectargs
'--osd_heartbeat_grace 60'
-
單條命令可以改變多個參數:
ceph tell <mon
/osd/mds
>.1 injectargs
'--<參數名> <參數值> --<參數名> <參數值>'
設置OSD 1的heartbeat超時時間,及發起heartbeat的時間間隔
ceph tell osd.1 injectargs
'--osd_heartbeat_grace 60 --osd_heartbeat_interval 10'
-
當然,上面兩個可以結合使用:
ceph tell <mon
/osd/mds
>.* injectargs
'--<參數名> <參數值> --<參數名> <參數值>'
設置所有OSD的heartbeat超時時間,及發起heartbeat的時間間隔
ceph tell osd.* injectargs
'--osd_heartbeat_grace 60 --osd_heartbeat_interval 10'
源碼分析
那麼Ceph內部是如何實現上面提到的動態更新呢?我們來深入代碼中一探究竟。
tell方式的實現
我們先看tell...injectargs方式的實現:
ceph命令行的輸入,源碼入口都是ceph.in文件,是python文件,也就是/usr/bin/下的可執行文件ceph。
ceph.in中有關tell的代碼:
|
從上面的代碼,可知,tell命令,並沒有走daemon的admin socket來進行通信,而是走正常的client->server的通信,而且,'*'也沒有那麼高效,就是拿到所有的id,然後逐個發送消息。
然後再經過librados、osdc等模塊,將消息發送給具體的Monitor、OSD、MDS。
我們接下來直接看OSD端收到injectargs命令後,如何處理。
在OSD.cc中,do_command函數就是專門處理各種命令的,我們直接看injectargs分支:
|
拿到client想要更新的所有參數,然後調用了injectargs,每個OSD都有一個CephContext類變量cct,md_config_t類型的_conf變量也是CephContext類的成員。
|
上面代碼完成了參數的更新,主要有兩點:
- debug_類的參數(日誌級別參數)單獨走的一套,和其他參數不是同一種處理。
- 其他參數,先更新內存中的參數值;然後更重要的是:需要使其生效,即能夠讓系統真實的使用這些參數了。
後續的apply_changes是如何實現的?我們先暫放一下,先來看看daemon方式修改參數,因爲這兩種最終都會通過_apply_changes來使參數生效。
daemon方式的實現
我們繼續看看daemon...config set方式的實現:
ceph.in中有關daemon的代碼:
|
Admin socket是什麼呢?其實就是UNIX Domain Socket,是在socket架構上發展出的一種IPC機制,雖然網絡socket也可用於同一臺主機的進程間通訊(通過loopback地址127.0.0.1),但是UNIX Domain Socket用於IPC更有效率:不需要經過網絡協議棧,不需要打包拆包、計算校驗和、維護序號和應答等,只是將應用層數據從一個進程拷貝到另一個進程。這是因爲,IPC機制本質上是可靠的通訊,而網絡協議是爲不可靠的通訊設計的。UNIX Domain Socket也提供面向流和麪向數據包兩種API接口,類似於TCP和UDP,但是面向消息的UNIX Domain Socket也是可靠的,消息既不會丟失也不會順序錯亂。
UNIX Domain Socket與網絡socket編程最明顯的不同在於地址格式不同,用結構體sockaddr_un表示,網絡編程的socket地址是IP地址加端口號,而UNIX Domain Socket的地址是一個socket類型的文件在文件系統中的路徑,這個socket文件由bind()調用創建,如果調用bind()時該文件已存在,則bind()錯誤返回。
從上面的代碼可以看到,我們現在是一個client,嘗試進行socket連接、通信,那server端在哪裏?
每個OSD實例在啓動的時候,都有一個CephContext類變量cct變量,這個變量中會new AdminSocket對象,在ceph_osd.cc的main函數中會有cct的初始化操作:common_init_finish(g_ceph_context)。
我們先來看看common_init_finish函數:
|
可以看到:UNIX Domain Socket server已經準備就緒,開始監聽網絡。
在CephContext類構造函數中,創建了一個AdminSocket類變量_admin_socket,註冊了一些_admin_socket可以處理的command:
|
從上面的代碼可以看到,配置參數相關的這幾個都已經註冊在admin_socket中,而且這些命令相關的hook是CephContextHook類。
同時,在OSD啓動時,admin_socket初始化成功後,調用了final_init,也註冊了一些admin_socket可以識別的command。
我們再看看OSD的inal_init函數:
|
在這裏繼續註冊了一些可以通過admin socket來執行的命令,這裏的hook是OSDSocketHook類。
AdminSocket類繼承的Thread類,在線程入口函數entry中,會通過poll方式等待event,然後有connection的時候,會進行do_accept,然後進行正常的網絡stream讀寫。
當client通過admin socket向server端發送了命令後,admin socket server會接收消息,在do_accept函數中,會判斷這個command是否註冊,如果註冊了,調用相應的hook->call處理,最後將結果回覆給client。
在CephContext類中,hook是CephContextHook類變量:
|
在OSD類中,hook是一個OSDSocketHook類變量:
|
在這裏,我們先只看config set相關的處理,在CephContext的do_command函數中:
|
可以看到,和tell方式一樣,也是先改變參數值,然後再調用_apply_changes來生效之。
參數如何應用到運行中的系統
現在,我們來看看_apply_changes的實現:
|
上面代碼中註釋挺詳細的了,我們在這裏說一下observers是什麼?就是具體的MON、OSD、MDS實例,這裏用的是觀察者模式,觀察者關注自己關心的參數,這些參數一旦有更新,觀察者會通過handle_conf_change使其生效。
那麼,這些observer是什麼時候開始watch的?我們這裏只說OSD,在OSD::pre_init中,調用了cct->_conf->add_observer(this); pre_init也是在OSD啓動時調用的。
|
我們先看看OSD都關注了哪些config options:(其實很少的,除了一些log的,就10幾個)
|
看了上面的參數列表,是不是很驚訝,就這些?難道我經常設置的參數,貌似生效了(參數值確實改變了),其實根本沒有真正的生效。
我們再來看OSD的handle_conf_changes:
|
這個函數就會根據不同的參數,調用具體的函數,將新的參數值應用到運行的系統中。
到這裏,我們也基本搞清了動態更新參數的實現過程,當然,還有很多細節並沒有研究。我們還是先搞清整個流程、原理,然後遇到問題,再深入細節分析。
總結
- 通過daemon admin socket修改參數走的是UNIX Domain Socket模式。所以,每次只能設置一個實例的參數;並且,只能在本地設置本地實例的參數。
- 通過tell修改參數走的是正常的命令發送流程。可以通過'*'的方式設置所有的實例的參數。
- 非log級別debug_類參數的動態更新,都經歷了兩步:設置進程內存中的參數值;調用相應的observer生效之。
- log級別的debug_類參數,是直接設置參數並生效的(當然,依賴於ceph中log的實現)。
- Ceph使用過程中,很多動態更新的參數,其實並沒有真正的生效,修改參數時,返回的結果中有"unchangeable",或“not observed, change may require restart”這些字串的都沒生效。 ----------------------------------------------------更正:感謝各位指正,“參數設置返回‘unchangeable’的,都沒生效”確實有點問題,只能說可能沒生效。如果在MON、OSD、MDS運行中使用某個參數時,直接取的cct->_conf->{參數名},這種着實生效了;如果是在進程啓動時,就已經加載的,運行時直接使用,並沒有實時取_conf中參數的,是不生效的。所以,具體的參數還要具體分析,尤其是在調優或debug問題的時候,一定到代碼裏看看參數的使用,如果並沒真正生效,可能會誤以爲該參數沒有調優效果。