Erlang R15最大的卖点Native Process
原创文章,转载请注明: 转载自系统技术非业余研究
本文链接地址: Erlang R15最大的卖点Native Process
R15最激动人心的东西就是这个Native Process,请参看Rickard Green写的Future Extensions to the Native Interface:看 这里
我来blabla下。 做过Erlang规模程序的人都知道有个痛, Erlang的公平调度引起的痛。 举个例子,比如说日志服务。当我们的系统有成千上万的进程需要日志服务的时候,我们通常会把日志的内容发给一个日志进程由它来负责持久化。这是个典型的模块划分方法,我们之前的c程序也都是这么干的。但是在erlang下这样很容易有问题。大家知道Erlang讲究公平,进程调度是公平的,port调度是公平的,bif使用是公平的,甚至ets这样的模块使用也是公平的。那么我们就可以这么理解。如果一个系统里面有N个进程需要服务,那么总进程数目是N+1, 平均来分配时间片。反过来说就是这个日志进程只能分的整个系统的1/(N+1)的cpu计算能力,但是要干N个活。后果就是这个进程忙不过来,导致消息队列不停的加大,消息队列需要堆内存,这个内存需求越来越大,最后系统分配不出内存,最终vm挂掉了。
这个典型的设计问题是几乎每个刚做erlang程序的人都会碰到的。 那我们如何来解决呢?一个解决方案就是加大日志服务进程的数目,来多分点CPU。还有一个方案就是绕过公平调度的原则。 Erlang的每个进程执行的时候是分配一定的时间片,每执行一个函数就消耗一个时间片,当时间片消耗完毕的时候,进程就被抢占调度。那么知道了这个工作原理,我们就可以耍赖来在函数执行的时候不减掉时间片,这样这个进程就可以无限制的执行。这也是我过去绕开这个问题的解决方案,缺点是你需要对vm很熟悉,并且知道如何改。
这时候Native Process来救助了。我们知道目前的版本是支持nif的,也即是说用户可以自己写函数来处理逻辑。但是nif有限制的,它只是被动的被调度器来执行,主控是由调度器来完成的。如果调度器在合适的事件上把相应的钩给我们,那我们就有可能能够影响调度器的运作。
Native Process就是这个思路。
Native Process进程本身是个和原来兼容的进程,不影响系统原理的语义。在hook后自己进入自己的事件循环,来独立处理IO时间或者执行计算,在必要的时候再变成原来的进程。
通过暴露更多的钩给nif, 用户可以自己定义自己的事件处理逻辑来干预调度器的行为。比如说在调度器切出一个进程的时候,允许用户对进程的时间片计数器修改,这样就可以绕过PREEMPTABLE NIF and Driver。由于Erlang是进程间完全隔离的,那我们也可以绕过这个限制,可以直接修改进程的数据,达到在VM里面共享状态的目的。hook使得这些事情都变得可能。
具体如何使用请参考ppt。
我们来总结下,它的适用场景是:
- Optimizing
- Access functionality not available in Erlang
- Operating System services
- Third party libraries
他能够解决下面的问题:
- Native processes make drivers obsolete
- Will be able to handle any protocol
- Possible to minimize the need for native code
- Will have access to the same functionality as ordinary processes
- More efficient
- Distribution transparent
- Share implementations
期待 otp团队给力!
祝玩得开心!
Post Footer automatically generated by wp-posturl plugin for wordpress.
前两天的Erlang mial list中,有人问过日志拥塞的问题,记得当时Ulf Wiger说,日志进程涉及I/O是个主要的原因,只要在启动时把Asyn的个数设置多点就可以解决这个问题。
Yu Feng Reply:
July 22nd, 2011 at 9:13 am
没那么简单
一个解决方案就是加大日志服务进程的数目。
我不打算使用上面这种方法,感觉 没有达到理想的进程并发,你看下面方法可行。
我采用gen_server作为第三方服务放入到系统中,为了解决瓶颈问题
我在handle_call里面采用noreply,且只创建一个进程Pid,然后给此进程Pid发消息,剩下的业务逻辑交给Pid进进程处理.计算完进程自动销毁(receive没有循环)
麻烦老大看看 此方案存在什么问题。
31 handle_call({testnoreply}, From, Tab) ->
32 Pid = spawn(?MODULE, test_reply,[]),
33 Pid ! {From,test},
34 {noreply, Tab};
业务逻辑放在test_reply处理
22 test_reply()->
23 receive
24 {From, X} ->
25 io:format(“X===~p,=From=~p==~n”,[X,From]),
26 gen_server:reply(From,test)
27 end.
Yu Feng Reply:
July 27th, 2011 at 3:39 pm
成本太高
langxianzhe Reply:
July 27th, 2011 at 4:28 pm
成本太高 ?不是很理解。只是spawn创建进程和自动销毁 浪费了很多资源?
Yu Feng Reply:
July 27th, 2011 at 5:19 pm
spawn后面要做多少事情你了解吗?
langxianzhe Reply:
July 27th, 2011 at 5:58 pm
看过spawn 部分代码,对于虚拟机部分不了解。推荐个资料看看吧。谢谢
ammer Reply:
September 6th, 2011 at 10:15 am
从这个思路深入,似乎也是个办法:
注册一个日志处理进程,在其消息循环里判断当前自己消息队列的长度,如果过长,spawn另外一个日志进程,并且将名字转让给它,这样以后的日志请求就发由它来处理。
另外在消息循环超时(after)部分判断自己是否拥有这个名字,如果没有则退出。
还可以纪录日志进程个数并且控制最大数量。
其实也就是个负载均衡。
(sorry,也是发错地方了,放这才合适)
Yu Feng Reply:
September 6th, 2011 at 1:23 pm
spawn的代价相对于这样的消息处理很大,所以这样不合适。
ammer Reply:
September 6th, 2011 at 9:55 pm
我也是初学erlang,并且在做一个实际的项目。
早上逛完这里回头就将项目中日志部分的实现改了,大体效果是,当日志进程累积未处理的消息数超过譬如10w条,或者经垃圾回收后占用内存超过了200M将创建一个新的进程处理新来的日志请求,老的日志进程在处理完累积未处理的消息后退出。新日志处理进程流程是一样的,所以当请求量相当大时可能会同时存在多个日志进程,但只有最后创建的那个会接收新的请求。
效果似乎还可以,没做严格的测试拿不出具体数据,但是cpu占用率有了相当大的提高,内存泄漏也以实用主义的方式解决了。
spawn代价再高也是个绝对的数量吧,架不住相对量的对冲的,呵呵
Xiaotie Reply:
August 17th, 2015 at 12:09 pm
你这个方法有个明显的漏洞啊,注册进程名的话,如果取消然后新注册,中间如果有进程对注册名发送消息,会抛异常,这种情况在大量日志打印的情况下很常见,我们的项目就是这种级别。。。。单进程肯定不行。
老大除了 Native Process 和创建分割多个gen_server之外 还有没有其他方法
Yu Feng Reply:
July 27th, 2011 at 9:11 pm
在另外一个节点做。
piboyeliu Reply:
February 1st, 2012 at 8:08 pm
我也是这样搞的
Yu Feng Reply:
February 1st, 2012 at 11:27 pm
具体如何搞的?
piboyeliu Reply:
February 2nd, 2012 at 11:45 am
我写了个 comet 程序, mochi-web 接入大量连接, 每个连接会有一个进程,这产生了代理的进程,而我的消息分发和其它功能是gen_server的形式,进程数不多; 也有你上面说的公平调度的问题, 所以我打算启两个 node, 一个node 跑 mochi-web, 另一个node跑剩下的模块。
Yu Feng Reply:
February 2nd, 2012 at 11:48 am
恩,是典型的场景~
VM里实际上有4个调度队列,每个队列里面的process才是被公平调度的。在高优先级的队列里只要有process在运行或者需要运行,低优先级的队列里的process就不对被调度。
在优先级由高到低的4个max, high, normal, low队列里,max队列是内部使用的。我们可以将需要高运算能力的,或者说需要更多CPU的日志进程放到high队列里,这样以得到优先调度: process_flag(priority, high).
以解决楼主先生的:”反过来说就是这个日志进程只能分的整个系统的1/(N+1)的cpu计算能力,但是要干N个活。后果就是这个进程忙不过来,导致消息队列不停的加大…”的问题。
提前预祝楼主立秋快乐!
Yu Feng Reply:
August 8th, 2011 at 10:00 am
优先调度,并没有解决公平调动的问题,虽然进程会被优先调度,但是每个进程的reds还是一样的。
catsunny2010 Reply:
August 8th, 2011 at 10:04 am
不同队列间是不论reds的。只要高优先级队列里的需要运行(例如:没有阻塞在receive上), 别的低优先级的进程是不会被调度的。例如,10个normal队列里的普通进程给high队列级的1个进程发消息,后者处理,只要后者没被阻塞,需要CPU, 那么前者是不会有机会继续运行(发消息的).
Yu Feng Reply:
August 8th, 2011 at 10:13 am
好吧,不争了,做个实验验证你自己把…
KarlMa Reply:
August 18th, 2011 at 10:55 am
如果这样的话,日志处理进程每次执行时是不是需要将队列中的所有消息都处理完后再交出CPU呢?
如果不是它还可能会拥塞。
Yu Feng Reply:
August 18th, 2011 at 11:18 am
全部逻辑处理完后才交出CPU。。。
xiaofeng_liao Reply:
January 16th, 2015 at 3:38 pm
今天看到这个,出于好奇做了一下实验,具体是:
启动1个main的进程,设置优先级为high,receive 一个整数,然后做math:pow()运算。
启动1000个branch进程,设置优先级为low,向main不停的发一个整数。
用erlang:process_info查看进程状态,结果是:
当不启动smp时,压力在branch,main的消息队列一直为空。
当启动smp时,压力在main,main消息队列会阻塞大量消息处理不过来。
所以我认为,前面这位的说法应该有道理,优先级确实影响了公平调度,多核的时候,main
虽然能被优先调度,但是只能在一个核上被执行,其它核就会执行branch.
不知道我分析得是否正确,霸爷看到请回复一下。
sunface Reply:
June 25th, 2015 at 1:32 pm
其实在消息量不大的时候是一个解决办法,但是消息量大的时候就没用了,就一个进程忙到死也处理不过来,所以还是用更好的设计架构去解决更合理,单纯这种脚疼医脚的办法并不好
你好,我对erlang不是很了解,
但照你这样说:erlang是不是只适合处理信息量比较少的东西~比如140个字符的短信。
要是碰到信息量比较大,处理会占用时间的东西,是不是大规模并发的能力也和C语言或者java差不多呢?因为这种情况下erlang的处理的流程 方式 是和c java 差不多的方式啊????
(刚才发错地方了 不好意思。。)
@langxianzhe
从这个思路深入,似乎也是个办法:
注册一个日志处理进程,在其消息循环里判断当前自己消息队列的长度,如果过长,spawn另外一个日志进程,并且将名字转让给它,这样以后的日志请求就发由它来处理。
另外在消息循环超时(after)部分判断自己是否拥有这个名字,如果没有则退出。
还可以纪录日志进程个数并且控制最大数量。
其实也就是个负载均衡。
@langxianzhe
这个思路非常好,也确实能解决问题。
看了标题,以为是可以使用OS进程。原来是调度接口,来打破调度器的公平分配原则,非常nice。
R15B03还是没有native process。
为什么不把log功能独立为单个或多个node来进行处理呢?毕竟日志是IO密集的工作,与其他模块不太一样,不好放在同一个node里面。
Yu Feng Reply:
December 27th, 2012 at 11:56 am
首先log在概念上是单点的,其次节点间通讯的开销也很大,在另外一个节点也就是缓解这个问题,不能在本质上解决问题。
我一直没搞明白,为什么erlang的每个进程都要公平调度呢?能举几个相关的例子吗?
Yu Feng Reply:
May 28th, 2013 at 7:46 pm
举个例子给你: 你做个api服务器,会同时有很多用户向你发起请求,每个用户请求的复杂度不同,需要耗费的时间和资源也不同,这些请求通常会多线程同时处理对吧。也即是说总的请求数目/线程数=每个线程要处理的请求数目。这些请求对每个线程来讲是挨个排队执行的,有可能很多简单的请求前面有个复杂的请求,这种情况下,简单的请求必须等到复杂的请求处理完毕后才能被处理。我们假设简单的和复杂的请求耗费的时间是1:100。 那么服务一个复杂的请求,要阻塞100个简单的请求。用户的体验就非常差。而公平调度能够做到每个请求都分配一定的时间片,那么在前面的那种情况下,复杂的请求在指点的时间片内如果不能完成的话,会被切出,服务后续的简单请求,等简单的请求服务完再接着完成复杂的请求。系统的反应就非常灵敏。想下抢占式操作系统和协调操作系统的差别,你就明白了。