EMQX源碼分析--- esockd_rate_limiter 模塊源碼分析

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

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章