Erlang generic standard behaviours -- gen_server hibernate

hibernate 主要用於在內存空閒時,經過整理進程的stack,回收進程的heap 來達到回收內存節省資源的效果.html

hibernate 可用於OTP 進程以及普通進程, hibernate 的官方文檔 erlang:hibernate/3web

Puts the calling process into a wait state where its memory allocation has been reduced as much as possible, which is useful if the process does not expect to receive any messages in the near future.數據結構

The process will be awaken when a message is sent to it, and control will resume in Module:Function with the arguments given by Argswith the call stack emptied, meaning that the process will terminate when that function returns. Thus erlang:hibernate/3 will never return to its caller.app

If the process has any message in its message queue, the process will be awaken immediately in the same way as described above.框架

In more technical terms, what erlang:hibernate/3 does is the following. It discards the call stack for the process. Then it garbage collects the process. After the garbage collection, all live data is in one continuous heap. The heap is then shrunken to the exact same size as the live data which it holds (even if that size is less than the minimum heap size for the process).less

If the size of the live data in the process is less than the minimum heap size, the first garbage collection occurring after the process has been awaken will ensure that the heap size is changed to a size not smaller than the minimum heap size.socket

Note that emptying the call stack means that any surrounding catch is removed and has to be re-inserted after hibernation. One effect of this is that processes started using proc_lib (also indirectly, such as gen_server processes), should use proc_lib:hibernate/3 instead to ensure that the exception handler continues to work when the process wakes up.ide

1, 主要用於某進程可能會在將來短時間內空閒時函數

2, hibernate 會清空進程的stack 而後進程GCoop

從Erlang 進程PCB(process control block) 的角度來看, heap 主要用來存儲複雜數據結構(如:tuples, lists, big integers), 而stack 主要存儲簡單的數據結構和指向在heap 中的複雜數據結構的引用. hibernate 清空進程stack 以後再進行進程GC ,會盡量的保證heap 中的資源回收. 相比普通的GC 更完全.

gen_server hibernate

gen_server module 對hibernate 調用的支持主要體如今 callback 返回參數(如:{reply, R, S, hibernate}) 以及enter_loop 函數.

1 loop(Parent, Name, State, Mod, hibernate, Debug) ->
2     proc_lib:hibernate(?MODULE,wake_hib,[Parent, Name, State, Mod, Debug]);
3 
4 wake_hib(Parent, Name, State, Mod, Debug) ->
5     Msg = receive
6               Input ->
7                   Input
8           end,
9     decode_msg(Msg, Parent, Name, State, Mod, hibernate, Debug, true).

若是loop/6 函數的Timeout 參數值爲 'hibernate', gen_server process 就會調用proc_lib:hibernate/3 函數使進程進入hibernate 狀態. 而當有消息被髮送至本進程時, 進程就會被resume並調用wake_hib/5 回到正常的decode_msg 流程.

而enter_loop 函數的做用是 make 一個已經存在的process 變成gen_server 進程:

The calling process will enter the gen_server receive loop and become a gen_server process.

各界對hibernate 的使用

進程hibernate 會清空進程的stack 並進行GC ,而且進程被resume 時,須要爲進程從新分配heap 資源. hibernate 可以爲虛擬機節省更多的內存資源, 可是也會加劇CPU的負擔. 那社區裏對這一特性是怎麼使用的?

Mochiweb

在大名鼎鼎的 A Million-user Comet Application with Mochiweb 中, 做者提到了使用hibernate 做爲優化手段之一.

This sounds reasonable - let's try hibernating after every message and see what happens.

而且起到了立竿見影的效果:

Judicious use of hibernate means the mochiweb application memory levels out at 78MB Resident with 10k connections, much better than the 450MB we saw in Part 1. There was no significant increase in CPU usage.

是的,Mochiweb 的使用方式, 是在接收到message 以後當即使用hibernate.

Ejabberd

Ejabberd 對hibernate 的使用比較謹慎, 只有在進程未收到任何信息一段時間後, 才使用hibernate .

