Ejabberd源码学习——启动流程
2016-04-11 14:35
585 查看
这篇文章是我之前在RYTong内部分享的一篇文章,将简单介绍一下Ejabberd在启动时候的流程,以及启动过程中一些关键的逻辑。
PS:Ejabberd版本是2.1.11
在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)方法产生日志事件。
Ejabberd的监控进程树通过ejabberd_sup模块构建。ejabberd_sup进程在启动后会启动若干Supervisor进程并监控它们。这里我们先不去关注这些Supervisor,来看一下ejabberd_tmp_sup模块。ejabberd_sup.erl代码里可以看到很多Supervisor都是用ejabberd_tmp_sup作为启动模块的,为什么用这一个模块来启动不同的Supervisor呢?
通过以上源码可以了解到,原来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进程是再合适不过的了。
Ejabberd启动时,会为每个虚拟主机启动modules配置的服务,并最终调用配置模块的start/2方法,第一个参数为虚拟主机,第二个参数为配置的启动参数。
ejabberd_listener本身是一个Supervisor进程,被ejabberd_sup监控。ejabberd_sup启动时会启动ejabberd_listener。start_listeners/0方法最终会为每个端口启动一个Worker进程(如下代码),Worker进程启动时会调用ejabberd_listener:start/3方法。
ejabberd_listener:start/3方法实现了具体的端口监听逻辑。这个监听逻辑到底是怎样的呢?Ejabberd又是如何接收连接并在每个XMPP实体之间转发报文的呢?在下一篇文章找答案吧,童鞋们!
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实体之间转发报文的呢?在下一篇文章找答案吧,童鞋们!
相关文章推荐
- 从源码安装Mysql/Percona 5.5
- 浅析Ruby的源代码布局及其编程风格
- asp.net 抓取网页源码三种实现方法
- JS小游戏之仙剑翻牌源码详解
- JS小游戏之宇宙战机源码详解
- jQuery源码分析之jQuery中的循环技巧详解
- 本人自用的global.js库源码分享
- java中原码、反码与补码的问题分析
- ASP.NET使用HttpWebRequest读取远程网页源代码
- PHP网页游戏学习之Xnova(ogame)源码解读(六)
- C#获取网页HTML源码实例
- PHP网页游戏学习之Xnova(ogame)源码解读(八)
- PHP网页游戏学习之Xnova(ogame)源码解读(四)
- JS小游戏之极速快跑源码详解
- JS小游戏之象棋暗棋源码详解
- android源码探索之定制android关机界面的方法
- 基于Android设计模式之--SDK源码之策略模式的详解
- Android游戏源码分享之2048
- C语言借助EasyX实现的生命游戏源码
- C实现的非阻塞方式命令行端口扫描器源码