《supervisor.erl 源码解读》

简介: erlang程序员研究OTP,如同C++程序员研究STL一样重要。

Why?

erlang程序员研究OTP,如同C++程序员研究STL一样重要。

监督者模式,在任何一个项目都必不可少。在阅读开源项目的时候,只有了解每一个细节,才能体会到妙处。

supervisor实际上是基于gen_server的系统进程,监控子进程的退出状态并设置一定的重启机制。

init函数

同样的,init是同步调用。

4adf2b9a25da2f698f475af355a03838d900fb9c.png

在这个例子里Mod模块是一个sup程序,它的启动会调用supervisor:start_link,而start_link实际上调用的gen_server:start_link并存入Mod模块的名字和参数.

spawn出来的gen进程会先调用supervisor:init函数. 接着把gen进程设置为系统进程, 这样就可以捕获子进程退出信号, 然后根据Args里的Mod模块名和参数,再次调用到Mod:init.

Mod的init函数返回的是一个{ok, SupFlags, StartSpec}的元组. SupFlags是supervisor管理的进程的启动策略和可重启的范围窗口,StartSpec是一个列表,保存多个子进程的MFA等信息.

接下来的init_state函数会把SupFlags, StartSpec存储在gen进程的State里, gen进程在整个生命周期一直围绕着这个State. 我们来看看State的结构,明白这个结构,supervisor就很好理解了.

-record(state, {name,
        strategy               :: strategy(),
        children = []          :: [child_rec()],
        dynamics               :: ?DICT(pid(), list()) | ?SET(pid()),
        intensity              :: non_neg_integer(),
        period                 :: pos_integer(),
        restarts = [],
            module,
            args}).

strategy、intensity、period和SupFlags里信息一一对应,module、args就是这个supervisor的模块名字和参数。

额外的字段说明: restarts用于存储子进程的每个重启时间点, 这个后面会解释.

dynmiacs用于当策略是simple_one_for_one策略时存储子进程的信息的字典,子进程的异常退出和重启都要用到到它;对于其它的策略,子进程信息存储在children里,它是一个child记录的列表.

-record(child, {% pid is undefined when child is not running
            pid = undefined :: child()
                             | {restarting, pid() | undefined}
                             | [pid()],
        name            :: child_id(),
        mfargs          :: mfargs(),
        restart_type    :: restart(),
        shutdown        :: shutdown(),
        child_type      :: worker(),
        modules = []    :: modules()}).

child记录和StartSpec里一一对应很好理解不解释了.

要注意: 对于非simple_one_for_one策略StartSpec里可以有多个子进程spec, 而如果是simple_one_for_one策略只能有一个进程的spec.下面的simple_one_for_one类策略的StartSpec保存函数可看到, 如果列表里的成员个数超过一个则报出bad_start_spec的错误.

init_dynamic(State, [StartSpec]) ->
    case check_startspec([StartSpec]) of
    ....
init_dynamic(_State, StartSpec) ->
    {stop, {bad_start_spec, StartSpec}}.

这里有个技巧:[StartSpec] 匹配的是只包含一个元素的列表。

存储好State信息后,gen进程根据策略的不同走到不同的分支, 如果策略是simple_one_for_one, 则在init的时候不启动任何子进程, 而由之后的start_child函数启动;对于其它的策略,则是遍历children列表把子程一一启动,并填充#child.pid字段. 等子进程都准备好了就发送ack通知调用进程,子进程都已经准备好了让我们开始吧,于是调用进程就成功退出。

start_child 函数

start_child 是同步调用。

c68a17450ea21f05c83ff90b47986a0efad78b03.png

start_child实际上是通过gen_server:call来通知gen进程启动一个子进程,然后gen进程回调调用handle_call函数。

handle_call({start_child, EArgs}, _From, State) when ?is_simple(State) ->
    #child{mfa = {M, F, A}} = hd(State#state.children),
    Args = A ++ EArgs,
    case do_start_child_i(M, F, Args) of
...

gen进程接收到消息后, 会从State里获取需要启动的子进程的MFA, 最后调用apply(M,F,A)启动这个子进程。子进程的参数来自于ssupervisor:start_link和supervisor:start_child的两次参数组合, do_start_child_i(M, F, Args) 就是启动这个子进程的函数, 之后把{Pid, Args}信息再存储在#state.dynmiac里方便重启或者which_children展示子进程状态,.

进程异常退出

由于gen进程是一个系统进程,所以子进程退出之际会向gen进程发送’EXIT’消息。根据gen_server的特点,这个消息会触发gen进程调度handle_info函数.

handle_info({'EXIT', Pid, Reason}, State) ->
    case restart_child(Pid, Reason, State) of
    {ok, State1} ->
        {noreply, State1};
    {shutdown, State1} ->
        {stop, shutdown, State1}
    end;

于是gen进程就开始重启子进程.

restart_child

45d64865e188691f7205b84b2c86a9ebc27dddc6.png

重启进程会根据消息里Pid查找到进程的信息, 如前面所述, 对与simple_one_for_one策略的进程信息来自于从#state.dynmaics, 而其它的是从#state.children里读取MFA,RestartType等信息.