ejabberd_receiver module 是 Ejabberd 框架中的socket message 接收module, ejabberd_receiver 是gen_server 進程,銜接socket 和C2S gen_fsm 進程. ejabberd_receiver 設置了一個超時時間, 超時時間被觸發(也就是進程收到timeout)後, ejabberd_receiver 進程會調用proc_lib:hibernate/3 .而當有新的消息sent 到該進程以後, hibernate 會使用 gen_server:enter_loop/3 函數, 喚醒ejabberd_receiver 進程並從新進入gen_server process .

能夠看出 Ejabberd 對 hibernate 的使用,比較謹慎,只有當進程在一段時間內未收到消息(也就是一段時間內空閒),纔會使用hibernate .

1 handle_info(timeout, State) ->
2     proc_lib:hibernate(gen_server, enter_loop,
3                [?MODULE, [], State]),

RabbitMQ

RabbitMQ 一樣使用了hibernate, 是在gen_server2 代碼中,一樣是使用了Ejabberd 相似的方式(進程一段時間空閒後, 才使用hibernate).

使用了 gen_server2 behavior module 的init 函數:

1 init(Q) ->
2     process_flag(trap_exit, true),
3     ?store_proc_name(Q#amqqueue.name),
4     {ok, init_state(Q#amqqueue{pid = self()}), hibernate,
5      {backoff, ?HIBERNATE_AFTER_MIN, ?HIBERNATE_AFTER_MIN, ?DESIRED_HIBERNATE},
6     ?MODULE}.

L5 定義了hibernate 相關的參數.

而在gen_server2 module 中, 回調user module init 的方法:

1         {ok, State, Timeout, Backoff = {backoff, _, _, _}, Mod1} ->
2             Backoff1 = extend_backoff(Backoff),
3             proc_lib:init_ack(Starter, {ok, self()}),
4             loop(GS2State #gs2_state { mod           = Mod1,
5                                        state         = State,
6                                        time          = Timeout,
7                                        timeout_state = Backoff1 });

會將backoff hibernate 相關參數與gs2_state 一同做爲 MAIN loop 的參數.緊接着,gen_server2 loop/1 函數進入 process_next_msg/1 :

 1 process_next_msg(GS2State = #gs2_state { time          = Time,
 2                                          timeout_state = TimeoutState,
 3                                          queue         = Queue }) ->
 4     case priority_queue:out(Queue) of
 5         {{value, Msg}, Queue1} ->
 6             process_msg(Msg, GS2State #gs2_state { queue = Queue1 });
 7         {empty, Queue1} ->
 8             {Time1, HibOnTimeout}
 9                 = case {Time, TimeoutState} of
10                       {hibernate, {backoff, Current, _Min, _Desired, _RSt}} ->
11                           {Current, true};
12                       {hibernate, _} ->
13                           %% wake_hib/7 will set Time to hibernate. If
14                           %% we were woken and didn't receive a msg
15                           %% then we will get here and need a sensible
16                           %% value for Time1, otherwise we crash.
17                           %% R13B1 always waits infinitely when waking
18                           %% from hibernation, so that's what we do
19                           %% here too.
20                           {infinity, false};
21                       _ -> {Time, false}
22                   end,
23             receive
24                 Input ->
25                     %% Time could be 'hibernate' here, so *don't* call loop
26                     process_next_msg(
27                       drain(in(Input, GS2State #gs2_state { queue = Queue1 })))
28             after Time1 ->
29                     case HibOnTimeout of
30                         true ->
31                             pre_hibernate(
32                               GS2State #gs2_state { queue = Queue1 });
33                         false ->
34                             process_msg(timeout,
35                                         GS2State #gs2_state { queue = Queue1 })
36                     end
37             end
38     end.

在priority_queue 隊列爲空(L7)的狀況下, 等待message(L23) 並設置 ?HIBERNATE_AFTER_MIN (L11, L28)超時, 超時觸發以後, 首先回調 user module 的handle_pre_hibernate callback 方法(gen_server2 特有), 最後調用hibernate .

能夠看出RabbitMQ 使用hibernate 的方式更爲謹慎.

參考文獻:

1, http://blog.yufeng.info/archives/1615

2, http://www.erlang.org/doc/man/erlang.html#hibernate-3

3, http://www.metabrew.com/article/a-million-user-comet-application-with-mochiweb-part-2

4, Characterizing the Scalability of Erlang VM

相關文章
相關標籤/搜索