问题由来

  前些天对系统做了一个优化,将原来从queue 轮询刷出数据后每条消息一个 spawn 进程单独处理,修改为批量刷出。一条一条刷轮询刷 queue 存在问题:刷queue 进程太多时,占用CPU,刷queue 少时容易受网络延时影响。修改后,queue 中数据越多,batch 数越大,提高刷queue速度。

但问题随之而来:erlang节点,进程数蹭蹭往上涨,CPU 占用很低,直到 outofmemory, 节点remsh 无法连接。

问题排查

  通过kill -SIGUSR1 PID 创建crashdump 发现,我们使用的一个一致性hash gen_server 里面的message_queue_len 30w+,堵塞。

存在问题代码: https://github.com/chrismoos/hash-ring 使用计算一致性hash 节点的gen_server

  猜想原因:

  1. hash_ring 效率低?测试单程 30w/s +,  排除

  2. 进程优先级不高

  erlang 所有进程优先级一样,平等执行,大量进程情况下如果CPU较高,热点进程消息往往得不到处理,恶性顺序,将这些进程优先级process_flag(priority, hight) 这个设置曾经帮忙解决过问题,不过这次无效

3. Port 进程优先级

    实际业务由Port 完成,Port 调度也是平等的,但是没有找到设置Port优先级的方式

   4. 从问题产生代码修改

    与原来相比 就是spawn 进程变得一波一波 速度比较快,产生大量进程。也就是 gen_server:call 数量较多,gen_server  和Port 交互的时候往往receive match 方式,看下代码果然。

  问题原因分析:

      gen_server:call虽然都是同步调用,但是如果有10w 进程都是调用,那么gen_server 的message_queue 将是10w;

    gen_server 内部处理消息:

     Port ! Msg,

receive

Match -> dostring

end

这个时候receive, Match 操作非常费时,因为Match 需要因为不断新的gen_server:call 对10w 的消息一遍又一遍的遍历匹配执行,能快才怪呢。

解决方案

参考文章:http://wqtn22.iteye.com/blog/1572258

我们选用第二种方法

   当然也想过使用gen_server2 处理消息前将message_queue 倒入state 中自己队列,那样消息reply 会更及时一点(Port 的reply会放到队尾,最后处理),不过消耗还是有一些的,handle_call 期间可以有很大消息进来。

  测试 并行 spwan 10w 进程,老版本执行2分钟以上,改造后1s内执行完成。

