在Erlang 系统中,经常需要gen_server 进程来处理共享性的数据,也就是总希望一个gen_server 进程来为多个普通进程提供某种通用性的服务,这也是gen_server 设计的初衷.但是,由于公平调度的原因,在Erlang体系中,每个process 能获得的资源都是同等的:同等的CPU时间片(还有默认情况下同等的初始化内存). 也就是gen_server 进程只能获得1/(N+1)的CPU时间片,为N个进程提供通用性的服务,而无法违背公平调度的原则使gen_server 进程获得更多的资源.这也就是经常说的,Erlang 单进程性能差,Erlang 单进程单点.

为了解决上面提到的问题,目前有几种方案:

1, gen_server 进程组,就是利用多个功能相同的gen_server 进程形成group,以获得更多的进程资源;

2, 提高关键进程(热点进程)的进程优先级,保证热点进程的调度执行;

3, 避免gen_server 进程中消息队列(请求)的堆积,使用noblock call 的方式.

gen_server 进程组, gen_server pool的方式在社区中使用广泛,pool 相关的开源项目就有poolboy pooler 等, RabbitMQ 使用了worker_pool 的方式,使用与schedulers 等数量的进程组成 gen_server(2) 进程组.

在开源社区中,很少见有项目改变某些进程的进程优先级,反而在Erlang 源代码中, net_kernel 进程, 使用了max 的进程优先级, 来保证net_kernel 进程的调度执行.

而在rpc module 中,使用了call以及block_call 两种不同的请求方式(其中call 即为noblock call),同时,net_kernel module 的call 请求也采用noblock call 的方式.

block call

在分析noblock call 之前,有必要分析下block call 方式的特点以及优缺点.

以下代码是rpc module 中处理block call 的代码片段

 handle_call({block_call, Mod, Fun, Args, Gleader}, _To, S) ->
MyGL = group_leader(),
set_group_leader(Gleader),
Reply =
case catch apply(Mod,Fun,Args) of
{'EXIT', _} = Exit ->
{badrpc, Exit};
Other ->
Other
end,
group_leader(MyGL, self()), % restore
{reply, Reply, S};

可以看到rpc module 在处理block call 请求时,基本的模式就是有请求达到时立即顺序处理,而在这种时候有其他请求达到,就只能存储在rpc 进程的消息队列中,待此次请求处理结束后才能将下一个请求从进程消息队列中检出,然后进行处理.

在请求是有状态的情况下,这种处理方式能够保证请求处理的状态性, 这也就是这种处理方式的优点. 但这种 block call 处理方式的缺点很明显, 当请求数过大, rpc module 单次请求耗时时, rpc 进程的消息队列就会不断挤压, 大量的请求就会超时. 恶性循环, 使系统整体性能下降.

noblock call

rpc noblock call

在rpc module 中, 还有一种处理call 请求的方式.

 handle_call({call, Mod, Fun, Args, Gleader}, To, S) ->
handle_call_call(Mod, Fun, Args, Gleader, To, S);
 handle_call_call(Mod, Fun, Args, Gleader, To, S) ->
RpcServer = self(),
%% Spawn not to block the rpc server.
{Caller,_} =
erlang:spawn_monitor(
fun () ->
set_group_leader(Gleader),
Reply =
%% in case some sucker rex'es
%% something that throws
case catch apply(Mod, Fun, Args) of
{'EXIT', _} = Exit ->
{badrpc, Exit};
Result ->
Result
end,
RpcServer ! {self(), {reply, Reply}}
end),
{noreply, gb_trees:insert(Caller, To, S)}.

在handle_call_call 的实现中,rpc 进程spawn_monitor (L5)一个新的进程处理实际的请求(L11), 而后立即重新进入 gen_server 的MAIN loop 中, 继而处理其他的请求.在被spawn_monitor 的新进程处理结束后, 将处理结果发回给rpc 进程(L17).

rpc 进程在handle_info callback 函数中,处理spawn_monitor 进程的放回结果, 并将结果通过gen_server:reply/2 发回给调用进程,完成本地请求的处理.

 handle_info({Caller, {reply, Reply}}, S) ->
case gb_trees:lookup(Caller, S) of
{value, To} ->
receive
{'DOWN', _, process, Caller, _} ->
gen_server:reply(To, Reply),
{noreply, gb_trees:delete(Caller, S)}
end;
none ->
{noreply, S}
end;

net_kernel noblock call

net_kernel module 是Erlang 分布式特性中最为重要的一个模块,net_kernel 进程的优先级为max .

 init({Name, LongOrShortNames, TickT}) ->
