Erlang tool -- lager overload protection

log 这个事, 说大不大说小又不小. 大点的, 可以用scribe flume 这样的系统去做, 小点的, 也就打印一个调试信息而已. 在Erlang 中, log 这事情确实比较伤, error_logger 是个单点, io:format 容易导致节点崩溃. 在开源社区, lager 算是使用比较广泛的一个, 然而, 同样不能完全避免单点的问题. 因为在lager 中, lager_event 作为 core, 是单个进程存在的.

 1 dispatch_log(Severity, Metadata, Format, Args, Size) when is_atom(Severity)->
 2     SeverityAsInt=lager_util:level_to_num(Severity),
 3     case {whereis(lager_event), lager_config:get(loglevel, {?LOG_NONE, []})} of
 4         {undefined, _} ->
 5             {error, lager_not_running};
 6         {Pid, {Level, Traces}} when (Level band SeverityAsInt) /= 0 orelse Traces /= [] ->
 7             do_log(Severity, Metadata, Format, Args, Size, SeverityAsInt, Level, Traces, Pid);
 8         _ ->
 9             ok
10     end.

也就是在lager 中, 所有的log 都需要经过 lager_event 进程.

但是在lager 中, 有过载保护机制.

Prior to lager 2.0, the gen_event at the core of lager operated purely in synchronous mode. Asynchronous mode is faster, but has no protection against message queue overload. In lager 2.0, the gen_event takes a hybrid approach. it polls its own mailbox size and toggles the messaging between synchronous and asynchronous depending on mailbox size.

{async_threshold, 20},
{async_threshold_window, 5}

This will use async messaging until the mailbox exceeds 20 messages, at which point synchronous messaging will be used, and switch back to asynchronous, when size reduces to 20 - 5 = 15.

If you wish to disable this behaviour, simply set it to undefined. It defaults to a low number to prevent the mailbox growing rapidly beyond the limit and causing problems. In general, lager should process messages as fast as they come in, so getting 20 behind should be relatively exceptional anyway.

If you want to limit the number of messages per second allowed from error_logger, which is a good idea if you want to weather a flood of messages when lots of related processes crash, you can set a limit:

{error_logger_hwm, 50}

It is probably best to keep this number small.

简单点说就是, 相比较同步模式, 异步模式很快, 但是容易导致lager_event 单进程 message_queue 过大. 再且, 若设计使用不当, 导致lager_event 进程 large_heap 使整个VM崩溃也是分分钟的事.

现在, lager 采用的是hybrid 的方式, 也就是当lager_event 的message_queue_len 小于某个值时, 采用异步模式; 若message_queue_len 大于某个值, 就是用同步的方式. 很好理解, 继续瞧瞧内部的怎么实现的:

1, lager_backend_throttle 的启动

lager_backend_throttle 进程在lager_app 模块的start/2 启动, 也就是整个lager application 启动的时候.

 1         {ok, Threshold} when is_integer(Threshold), Threshold >= 0 ->
 2             DefWindow = erlang:trunc(Threshold * 0.2), % maybe 0?
 3             ThresholdWindow =
 4                 case application:get_env(lager, async_threshold_window) of
 5                     undefined ->
 6                         DefWindow;
 7                     {ok, Window} when is_integer(Window), Window < Threshold, Window >= 0 ->
 8                         Window;
 9                     {ok, BadWindow} ->
10                         error_logger:error_msg(
11                           "Invalid value for ‘async_threshold_window‘: ~p~n", [BadWindow]),
12                         throw({error, bad_config})
13                 end,
14             _ = supervisor:start_child(lager_handler_watcher_sup,
15                                        [lager_event, lager_backend_throttle, [Threshold, ThresholdWindow]]),
16             ok;

简单易懂的代码,就不需要过多的解释了.

2, lager_backend_throttle 的作用

