在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. go语言之并发编程 channel(1)

    单向channel: 单向通道可分为发送通道和接收通道.但是无论哪一种单向通道,都不应该出现在变量的声明中,假如初始化了这样一个变量 var uselessChan chan <- int =m ...

  2. ABAP操作EXCEL (号称超级版)

    [转自http://www.cnblogs.com/VerySky/articles/2170014.html] *------------------------------------------ ...

  3. SVG嵌入HTML

    将SVG图像嵌入到HTML文件有多种方法: 使用<iframe>元素来嵌入SVG图像 使用<img>元素来嵌入SVG图像 将SVG图像作为背景图像嵌入 直接使用<svg& ...

  4. c语言操作mysql数据库

    c语言操作Mysql数据库,主要就是为了实现对数据库的增.删.改.查等操作,操作之前,得先连接数据库啊,而连接数据库主要有两种方法.一.使用mysql本身提供的API,在mysql的安装目录中可可以看 ...

  5. LeetCode:位运算实现加法

    LeetCode:位运算实现加法 写在前面 位运算符 实现加法的思路 两个加数,比如5(101)和6(110),如何不用加法就能得出两者之和呢? 我们知道二进制计算中,如果使用异或将会产生无进位的两者 ...

  6. 3.19课·········HTML---标签与表格

    HTML(Hyper Text Markup Language,超文本标记语言) <html> ------开始标签 <head>----网页上的控制信息 <title& ...

  7. 《程序员代码面试指南》第三章 二叉树问题 二叉树按层打印和ZigZag打印

    题目 二叉树按层打印和ZigZag打印 java代码 package com.lizhouwei.chapter3; import java.util.LinkedList; import java. ...

  8. HDU Rightmost Digit

                                                     Rightmost Digit Time Limit:1000MS     Memory Limit: ...

  9. Charles进行HTTPS抓包(iOS为例)

    各种抓包工具的原理都是一样的,使用方面也都是差不多的,因为最近在用Mac,所以抓包工具开始用Charles了,记录一下抓取HTTPS的步骤. 连接代理, 开启抓包工具, 手机设置代理服务器,端口号(默 ...

  10. java深入探究01

    经过前面基础部门的学习,希望大家都把基础打闹再继续深入探究java应用层面的知识,以后的日子我会继续更新java进阶知识,深入探究实际工作中的java应用,说的不好的地方还请见谅,如果能提出你宝贵的建 ...