单客户端异常致Chumak崩溃断连所有客户端的问题咨询
从你提供的错误日志来看,服务器崩溃的直接原因是:
** Reason for termination == **
{{noproc,{gen_server,call,[<0.10180.501>,incomming_queue_out]}},
[{gen_server,call,2,[{file,"gen_server.erl"},{line,206}]},
{chumak_router,recv_message,2,
[{file,"/opt/system/system_server/src/_build/default/lib/chumak/src/chumak_router.erl"},
{line,101}]}...]}
简单说,chumak的Router进程(从日志里的chumak_router可以看出,你提到的REP服务应该是基于Router模式实现的)尝试调用一个已经不存在的客户端队列进程(<0.10180.501>),抛出了noproc异常,但chumak代码没有捕获这个异常,导致整个Router gen_server直接终止,进而引发服务器重启、所有客户端断连。
这个问题完全可避免,本质是chumak库的健壮性不足,未处理客户端进程意外退出的场景。下面是几个可行的解决/缓解方案:
1. 修复chumak库的异常处理(最直接根治)
你需要修改chumak的chumak_router.erl代码,在调用gen_server:call时捕获noproc异常,并清理无效的客户端映射:
找到recv_message函数(对应日志第101行),原代码大概是这样:
recv_message(Identity, _State) -> gen_server:call(Identity, incomming_queue_out).
修改为带异常捕获的版本:
recv_message(Identity, State = #state{lb_state = LbsState}) -> try gen_server:call(Identity, incomming_queue_out) of Message -> {ok, Message, State} catch exit:{noproc, _} -> % 清理该客户端的身份与进程映射 {NewLbs, _RemovedIdentity} = chumak_lbs:remove_peer(LbsState, Identity), NewState = State#state{lb_state = NewLbs}, {error, client_disconnected, NewState} end.
接着修改调用recv_message的queue_ready函数(日志第91行),处理返回的错误情况,阻止异常向上传播:
queue_ready(Identity, _Message, State) -> case recv_message(Identity, State) of {ok, ReceivedMessage, NewState} -> % 保留原有的消息分发逻辑 dispatch_message(ReceivedMessage, NewState); {error, client_disconnected, NewState} -> % 忽略无效客户端的消息,返回更新后的状态 {noreply, NewState} end.
这样当客户端因网络问题进程退出后,Router会自动清理对应映射,不会因调用不存在的进程而崩溃。
2. 添加进程监控(提前预防无效调用)
在chumak的Socket或Router模块中,给每个客户端队列进程添加erlang:monitor(process, Pid)监控。当收到进程退出的{'DOWN'}消息时,立即清理该客户端的映射,从根源上避免noproc异常。
比如在chumak_router的add_peer函数里添加监控:
add_peer(Identity, Pid, State = #state{lb_state = LbsState}) -> % 监控客户端队列进程 erlang:monitor(process, Pid), NewLbs = chumak_lbs:add_peer(LbsState, Identity, Pid), State#state{lb_state = NewLbs}.
然后在handle_info中处理DOWN消息:
handle_info({'DOWN', _MonitorRef, process, Pid, _Reason}, State = #state{lb_state = LbsState}) -> % 根据进程ID清理映射 {NewLbs, _RemovedIdentity} = chumak_lbs:remove_peer_by_pid(LbsState, Pid), {noreply, State#state{lb_state = NewLbs}}; handle_info(OtherMsg, State) -> % 保留原有的其他消息处理逻辑 {noreply, State}.
这个方案能提前清除无效的客户端进程记录,避免后续的无效调用。
3. 服务器进程容错包装(临时应急方案)
如果暂时无法修改chumak库,可以在你的Erlang服务代码中,给chumak的Router进程添加监督进程(Supervisor),设置one_for_one重启策略,并限制重启频率(比如{max_restart, 3}, {max_time, 60})。这样即使Router崩溃,监督进程会快速重启它,减少对客户端的影响。
注意:这只是临时的容错手段,不能解决根本问题,优先推荐修复chumak的异常处理。
4. 客户端侧辅助优化
虽然问题根源在服务器,但客户端可以优化减少触发概率:
- 网络恢复后,先重新建立ZeroMQ连接,再发送请求,不要直接发重复请求;
- 设计幂等请求,确保服务器收到重复请求也不会引发逻辑问题;
- 给客户端发送操作设置超时,避免无意义的重复发送。
总结:这个问题不是chumak的固有缺陷,只是缺少对客户端进程意外退出场景的处理。通过修改chumak添加异常捕获和进程监控,就能彻底解决服务器崩溃的问题,避免单个客户端异常引发的DoS风险。
内容的提问来源于stack exchange,提问作者Ken - Enough about Monica