lager_backend_throttle 是 lager_event event server 中的一个handler , 会处理 message-tag 为log 的所有event, 但仅仅是全量接受而已, 不做实际的log 处理.

 1 handle_event({log, _Message},State) ->
 2     {message_queue_len, Len} = erlang:process_info(self(), message_queue_len),
 3     case {Len > State#state.hwm, Len < State#state.window_min, State#state.async} of
 4         {true, _, true} ->
 5             %% need to flip to sync mode
 6             lager_config:set(async, false),
 7             {ok, State#state{async=false}};
 8         {_, true, false} ->
 9             %% need to flip to async mode
10             lager_config:set(async, true),
11             {ok, State#state{async=true}};
12         _ ->
13             %% nothing needs to change
14             {ok, State}
15     end;

在接收到 message-tag 为 log 的 event 后, 通过process_info/2 函数获取当前的message_queue_len, 然后切换 async 模式.

3, async 模式的影响

async sync 模式会在message_queue_len 增大/降低过程中切换, 最终会对log 的notify 方式产生不同的影响.

 1 do_log(Severity, Metadata, Format, Args, Size, SeverityAsInt, LevelThreshold, TraceFilters, Pid) when is_atom(Severity) ->
 2     Destinations = case TraceFilters of
 3         [] ->
 4             [];
 5         _ ->
 6             lager_util:check_traces(Metadata,SeverityAsInt,TraceFilters,[])
 7     end,
 8     case (LevelThreshold band SeverityAsInt) /= 0 orelse Destinations /= [] of
 9         true ->
10             Msg = case Args of
11                 A when is_list(A) ->
12                     safe_format_chop(Format,Args,Size);
13                 _ ->
14                     Format
15             end,
16             LagerMsg = lager_msg:new(Msg,
17                 Severity, Metadata, Destinations),
18             case lager_config:get(async, false) of
19                 true ->
20                     gen_event:notify(Pid, {log, LagerMsg});
21                 false ->
22                     gen_event:sync_notify(Pid, {log, LagerMsg})
23             end;
24         false ->
25             ok
26     end.

在async 为 true 的情况下, 使用 gen_event:notify(异步方式) 将log 交给lager_event 进程, async 为false 的情况下, 则使用gen_event:sync_notify(同步方式)将 log 交给 lager_event 进程.

notify/sync_notify

1, notify

notify 是用户进程将Event 组装成 message-tag 为 notify 的消息, 通过erlang:send/2 方式发给 event 进程(在lager 中也就是lager_event), 然后在event 进程中依次执行 handlers 中的handle_event callback 函数.

 1 server_update(Handler1, Func, Event, SName) ->
 2     Mod1 = Handler1#handler.module,
 3     State = Handler1#handler.state,
 4     case catch Mod1:Func(Event, State) of
 5         {ok, State1} ->
 6             {ok, Handler1#handler{state = State1}};
 7         {ok, State1, hibernate} ->
 8             {hibernate, Handler1#handler{state = State1}};
 9         {swap_handler, Args1, State1, Handler2, Args2} ->
10             do_swap(Mod1, Handler1, Args1, State1, Handler2, Args2, SName);
11         remove_handler ->
12             do_terminate(Mod1, Handler1, remove_handler, State,
13                  remove, SName, normal),
14             no;
15         Other ->
16             do_terminate(Mod1, Handler1, {error, Other}, State,
17                  Event, SName, crash),
18             no
19     end.

2, sync_notify

sync_notify 是用户进程通过gen:call/4 函数调用event 进程, 消息的message-tag 为sync_notify. event 进程同样是依次执行handlers 中的handle_event callback 函数, 不同的是, 还会向用户进程reply 消息(ok).

highwatermark

在lager 中, error_logger_lager_h 作为 error_logger 的一个 handler, 主要用来限制每秒 from error_logger 的 message 数量.

同样是在lager application 启动的时候, 在start/2 中调用启动.

在每次接收到event 时, 就会执行check_hwm/1 函数:

 1 check_hwm(State = #state{hwm = Hwm, lasttime = Last, dropped = Drop}) ->
 2     %% are we still in the same second?
 3     {M, S, _} = Now = os:timestamp(),
 4     case Last of
 5         {M, S, _} ->
 6             %% still in same second, but have exceeded the high water mark
 7             NewDrops = discard_messages(Now, 0),
 8             {false, State#state{dropped=Drop+NewDrops}};
 9         _ ->
10             %% different second, reset all counters and allow it
11             case Drop > 0 of
12                 true ->
13                     ?LOGFMT(warning, self(), "lager_error_logger_h dropped ~p messages in the last second that exceeded the limit of ~p messages/sec",
14                         [Drop, Hwm]);
15                 false ->
16                     ok
17             end,
18             {true, State#state{dropped = 0, mps=1, lasttime = Now}}
19     end.

总结:

1, sync 模式速度慢, async 速度快, 但是不利于稳定系统的负载;

2, log 这事说大不大, 说小真不小; 本地的log 也就是用来调试和debug 来用, 正经的还是应该使用外部的日志系统;

3, 认真了解application 的每一个参数, 对于参数的设定才更有把握.

时间: 2024-10-10 07:49:55

Erlang tool -- lager overload protection的相关文章

Erlang tool -- recon

遇见recon 以来, 每次定位系统瓶颈, 总是能让我眼前一亮. 比如说, 定位非尾递归导致的内存暴涨, 定位引发CPU满载的进程.得心应手,每每额手称庆. recon 是ferd 大神 释出的一个 用于生产环境诊断Erlang 问题的一个工具, 不仅仅是对Erlang stdlib 接口的封装, 还有memory fragmentation 相关的函数. CPU 统计相关 在ferd 大神放出的 Erlang_In_Anger 中提到了 The reduction count has a di

Erlang库 -- 有意思的库汇总

首先,库存在的目的大致可分为:1.提供便利2.尽可能解决一些痛点 首先,我们先明确一下Erlang编程语言的一些痛点(伪痛点):1,单进程问题Erlang虚拟机属于抢占式调度,抢占式调度有很多好处,但是同样也存在这弊端.虚拟机在默认情况下分配个每个进程的资源都是相同的,但是若一个进程(gen_server/event/fsm)要为其他许多进程提供服务,这个进程就极有可能成为整个Erlang系统的瓶颈所在.http://www.cnblogs.com/--00/p/4277640.html2,列表

关于Weblogic Server(介绍)

Weblogic, 美国Oracle公司名下产品,是一个基于 J2EE 架构.可扩展的应用服务器. 本文档选取部分官方文档翻译 总览 支持多种类型的分布式应用 基于 SOA 应用的理想架构 完整实现 J2EE 6.0 标准, 提供标准 API ,访问多种服务,如数据库.消息服务 支持 Spring 框架 确保应用环境可靠.安全.高可用.可扩展 支持服务器集群.避免错误的影响 诊断工具,辅助系统管理员,监视和调整性能 安全性,保护服务访问,确保数据安全,防止恶意攻击 编程模型 支持以下相关编程:

RHM-M60型挖掘机力矩限制器/载荷指示器

RHM-M60挖掘机力矩限制器RHM-M60 excavator crane moment limiter     RHM-M60型挖掘机力矩限制器是臂架型起重机机械的安全保护装置,本产品采用32位高性能微处理器为硬件平台 ,软件算法采用国内最先进的液压取力算法,该算法吸收多年的现场经验,不断改进完善而成.本产品符合<GB12602-2009起重机械超载保护装置安全技术规范>. RHM-M60 excavator crane moment limiter is a safety protect

《CS:APP》 chapter 9 Vitrual Memory 笔记

Vitrual Memory In order to manage memory more efficiently and with fewer errors, modern systems provide an abstraction of main memory known as virtual memory (VM). Virtual memory is an elegant interaction of hardware exceptions, hardware ad-dress tra

{ICIP2014}{收录论文列表}

This article come from HEREARS-L1: Learning Tuesday 10:30–12:30; Oral Session; Room: Leonard de Vinci 10:30  ARS-L1.1—GROUP STRUCTURED DIRTY DICTIONARY LEARNING FOR CLASSIFICATION Yuanming Suo, Minh Dao, Trac Tran, Johns Hopkins University, USA; Hojj

Using Sessions and Session Persistence---reference

Using Sessions and Session Persistence The following sections describe how to set up and use sessions and session persistence: Overview of HTTP Sessions Setting Up Session Management Configuring Session Persistence Using URL Rewriting Instead of Cook

RHM-M60型挖掘机载荷限制器/力矩限制器

RHM-M60挖掘机力矩限制器RHM-M60 excavator crane moment limiter     RHM-M60型挖掘机力矩限制器是臂架型起重机机械的安全保护装置,本产品采用32位高性能微处理器为硬件平台 ,软件算法采用国内最先进的液压取力算法,该算法吸收多年的现场经验,不断改进完善而成.本产品符合<GB12602-2009起重机械超载保护装置安全技术规范>. RHM-M60 excavator crane moment limiter is a safety protect

记-OSPF学习

LSA Type 1:Router LSA1.传播范围 :只能在本区域2.通告者 :每台路由器 (router-id作为标识)3.内容 :路由和拓扑信息show ip ospf database routeLSA Type 2:Network LSA (出现在MA网络)1.传播范围 :只能在本区域2.通告者 :DR通告3.内容 :拓扑 掩码show ip ospf database networkLSA Type 3:Summary LSA1.传播范围 :这个自治系统2.通告者 :ABR3.内容