`
wqtn22
  • 浏览: 99678 次
  • 性别: Icon_minigender_1
  • 来自: 杭州
社区版块
存档分类
最新评论

异步gen_server进行port访问时性能严重下降的原因和应对方法(四)文件写的应对

 
阅读更多

对于“进程允许异步投递,但进程内部有调用port(receive_match)的模式出现这个问题的解决,有这么几个办法:

1从大道理上来讲,需要开发者预估一个进程的处理能力,不要向进程投递过多的消息以致于处理不完,如果处理不完,则需要重新设计,将消息分布到多个进程中处理;

2将异步接收消息的进程与调用port(receive_match)的模式的进程分开;

3拆分向port投递命令的过程,由进程来接收port回传的结果,而不是由模块接收;

4不使用port编写的模块,利用nif重新实现一套;

5其它。

 

由于本人技术功底尚浅,因此将挑选最常见的两种场景进行分析和解决,这两种场景是文件写和套接字发送。

这里先介绍文件写的场景。

对于1,这确实是一个使用广泛的大道理,能解决一切,却好像又什么都没有解决,开发者需要不断摸索才能做到,我还在摸索中,所以就略过;

对于2,这确实是一个非常通用的解决方案,思路如下:将consumer拆分成两部分,形成两个单独的进程,其中一个进程专用于接收异步消息,另一个进程用于处理消息。接收进程将单条异步消息打包成一个大消息,投递到处理进程处,投递是要检查处理进程的消息队列长度,不能太长,太长会影响处理进程的receive_match性能,也不能太短,太短则会使得处理进程的异步处理流水线不能满载,实测该消息队列长度在8~32范围内较为合适。

优点:实现简单,对file:write没有破坏性;

缺点:需要额外的进程多缓存一次消息,增加了内存和cpu开销,需要消息处理进程处理大包消息,仅为解决问题而存在。

对于3,这种方案需要较高的编程技巧,同时需要对文件访问过程很熟悉,且仅能用于以raw选项打开文件的场景,思路如下:

prim_file:write本身分为两个阶段:port_command和receive_match,因此在写文件时直接调用prim_file:write的前半部分的port_command过程,而不进行下一步的receive_match,并将receive_match放到gen_server的handle_info过程中做。这样又会导致两个新问题:

1 prim_file:write在写过程中产生的错误无法及时处理,由于prim_file:write仅会返回两种错误码ebadf和enospc,前者表示文件打开时没有设置写标识,后者表示文件所处设备没有空间,这两个错误都容易避免;

2 无法继续在同一个进程中调用其它prim_file函数,如read等,因为此时所有从efile port_driver接收的消息都已经紊乱,不能另下一次对read的调用接收到属于它的消息,对于这个问题,一个办法是在调用read等需要消息内容的函数时,进行一次消息同步,即清空消息队列中所有从efile port_driver发来的消息,然后再调用read,在实际实现时,需要使用一个计数器,在每次write的port_command调用时,增加计数器,而每收到一个来自于efile port_driver的write结果消息时,减少计数器,调用其它prim_file函数时,必须接收计数器条write结果歇息时,才能调用其它函数。

优点:工作过程不受receive_match的影响,不需要多份消息拷贝,性能也较好;

缺点:实现复杂,文件访问过程与进程紧耦合,需要对文件写过程较为熟悉,且实现依赖于可能会发生变动的prim_file;

对于4,这几乎是一种完美的解决方法,通过nif解决问题,绕过port体系的影响,riak的bitcask存储引擎有了真正的实现,bitcask_nif.c是它的实现。

优点:不通过port体系,而是通过nif实现文件访问,完全没有receive_match引发的问题;

缺点:不通过port体系,则进程的调度会受到一定程度的影响,原先通过port体系实现,写大量数据的进程会受到惩罚,其reductions也会被bump,但是nif中没有这种自动惩罚机制,需要用户自行实现。

对于5,由于文件数据可以被缓存,因此可以使用如下几种方法:

A打开文件时,设置delayed_write选项,在file:write进入port_driver时,首先将要写的数据缓存到文件port的写缓存中,此时可以令file:write迅速返回,从而加快处理速度,减少进程消息队列堆积量,但该方法终有瓶颈,不能无限制应对消息增加;

B将数据先缓存起来,聚集成大块后,再调用file:write,以减少调用次数,rabbitmq和disk_log模块均使用了这种方法。

对于2、3、4、5A,我都真实的接触过,其中性能比较为2<5A<3<4,向riak的编写者们致敬!

未完待续...

分享到:
评论

相关推荐

Global site tag (gtag.js) - Google Analytics