Erlang OTP学习:supervisor [转]

本文详细解析了Erlang中的Supervisor模块,包括其核心组件、创建过程、重启策略、最大重启频率及子进程管理。通过具体例子展示了如何构建一个强大的容错系统。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

转自: http://diaocow.iteye.com/blog/1762895

 

今天细致的看了下supervisor,现在做个总结:

其中,方块代表supervisor process,它的功能很简单,就负责看管它下面的“小弟”(child processes) 并且在必要的时候对某个child process执行restart或者terminate操作;而圆形就代表worker process,它才是真正负责干活的process;特别注意,supervisor process 监控的不一定都是worker process 可以是别的supervisor(如上图)。通过以上方式,我们就可以按照一定层次结构将process管理起来,构建一个强健的容错系统 。

现在我们就看看如何创建一个supervisor:

类似gen_server, gen_event模块,erlang已经把如何创建一个supervisor解耦:分成非功能模块和功能模块。非功能模块是一个叫supervisor的module(后面简称为 S module),功能模块则由各个使用方以callback module形式提供,在callback模块中,你只需要编写一个init方法供S module回调即可,该init方法指定了将要创建的supervisor的三个属性:

1.重启策略(Restart Strategy)
a. one_for_one
当一个child process挂掉时,它的监控者(supervisor)仅重启该child process,而不会影响其他child process
b.one_for_all
当一个child process挂掉时,它的监控者(supervisor)将会terminate其余所有child  process,然后再重启所有child process
c.rest_for_one
当一个child process挂掉时,它的监控者(supervisor)只会terminate在该child process之后启动的process,然后再将这些process 通通重启
d.simple_one_for_one
重启策略与one_for_one相同,唯一的区别是:所有的child process都是动态添加的并且执行同样一份代码(稍后详述)

2.最大重启频率(Maximum Restart Frequency)
该属性的主要目的是为了防止child proces 频繁的terminate-restart,当某个child process超过这个频率,supervisor将会terminate所有的child process然后再terminate掉自己(根据我的测试结果,这个频率的计算是这次重启距离上次重启的的时间间隔)

3.Child Specification
这个属性说白了,就是告诉supervisor,你要监控哪些child process,你该怎么启动这些child process以及如何结束它们等等,该属性的详细格式如下:
{Id, StartFunc, Restart, Shutdown, Type, Modules}
    Id = term()
    StartFunc = {M, F, A}
    Restart = permanent | transient | temporary
    Shutdown = brutal_kill | integer()>0 | infinity
    Type = worker | supervisor
    Modules = [Module] | dynamic
其中:

Id 唯一标示了一个child process;
StartFunc告诉supervisor如何启动它(即调用哪一个方法),特别要注意的是:1. StartFunc 必须 create a link to  the child process(只有这样 supervisor才能够监控到child process,感知它的生死)2.若child process 创建成功,它必须返回 {ok, Child} 或者 {ok, Child,  Info},其中Child 为child process的Pid,Info值被supervisor忽略(我一开就在这里栽了跟头,没有按标准格式返回)
Restart 这个参数用来告诉supervisor,当该child process挂掉时,是否能够重启它,permanent表示永远可以(不管child process是以何种原因挂掉),temporary表示永远不可以(即挂掉了将不再重启),transient 有点特殊,它表示child process若是因为normal或者shutdown原因结束,则不再重启,否则可以restart(ps:Restart参数设置会覆盖Restart Strategy,譬如一个child process的Restart设置为temporary,supervisor的Restart Strategy是one_for_all,那么当其他某个child process挂掉后,将会导致该child process(temporay)被terminate并且不再被重启)
Shutdown 用来告诉supervisor当它想terminate某个child process该如何terminate,brutal_kill 顾名思义就是很粗鲁,很暴力的结束一个child process(supervisor内部调用exit(ChildPid, kill)方法,注意exit reason为kill的exit signal是不可被捕获的,无论ChildPid是否为system process);整型值TimeOut表示当supervisor想结束一个child process时,它调用exit(ChildPid, shutdow),若在Timout时间范围内supervisor没有收到来自child process的exit signal(因为supervisor linked to child process,所以当child process挂掉时,supervisor会收到一个exit signal),那么supervisor将会调用exit(ChildPid, kill)方法,暴力的terminate child process(这里我突然疑惑了?这样不也是会导致supervisor 收到一个不可捕获的exit signal?);infinity:当你的child process 也是一个supervisor并且你需要terminate,这时你需要将Shutdown参数设置为infinity,从而保证child process(supervisor)能够有充分的时间结束它的supervision tree;
Type:用来指定child process的类型(worker or superviosr)
Module: 这个参数我目前还不是很明白,暂且搁置


