使用hibernate的话,有助于及时减少不活跃进程的内存占用

The gen_server process can go into hibernation (see erlang(3)) if a callback function specifies ‘hibernate’ instead of a timeout value. This might be useful if the server is expected to be idle for a long time. However this feature should be used with care as hibernation implies at least two garbage collections (when hibernating and shortly after waking up) and is not something you’d want to do between each call to a busy server.

玩家登录的时候,我们将玩家数据从MySQL加载到gen_server的State里面去,然后基于这个State做各种逻辑,如果玩家离线的话,这个进程还是保留一段时间,并没有马上被清理掉。这样做的目的,就是想让玩家连回来的时候,有比较快的响应速度,同时减少数据库读的压力。

但这样,会造成很多空闲的进程,常驻内存了,如果量多了,多多少少有点浪费。所以,我们可以考虑进程空闲的时候,将其挂起,从而节约内存。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
handle_info(cmd_auto_save, State) ->
DbSaveSpan = app_misc:mochi_get(db_save_span),
case already_save(State) of
true ->
case get(last_cmd_timestamp) of
undefined ->
erlang:send_after(DbSaveSpan, self(), cmd_auto_save),
{noreply, State};
LastCmdTimestmp ->
Now = time_misc:unixtime(),
User = ?USER(State),
Seconds = if
User#user.lv =< 3 ->
%% 新手玩家
?ONE_HOUR_SECONDS;
true ->
24*?ONE_HOUR_SECONDS
end,
if
LastCmdTimestmp < Now - Seconds ->
if
State?READER_PID =:= undefined ->
{stop, normal, State};
true ->
erlang:send_after(DbSaveSpan, self(), cmd_auto_save),
{noreply, State}
end;
true ->
if
State?READER_PID =:= undefined ->
%% ?DEBUG("memory ~p, it will hibernate~n", [erlang:process_info(self(), [memory])]),
erlang:send_after(DbSaveSpan*3, self(), cmd_auto_save),
{noreply, State, hibernate};
true ->
erlang:send_after(DbSaveSpan, self(), cmd_auto_save),
{noreply, State}
end
end
end;
false ->
?DEBUG("cmd_auto_save"),
NewState = nonblocking_save_to_db(State),
erlang:send_after(DbSaveSpan, self(), cmd_auto_save),
{noreply, NewState}
end;

经过测试,一个活跃的进程到挂起,可以有10倍的差距。当然这个倍数关系,不同项目还是不一样,只能说,多多少少可以gc掉一些不用的内存。我们线上环境,71934个进程,其中玩家进程是69967个,活跃的有1757个(夜间了,没多少人玩了),进程占用内存是1402 MB,平均下来才20k,真的很节约。