在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. PHP自定义函数: 下载远程文件

    function httpcopy($url, $file="", $timeout=60) { $file = empty($file) ? pathinfo($url,PATH ...

  2. 虚拟机 minimal 安装增强包

    在虚拟机下安装了一个centos的minimal镜像,发现增强包不能安装,鼠标不能在虚拟机和物理机间自由切换.不能共享粘贴板,非常是不爽,这里摸索出在centos  minimal OS下安装增强包的 ...

  3. 遇到IIS configuration error错误的可以看看,不一定是权限问题

    最近接手了别人的一个 DOT NET项目,编译.调试一切都OK(心里暗暗高兴),发布吧,结果放到服务器上一运行出现Configuration Error错误,提示:“Access to the pat ...

  4. 找到最大或最小的N个值

    对于python原生的数据类型来说,并不存在直接的方法可以找到最大或最小的N个值, 传统的方法必须先排序,然后再截取相应的值,而且对于集合这类数据类型来说还不能直接排序, 需要先转化为列表才行,有的时 ...

  5. redis于spring整合之RedisTemplate

    原文地址: http://www.jianshu.com/p/7bf5dc61ca06

  6. 事务的四大特性ACID

    ACID是指数据库事务的四大特性,是由Jim Gray在19世纪70年代后期提出的概念,1983年Andreas Reuter and Theo Härder创造了ACID这个缩略语用来描述这四大特性 ...

  7. 【leetcode刷题笔记】Remove Duplicates from Sorted List II

    Given a sorted linked list, delete all nodes that have duplicate numbers, leaving only distinct numb ...

  8. python开发环境必备之vim配置

    俗话说:工欲善其事,必先利其器.最近使用python,习惯了liunx和vim,打算将vim作为python开发工具,下面就配置vim,以让它成为python开发的利器,增强我们的开发体验!废话少说, ...

  9. 《UNIX网络编程》daytimetcpcli测试

    对于刚刚接触网络的人来说,<UNIX网络编程>中第一个例子(daytimetcpcli)可能就测试不通过.也许你试着继续向后读来,自己写一个服务程序来解决这个问题,但是daytime服务也 ...

  10. NetBeans字体设置

    01.找到自己java字体目录.我的目录是[C:\Program Files\Java\jdk1.7.0_21\jre\lib] 02.复制fontconfig.properties.src, 重命名 ...