process_flag(trap_exit,true),
case init_node(Name, LongOrShortNames) of
{ok, Node, Listeners} ->
process_flag(priority, max),
Ticktime = to_integer(TickT),
Ticker = spawn_link(net_kernel, ticker, [self(), Ticktime]),
{ok, #state{name = Name,
node = Node,
type = LongOrShortNames,
tick = #tick{ticker = Ticker, time = Ticktime},
connecttime = connecttime(),
connections =
ets:new(sys_dist,[named_table,
protected,
{keypos, 2}]),
listen = Listeners,
allowed = [],
verbose = 0
}};
Error ->
{stop, Error}
end.

在进程init 的时候,设置了process 的 priority 为 max (L6).

net_kernel 其中一个作用是处理 remote node spawn process . 见:

 %%
%% The spawn/4 BIF ends up here.
%%
handle_call({spawn,M,F,A,Gleader},{From,Tag},State) when is_pid(From) ->
do_spawn([no_link,{From,Tag},M,F,A,Gleader],[],State); %%
%% The spawn_link/4 BIF ends up here.
%%
handle_call({spawn_link,M,F,A,Gleader},{From,Tag},State) when is_pid(From) ->
do_spawn([link,{From,Tag},M,F,A,Gleader],[],State); %%
%% The spawn_opt/5 BIF ends up here.
%%
handle_call({spawn_opt,M,F,A,O,L,Gleader},{From,Tag},State) when is_pid(From) ->
do_spawn([L,{From,Tag},M,F,A,Gleader],O,State);

而处理 使用的都是noblock call 的方式.

 do_spawn(SpawnFuncArgs, SpawnOpts, State) ->
[_,From|_] = SpawnFuncArgs,
case catch spawn_opt(?MODULE, spawn_func, SpawnFuncArgs, SpawnOpts) of
  {'EXIT', {Reason,_}} ->
async_reply({reply, {'EXIT', {Reason,[]}}, State}, From);
  {'EXIT', Reason} ->
  async_reply({reply, {'EXIT', {Reason,[]}}, State}, From);
  _ ->
   {noreply,State}
end.

当创建新的工作进程(L3)正常时,net_kernel 进程立即{noreply, State} 进入gen_server 的MAIN loop .而 L3处 的spawn_func 的处理如下:

 %% This code is really intricate. The link will go first and then comes
%% the pid, This means that the client need not do a network link.
%% If the link message would not arrive, the runtime system shall
%% generate a nodedown message spawn_func(link,{From,Tag},M,F,A,Gleader) ->
link(From),
gen_server:reply({From,Tag},self()), %% ahhh
group_leader(Gleader,self()),
apply(M,F,A);
spawn_func(_,{From,Tag},M,F,A,Gleader) ->
gen_server:reply({From,Tag},self()), %% ahhh
group_leader(Gleader,self()),
apply(M,F,A).

在L8 或者L12 处, 使用gen_server:reply/2 返回给调用进程, 完成此次请求的处理.

总结

在使用gen_server 进程时,要充分考虑到Erlang 单进程的效率问题, 密切关注进程的message_queue_len , 防止因进程消息队列积压,导致的进程请求超时引发的系统整体性能下降.

当然,解决这类问题的方式还有:

1, pool

2, 借助ets尽可能将处理放在请求进程本身而不是gen_server 单进程(借鉴Ejabberd)

总之,就是要对整体系统的单点密切关注,尽可能消除之.

参考

1, https://github.com/chrismoos/hash-ring/pull/11

2, http://jlouisramblings.blogspot.com/2013/01/how-erlang-does-scheduling.html

3, https://www.erlang-solutions.com/resources/webinars/understanding-erlang-scheduler

4, http://blog.yufeng.info/archives/1438

5, http://erlangdisplay.iteye.com/blog/433843

6, https://github.com/redink/Emysql/commits/feature/add_pool_mgr