修改如下:https://github.com/chrismoos/hash-ring/pull/11/files

 
  1. @@ -97,14 +97,16 @@ set_mode(Ring, Mode) when is_integer(Mode) ->
   
  1. %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
   
  1. -record(state, {
   
  1. port,
   
  1. - rings
   
  1. + rings,
   
  1. + queue
   
  1. }).
     
   
  1. init(Drv) ->
   
  1. Port = open_port({spawn, Drv}, [binary]),
   
  1. {ok, #state{
   
  1. port = Port,
   
  1. - rings = dict:new() }}.
   
  1. + rings = dict:new(),
   
  1. + queue = queue:new() }}.
     
     
     
 
  1. @@ -160,21 +162,12 @@ handle_call({remove_node, {Ring, Node}}, _From, #state{ port = Port, rings = Rin
   
  1. {reply, {error, ring_not_found}, State}
   
  1. end;
     
   
  1. -handle_call({find_node, {Ring, Key}}, _From, #state{ port = Port, rings = Rings } = State) ->
   
  1. +handle_call({find_node, {Ring, Key}}, From, #state{ port = Port, rings = Rings, queue = Queue } = State) ->
   
  1. case dict:find(Ring, Rings) of
   
  1. {ok, Index} ->
   
  1. KeySize = size(Key),
   
  1. Port ! {self(), {command, <<5:8, Index:32, KeySize:32, Key/binary>>}},
   
  1. - receive
   
  1. - {Port, {data, <<3:8>>}} ->
   
  1. - {reply, {error, invalid_ring}, State};
   
  1. - {Port, {data, <<2:8>>}} ->
   
  1. - {reply, {error, node_not_found}, State};
   
  1. - {Port, {data, <<1:8>>}} ->
   
  1. - {reply, {error, unknown_error}, State};
   
  1. - {Port, {data, <<Node/binary>>}} ->
   
  1. - {reply, {ok, Node}, State}
   
  1. - end;
   
  1. + {noreply, State#state{queue = queue:in(From, Queue)}};
   
  1. _ ->
   
  1. {reply, {error, ring_not_found}, State}
   
  1. end;
 
  1. @@ -198,6 +191,21 @@ handle_call(stop, _From, State) ->
     
   
  1. handle_cast(_, State) ->
   
  1. {noreply, State}.
   
  1. +handle_info({Port, {data, Data}}, #state{port = Port, queue = Queue} = State) ->
   
  1. + R =
   
  1. + case Data of
   
  1. + <<3:8>> ->
   
  1. + {error, invalid_ring};
   
  1. + <<2:8>> ->
   
  1. + {error, node_not_found};
   
  1. + <<1:8>> ->
   
  1. + {error, unknown_error};
   
  1. + <<Node/binary>> ->
   
  1. + {ok, Node}
   
  1. + end,
   
  1. + {{value, Pid}, QTail} = queue:out(Queue),
   
  1. + safe_reply(Pid, R),
   
  1. + {noreply, State#state{queue = QTail}};
     
   
  1. handle_info({'EXIT', Port, _Reason} = PortExit, #state{ port = Port } = State ) ->
   
  1. {stop, PortExit, State}.
 
  1. @@ -214,6 +222,10 @@ terminate(_, #state{ port = Port }) ->
   
  1. code_change(_OldVsn, State, _Extra) ->
   
  1. {ok, State}.
     
   
  1. +safe_reply(undefined, _Value) ->
   
  1. + ok;
   
  1. +safe_reply(From, Value) ->
   
  1. + gen_server:reply(From, Value).
   
  1. %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
   
  1. %% Tests
   
  1. %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
 

gen_server port 调用receive_match 问题的更多相关文章

  1. erlang 健壮性

    erlang 提供了简单易用的并发编程模型,基本不需要再考虑多线程并发问题.但实际应用中并不是那么的完美,很多地方需要注意,就算标准库也有不少问题.很多在多线程编程中很多很容易解决的事情,在erlan ...

  2. Gen_server行为分析与实践

    1.简介 Gen_server实现了通用服务器client_server原理,几个不同的客户端去分享服务端管理的资源(如图),gen_server提供标准的接口函数和包含追踪功能以及错误报告来实现通用 ...

  3. 如何设置gen_server在退出时执行相关操作

    如果gen_server在监控树中不需要stop函数,gen_server会由其supervisor根据shutdown策略自动终止掉.如果要在进程终止之前执行清理,shutdown策略必须设定一个t ...

  4. Erlang模块gen_server翻译

    gen_server 概要: 通用服务器行为描述: 行为模块实现服务器的客户端-服务器关系.一个通用的服务器进程使用这个模块将实现一组标准的接口功能,包括跟踪和错误报告功能.它也符合OTP进程监控树. ...

  5. Erlang generic standard behaviours -- gen_server terminate

    gen_server 主体 module 已经分析完了(http://www.cnblogs.com/--00/p/4271982.html),接着,分析下gen_server 中的terminate ...

  6. gen_server的enter_loop分析

    http://my.oschina.net/astute/blog/119250?p=1 在看ranch user guide的过程中,发现实现protocol handler需要使用特殊的gen_s ...

  7. Erlang/OTP设计原则(文档翻译)

    http://erlang.org/doc/design_principles/des_princ.html 图和代码皆源自以上链接中Erlang官方文档,翻译时的版本为20.1. 这个设计原则,其实 ...

  8. nginx源码分析之网络初始化

    nginx作为一个高性能的HTTP服务器,网络的处理是其核心,了解网络的初始化有助于加深对nginx网络处理的了解,本文主要通过nginx的源代码来分析其网络初始化. 从配置文件中读取初始化信息 与网 ...

  9. [Erlang 0104] 当Erlang遇到Solr

        Joe Armstrong的访谈中有一段关于"打开黑盒子"的阐述,给我留下很深的印象:Joe Armstrong在做XWindows开发时没有使用对应的类库,而是在了解XW ...

随机推荐

  1. Android 第三方图表类 MPChart 的使用

    先看看条形图的的效果还不错是吧,实现这样的效果很合适呢! 还有折线图.饼图很多效果 效果不错对吧~ 下面我们就先来看看条形图的实现方法吧! 第一步: 引入第三方包 MPChart 如果你碰巧看过我之前 ...

  2. Android自定义九宫格图案解锁

    转自: http://blog.csdn.net/shineflowers/article/details/50408350

  3. 解决win7系统重启后ip丢失问题,即每次电脑重启都要重新设置ip地址,重启后ip地址没了

    自己制作的Ghost盘上网有点问题,每次重启后电脑的ip地址被还原,要重新设置 百度后终于找解决办法,在此记录. 第一步:点击左下角的WIN图标,输入CMD然后回车,打开DOS模式窗口. 第二步:在D ...

  4. C#图像处理笔记

    1.灰度拉伸 灰度拉伸又叫对比度拉伸,它是最基本的一种灰度变换,使用的是最简单的分段线性变换函数,它的主要思想是提高图像处理时灰度级的动态范围.

  5. SOD让你的旧代码焕发青春

    最近接手了一个旧的系统,各种陈旧的问题比较多,其中最棘手的就是操作数据库的部分,具体如下: 1.核心库是一个最后修改时间为2008年的库,先不说有多陈旧,现在这个库只是一个DLL文件,没有源码,也已经 ...

  6. 微软 消息队列 MessageQueue 简单使用

    1.在服务电脑上打开 消息队列 ①进入控制面板>程序>启用或关闭windows功能 ②将需要的勾选(我自己全选了哈哈哈) ③我的电脑 右键 打开管理 见到消息队列 在专用队列上新建专用队列 ...

  7. 爬虫框架--webmagic

    官方有详细的使用文档:http://webmagic.io/docs/zh/ 简介:这只是个java爬虫框架,具体使用需要个人去定制,没有图片验证,不能获取js渲染的网页,但简单易用,可以通过xpat ...

  8. 解决安卓微信浏览器中location.reload 或者 location.href失效的问题

    在移动wap中,经常会使用window.location.href去跳转页面,这个方法在绝大多数浏览器中都不会 存在问题,但早上测试的同学会提出了一个bug:在安卓手机的微信自带浏览器中,这个是失效的 ...

  9. 关于ACM的总结

    看了不少大神的退役帖,今天终于要本弱装一波逼祭奠一下我关于ACM的回忆. 从大二上开始接触到大三下结束,接近两年的时间,对于大神们来说两年的确算不上时间,然而对于本弱来说就是大学的一半时光.大一的懵懂 ...

  10. bat基础

    首先所有命令在cmd命令行中都能找到说明: 例如 想知道type用法 输入type /? 其他命令都一样 type [drive:][path] filename 显示文本文件内容 虽然有点鸡肋 1 ...