根据RestartType的不同决定是否要重启

do_restart(permanent, Reason, Child, State) ->
    report_error(child_terminated, Reason, Child, State#state.name),
    restart(Child, State);
do_restart(_, normal, Child, State) ->
    NState = state_del_child(Child, State),
    {ok, NState};
do_restart(_, shutdown, Child, State) ->
    NState = state_del_child(Child, State),
    {ok, NState};
do_restart(_, {shutdown, _Term}, Child, State) ->
    NState = state_del_child(Child, State),
    {ok, NState};
do_restart(transient, Reason, Child, State) ->
    report_error(child_terminated, Reason, Child, State#state.name),
    restart(Child, State);
do_restart(temporary, Reason, Child, State) ->
    report_error(child_terminated, Reason, Child, State#state.name),
    NState = state_del_child(Child, State),
    {ok, NState}.

可以看到:

如果是permananet则永远重启;

transient仅在子进程退出Reason非normal、shutdown才重启;

而对于temprory状态则永远不重启,并删除State里的这个子进程的信息.

接着来到restart(Child, State)函数.

这个函数首先通过add_restart函数记录一个重启时间点到#state.restarts里,这样#state.restarts里保存着最近的重启时间点。计算当前时间的period时间之内的重启时间点的个数是否超过intensity, 如果超过则表示在period时间内子进程重启的次数超过maxintensity,是则放弃这次重启,否则继续重启来到restart函数.

restart函数有会根据#state.strategy来决定是重启单个进程还是所有进程,最后调用到do_start_child或者do_start_child_i函数,然后就是 apply(M,F,A)于是进程就重启了,然后再更新State里的信息等收尾工作。

which_children

这个命令很有用,可以看到一个supervisor进程下有多少个子进程和他的状。

相关文章
|
6月前
|
监控 网络协议 Unix
Supervisor的简单使用
Supervisor的简单使用
48 0
Supervisor的简单使用
|
2月前
|
设计模式 算法 前端开发
Tomcat的源码剖析, 启动过程你会吗?
基于JMX Tomcat会为每个组件进行注册过程,通过Registry管理起来,而Registry是基于JMX来实现的,因此在看组件的init和start过程实际上就是初始化MBean和触发MBean的start方法,会大量看到形如: Registry.getRegistry(null, null).invoke(mbeans, "init", false); Registry.getRegistry(null, null).invoke(mbeans, "start", false); 这样的代码,这实际上就是通过JMX管理各种组件的行为和生命期。
8 0
|
3月前
|
监控 应用服务中间件 nginx
Supervisor快速入门 | 使用Supervisor守护Nginx进程
Supervisor快速入门 | 使用Supervisor守护Nginx进程
42 0
|
4月前
|
Java API 开发者
深入浅出Zookeeper源码(四):Watch实现剖析
用过zookeeper的同学都知道watch是一个非常好用的机制,今天我们就来看看它的实现原理。 在正文开始前,我们先来简单回忆一下watch是什么? zk提供了分布式数据的发布/订阅功能——即典型的发布订阅模型,其定义了一种一对多的订阅关系,能够让多个订阅者同时监听某个主题对象,当这个主题对象自身状态变化时,则会通知所有订阅者。具体来说,则是zk允许一个客户端向服务端注册一个watch监听,当服务端的一些指定事件触发了这个watch,那么就会向该客户端发送事件通知。
90 0
|
PHP 容器
hyperf| hyperf 源码解读 2: start
上篇我们跟着 `php bin/hyperf.php` 命令, 看到了框架的核心 `container`, 这篇我们跟着 `php bin/hyperf.php start`, 来会一会强大到爆炸的 `swoole`
598 0
BATMJ技术实战之多线程+JVM+Nginx+Redis+SpringBoot(书籍赠送)
感谢各位的关注!!!!请看下面这就是小编免费赠送给大家和粉丝的福利哦 Java多线程编程核心实战(文档) 深入理解Java虚拟机:JVM实战(文档) 深入浅出Nginx实战(文档) Redis核心实战(文档) 深入浅出SpringBoot以及SpringBoot2.x(文档) 由于细节内容实在太多啦,所以只把部分知识点截图出来粗略的介绍,每个小节点里面都有更细化的内容!
|
消息中间件 存储 前端开发
celery 源码阅读 - 1
Celery是一款非常简单、灵活、可靠的分布式系统,可用于处理大量消息,并且提供了一整套操作此系统的工具。Celery 是一款消息队列工具,可用于处理实时数据以及任务调度。
204 0
|
Java Unix 应用服务中间件
Gunicorn 源码阅读
gunicorn “Green Unicorn”,脱胎于ruby社区的Unicorn,是一个 WSGI HTTP Server。学习gunicorn后,我们可以把之前的 Bottle 程序正式部署起来。
221 0
|
程序员 PHP 容器
hyperf| hyperf 源码解读 1: 启动
hyperf| hyperf 源码解读 1: 启动
1020 0
|
监控 Ubuntu Shell
supervisor简介、安装与入门使用
supervisor简介、安装与入门使用
253 0

热门文章

最新文章