Gen_server行为分析与实践
1.简介
Gen_server实现了通用服务器client_server原理,几个不同的客户端去分享服务端管理的资源(如图),gen_server提供标准的接口函数和包含追踪功能以及错误报告来实现通用的服务器,同时可以作为OTP监控树的一部分。
Gen_server函数与回调函数之间的关系:
gen_server moduleCallbackmodule
--------------------------------
gen_server:start_link ----->Module:init/1
gen_server:call
gen_server:multi_call ----->Module:handle_call/3
gen_server:cast
gen_server:abcast ----->Module:handle_cast/2
------>Module:handle_info/2
------>Module:terminate/2
------>Module:code_change/3如果回调函数失败或者是返回bad value,gen_server将终止。Gen_server可以处理来自系统的消息,通过sys模块可以调试一个gen_server.(未实践)注意:一个gen_server不能自动的捕获exit信号,必须在回调Module:init时设置process_flag(trap_exit,true)(实例3.2).如果请求的gen_server不存在或参数是bad arguments,那么gen_server的所有请求都将fail.如果回调函数中指定了hibernate,那么gen_server进程将进入hibernate,这对于一个长时间的空闲的进程非常有用,因为可以进行垃圾回收和减少内存占用。但是,要非常小心的使用hibernate,因为在hibernate到wake_up之间,至少有两个垃圾回收器,对于一个请求频繁的server不划算。
2.函数
2.1 导出函数
start(Module, Args, Options) -> Resultstart(ServerName, Module, Args, Options) -> Result
start_link(ServerName, Module, Args, Options) -> Resultstart_link(Module, Args, Options) -> Result
start_link与start的区别是:1.start用于创建一个独立的gen_server,但是可以通过参数{spawn_opt,[link]}来达到start_link的效果;2.start_link用于创建一个在监控树下的gen_server对其参数的解析:ServerName = {local,Name} | {global,GlobalName} | {via,Module,ViaName}
{local,Name}:在本地节点命名为Name,通过register/2{debug,Dbgs}回去调用sys模块中指定的方法
Dbgs = [trace | log | statistics | {log_to_file,FileName} | {install,{Func,FuncState}}]
{global,Name}:在全局命名为Name,通过global:register_name/2
{via,Module,ViaName}:通过Module来命名,其原理象global,必须导出register_name/2,unregister_name/1, whereis_name/1 , send/2等函数如果没有该参数,则用pid()来作为名字
Module:回调模块Args:是回调方法init的参数Options:[{debug,Dbgs} | {timeout,Time} | {spawn_opt,SOpts}]
{timeout,Time}是初始化的的时间限制,超出时间限制返回{error,timeout}{spawn_opt,SOpt}是在生成gen_server可以设置的参数,通过内建函数spawn_opt来实现
SOpt = Option = link %% 与创建的gen_server连接
| monitor %% 在这里会报错| {priority, Level :: priority_level()} %%设置优先级| {fullsweep_after, Number :: integer() >= 0} %%多长时间进行一次全局扫描,进行垃圾回收| {min_heap_size, Size :: integer() >= 0} %%最小堆内存| {min_bin_vheap_size, VSize :: integer() >= 0} %%最小二进制虚拟堆内存
priority_level() = low | normal | high | max
如果创建gen_server成功返回{ok, Pid()},如果创建的进程已经存在返回{error,{already_started,Pid}}如果回调函数init失败返回{error, Reason},如果回调函数返回{stop,Reason}或ignore则返回{error,Reason}或ignore
call(ServerRef, Request) -> Replycall(ServerRef, Request, Timeout) -> Reply
ServerRef = Name | {Name,Node} | {global,GlobalName} | {via,Module,ViaName} | pid()
Name:本地注册名称为Name的gen_server.{Name,Node}:调用Node节点上注册名称为Name的gen_server.{global,GlobalName}:调用全局名称为GlobalName的gen_server.{via,Module,ViaName}:调用注册在Module名称为ViaName的gen_server.(原理同global)pid():调用进程pid上的gen_server.
Timeout = int()>0 | infinity
同步调用的时间限制,infinity表示无穷大,默认为5000毫秒当超时时就会发生错误,如果捕获该错误,将继续运行
multi_call(Name, Request) -> Result %%会调用所有节点
multi_call(Nodes, Name, Request) -> Result
multi_call(Nodes, Name, Request, Timeout) -> Result %%调用指定列表节点,并有时间限制该函数的作用是对指定节点列表上本地注册名称是Name的gen_server发起请求,然后等待返回
其回调函数为Module:handle_call/3
Nodes = [Node]
节点列表Timeout = int()>=0 | infinity
同步调用的时间限制,infinity表示无穷大,默认为infinity毫秒如果在指定的时间限制内未返回,该节点为BadNodes
注意:对于非Erlang节点等待可能无穷大,例如Java或C节点。(未验证)
Result = {Replies,BadNodes}
Replies = [{Node,Reply}]
BadNodes = [Node]
没有响应的节点列表。
cast(ServerRef, Request) -> okServerRef = Name | {Name,Node} | {global,GlobalName} | {via,Module,ViaName} | pid()参数信息和gen_server:call同义发送一个异步请求给Module:handle_cast处理,并立即返回ok.如果节点或gen_server不存在请求将被ignore.abcast(Name, Request) -> abcastabcast(Nodes, Name, Request) -> abcast发送一个异步请求给指定节点并且本地注册名称为Name的gen_server,并立即返回abcast.如果节点不存在或者Name不存在,请求将被忽略掉ignore.
reply(Client, Reply) -> ResultClient - see below该函数用于gen_server向一个指定的客户端发送信息,但是,请求函数call or multi_call的回调函数Module:handle_call是没有定义Reply.客户端必须是回调函数提供的From,Reply是任意的数据结构作为call or multi_call的返回值。enter_loop(Module, Options, State)enter_loop(Module, Options, State, ServerName)enter_loop(Module, Options, State, Timeout)enter_loop(Module, Options, State, ServerName, Timeout)Options = [Option]Option = {debug,Dbgs}Dbgs = [trace | log | statistics | {log_to_file,FileName} | {install,{FuncState}}]ServerName = {local,Name} | {global,GlobalName} | {via,Module,ViaName}Timeout = int() | infinity该函数的功能是让一个已经存在的进程成为gen_server进程,通过请求进程让它进入gen_server循环成为gen_server进程,该普通进程的创建必须通过proc_lib模块(实例3.4)。这个函数更加的有用对于要进行比gen_server还要复杂的初始化时。Module, Options and ServerName 的意义与gen_server:start_link一样.然而,如果Servername被指定这个进程被调用前一定要相应的先注册。State and Timeout与回调函数的Module:init一样。Failure: 如果请求进程不是一个由proc_lib创建的进程或使用了没有注册的ServerName.
2.2 回调函数
Module:init(Args) -> ResultTypes:Result = {ok,State} | {ok,State,Timeout} | {ok,State,hibernate}| {stop,Reason} | ignoreState = term()Timeout = int()>=0 | infinity通过start或start_link初始化一个新的进程。若初始化成功返回{ok,State} | {ok,State,Timeout} | {ok,State,hibernate}。State是gen_server的内部状态;Timeout指进程初始化后等待接受请求的时间限制,超过时间限制将向handle_info发送请求为timeout的信息,默认是infinity;hibernate指可通过调用proc_lib:hibernate/3使进程进入冬眠状态从而进行GC,当有消息请求该进程,处理该请求,然后冬眠并进行GC.注意应该小心使用'hibernate',主要针对空闲时间比较长的进程,因为至少有两个GC回收器,对于请求比较平凡的进程,资源的消耗高。如果初始化失败将返回{stop,Reason} | ignoreModule:handle_call(Request, From, State) -> ResultTypes:From = {pid(),Tag}Result = {reply,Reply,NewState} | {reply,Reply,NewState,Timeout}| {reply,Reply,NewState,hibernate}| {noreply,NewState} | {noreply,NewState,Timeout}| {noreply,NewState,hibernate}| {stop,Reason,Reply,NewState} | {stop,Reason,NewState}Timeout = int()>=0 | infinity处理call或multi_call的请求。若返回{reply,Reply,NewState} | {reply,Reply,NewState,Timeout} | {reply,Reply,NewState,hibernate},Reply将返回给请求函数call or multi_call.Timeout | hibernate的意义与Module:init中意义相同。若返回{noreply,NewState} | {noreply,NewState,Timeout} | {noreply,NewState,hibernate},gen_server将继续执行但没有返回,若要返回需要显示的调用gen_server:reply/2来返回。若返回{stop,Reason,Reply,NewState} | {stop,Reason,NewState} ,前者的Reply将返回给调用函数,后者没有返回,若要返回显示调用gen_server:reply/2;两者最终都将调用Module:terminate/2来终止进程。Module:handle_cast(Request, State) -> ResultTypes:Result = {noreply,NewState} | {noreply,NewState,Timeout}| {noreply,NewState,hibernate}| {stop,Reason,NewState}Timeout = int()>=0 | infinity处理cast or abcast的请求。其参数的描述信息与Module:handle_call中的一致。Module:handle_info(Info, State) -> ResultTypes:Info = timeout | term()Result = {noreply,NewState} | {noreply,NewState,Timeout}| {noreply,NewState,hibernate}| {stop,Reason,NewState}Timeout = int()>=0 | infinityReason = normal | term()处理同步或异步异步请求的timeout信息,以及receive的信息。其参数的描述信息与Module:handle_call中的一致。Module:terminate(Reason, State)Types:Reason = normal | shutdown | {shutdown,term()} | term()State = term()这个函数在gen_server终止时被调用。它和Module:init是相对的可以在终止前进行一些清理工作。当gen_server终止的Reason返回时,这个返回将被ignore.终止的Reason依赖于为什么终止,如果它是因为回调函数返回一个stop元组{stop, ...}那么终止Reason就是指定的终止原因;如果是由于失败(failure),则Reason是error原因。如果gen_server是监控树的一部分,被它的监控树有序的终止并且满足,1.被设置成可捕获的退出信号;2.关闭策略被设置成一个整数的timeout,而不是brutal_kill.则它的Reason是shutdown。甚至gen_server并不是监控树的一部分,只要它从父进程接收到'EXIT'消息,则Reason则是'EXIT'。注意:无论由于任何原因[normal | shutdown | {shutdown,term()} | term()]终止,终止原因都是由于一个error或一个error report issued(error_logger:format/2)
Module:code_change(OldVsn, State, Extra) -> {ok, NewState} | {error, Reason}
该函数主要用于版本的热更新,后续相关专题会介绍。
3.实例
3.1 请求
一 同步请求
通过gen_server:call或gen_server:multi_call发起同步请求,然后等待返回。
add1(Num1,Num2)->
io:format("~nsync start~n"),
Res= gen_server:call(?SERVER,{add1,Num1,Num2}),%%同步请求
io:format("~nsync end~n"),
Res.
handle_call({add1,Num1,Num2},_From,State)->%%消息的接受处理方式
Num=Num1+Num2,
timer:sleep(3000),
io:format("sleep end~n"),
{reply,{ok, add1,Num},State}.
二 异步请求
通过gen_server:cast或gen_server:abcast发起异步请求立即返回ok|abcast,如果节点|gen_server|Name不存在请求ignore.
add2(Num1,Num2)->
io:format("~nasync start~n"),
Res= gen_server:cast(?SERVER,{add2,Num1,Num2}),%%异步请求
io:format("~nasync end~n"),
Res.
handle_cast({add2,Num1,Num2},State)->%%消息的接受处理方式:
Num=Num1+Num2,
io:format("~n~p~n",[Num]),
timer:sleep(3000),
io:format("sleep end~n"),
{noreply,State}.
三 其他消息处理
异步接收发送过来的消息,并进行相应的处理。例如在回调函数中返回{ok,State,Timeout},如果超时就会发送一条timeout消息gen_server(实例3.5).
add3(Num1,Num2)->
io:format("~nsend start~n"),
Res= erlang:send(?SERVER,{add3,Num1,Num2}),%%进程消息发送(异步)
io:format("~nsend end~n"),
Res.
handle_info({add3,Num1,Num2},State)->%%消息的接受处理方式
Num=Num1+Num2,
io:format("~n~p~n",[Num]),
timer:sleep(3000),
io:format("sleep end~n"),
{noreply,State}.
3.2 捕获异常消息
通过在gen_server初始化时设置process_flag(trap_exit,true)可以捕获本进程的退出消息。注意:要捕获exit就要进程之间要建立连接。
exit(Msg)->
link(whereis(?MODULE)),
erlang:exit(Msg).
handle_info({'EXIT',From,Reson},State)->
io:format("~p~p~n",[From,Reson]),
{noreply,State};
handle_info(_Info,State)->
{noreply,State}.调用结果:
3.3 gen_server:reply/2
当Modile:handle_call没有返回时{noreply,NewState},可以通过gen_server:reply(Client, Reply)来返回给gen_server:call调用端。
add10(Num1,Num2)->
io:format("~nsync start~n"),
Res= gen_server:call(?SERVER,{noreply,Num1,Num2}),
io:format("~nsync end~n"),
Res.
handle_call({noreply,Num1,Num2},_From,State)->
Num=Num1+Num2,
timer:sleep(3000),
io:format("sleep end~n"),
gen_server:reply(_From,{ok, gen_server_reply,Num}),%%给gen_server返回Replay
{noreply,State};%%无返回调用端返回:
3.4 gen_server:enter_loop/3
gen_server:enter_loop方法可以让一个存在的普通进程成为一个gen_server进程。
-module(enter).
-author("EricLw").
%% API
-export([start_link/0, init/1]).
%% API
-export([ add1/2]).
%% gen_server callbacks
-export([
handle_call/3,
handle_cast/2,
handle_info/2,
terminate/2,
code_change/3]).
-define(SERVER,?MODULE).
-record(state,{}).
%%%===================================================================
%%% API
%%%===================================================================
start_link()->
proc_lib:start_link(?SERVER, init,[self()]).%%通过proc_link来穿件普通进程
add1(Num1,Num2)->
io:format("~nsync start~n"),
Res= gen_server:call(?SERVER,{add1,Num1,Num2}),
io:format("~nsync end~n"),
Res.
init(Person)->
proc_lib:init_ack(Person,{ok,self()}),
register(?MODULE,self()),
gen_server:enter_loop(?MODULE,[],#state{},{local,?MODULE}). %%指定了ServerName必须先注册Name
handle_call({add1,Num1,Num2},_From,State)->
Num=Num1+Num2,
timer:sleep(3000),
io:format("sleep end~n"),
{reply,{ok, add1,Num},State};
handle_call(_Request,_From,State)->
{reply, ok,State}. handle_cast(_Request,State)->
{noreply,State}. handle_info(_Info,State)->
{noreply,State}.
terminate(_Reason,_State)->
timer:sleep(3000),
io:format("~nclean up~n"),
ok.
code_change(_OldVsn,State,_Extra)->
{ok,State}.注意:如果Servername被指定这个进程被调用前一定要相应的先注册。
3.5 timeout以及hibernate
在gen_server的回调函数中如init,handle_call,handle_cast,handle_info中返回了Timeout | hibernate是这是就会进入相应的处理过程。若返回了Timeout,表示在指定的时间范围内没有接收到请求或消息就会就会发出一条timeout消息,然后进行后续处理;若返回hibernate则表示进程就如hibernate状态,便于GC,当向该进程发送消息或请求的时候,唤醒该进程然后进行后续处理。一 Timeout
-module(add).
-behaviour(gen_server).
%% API
-export([start_link/0, add1/2]).
%% gen_server callbacks
-export([init/1,
handle_call/3,
handle_cast/2,
handle_info/2,
terminate/2,
code_change/3]).
-define(SERVER,?MODULE).
-record(state,{}).
start_link()->
gen_server:start_link({local,?SERVER},?MODULE,[],[]).
add1(Num1,Num2)->
io:format("~nsync start~n"),
Res= gen_server:call(?SERVER,{add1,Num1,Num2}),
io:format("~nsync end~n"),
Res.
%%%===================================================================
%%% gen_server callbacks
%%%===================================================================
init([])->
%%erlang:send_after(2000,?SERVER,{add3,1,1}),
%%{ok,#state{}, hibernate}.
{ok,#state{},3000}.
handle_call({add1,Num1,Num2},_From,State)->
Num=Num1+Num2,
timer:sleep(3000),
%%io:format("sleep end~n"),
{reply,{ok, add1,Num},State};
handle_call(_Request,_From,State)->
{reply, ok,State}.
handle_cast(_Request,State)->
{noreply,State}.
handle_info(timeout,State)->
io:format("~ntimeout_ericlw~n"),
{noreply,State};
handle_info({add3,Num1,Num2},State)->
Num=Num1/Num2,
io:format("~n~p~n",[Num]),
timer:sleep(3000),
io:format("sleep end~n"),
{noreply,State};
handle_info(_Info,State)->
{noreply,State}.
terminate(_Reason,_State)->
timer:sleep(3000),
io:format("~nclean up~n"),
ok.
code_change(_OldVsn,State,_Extra)->
{ok,State}.打印的过时信息:若在时限内接受到消息:
二 hibernate
在返回中用hibernate代替Timeout
init([])->
{ok,#state{}, hibernate}.后面会用专题来分析该特性,它是重要的优化手段。详细信息:erlang:hibernate和proc_lib:hibernate.
4.总结
Gen_server行为作为通用服务器,良好的将业务部分与通用部分进行了分离,只要专注与业务部分也能够构建良好的系统。我们只需关心导出函数与回调函数部分,明确调用函数与回调函数的意义与联系。gen_server一般是业务模块的核心处理进程,对于请求与消息的处理,应该根据业务来定。对于需要进行非常复杂化的初始化过程,可以通过enter_loop讲一个已经初始化好的进程变成gen_server进程,对于不经常访问的进程记得返回hibernate来进行及时的GC.总之,gen_server为我们构建服务器提供了极大的便利。
Gen_server行为分析与实践的更多相关文章
- Log4j2分析与实践
当前网络上关于Log4j2的中文文章比较零散,这里整理了一下关于Log4j2比较全面的一些文章,供广大技术人员参考 Log4j2分析与实践-认识Log4j2 Log4j2分析与实践-架构 Log4j2 ...
- 苏宁基于Spark Streaming的实时日志分析系统实践 Spark Streaming 在数据平台日志解析功能的应用
https://mp.weixin.qq.com/s/KPTM02-ICt72_7ZdRZIHBA 苏宁基于Spark Streaming的实时日志分析系统实践 原创: AI+落地实践 AI前线 20 ...
- 《Linux内核分析》实践4
<Linux内核分析> 实践四--ELF文件格式分析 20135211李行之 一.概述 1.ELF全称Executable and Linkable Format,可执行连接格式,ELF格 ...
- 自定义View系列教程04--Draw源码分析及其实践
深入探讨Android异步精髓Handler 站在源码的肩膀上全解Scroller工作机制 Android多分辨率适配框架(1)- 核心基础 Android多分辨率适配框架(2)- 原理剖析 Andr ...
- Supervisor行为分析和实践
1.简介 Erlang要编写高容错性.稳定性的系统,supervisor就是用来解决这一问题的核心思想.通过建立一颗监控树,来组织进程之间的关系,通过确定重启策略.子进程说明书等参数信息来确定 ...
- Gen_event行为分析和实践
1.简介 Gen_event实现了通用事件处理,通过其提供的标准接口方法以及回调函数,在OTP里面的事件处理模块是由一块通用的事件管理器和任意数量的事件处理器,并且这些事件处理器可以动态的添加和删除. ...
- AWVS结果分析与实践-XSS
今天趁着老师接项目,做了一丢丢实践,以下是一点点感触. 都知道AWVS是神器,可是到我手里就是不灵.拿了它扫了一个URL,结果提示XSS漏洞,实践没反应,只好愉快地享受了过程.来看看. ...
- 基于redis的分布式锁的分析与实践
前言:在分布式环境中,我们经常使用锁来进行并发控制,锁可分为乐观锁和悲观锁,基于数据库版本戳的实现是乐观锁,基于redis或zookeeper的实现可认为是悲观锁了.乐观锁和悲观锁最根本的区别在于 ...
- FastText 分析与实践
一. 前言 自然语言处理(NLP)是机器学习,人工智能中的一个重要领域.文本表达是 NLP中的基础技术,文本分类则是 NLP 的重要应用.在 2016 年, Facebook Research 开源了 ...
随机推荐
- JVM的组成部分与内存管理
JVM的组成部分与内存管理 JVM区域划分 由于Java程序是交由JVM执行的,所以我们在谈Java内存区域划分的时候事实上是指JVM内存区域划分.在讨论JVM内存区域划分之前,先来看一下Java程序 ...
- speed up your sharepoint
1. warm up http://blog.nowan.hu/post/SPWakeUp-Wake-up-your-SharePoint-quickly http://blogs.msdn.com/ ...
- Excel 隐藏功能区命令
Application.ExecuteExcel4Macro "SHOW.TOOLBAR(""Ribbon"",False)"
- express中ejs模板引擎
1.在 app.js 中通过以下两个语句设置了 引擎类型 和页面模板的位置: app.set('views', __dirname + '/views'); app.set('view engine' ...
- Hex string convert to integer with stringstream
#include <sstream>#include <iostream>int main() { unsigned int x; std::stringstream ss; ...
- ***phpredis扩展安装总结
phpredis扩展安装总结:PHP扩展安装在[root@iZ254lfyd6nZ lampp]# cd include 目录下创建一个目录phpredis下载扩展:wget https://gith ...
- sql快捷键
请柬:https://msdn.microsoft.com/zh-cn/library/ms174205.aspx
- WinAPI你知道多少?!(上千个,好多都没见过)
http://www.cnblogs.com/vanver/archive/2013/06/13/NO-2013_06_13pm.html 播客开篇,讲讲废话:本篇播客只是推荐给热与钻研的同学们... ...
- Oracle Exception 处理
1.问题来源Oracle中可以用dbms_output.put_line来打印提示信息,但是很容易缓冲区就溢出了.可以用DBMS_OUTPUT.ENABLE(1000000);来设置缓冲区的大小.但是 ...
- caffe简易上手指南(一)—— 运行cifar例子
简介 caffe是一个友好.易于上手的开源深度学习平台,主要用于图像的相关处理,可以支持CNN等多种深度学习网络. 基于caffe,开发者可以方便快速地开发简单的学习网络,用于分类.定位等任务,也可以 ...