EMQXソースコード分析--- esockd_rate_limiterモジュールのソースコード分析

esockd_rate_limiterモジュールはワーカープロセスであり、主にetsesockd_rate_limiterテーブルに基づいてソケットレートを制限するために使用されます。コードは次のとおりです。

-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を参照してください

おすすめ

転載: blog.csdn.net/qq513036862/article/details/87988624