说完了这么多,我们来看一个简单的例子(child process 每5s由它的supervisor重启一次):

worker process


该worker process做的事情很简单,启动时会打印start...,然后暂停5s,最后退出打印一条quit...消息,其中start_link供supervisor调用

supervisor


我们重点看下几个参数的设置:
supervisor重启策略:one_for_one
supervisor最大重启频率:1 / s
child process的StartFunc: tick模块的start_link方法,参数为空
child process的Restart属性:permanent(这个是child process 挂掉后能被重启的关键)

我们看下程序运行效果:


第一行调用supervisor:start_link(my_supervisor, []) 创建一个supervisor(其中my_supervisor是callback module),若成功创建supervisor(它所监控的child process也创建成功),则返回{ok, SupPid}(譬如这里的{ok, <0.34.0>),之后我们就看到屏幕上一直循环打印start.... quit.... 并且每一个pid都不一样,这就说明,当supervisor发现child process挂掉后(不论什么原因,哪怕是正常退出),都会restart child process(你可以尝试把child process的permanent修改为temporary,看看运行结果又是如何)

至此我们已经完成了一个supervisor的例子,别看它简单,但确实构建了一个supervision tree,关于supervisor的更多细节,请参看下列文档:
http://www.erlang.org/doc/design_principles/sup_princ.html
http://www.erlang.org/doc/man/supervisor.html

最后我们在看下:simple_one_for_one,这种Restart Strategy和one_for_one基本相同(即当一个child process挂掉后,仅仅重启该child process 而不影响其他child process),唯一不同的是:simple_one_for_one 只能够动态的添加child process并且所有的child process执行同一份代码 ,我们来看一个例子(来自otp 官方文档)



注意这里的StartFunc: {call, start_link, []} 并不会真正的去启动一个child process,而必须通过调用 supervisor:start_child(Sup, List) 动态添加child process,其中第一个参数Sup是表示你要往哪个supervisor下添加child process,第二个参数用来在创建child process时传递给它(内部调用apply(M, F, A++List))


=======================================附加

1. 监督规则

一个监督者负责启动、停止、监控他的子进程。监督者的一个基本概念就是当必要的时候重启子进程保证它们的存活

哪个子进程要重启和被监控是由一个子规程列表决定的,子进程按照列表中指定的顺序启动,并按相反的顺序终止

2. 实例

监督者的回调模块

-module(ch_sup).
 -behaviour(supervisor).
 -export([start_link/0]).
 -export([init/1]).
 start_link() ->    
supervisor:start_link(ch_sup, []).
 init(_Args) ->   
   {ok, {{one_for_one, 1, 60},      
    [{ch3, {ch3, start_link, []},      
      permanent, brutal_kill, worker, [ch3]}]}}.
 
  

one_for_one是重启策略
1和60定义了最大重启频率
{ch3, …}是子规程

3. 重启策略

one_for_one

假如一个进程终止了,仅仅这个进程会被重启

 

one_for_all

假如一个进程停止了,所有其他子进程也要被停止,然后所有子进程,包括这个引发停止的子进程都被重启

 

rest_for_one

假如一个进程停止了,它后面的子进程,也就是以启动顺序来说这个被终止的进程后面的子进程都将被停止,然后他们又被启动。

 

4. 最大启动频率

监督者有一个内建机制限制在给定的时间间隔里的重启次数,这由子进程启动规程中的两个参数值决定,MaxR和MaxT,它们定义在回调函数init中

init(...) ->   
 {ok, {{RestartStrategy, MaxR, MaxT},       
   [ChildSpec, ...]}}.

如果在时间MaxT里重启次数大于MaxR ,监督者进程就停止它所有子进程,然后再终止自己。

当监督者进程终止了,那么更高级别的监督者要采取些动作,它或者重启被终止的监督者或者停止自己

这个重启机制的目的是预防一个进程因某种原因频繁的终止,然后简单的重启。

5. 子规范

下面的是类型定义

{Id, StartFunc, Restart, Shutdown, Type, Modules}   
 Id = term()   
 StartFunc = {M, F, A}  
      M = F = atom()   
     A = [term()] 
   Restart = permanent | transient | temporary  
  Shutdown = brutal_kill | integer() &gt;=0 | infinity   
 Type = worker | supervisor    
Modules = [Module] | dynamic      
  Module = atom()
  • Id用来内部标识子规范
  • StartFunc是启动子进程时调用的函数,它将成为对supervisor:start_link, gen_server:start_link, gen_fsm:start_link or gen_event:start_link的调用
  • Restart标识一个进程终止后将怎样重启,一个permanent 进程总会被重启;一个temporary 进程从不会被重启;一个transient 进程仅仅当是不正常的被终止后才重启,例如非normal得退出原因
  • Shutdown 定义一个进程将怎样被终止,brutal_kill意味着子进程被exit(Child, kill)无条件的终止;一个整数值的超时时间意味着监督者告诉子进程通过调用exit(Child, shutdown)而被终止,然后等待一个返回的退出信号,假如在指定的时间里没有收到退出信号,那么子进程用exit(Child, kill)被无条件终止。
  • Type指定子进程是supervisor还是worker
  • Modules 是有一个元素的列表[Module],假如子进程是supervisor、gen_server 或 gen_fsm,那么Module 是回调模块的名称;假如子进程是gen_event,那么Modules 应该是dynamic

例如:子规范用于启动一个服务器ch3

{ch3, {ch3, start_link, []}, permanent, brutal_kill, worker, [ch3]}

子规范用于启动一个事件管理器

{error_man, {gen_event, start_link, [{local, error_man}]}, permanent, 5000, worker, dynamic}

 

监督者然后根据子规程启动所有子进程,这个例子中是一个子进程ch3

6. 启动supervisor

像这样

start_link() ->   
 supervisor:start_link(ch_sup, []).

启动

监督者进程调用init

init(_Args) ->    
{ok, {{one_for_one, 1, 60},       
   [{ch3, {ch3, start_link, []},      
      permanent, brutal_kill, worker, [ch3]}]}}.

并期待init返回{ok, StartSpec}

注意supervisor:start_link是同步的,它一直等到所有子进程都启动了才返回

7. 添加子进程

除静态监控树外,我们也可以通过supervisor:start_child(Sup, ChildSpec)向监督者动态添加子进程,Sup 是监督者的pid或名称,ChildSpec 是一个子规范。子进程用start_child/2来添加。注意:假如监督者死掉后重启,那么所有动态添加的子进程都不复存在

8. 停止子进程

任何静态动态添加的子进程都可以用supervisor:terminate_child(Sup, Id)来停止。一个停止子进程规范可以用supervisor:delete_child(Sup, Id)来删除。Sup是监督者的pid或名称,Id是子规范的id

9. Simple-One-For-One

监督者的simple_one_for_one启动策略是one_for_one的简版,所有子进程都是同一进程实例而被动态添加,下面是一个simple_one_for_one监督者的实例

-module(simple_sup).
-behaviour(supervisor).
-export([start_link/0]).
-export([init/1]).
start_link() ->    supervisor:start_link(simple_sup, []).
init(_Args) -> 
   {ok, {{simple_one_for_one, 0, 1},   
       [{call, {call, start_link, []}, 
           temporary, brutal_kill, worker, [call]}]}}.

当启动时,监督者不启动任何子进程,取而代之的是所有子进程都通过调用supervisor:start_child(Sup, List)来动态添加,Sup 是监督者的pid或名称,List 是添加给子规范中指定参数列表term列表,如果启动函数是{M, F, A}这种形式,那么子进程通过调用apply(M, F, A++List)而被启动

例如,给上面的例子添加一个子进程

supervisor:start_child(Pid, [id1])

那么子进程通过调用apply(call, start_link, []++[id1])而被启动,实际上就是call:start_link(id1)

10. 停止

因为监控者是监控树的一部分,它自动被他的监督者停止,根据相应规范,它反序停止它的所有子进程,然后终止自己

至此,四种behavour已经全部翻译完了,熟练应用他们是你构建高扩展、高容错、高并发应用的基础,努力吧!

转载于:https://www.cnblogs.com/jluzhsai/p/4432841.html

基于数据挖掘的音乐推荐系统设计与实现 需要一个代码说明,不需要论文 采用python语言,django框架,mysql数据库开发 编程环境:pycharm,mysql8.0 系统分为前台+后台模式开发 网站前台: 用户注册, 登录 搜索音乐,音乐欣赏(可以在线进行播放) 用户登陆时选择相关感兴趣的音乐风格 音乐收藏 音乐推荐算法:(重点) 本课题需要大量用户行为(如播放记录、收藏列表)、音乐特征(如音频特征、歌曲元数据)等数据 (1)根据用户之间相似性或关联性,给一个用户推荐与其相似或有关联的其他用户所感兴趣的音乐; (2)根据音乐之间的相似性或关联性,给一个用户推荐与其感兴趣的音乐相似或有关联的其他音乐。 基于用户的推荐和基于物品的推荐 其中基于用户的推荐是基于用户的相似度找出相似相似用户,然后向目标用户推荐其相似用户喜欢的东西(和你类似的人也喜欢**东西); 而基于物品的推荐是基于物品的相似度找出相似的物品做推荐(喜欢该音乐的人还喜欢了**音乐); 管理员 管理员信息管理 注册用户管理,审核 音乐爬虫(爬虫方式爬取网站音乐数据) 音乐信息管理(上传歌曲MP3,以便前台播放) 音乐收藏管理 用户 用户资料修改 我的音乐收藏 完整前后端源码,部署后可正常运行! 环境说明 开发语言:python后端 python版本:3.7 数据库:mysql 5.7+ 数据库工具:Navicat11+ 开发软件:pycharm
MPU6050是一款广泛应用在无人机、机器人和运动设备中的六轴姿态传感器,它集成了三轴陀螺仪和三轴加速度计。这款传感器能够实时监测并提供设备的角速度和线性加速度数据,对于理解物体的动态运动状态至关重要。在Arduino平台上,通过特定的库文件可以方便地与MPU6050进行通信,获取并解析传感器数据。 `MPU6050.cpp`和`MPU6050.h`是Arduino库的关键组成部分。`MPU6050.h`是头文件,包含了定义传感器接口和函数声明。它定义了类`MPU6050`,该类包含了初始化传感器、读取数据等方法。例如,`begin()`函数用于设置传感器的工作模式和I2C地址,`getAcceleration()`和`getGyroscope()`则分别用于获取加速度和角速度数据。 在Arduino项目中,首先需要包含`MPU6050.h`头文件,然后创建`MPU6050`对象,并调用`begin()`函数初始化传感器。之后,可以通过循环调用`getAcceleration()`和`getGyroscope()`来不断更新传感器读数。为了处理这些原始数据,通常还需要进行校准和滤波,以消除噪声和漂移。 I2C通信协议是MPU6050与Arduino交互的基础,它是一种低引脚数的串行通信协议,允许多个设备共享一对数据线。Arduino板上的Wire库提供了I2C通信的底层支持,使得用户无需深入了解通信细节,就能方便地与MPU6050交互。 MPU6050传感器的数据包括加速度(X、Y、Z轴)和角速度(同样为X、Y、Z轴)。加速度数据可以用来计算物体的静态位置和动态运动,而角速度数据则能反映物体动的速度。结合这两个数据,可以进一步计算出物体的姿态(如角度和角速度变化)。 在嵌入式开发领域,特别是使用STM32微控制器时,也可以找到类似的库来驱动MPU6050。STM32通常具有更强大的处理能力和更多的GPIO口,可以实现更复杂的控制算法。然而,基本的传感器操作流程和数据处理原理与Arduino平台相似。 在实际应用中,除了基本的传感器读取,还可能涉及到温度补偿、低功耗模式设置、DMP(数字运动处理器)功能的利用等高级特性。DMP可以帮助处理传感器数据,实现更高级的运动估计,减轻主控制器的计算负担。 MPU6050是一个强大的六轴传感器,广泛应用于各种需要实时运动追踪的项目中。通过 Arduino 或 STM32 的库文件,开发者可以轻松地与传感器交互,获取并处理数据,实现各种创新应用。博客和其他开源资源是学习和解决问题的重要途径,通过这些资源,开发者可以获得关于MPU6050的详细信息和实践指南
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值