Ejabberd源碼學習——啓動流程

這篇文章是我之前在RYTong內部分享的一篇文章,將簡單介紹一下Ejabberd在啓動時候的流程,以及啓動過程中一些關鍵的邏輯。

PS:Ejabberd版本是2.1.11

啓動入口

Ejabberd的啓動函數是ejabberd:start/0方法,並最終調用ejabberd_app:start/2方法。這個方法實現了啓動的主體邏輯,順着該方法的源碼邏輯就可以瞭解Ejabberd啓動都做了些什麼了。

start(normal, _Args) ->
    ejabberd_loglevel:set(4),
    write_pid_file(),
    application:start(sasl),
    randoms:start(),
    db_init(),
    sha:start(),
    stringprep_sup:start_link(),
    xml:start(),
    start(),
    translate:start(),
    acl:start(),
    ejabberd_ctl:init(),
    ejabberd_commands:init(),
    ejabberd_admin:start(),
    gen_mod:start(),
    ejabberd_config:start(),
    ejabberd_check:config(),
    connect_nodes(),
    %% Loading ASN.1 driver explicitly to avoid races in LDAP
    catch asn1rt:load_driver(),
    Sup = ejabberd_sup:start_link(),
    ejabberd_rdbms:start(),
    ejabberd_auth:start(),
    cyrsasl:start(),
    % Profiling
    %ejabberd_debug:eprof_start(),
    %ejabberd_debug:fprof_start(),
    maybe_add_nameservers(),
    start_modules(),
    ejabberd_listener:start_listeners(),
    ?INFO_MSG("ejabberd ~s is started in the node ~p", [?VERSION, node()]),
    Sup;
start(_, _) ->
    {error, badarg}.

關鍵功能解析

日誌

Ejabberd在ejabberd.hrl中定義了若干級別的日誌宏,這些宏最終調用了ejabberd_logger模塊的函數來打印日誌,但在源碼路徑中是搜索不到這個模塊的。這是一個神奇的模塊,它會在Ejabberd啓動的時候自動生成。生成ejabberd_logger模塊是由ejabberd_loglevel:set(4)方法完成的,這個set方法傳入的參數是loglevel。

%%
%% loglevel: Verbosity of log files generated by ejabberd.
%% 0: No ejabberd log at all (not recommended)
%% 1: Critical
%% 2: Error
%% 3: Warning
%% 4: Info
%% 5: Debug
%%
{loglevel, 5}.

在ejabberd.cfg配置文件中(上圖)有loglevel配置項,Ejabberd將日誌分成了debug、info、warning等等若干日誌級別,每個日誌級別對應一個整數。配置一個級別後,Ejabberd在運行時將不會打印比這個級別高的日誌信息。因此,我們在開發時配置loglevel爲5(最低),便可以查看到所有級別的日誌。在投產時配置loglevel爲2,便只會看到error及以下級別的信息。

ejabberd_loglevel:set/1方法會根據傳入的loglevel動態的生成ejabberd_logger模塊,使得高於這個級別的日誌方法不會打印內容。有了這個方法,我們就可以在不重啓的情況下切換日誌級別了。麻麻再也不用擔心了。

另外,ejabberd_logger的日誌函數並不是最終寫日誌的函數。ejabberd_app:start/2會調用error_logger:add_report_handler(ejabberd_logger_h, LogPath)方法將error_logger產生的日誌事件的handler改成 ejabberd_logger_h,這個模塊處理日誌事件並完成寫日誌的功能。ejabberd_logger的日誌函數最終都會調用gen_event:notify(error_logger, LoggerMsg)方法產生日誌事件。

進程監控樹

每個Erlang Node都會有自己的監控樹。監控樹由若干Supervisor和Worker進程組成,Supervisor進程可以監控其他Supervisor或Worker進程。Worker是實現具體功能的進程。當被監控的進程意外退出時,Supervisor進程將會根據監控策略重啓所監控的進程。關於Supervisor的介紹可以參考Erlang的官方文檔。

Ejabberd的監控進程樹通過ejabberd_sup模塊構建。ejabberd_sup進程在啓動後會啓動若干Supervisor進程並監控它們。這裏我們先不去關注這些Supervisor,來看一下ejabberd_tmp_sup模塊。ejabberd_sup.erl代碼裏可以看到很多Supervisor都是用ejabberd_tmp_sup作爲啓動模塊的,爲什麼用這一個模塊來啓動不同的Supervisor呢?

start_link(Name, Module) ->
    supervisor:start_link({local, Name}, ?MODULE, Module).


init(Module) ->
    {ok, {{simple_one_for_one, 10, 1},
      [{undefined, {Module, start_link, []},
        temporary, brutal_kill, worker, [Module]}]}}.

通過以上源碼可以瞭解到,原來ejabberd_tmp_sup啓動的Supervisor所監控的進程都是Worker進程,並且使用simple_one_for_one的重啓策略。Supervisor使用simple_one_for_one重啓策略時,不會馬上啓動所監控的Worker進程,而是通過supervisor:start_child/2方法動態添加Worker進程。並且這些Worker進程在退出之後,並不會被重啓,也不會影響該Supervisor監控的其他Worker進程。

一個ejabberd_tmp_sup監控的都是做相同事情的臨時Worker進程,比如Ejabberd與每個客戶端建立連接後都會創建一個Worker進程來接收TCP包,這個進程就是由ejabberd_tmp_sup監控的。在斷開連接後,這個Worker進程就會退出,此時不需要重啓該進程,也不需要重啓其他Worker進程。因此使用ejabberd_tmp_sup來監控這些Worker進程是再合適不過的了。

Modules啓動

ejabberd.cfg配置文件中有modules配置項,這個配置用來管理一些類似於插件的服務模塊,這些服務模塊用來提供部分XEP擴展協議定義的服務,如mod_register提供了註冊服務。hosts配置項定義了當前Ejabberd服務的虛擬主機,當Ejabberd收到一個XMPP報文時會檢查該報文的目的主機是否爲已配置的虛擬主機,如果是則會在本地處理這個報文。如果不是,則會嘗試轉發給其他XMPP主機或返回錯誤。

Ejabberd啓動時,會爲每個虛擬主機啓動modules配置的服務,並最終調用配置模塊的start/2方法,第一個參數爲虛擬主機,第二個參數爲配置的啓動參數。

端口監聽

XMPP基於TCP協議,因此Ejabberd需要監聽一些端口來接收TCP連接。Ejabberd啓動監聽的邏輯在ejabberd_listener:start_listeners/0函數裏實現。該函數會監聽ejabberd.cfg裏listen配置項配置的每個端口。

ejabberd_listener本身是一個Supervisor進程,被ejabberd_sup監控。ejabberd_sup啓動時會啓動ejabberd_listener。start_listeners/0方法最終會爲每個端口啓動一個Worker進程(如下代碼),Worker進程啓動時會調用ejabberd_listener:start/3方法。

start_listener_sup(Port, Module, Opts) ->
    ChildSpec = {Port,
         {?MODULE, start, [Port, Module, Opts]},
         transient,
         brutal_kill,
         worker,
         [?MODULE]},
    supervisor:start_child(ejabberd_listeners, ChildSpec).

ejabberd_listener:start/3方法實現了具體的端口監聽邏輯。這個監聽邏輯到底是怎樣的呢?Ejabberd又是如何接收連接並在每個XMPP實體之間轉發報文的呢?在下一篇文章找答案吧,童鞋們!

發佈了29 篇原創文章 · 獲贊 6 · 訪問量 18萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章