feat(limiter): change zone to group and simplify config

This commit is contained in:
lafirest 2022-03-09 18:57:02 +08:00 committed by firest
parent 376d8d263f
commit e7dec7835f
6 changed files with 231 additions and 153 deletions

View File

@ -5,51 +5,49 @@
limiter {
## rate limiter for message publish
bytes_in {
bucket.default {
aggregated.rate = infinity
aggregated.capacity = infinity
per_client.rate = infinity
per_client.capacity = infinity
group.default {
bucket.default {
rate = infinity
capacity = infinity
}
}
}
## rate limiter for message publish
message_in {
bucket.default {
aggregated.rate = infinity
aggregated.capacity = infinity
per_client.rate = infinity
per_client.capacity = infinity
group.default {
bucket.default {
rate = infinity
capacity = infinity
}
}
}
## connection rate limiter
connection {
bucket.default {
aggregated.rate = infinity
aggregated.capacity = infinity
per_client.rate = infinity
per_client.capacity = infinity
group.default {
bucket.default {
rate = infinity
capacity = infinity
}
}
}
## rate limiter for message deliver
message_routing {
bucket.default {
aggregated.rate = infinity
aggregated.capacity = infinity
per_client.rate = infinity
per_client.capacity = infinity
group.default {
bucket.default {
rate = infinity
capacity = infinity
}
}
}
## Some functions that don't need to use global and zone scope, them can shared use this type
shared {
bucket.retainer {
aggregated.rate = infinity
aggregated.capacity = infinity
per_client.rate = infinity
per_client.capacity = infinity
rate = infinity
capacity = infinity
}
}
}

View File

@ -23,8 +23,8 @@
%% API
-export([ start_link/0, start_server/1, find_bucket/1
, find_bucket/3, insert_bucket/2, insert_bucket/4
, make_path/3, restart_server/1]).
, find_bucket/2, insert_bucket/2, insert_bucket/3
, make_path/2, make_path/3, restart_server/1]).
%% gen_server callbacks
-export([init/1, handle_call/3, handle_cast/2, handle_info/2,
@ -36,6 +36,7 @@
-type limiter_type() :: emqx_limiter_schema:limiter_type().
-type zone_name() :: emqx_limiter_schema:zone_name().
-type bucket_name() :: emqx_limiter_schema:bucket_name().
-type bucket_path() :: emqx_limiter_schema:bucket_path().
%% counter record in ets table
-record(bucket, { path :: path()
@ -57,10 +58,10 @@ start_server(Type) ->
restart_server(Type) ->
emqx_limiter_server_sup:restart(Type).
-spec find_bucket(limiter_type(), zone_name(), bucket_name()) ->
-spec find_bucket(limiter_type(), bucket_path()) ->
{ok, bucket_ref()} | undefined.
find_bucket(Type, Zone, BucketId) ->
find_bucket(make_path(Type, Zone, BucketId)).
find_bucket(Type, BucketPath) ->
find_bucket(make_path(Type, BucketPath)).
-spec find_bucket(path()) -> {ok, bucket_ref()} | undefined.
find_bucket(Path) ->
@ -72,21 +73,26 @@ find_bucket(Path) ->
end.
-spec insert_bucket(limiter_type(),
zone_name(),
bucket_name(),
bucket_path(),
bucket_ref()) -> boolean().
insert_bucket(Type, Zone, BucketId, Bucket) ->
inner_insert_bucket(make_path(Type, Zone, BucketId),
Bucket).
insert_bucket(Type, BucketPath, Bucket) ->
inner_insert_bucket(make_path(Type, BucketPath), Bucket).
-spec insert_bucket(path(), bucket_ref()) -> true.
insert_bucket(Path, Bucket) ->
inner_insert_bucket(Path, Bucket).
-spec make_path(limiter_type(), bucket_path()) -> path().
make_path(Type, BucketPath) ->
[Type | BucketPath].
-spec make_path(limiter_type(), zone_name(), bucket_name()) -> path().
make_path(Type, Name, BucketId) ->
[Type, Name, BucketId].
make_path(shared, _GroupName, BucketName) ->
[shared, BucketName];
make_path(Type, GroupName, BucketName) ->
[Type, GroupName, BucketName].
%%--------------------------------------------------------------------
%% @doc

View File

@ -20,7 +20,7 @@
-export([ roots/0, fields/1, to_rate/1, to_capacity/1
, minimum_period/0, to_burst_rate/1, to_initial/1
, namespace/0]).
, namespace/0, to_bucket_path/1, default_group_name/0]).
-define(KILOBYTE, 1024).
@ -36,6 +36,7 @@
-type burst_rate() :: 0 | float().
-type capacity() :: infinity | number(). %% the capacity of the token bucket
-type initial() :: non_neg_integer(). %% initial capacity of the token bucket
-type bucket_path() :: list(atom()).
%% the processing strategy after the failure of the token request
-type failure_strategy() :: force %% Forced to pass
@ -46,17 +47,20 @@
-typerefl_from_string({burst_rate/0, ?MODULE, to_burst_rate}).
-typerefl_from_string({capacity/0, ?MODULE, to_capacity}).
-typerefl_from_string({initial/0, ?MODULE, to_initial}).
-typerefl_from_string({bucket_path/0, ?MODULE, to_bucket_path}).
-reflect_type([ rate/0
, burst_rate/0
, capacity/0
, initial/0
, failure_strategy/0
, bucket_path/0
]).
-export_type([limiter_type/0, bucket_name/0, zone_name/0]).
-import(emqx_schema, [sc/2, map/2]).
-define(UNIT_TIME_IN_MS, 1000).
namespace() -> limiter.
@ -67,43 +71,43 @@ fields(limiter) ->
, {message_in, sc(ref(limiter_opts), #{})}
, {connection, sc(ref(limiter_opts), #{})}
, {message_routing, sc(ref(limiter_opts), #{})}
, {shared, sc(ref(shared_limiter_opts),
, {shared, sc(ref(shared_opts),
#{description =>
<<"Some functions that do not need to use global and zone scope,"
"them can shared use this type">>})}
];
fields(limiter_opts) ->
[ {global, sc(ref(rate_burst), #{required => false})}
, {zone, sc(map("zone name", ref(rate_burst)), #{required => false})}
, {bucket, sc(map("bucket_id", ref(bucket)),
#{desc => "Token bucket"})}
];
fields(rate_burst) ++ %% the node global limit
[ {group, sc(map("group name", ref(group_opts)), #{})}
];
fields(shared_limiter_opts) ->
[{bucket, sc(map("bucket_id", ref(bucket)),
#{desc => "Token bucket"})}
];
fields(group_opts) ->
fields(rate_burst) ++ %% the group limite
[ {bucket, sc(map("bucket name", ref(bucket_opts)), #{})}
];
fields(rate_burst) ->
[ {rate, sc(rate(), #{})}
[ {rate, sc(rate(), #{default => "infinity"})}
, {burst, sc(burst_rate(), #{default => "0/0s"})}
];
fields(bucket) ->
[ {zone, sc(atom(), #{desc => "The bucket's zone", default => default})}
, {aggregated, sc(ref(bucket_aggregated), #{})}
, {per_client, sc(ref(client_bucket), #{})}
];
fields(shared_opts) ->
[{bucket, sc(map("bucket name", ref(bucket_opts)), #{})}];
fields(bucket_aggregated) ->
fields(bucket_opts) ->
[ {rate, sc(rate(), #{})}
, {initial, sc(initial(), #{default => "0"})}
, {capacity, sc(capacity(), #{})}
, {per_client, sc(ref(client_bucket),
#{default => #{},
desc => "The rate limit for each user of the bucket,"
"this field is not required"
})}
];
fields(client_bucket) ->
[ {rate, sc(rate(), #{})}
[ {rate, sc(rate(), #{default => "infinity"})}
, {initial, sc(initial(), #{default => "0"})}
%% low_water_mark add for emqx_channel and emqx_session
%% both modules consume first and then check
@ -113,13 +117,14 @@ fields(client_bucket) ->
#{desc => "If the remaining tokens are lower than this value,
the check/consume will succeed, but it will be forced to wait for a short period of time.",
default => "0"})}
, {capacity, sc(capacity(), #{desc => "The capacity of the token bucket."})}
, {capacity, sc(capacity(), #{desc => "The capacity of the token bucket.",
default => "infinity"})}
, {divisible, sc(boolean(),
#{desc => "Is it possible to split the number of requested tokens?",
default => false})}
, {max_retry_time, sc(emqx_schema:duration(),
#{ desc => "The maximum retry time when acquire failed."
, default => "5s"})}
, default => "10s"})}
, {failure_strategy, sc(failure_strategy(),
#{ desc => "The strategy when all the retries failed."
, default => force})}
@ -132,6 +137,10 @@ minimum_period() ->
to_rate(Str) ->
to_rate(Str, true, false).
%% default group name for shared type limiter
default_group_name() ->
'_default'.
%%--------------------------------------------------------------------
%% Internal functions
%%--------------------------------------------------------------------
@ -145,22 +154,38 @@ to_rate(Str, CanInfinity, CanZero) ->
case Tokens of
["infinity"] when CanInfinity ->
{ok, infinity};
["0", _] when CanZero ->
{ok, 0}; %% for burst
[Quota, Interval] ->
{ok, Val} = to_capacity(Quota),
case emqx_schema:to_duration_ms(Interval) of
{ok, Ms} when Ms > 0 ->
{ok, Val * minimum_period() / Ms};
_ ->
{error, Str}
end;
[QuotaStr] -> %% if time unit is 1s, it can be omitted
{ok, Val} = to_capacity(QuotaStr),
check_capacity(Str, Val, CanZero,
fun(Quota) ->
Quota * minimum_period() / ?UNIT_TIME_IN_MS
end);
[QuotaStr, Interval] ->
{ok, Val} = to_capacity(QuotaStr),
check_capacity(Str, Val, CanZero,
fun(Quota) ->
case emqx_schema:to_duration_ms(Interval) of
{ok, Ms} when Ms > 0 ->
{ok, Quota * minimum_period() / Ms};
_ ->
{error, Str}
end
end);
_ ->
{error, Str}
end.
check_capacity(_Str, 0, true, _Cont) ->
{ok, 0};
check_capacity(Str, 0, false, _Cont) ->
{error, Str};
check_capacity(_Str, Quota, _CanZero, Cont) ->
Cont(Quota).
to_capacity(Str) ->
Regex = "^\s*(?:(?:([1-9][0-9]*)([a-zA-z]*))|infinity)\s*$",
Regex = "^\s*(?:([0-9]+)([a-zA-z]*))|infinity\s*$",
to_quota(Str, Regex).
to_initial(Str) ->
@ -175,9 +200,9 @@ to_quota(Str, Regex) ->
Val = erlang:list_to_integer(Quota),
Unit2 = string:to_lower(Unit),
{ok, apply_unit(Unit2, Val)};
{match, [Quota]} ->
{match, [Quota, ""]} ->
{ok, erlang:list_to_integer(Quota)};
{match, []} ->
{match, ""} ->
{ok, infinity};
_ ->
{error, Str}
@ -188,3 +213,14 @@ apply_unit("kb", Val) -> Val * ?KILOBYTE;
apply_unit("mb", Val) -> Val * ?KILOBYTE * ?KILOBYTE;
apply_unit("gb", Val) -> Val * ?KILOBYTE * ?KILOBYTE * ?KILOBYTE;
apply_unit(Unit, _) -> throw("invalid unit:" ++ Unit).
to_bucket_path(Str) ->
Dirs = [erlang:list_to_atom(string:trim(T)) || T <- string:tokens(Str, ".")],
case Dirs of
[_Group, _Bucket] = Path ->
{ok, Path};
[_Bucket] = Path ->
{ok, Path};
_ ->
{error, Str}
end.

View File

@ -1,4 +1,4 @@
%%--------------------------------------------------------------------
%--------------------------------------------------------------------
%% Copyright (c) 2021-2022 EMQ Technologies Co., Ltd. All Rights Reserved.
%%
%% Licensed under the Apache License, Version 2.0 (the "License");
@ -53,7 +53,9 @@
-type bucket() :: #{ id := node_id()
, name := bucket_name()
, zone := zone_name() %% pointer to zone node, use for burst
%% pointer to zone node, use for burst
%% it also can use nodeId, nodeId is more direct, but nodeName is clearer
, zone := zone_name()
, rate := rate()
, obtained := non_neg_integer()
, correction := emqx_limiter_decimal:zero_or_float() %% token correction value
@ -82,9 +84,11 @@
-type capacity() :: decimal().
-type decimal() :: emqx_limiter_decimal:decimal().
-type index() :: pos_integer().
-type bucket_path() :: emqx_limiter_schema:bucket_path().
-define(CALL(Type), gen_server:call(name(Type), ?FUNCTION_NAME)).
-define(OVERLOAD_MIN_ALLOC, 0.3). %% minimum coefficient for overloaded limiter
-define(CURRYING(X, Fun2), fun(Y) -> Fun2(X, Y) end).
-export_type([index/0]).
-import(emqx_limiter_decimal, [add/2, sub/2, mul/2, put_to_counter/3]).
@ -95,17 +99,28 @@
%% API
%%--------------------------------------------------------------------
-spec connect(limiter_type(),
bucket_name() | #{limiter_type() => bucket_name()}) -> emqx_htb_limiter:limiter().
connect(Type, BucketName) when is_atom(BucketName) ->
Path = [limiter, Type, bucket, BucketName],
case emqx:get_config(Path, undefined) of
undefined ->
?SLOG(error, #{msg => "bucket_config_not_found", path => Path}),
throw("bucket's config not found");
#{zone := Zone,
aggregated := #{rate := AggrRate, capacity := AggrSize},
per_client := #{rate := CliRate, capacity := CliSize} = Cfg} ->
case emqx_limiter_manager:find_bucket(Type, Zone, BucketName) of
bucket_path() | #{limiter_type() => bucket_path() | undefined}) ->
emqx_htb_limiter:limiter().
%% If no bucket path is set in config, there will be no limit
connect(_Type, undefined) ->
emqx_htb_limiter:make_infinity_limiter(undefined);
%% Shared type can use bucket name directly
connect(shared, BucketName) when is_atom(BucketName) ->
connect(shared, [BucketName]);
connect(Type, BucketPath) when is_list(BucketPath) ->
FullPath = get_bucket_full_cfg_path(Type, BucketPath),
case emqx:get_config(FullPath, undefined) of
undefined ->
io:format(">>>>> config:~p~n fullpath:~p~n", [emqx:get_config([limiter]), FullPath]),
io:format(">>>>> ets:~p~n", [ets:tab2list(emqx_limiter_counters)]),
?SLOG(error, #{msg => "bucket_config_not_found", path => BucketPath}),
throw("bucket's config not found");
#{rate := AggrRate,
capacity := AggrSize,
per_client := #{rate := CliRate, capacity := CliSize} = Cfg} ->
case emqx_limiter_manager:find_bucket(Type, BucketPath) of
{ok, Bucket} ->
if CliRate < AggrRate orelse CliSize < AggrSize ->
emqx_htb_limiter:make_token_bucket_limiter(Cfg, Bucket);
@ -115,13 +130,14 @@ connect(Type, BucketName) when is_atom(BucketName) ->
emqx_htb_limiter:make_ref_limiter(Cfg, Bucket)
end;
undefined ->
?SLOG(error, #{msg => "bucket_not_found", path => Path}),
io:format(">>>>> ets:~p~n", [ets:tab2list(emqx_limiter_counters)]),
?SLOG(error, #{msg => "bucket_not_found", path => BucketPath}),
throw("invalid bucket")
end
end;
connect(Type, Names) ->
connect(Type, maps:get(Type, Names, default)).
connect(Type, Paths) ->
connect(Type, maps:get(Type, Paths, undefined)).
-spec info(limiter_type()) -> state().
info(Type) ->
@ -374,6 +390,7 @@ maybe_burst(#{buckets := Buckets,
nodes := Nodes} = State) when Burst > 0 ->
%% find empty buckets and group by zone name
GroupFun = fun(Id, Groups) ->
%% TODO filter undefined counter
#{counter := Counter,
index := Index,
zone := Zone} = maps:get(Id, Nodes),
@ -426,7 +443,8 @@ dispatch_burst(GroupL,
0 -> NodeAcc;
ZoneFlow ->
EachFlow = ZoneFlow / erlang:length(Childs),
{Alloced, NodeAcc2} = dispatch_burst_to_buckets(Childs, EachFlow, 0, NodeAcc),
{Alloced, NodeAcc2} =
dispatch_burst_to_buckets(Childs, EachFlow, 0, NodeAcc),
Zone2 = Zone#{obtained := Obtained + Alloced},
NodeAcc2#{ZoneId := Zone2}
end
@ -434,7 +452,8 @@ dispatch_burst(GroupL,
State#{nodes := lists:foldl(Dispatch, Nodes, GroupL)}.
-spec dispatch_burst_to_buckets(list(node_id()),
float(), non_neg_integer(), nodes()) -> {non_neg_integer(), nodes()}.
float(),
non_neg_integer(), nodes()) -> {non_neg_integer(), nodes()}.
dispatch_burst_to_buckets([ChildId | T], InFlow, Alloced, Nodes) ->
#{counter := Counter,
index := Index,
@ -451,76 +470,91 @@ dispatch_burst_to_buckets([], _, Alloced, Nodes) ->
-spec init_tree(emqx_limiter_schema:limiter_type(), state()) -> state().
init_tree(Type, State) ->
case emqx:get_config([limiter, Type]) of
#{global := Global,
zone := Zone,
bucket := Bucket} -> ok;
#{bucket := Bucket} ->
Global = default_rate_burst_cfg(),
Zone = #{default => default_rate_burst_cfg()},
Cfg = emqx:get_config([limiter, Type]),
GlobalCfg = maps:merge(#{rate => infinity, burst => 0}, Cfg),
case GlobalCfg of
#{group := Group} -> ok;
#{bucket := _} ->
Group = make_shared_default_group(GlobalCfg),
ok
end,
{Factor, Root} = make_root(Global, Zone),
State2 = State#{root := Root},
{NodeId, State3} = make_zone(maps:to_list(Zone), Factor, 1, State2),
State4 = State3#{counter := counters:new(maps:size(Bucket),
[write_concurrency])},
make_bucket(maps:to_list(Bucket), Global, Zone, Factor, NodeId, [], State4).
-spec make_root(hocons:confg(), hocon:config()) -> {number(), root()}.
make_root(#{rate := Rate, burst := Burst}, Zone) ->
ZoneNum = maps:size(Zone),
Childs = lists:seq(1, ZoneNum),
{Factor, Root} = make_root(GlobalCfg),
{Zones, Nodes, DelayBuckets} = make_zone(maps:to_list(Group), Type,
GlobalCfg, Factor, 1, #{}, #{}, #{}),
State2 = State#{root := Root#{childs := maps:values(Zones)},
zones := Zones,
nodes := Nodes,
buckets := maps:keys(DelayBuckets),
counter := counters:new(maps:size(DelayBuckets), [write_concurrency])
},
lists:foldl(fun(F, Acc) -> F(Acc) end, State2, maps:values(DelayBuckets)).
-spec make_root(hocons:confg()) -> {number(), root()}.
make_root(#{rate := Rate, burst := Burst}) ->
MiniPeriod = emqx_limiter_schema:minimum_period(),
if Rate >= 1 ->
{1, #{rate => Rate,
burst => Burst,
period => MiniPeriod,
childs => Childs,
childs => [],
consumed => 0}};
true ->
Factor = 1 / Rate,
{Factor, #{rate => 1,
burst => Burst * Factor,
period => erlang:floor(Factor * MiniPeriod),
childs => Childs,
childs => [],
consumed => 0}}
end.
make_zone([{Name, ZoneCfg} | T], Factor, NodeId, State) ->
#{rate := Rate, burst := Burst} = ZoneCfg,
#{zones := Zones, nodes := Nodes} = State,
make_zone([{Name, ZoneCfg} | T], Type, GlobalCfg, Factor, NodeId, Zones, Nodes, DelayBuckets) ->
#{rate := Rate, burst := Burst, bucket := BucketMap} = ZoneCfg,
BucketCfgs = maps:to_list(BucketMap),
FirstChildId = NodeId + 1,
Buckets = make_bucket(BucketCfgs, Type, GlobalCfg, Name, ZoneCfg, Factor, FirstChildId, #{}),
ChildNum = maps:size(Buckets),
NextZoneId = FirstChildId + ChildNum,
Zone = #{id => NodeId,
name => Name,
rate => mul(Rate, Factor),
burst => Burst,
obtained => 0,
childs => []},
State2 = State#{zones := Zones#{Name => NodeId},
nodes := Nodes#{NodeId => Zone}},
make_zone(T, Factor, NodeId + 1, State2);
childs => maps:keys(Buckets)
},
make_zone([], _, NodeId, State2) ->
{NodeId, State2}.
make_zone(T, Type, GlobalCfg, Factor, NextZoneId,
Zones#{Name => NodeId}, Nodes#{NodeId => Zone}, maps:merge(DelayBuckets, Buckets)
);
make_bucket([{Name, Conf} | T], Global, Zone, Factor, Id, Buckets, #{type := Type} = State) ->
#{zone := ZoneName,
aggregated := Aggregated} = Conf,
make_zone([], _Type, _Global, _Factor, _NodeId, Zones, Nodes, DelayBuckets) ->
{Zones, Nodes, DelayBuckets}.
make_bucket([{Name, Conf} | T], Type, GlobalCfg, ZoneName, ZoneCfg, Factor, Id, DelayBuckets) ->
Path = emqx_limiter_manager:make_path(Type, ZoneName, Name),
case get_counter_rate(Conf, Zone, Global) of
infinity ->
State2 = State,
Rate = infinity,
Capacity = infinity,
Counter = undefined,
Index = undefined,
Ref = emqx_limiter_bucket_ref:new(Counter, Index, Rate),
emqx_limiter_manager:insert_bucket(Path, Ref);
case get_counter_rate(Conf, ZoneCfg, GlobalCfg) of
infinity ->
Rate = infinity,
Capacity = infinity,
Ref = emqx_limiter_bucket_ref:new(undefined, undefined, Rate),
emqx_limiter_manager:insert_bucket(Path, Ref),
InitFun = fun(#{id := NodeId} = Node, #{nodes := Nodes} = State) ->
State#{nodes := Nodes#{NodeId => Node}}
end;
RawRate ->
#{capacity := Capacity} = Aggregated,
Initial = get_initial_val(Aggregated),
{Counter, Index, State2} = alloc_counter(Path, RawRate, Initial, State),
Rate = mul(RawRate, Factor)
#{capacity := Capacity} = Conf,
Initial = get_initial_val(Conf),
Rate = mul(RawRate, Factor),
InitFun = fun(#{id := NodeId} = Node, #{nodes := Nodes} = State) ->
{Counter, Idx, State2} = alloc_counter(Path, RawRate, Initial, State),
Node2 = Node#{counter := Counter, index := Idx},
State2#{nodes := Nodes#{NodeId => Node2}}
end
end,
Node = #{ id => Id
@ -530,14 +564,16 @@ make_bucket([{Name, Conf} | T], Global, Zone, Factor, Id, Buckets, #{type := Typ
, obtained => 0
, correction => 0
, capacity => Capacity
, counter => Counter
, index => Index},
, counter => undefined
, index => undefined},
State3 = add_zone_child(Id, Node, ZoneName, State2),
make_bucket(T, Global, Zone, Factor, Id + 1, [Id | Buckets], State3);
DelayInit = ?CURRYING(Node, InitFun),
make_bucket([], _, _, _, _, Buckets, State) ->
State#{buckets := Buckets}.
make_bucket(T,
Type, GlobalCfg, ZoneName, ZoneCfg, Factor, Id + 1, DelayBuckets#{Id => DelayInit});
make_bucket([], _Type, _Global, _ZoneName, _Zone, _Factor, _Id, DelayBuckets) ->
DelayBuckets.
-spec alloc_counter(emqx_limiter_manager:path(), rate(), capacity(), state()) ->
{counters:counters_ref(), pos_integer(), state()}.
@ -558,20 +594,10 @@ init_counter(Path, Counter, Index, Rate, Initial, State) ->
emqx_limiter_manager:insert_bucket(Path, Ref),
{Counter, Index, State}.
-spec add_zone_child(node_id(), bucket(), zone_name(), state()) -> state().
add_zone_child(NodeId, Bucket, Name, #{zones := Zones, nodes := Nodes} = State) ->
ZoneId = maps:get(Name, Zones),
#{childs := Childs} = Zone = maps:get(ZoneId, Nodes),
Nodes2 = Nodes#{ZoneId => Zone#{childs := [NodeId | Childs]},
NodeId => Bucket},
State#{nodes := Nodes2}.
%% @doc find first limited node
get_counter_rate(#{zone := ZoneName,
aggregated := Cfg}, ZoneCfg, Global) ->
Zone = maps:get(ZoneName, ZoneCfg),
get_counter_rate(BucketCfg, ZoneCfg, GlobalCfg) ->
Search = lists:search(fun(E) -> is_limited(E) end,
[Cfg, Zone, Global]),
[BucketCfg, ZoneCfg, GlobalCfg]),
case Search of
{value, #{rate := Rate}} ->
Rate;
@ -585,6 +611,7 @@ is_limited(#{rate := Rate, capacity := Capacity}) ->
is_limited(#{rate := Rate}) ->
Rate =/= infinity.
-spec get_initial_val(hocons:config()) -> decimal().
get_initial_val(#{initial := Initial,
rate := Rate,
capacity := Capacity}) ->
@ -599,5 +626,14 @@ get_initial_val(#{initial := Initial,
0
end.
default_rate_burst_cfg() ->
#{rate => infinity, burst => 0}.
-spec make_shared_default_group(hocons:config()) -> honcs:config().
make_shared_default_group(Cfg) ->
GroupName = emqx_limiter_schema:default_group_name(),
#{GroupName => Cfg#{rate => infinity, burst => 0}}.
-spec get_bucket_full_cfg_path(limiter_type(), bucket_path()) -> list(atom()).
get_bucket_full_cfg_path(shared, [BucketName]) ->
[limiter, shared, bucket, BucketName];
get_bucket_full_cfg_path(Type, [GroupName, BucketName]) ->
[limiter, Type, group, GroupName, bucket, BucketName].

View File

@ -1197,7 +1197,7 @@ base_listener() ->
#{ default => 'default'
})}
, {"limiter",
sc(map("ratelimit bucket's name", atom()), #{default => #{}})}
sc(map("ratelimit bucket's name", emqx_limiter_schema:bucket_path()), #{default => #{}})}
].
%% utils

View File

@ -493,6 +493,8 @@ typename_to_spec("failure_strategy()", _Mod) ->
#{type => string, example => <<"force">>};
typename_to_spec("initial()", _Mod) ->
#{type => string, example => <<"0M">>};
typename_to_spec("bucket_path()", _Mod) ->
#{type => string, example => <<"groupName.bucketName">>};
typename_to_spec(Name, Mod) ->
Spec = range(Name),
Spec1 = remote_module_type(Spec, Name, Mod),