Erlang generic standard behaviours -- gen_server noblock call的更多相关文章

  1. Erlang generic standard behaviours -- gen_server system msg

    这是Erlang generic standard behaviors gen_server 分析的系列的最后一篇,主要分析gen_server module 辅助性的功能函数. 在gen_serve ...

  2. Erlang generic standard behaviours -- gen_server module

    在分析完gen module (http://www.cnblogs.com/--00/p/4271386.html)之后,就可以开始进入gen_server 的主体module 了.gen_serv ...

  3. Erlang generic standard behaviours -- gen_server hibernate

    hibernate 主要用于在内存空闲时,通过整理进程的stack,回收进程的heap 来达到回收内存节省资源的效果. hibernate 可用于OTP 进程以及普通进程, hibernate 的官方 ...

  4. Erlang generic standard behaviours -- gen_server terminate

    gen_server 主体 module 已经分析完了(http://www.cnblogs.com/--00/p/4271982.html),接着,分析下gen_server 中的terminate ...

  5. Erlang generic standard behaviours -- gen

    在分析 gen_server (或者是gen_fsm )之前,首先应该弄明白,gen 这个module . -module(gen). -compile({inline,[get_node/1]}). ...

  6. Erlang generic standard behaviours -- summary

    gen_server 相关的片段分析得也差不多了, 这篇作为一个简要的总结.这一系列相关的分析暂且告一段落(之后如有必要,还会回来的 ^^ ),下一个系列主要是以pool 相关, 包括但不仅限于开源项 ...

  7. erlang四大behaviour之一gen_server

      来源:http://www.cnblogs.com/puputu/articles/1701017.html erlang程序设计里面有个设计原则就是把你的进程构造成树,把共用代码提出来,特定功能 ...

  8. erlang四大behaviour之一gen_server(转载)

    erlang程序设计里面有个设计原则就是把你的进程构造成树,把共用代码提出来,特定功能用自己的module实现,这也就是behaviour了,应用behaviour可以减少与本身事务无关的代码量,设计 ...

  9. OTP gen_server

    erlang behaviour小结之gen_server OTP入门 分类: Erlang2012-08-06 18:55 867人阅读 评论(0) 收藏 举报 servererlangcallba ...

随机推荐

  1. spring mvc注解和spring boot注解

    1 spring mvc和spring boot之间的关系 spring boot包含spring mvc.所以,spring mvc的注解在spring boot总都是可以用的吗? spring b ...

  2. 洛谷 P3629 [APIO2010]巡逻

    题目在这里 这是一个紫题,当然很难. 我们往简单的想,不建立新的道路时,从1号节点出发,把整棵树上的每条边遍历至少一次,再回到1号节点,会恰好经过每条边两次,路线总长度为$2(n-1)$,根据树的深度 ...

  3. [luogu4755]Beautiful Pair

    [luogu4755]Beautiful Pair luogu 第一次写最大值分治感觉有点丑 每次找到最大值mid,扫小的一边,主席树查大的一边小于等于\(\frac{a[mid]}{a[i]}\)的 ...

  4. Bootstrap 第一天

    Bootstrap第一天 1.什么是Bootstrap?     Bootstrap是由两位设计开发的.     Bootstrap主要是前端的框架(HTML.CSS.JS). 2.为什么使用Boot ...

  5. PAT 1059. C语言竞赛(20)

    C语言竞赛是浙江大学计算机学院主持的一个欢乐的竞赛.既然竞赛主旨是为了好玩,颁奖规则也就制定得很滑稽: 0. 冠军将赢得一份“神秘大奖”(比如很巨大的一本学生研究论文集……). 1. 排名为素数的学生 ...

  6. mysql 导入数据是报错:2006 - MySQL server has gone away

    导SQL数据库结构+数据时,如果数据是批量插入的话会报错:2006 - MySQL server has gone away. 解决办法:找到你的mysql目录下的my.ini配置文件,加入以下代码 ...

  7. Android:日常学习笔记(10)———使用LitePal操作数据库

    Android:日常学习笔记(10)———使用LitePal操作数据库 引入LitePal 什么是LitePal LitePal是一款开源的Android数据库框架,采用了对象关系映射(ORM)的模式 ...

  8. 每天一个Linux命令(24)tar命令

        tar命令可以为linux的文件和目录创建档案.     (1)用法: 用法:  tar  [选项]   [文件参数]     (2)功能:     功能:  用来压缩和解压文件.tar本身不 ...

  9. [原创]java WEB学习笔记30:Cookie Demo 之显示最近浏览的记录

    本博客为原创:综合 尚硅谷(http://www.atguigu.com)的系统教程(深表感谢)和 网络上的现有资源(博客,文档,图书等),资源的出处我会标明 本博客的目的:①总结自己的学习过程,相当 ...

  10. SS中的三种样式来源:创作人员、读者和用户代理

    CSS中的样式一共有三种来源:创作人员.读者和用户代理,来源的不同会影响到样式的层叠方式,很多第一次学习CSS的朋友,对这三种来源可能会存在一些困惑,下面我写一下自己的理解,若有错误的地方还请指正. ...