Packages

erlang common library

Current section

Files

Jump to
elib src ttl_monitor_cache.erl
Raw

src/ttl_monitor_cache.erl

-module(ttl_monitor_cache).
%% 带monitor的ttl服务,缓存svr,并且monitor如果发现服务down掉就移除
%% 主要是监控分布式的svr
%% svr 格式:
%% pid | {Name,Node}
-behaviour(gen_server).
%% API
-export([
start_link/0,
start_link/1
]).
-export([
find_svr/1,
find_svr/2,
put_svr/2,
put_svr/3,
put_svr/4
]).
%% gen_server callbacks
-export([
init/1,
handle_call/3,
handle_cast/2,
handle_info/2,
terminate/2,
code_change/3
]).
-define(CALL_TIMEOUT, 5000).
-define(MAX_TIMEOUT, 1000 * 60).
-define(DEF_TTL, 1000 * 60 * 5).
-record(state, {
cache_ets, % { Key, Svr, Ref, Timeout }
ref_ets, % { Ref, Key, Timeout }
timeout_ets % { Timeout, Key, Ref }
}).
%%%===================================================================
%%% API
%%%===================================================================
start_link() ->
start_link(?MODULE).
start_link(Name) when is_atom(Name) ->
gen_server:start_link({local, Name}, ?MODULE, [Name], []).
find_svr(Key) ->
find_svr(?MODULE, Key).
find_svr(Name, Key) ->
ETS = cache_ets_name(Name),
case ets:lookup(ETS, Key) of
[{_, Svr, _, _}] -> {ok, Svr};
[] -> {error, not_found}
end.
put_svr(Key, Svr) ->
put_svr(?MODULE, Key, Svr, ?DEF_TTL).
put_svr(Key, Svr, TTL) ->
put_svr(?MODULE, Key, Svr, TTL).
put_svr(Name, Key, Svr, TTL) ->
gen_server:call(Name, {put, Key, Svr, TTL}, ?CALL_TIMEOUT).
%%%===================================================================
%%% gen_server callbacks
%%%===================================================================
init([Name]) ->
CacheETS = cache_ets_name(Name),
Ref = ref_ets_name(Name),
TimeoutETS = timeout_ets_name(Name),
ets:new(CacheETS, [named_table, protected]),
ets:new(Ref, [named_table, protected]),
ets:new(TimeoutETS, [named_table, ordered_set, protected]),
{ok, #state{cache_ets = CacheETS, ref_ets = Ref, timeout_ets = TimeoutETS}}.
handle_call({put, Key, Svr, TTL}, _From, State) ->
handle_put(Key, Svr, TTL, State);
handle_call(_Request, _From, State) ->
{reply, ok, State}.
handle_cast(_Request, State) ->
{noreply, State}.
handle_info(timeout, State) ->
Now = timenow_microsecond(),
case timeout(Now, State) of
Timeout when is_integer(Timeout) -> {noreply, State, Timeout};
_ -> {noreply, State}
end;
handle_info({'DOWN', MonitorRef, _Type, _Object, _Info}, State) ->
handle_down(MonitorRef, State);
handle_info(_Info, State) ->
{noreply, State}.
terminate(_Reason, _State) ->
ok.
code_change(_OldVsn, State, _Extra) ->
{ok, State}.
%%%===================================================================
%%% Internal functions
%%%===================================================================
cache_ets_name(Name) ->
erlang:list_to_atom(lists:concat([Name, "_cache_ets"])).
ref_ets_name(Name) ->
erlang:list_to_atom(lists:concat([Name, "_ref_ets"])).
timeout_ets_name(Name) ->
erlang:list_to_atom(lists:concat([Name, "_timeout_ets"])).
handle_put(Key, Svr, TTL, #state{cache_ets = CacheETS, ref_ets = RefETS, timeout_ets = TimeoutETS} = State) ->
Now = timenow_microsecond(),
case ets:lookup(CacheETS, Key) of
[{Key, Svr, _Ref, _Timeout}] ->
{reply, pass, State, timeout(Now, State)};
[{Key, _OldSvr, OldRef, OldTimeout}] ->
case monitor_svr(Svr) of
Ref when is_reference(Ref) ->
erlang:demonitor(OldRef),
ets:delete(RefETS, OldRef),
ets:delete(TimeoutETS, OldTimeout),
Timeout = Now + TTL * 1000,
ets:insert(CacheETS, {Key, Svr, Ref, Timeout}),
ets:insert(RefETS, {Ref, Key, Timeout}),
ets:insert(TimeoutETS, {Timeout, Key, Ref}),
{reply, ok, State, timeout(Now, State)};
Err ->
{reply, Err, State, timeout(Now, State)}
end;
[] ->
case monitor_svr(Svr) of
Ref when is_reference(Ref) ->
Timeout = Now + TTL,
ets:insert(CacheETS, {Key, Svr, Ref, Timeout}),
ets:insert(RefETS, {Ref, Key, Timeout}),
ets:insert(TimeoutETS, {Timeout, Key, Ref}),
{reply, ok, State, timeout(Now, State)};
Err ->
{reply, Err, State, timeout(Now, State)}
end
end.
monitor_svr(Pid) when is_pid(Pid) ->
erlang:monitor(process, Pid);
monitor_svr({Svr, Node}) when is_atom(Node), is_atom(Svr) ->
erlang:monitor(process, {Svr, Node});
monitor_svr(_) ->
svr_format_error.
timenow_microsecond() ->
os:perf_counter(microsecond).
timeout(Now, #state{cache_ets = CacheETS, ref_ets = RefETS, timeout_ets = TimeoutETS} = State) ->
case ets:first(TimeoutETS) of
'$end_of_table' ->
infinity;
{Timeout, _Ref} when Timeout > Now ->
erlang:min(Timeout - Now, ?MAX_TIMEOUT);
TimeoutKey ->
case ets:lookup(TimeoutETS, TimeoutKey) of
[{_, Key, Ref}] ->
ets:delete(CacheETS, Key),
erlang:demonitor(Ref),
ets:delete(RefETS, Ref);
[] ->
pass
end,
ets:delete(TimeoutETS, TimeoutKey),
timeout(Now, State)
end.
handle_down(Ref, #state{cache_ets = CacheETS, ref_ets = RefETS, timeout_ets = TimeoutETS} = State) ->
Now = timenow_microsecond(),
case ets:lookup(RefETS, Ref) of
[{Ref, Key, Timeout}] ->
erlang:demonitor(Ref),
ets:delete(CacheETS, Key),
ets:delete(RefETS,Ref),
ets:delete(TimeoutETS, Timeout),
{noreply, State, timeout(Now, State)};
[] ->
{noreply, State, timeout(Now, State)}
end.