【问题标题】:gen_server run slower when mailbox get bigger邮箱变大时,gen_server 运行速度变慢
【发布时间】:2017-04-24 18:00:52
【问题描述】:

我有一个问题,我使用 gen server 来做一些简单的工作:

  one handle_cast to do a long time work(takes 60 seconds)
  one handle_cast to do a very fast work

流量低时一切正常。但是当服务器进程在进行第一次长时间的工作时,客户端向服务器发送了数千条消息(例如邮箱中的1000000条消息),长时间工作会变得非常缓慢,可能需要600秒才能完成。

这个问题就像stackoverflow上的这个问题In Erlang, when a process's mailbox growth bigger, it runs slower, why?

但我还是不明白。如果是因为垃圾收集,垃圾收集怎么会花这么长时间或这么频繁?

【问题讨论】:

  • 您能否显示需要很长时间的handle_cast 子句的代码?可能导致这种情况的一件事是,如果存在与特定消息匹配的 receive 表达式。
  • @legoscia 哦,不。这个过程只是在一个循环中将大约 30,0000 个文档插入到 mongodb 中。我的机器每秒可以写5000多个文档,也不是mongodb磁盘IO造成的。
  • @legoscia 这个问题就像遇到stackoverflow.com/questions/36216246/…
  • 这样的问题最好附上SSCCE。否则,其他人大多只能猜测发生了什么。

标签: erlang gen-server


【解决方案1】:

如果你有一个进程有这么大的邮箱,你的系统可能在设计上有错误。

因为所有流量都通过一个进程,这会造成瓶颈。

Erlang 的主要思想之一是,创建新进程既快速又便宜,所有事务都应该有自己的。

只有在这些事务应该对某些共享资源具有序列化访问权限的地方(通常是对某些 ETS 表的更新),才需要为所有事务创建一个进程。这种序列化访问(使用消息)应该尽可能短。

【讨论】:

  • 嗯,系统设计可能有误。但是,问题仍然是为什么邮箱会使进程变慢。
  • 可能是选择性接收造成的。
  • 不,gen server 没有选择性接收,我的代码中也没有任何接收语句
  • 选择性接收 - 例如见ndpar.blogspot.cz/2010/11/…
  • reciece 在 gen_rerver 里面 - 如果你有很长的邮件队列,每个接收检查队列中的每个项目。这么大的 que => 性能下降。更改您的代码以拥有一个以上的工作人员正在处理输入。
【解决方案2】:

不是因为你使用了句柄转换,所以转换中的代码没有阻塞,只有客户端的接口被释放,所以它不等待请求完成。

因此,如果一个请求需要 60 秒才能完成,您必须从服务器生成一个单独的进程来处理它。这是能够继续处理新传入消息的唯一方法。

一个新的潜在问题出现了:在 MongoDB 中是否真的可以在文档插入的同时处理新请求?

如果是,一切都很好,

如果不是,那么您将不得不修改您的设计,例如向请求与数据库插入不兼容的任何客户端返回否定确认(或如果可能,忽略该请求)。您必须尽快清空邮箱,在 60 秒内累积消息不是一个好选择,您正在推动 erlang 远远超出其正常使用范围,想象一下您的用例:

  • 服务器收到长请求,进入数据库更新循环;
  • 在此期间,收到的邮件在邮箱中累积,也就是说它们被复制到服务器的内存区域中;
  • 很快,进程将缺少内存,VM 将不得不暂停进程(因此您的数据库循环)以增加分配给服务器的内存,并最终制作一些数据副本。
  • 这种内存管理会降低服务器的速度,数据库更新需要更长的时间,并且会累积更多的消息,等等。

【讨论】:

  • 感谢您的回答。我将尝试重新设计我的服务器。但我还是觉得这个内存管理太长了,我的进程从 60s 暂停到 600s。
【解决方案3】:

终于在this paper找到了我的答案

在第 3 节中。 erlang 的内存架构是以进程为中心的。每个进程分配和管理自己的内存区域,通常包括 PCB、私有堆栈和私有堆。

这样造成的缺点:高内存碎片

一个进程不能利用另一个进程的内存(例如堆),即使该内存区域中有大量未使用的空间。这通常意味着默认情况下进程只能分配少量内存。这反过来通常会导致对垃圾收集器的大量调用。

邮箱太大会导致整个系统变慢。

但是,这仍然不是重点。遵循@pascal 的精彩评论,处理数据库的长时间成本,我忽略了 gen_sever 是关于发送和接收的。我在gen遇到了gen_sever:call,明显是选择性接收!原谅我稍微了解一下erlang~

【讨论】:

  • 另外,我怀疑更新 MongoDB 的 60 秒请求包含同步调用,这会迫使服务器进程多次解析其邮箱:对进程的同步访问是通过发送消息来完成的,并等待回应。接收语句在调用进程(您的服务器)中执行,每次消息到达时,它都必须解析邮箱。有一种机制可以优化这种解析,但我认为如果来自数据库的答案和其他传入请求交错,则优化效率较低。
  • 这真的是一个 grep 帖子点!我的 mongo 操作都是同步调用(由 poolboy 实现的 db_pool),我遇到了 erlang-opt gen_server github.com/erlang/otp/blob/OTP-18.3/lib/stdlib/src/gen.erl#L146 。仅仅因为选择性接收,我的进程变慢了!再次感谢。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2016-08-09
  • 1970-01-01
  • 1970-01-01
  • 2014-04-20
  • 2012-12-26
相关资源
最近更新 更多