esockd_rate_limiter模块是一个工作者进程,主要是实现基于ets esockd_rate_limiter表来限制socket的速率,其代码如下:
-module(esockd_rate_limiter).
-behaviour(gen_server).
-export([start_link/0]).
-export([create/2, create/3, consume/1, consume/2, delete/1]).
-export([buckets/0]).
%% for test
-export([stop/0]).
%% gen_server callbacks
-export([init/1, handle_call/3, handle_cast/2, handle_info/2, terminate/2, code_change/3]).
-type(bucket() :: term()).
-export_type([bucket/0]).
%%-record(bucket, {name, limit, period, last}).
-define(TAB, ?MODULE).
-define(SERVER, ?MODULE).
-spec(start_link() -> {ok, pid()}).
start_link() ->
%% io:format("escokd esockd_rate_limiter start_link ~n"),
gen_server:start_link({local, ?SERVER}, ?MODULE, [], []).
%% 创建桶方法
-spec(create(bucket(), pos_integer()) -> ok).
create(Bucket, Limit) when is_integer(Limit), Limit > 0 ->
create(Bucket, Limit, 1).
-spec(create(bucket(), pos_integer(), pos_integer()) -> ok).
create(Bucket, Limit, Period) when is_integer(Limit), Limit > 0, is_integer(Period), Period > 0 ->
%% 调用模块的同步方法去创建
gen_server:call(?SERVER, {create, Bucket, Limit, Period}).
%%消费桶
-spec(consume(bucket()) -> {integer(), integer()}).
consume(Bucket) ->
consume(Bucket, 1).
-spec(consume(bucket(), pos_integer()) -> {integer(), integer()}).
consume(Bucket, Tokens) when is_integer(Tokens), Tokens > 0 ->
%% 查询esockd_rate_limiter ets表中是否存在
try ets:update_counter(?TAB, {tokens, Bucket}, {2, -Tokens, 0, 0}) of
0 -> {0, pause_time(Bucket, os:timestamp())};
I -> {I, 0}
catch
error:badarg -> {-1, 1000} %% pause for 1 second
end.
%% @private
pause_time(Bucket, Now) ->
%% 查询esockd_rate_limiter ets表中是否存在 Bucket
case ets:lookup(?TAB, {bucket, Bucket}) of
%% 如果是空的,就返回1000
[] -> 1000; %% The bucket is deleted?
%% 如果存在
[{_Bucket, _Limit, Period, Last}] ->
max(1, Period * 1000 - timer:now_diff(Now, Last) div 1000)
end.
%% 删除桶
-spec(delete(bucket()) -> ok).
delete(Bucket) ->
%% 异步调用cast方法,有模块的handle_cast处理
gen_server:cast(?SERVER, {delete, Bucket}).
%% 获取所有的桶,返回记录数组
-spec(buckets() -> list(map())).
buckets() ->
[#{name => Name, limit => Limit, period => Period, tokens => tokens(Name), last => Last} || {{bucket, Name}, Limit, Period, Last} <- ets:tab2list(?TAB)].
%% 根据Name 获取tokens
tokens(Name) ->
ets:lookup_element(?TAB, {tokens, Name}, 2).
-spec(stop() -> ok).
stop() ->
gen_server:stop(?TAB).
%%------------------------------------------------------------------------------
%% gen_server callbacks
%%------------------------------------------------------------------------------
init([]) ->
%% 建立esockd_rate_limiter 表
_ = ets:new(?TAB, [public, set, named_table, {write_concurrency, true}]),
{ok, #{countdown => #{}, timer => undefined}}.
%% 处理插入数据消息
handle_call({create, Bucket, Limit, Period}, _From, State = #{countdown := Countdown}) ->
true = ets:insert(?TAB, {{tokens, Bucket}, Limit}),
true = ets:insert(?TAB, {{bucket, Bucket}, Limit, Period, os:timestamp()}),
NState = State#{countdown := maps:put({bucket, Bucket}, Period, Countdown)},
{reply, ok, ensure_countdown_timer(NState)};
handle_call(Req, _From, State) ->
error_logger:error_msg("unexpected call: ~p", [Req]),
{reply, ignored, State}.
%% 处理异步删除消息
handle_cast({delete, Bucket}, State = #{countdown := Countdown}) ->
true = ets:delete(?TAB, {bucket, Bucket}),
true = ets:delete(?TAB, {tokens, Bucket}),
NState = State#{countdown := maps:remove({bucket, Bucket}, Countdown)},
{noreply, NState};
handle_cast(Msg, State) ->
error_logger:error_msg("unexpected cast: ~p~n", [Msg]),
{noreply, State}.
%% 处理超时消息
handle_info({timeout, Timer, countdown}, State = #{countdown := Countdown, timer := Timer}) ->
Countdown1 = maps:fold(
fun(Key = {bucket, Bucket}, 1, Map) ->
%% 通key查找,返回一个数组
[{_Key, Limit, Period, _Last}] = ets:lookup(?TAB, Key),
%% 更新 esockd_rate_limiter
true = ets:update_element(?TAB, {tokens, Bucket}, {2, Limit}),
true = ets:update_element(?TAB, {bucket, Bucket}, {4, os:timestamp()}),
maps:put(Key, Period, Map);
(Key, C, Map) when C > 1 ->
maps:put(Key, C-1, Map)
end, #{}, Countdown),
NState = State#{countdown := Countdown1, timer := undefined},
{noreply, ensure_countdown_timer(NState)};
handle_info(Info, State) ->
error_logger:error_msg("unexpected info: ~p~n", [Info]),
{noreply, State}.
terminate(_Reason, _State) ->
ok.
code_change(_OldVsn, State, _Extra) ->
{ok, State}.
ensure_countdown_timer(State = #{timer := undefined}) ->
%% io:format("escokd esockd_rate_limiter ensure_countdown_timer State ~w~n",[State]),
TRef = erlang:start_timer(timer:seconds(1), self(), countdown),
State#{timer := TRef};
ensure_countdown_timer(State = #{timer := _TRef}) ->
State.
该模块暂时只做简单的代码注释,功能后面结合整体再来介绍,接下里介绍 esockd_server.erl 模块。另外限流知识请看https://www.jianshu.com/p/5d4fe4b2a726。