Merge pull request #8167 from lafirest/fix/limiter_period

fix(limiter): fix precision issue
This commit is contained in:
lafirest 2022-06-13 10:13:05 +08:00 committed by GitHub
commit 1963441472
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 31 additions and 42 deletions

View File

@ -374,7 +374,7 @@ return_pause(infinity, PauseType, Fun, Diff, Limiter) ->
%% workaround when emqx_limiter_server's rate is infinity %% workaround when emqx_limiter_server's rate is infinity
{PauseType, ?MINIMUM_PAUSE, make_retry_context(Fun, Diff), Limiter}; {PauseType, ?MINIMUM_PAUSE, make_retry_context(Fun, Diff), Limiter};
return_pause(Rate, PauseType, Fun, Diff, Limiter) -> return_pause(Rate, PauseType, Fun, Diff, Limiter) ->
Val = erlang:round(Diff * emqx_limiter_schema:minimum_period() / Rate), Val = erlang:round(Diff * emqx_limiter_schema:default_period() / Rate),
Pause = emqx_misc:clamp(Val, ?MINIMUM_PAUSE, ?MAXIMUM_PAUSE), Pause = emqx_misc:clamp(Val, ?MINIMUM_PAUSE, ?MAXIMUM_PAUSE),
{PauseType, Pause, make_retry_context(Fun, Diff), Limiter}. {PauseType, Pause, make_retry_context(Fun, Diff), Limiter}.
@ -408,5 +408,5 @@ may_return_or_pause(_, Limiter) ->
%% @doc apply the elapsed time to the limiter %% @doc apply the elapsed time to the limiter
apply_elapsed_time(Rate, Elapsed, Tokens, Capacity) -> apply_elapsed_time(Rate, Elapsed, Tokens, Capacity) ->
Inc = floor_div(mul(Elapsed, Rate), emqx_limiter_schema:minimum_period()), Inc = floor_div(mul(Elapsed, Rate), emqx_limiter_schema:default_period()),
erlang:min(add(Tokens, Inc), Capacity). erlang:min(add(Tokens, Inc), Capacity).

View File

@ -24,7 +24,7 @@
fields/1, fields/1,
to_rate/1, to_rate/1,
to_capacity/1, to_capacity/1,
minimum_period/0, default_period/0,
to_burst_rate/1, to_burst_rate/1,
to_initial/1, to_initial/1,
namespace/0, namespace/0,
@ -191,8 +191,8 @@ desc(client_bucket) ->
desc(_) -> desc(_) ->
undefined. undefined.
%% minimum period is 100ms %% default period is 100ms
minimum_period() -> default_period() ->
100. 100.
to_rate(Str) -> to_rate(Str) ->
@ -235,7 +235,7 @@ to_rate(Str, CanInfinity, CanZero) ->
%% if time unit is 1s, it can be omitted %% if time unit is 1s, it can be omitted
{match, [QuotaStr]} -> {match, [QuotaStr]} ->
Fun = fun(Quota) -> Fun = fun(Quota) ->
{ok, Quota * minimum_period() / ?UNIT_TIME_IN_MS} {ok, Quota * default_period() / ?UNIT_TIME_IN_MS}
end, end,
to_capacity(QuotaStr, Str, CanZero, Fun); to_capacity(QuotaStr, Str, CanZero, Fun);
{match, [QuotaStr, TimeVal, TimeUnit]} -> {match, [QuotaStr, TimeVal, TimeUnit]} ->
@ -250,7 +250,7 @@ to_rate(Str, CanInfinity, CanZero) ->
try try
case emqx_schema:to_duration_ms(Interval) of case emqx_schema:to_duration_ms(Interval) of
{ok, Ms} when Ms > 0 -> {ok, Ms} when Ms > 0 ->
{ok, Quota * minimum_period() / Ms}; {ok, Quota * default_period() / Ms};
{ok, 0} when CanZero -> {ok, 0} when CanZero ->
{ok, 0}; {ok, 0};
_ -> _ ->

View File

@ -57,13 +57,13 @@
burst := rate(), burst := rate(),
%% token generation interval(second) %% token generation interval(second)
period := pos_integer(), period := pos_integer(),
consumed := non_neg_integer() produced := float()
}. }.
-type bucket() :: #{ -type bucket() :: #{
name := bucket_name(), name := bucket_name(),
rate := rate(), rate := rate(),
obtained := non_neg_integer(), obtained := float(),
%% token correction value %% token correction value
correction := emqx_limiter_decimal:zero_or_float(), correction := emqx_limiter_decimal:zero_or_float(),
capacity := capacity(), capacity := capacity(),
@ -314,26 +314,26 @@ oscillation(
root := #{ root := #{
rate := Flow, rate := Flow,
period := Interval, period := Interval,
consumed := Consumed produced := Produced
} = Root, } = Root,
buckets := Buckets buckets := Buckets
} = State } = State
) -> ) ->
oscillate(Interval), oscillate(Interval),
Ordereds = get_ordered_buckets(Buckets), Ordereds = get_ordered_buckets(Buckets),
{Alloced, Buckets2} = transverse(Ordereds, Flow, 0, Buckets), {Alloced, Buckets2} = transverse(Ordereds, Flow, 0.0, Buckets),
maybe_burst(State#{ maybe_burst(State#{
buckets := Buckets2, buckets := Buckets2,
root := Root#{consumed := Consumed + Alloced} root := Root#{produced := Produced + Alloced}
}). }).
%% @doc horizontal spread %% @doc horizontal spread
-spec transverse( -spec transverse(
list(bucket()), list(bucket()),
flow(), flow(),
non_neg_integer(), float(),
buckets() buckets()
) -> {non_neg_integer(), buckets()}. ) -> {float(), buckets()}.
transverse([H | T], InFlow, Alloced, Buckets) when InFlow > 0 -> transverse([H | T], InFlow, Alloced, Buckets) when InFlow > 0 ->
{BucketAlloced, Buckets2} = longitudinal(H, InFlow, Buckets), {BucketAlloced, Buckets2} = longitudinal(H, InFlow, Buckets),
InFlow2 = sub(InFlow, BucketAlloced), InFlow2 = sub(InFlow, BucketAlloced),
@ -344,7 +344,7 @@ transverse(_, _, Alloced, Buckets) ->
%% @doc vertical spread %% @doc vertical spread
-spec longitudinal(bucket(), flow(), buckets()) -> -spec longitudinal(bucket(), flow(), buckets()) ->
{non_neg_integer(), buckets()}. {float(), buckets()}.
longitudinal( longitudinal(
#{ #{
name := Name, name := Name,
@ -381,7 +381,7 @@ longitudinal(
{Inc, Bucket2} = emqx_limiter_correction:add(Available, Bucket), {Inc, Bucket2} = emqx_limiter_correction:add(Available, Bucket),
counters:add(Counter, Index, Inc), counters:add(Counter, Index, Inc),
{Inc, Buckets#{Name := Bucket2#{obtained := Obtained + Inc}}}; {Available, Buckets#{Name := Bucket2#{obtained := Obtained + Available}}};
_ -> _ ->
{0, Buckets} {0, Buckets}
end; end;
@ -431,11 +431,11 @@ dispatch_burst([], _, State) ->
dispatch_burst( dispatch_burst(
Empties, Empties,
InFlow, InFlow,
#{root := #{consumed := Consumed} = Root, buckets := Buckets} = State #{root := #{produced := Produced} = Root, buckets := Buckets} = State
) -> ) ->
EachFlow = InFlow / erlang:length(Empties), EachFlow = InFlow / erlang:length(Empties),
{Alloced, Buckets2} = dispatch_burst_to_buckets(Empties, EachFlow, 0, Buckets), {Alloced, Buckets2} = dispatch_burst_to_buckets(Empties, EachFlow, 0, Buckets),
State#{root := Root#{consumed := Consumed + Alloced}, buckets := Buckets2}. State#{root := Root#{produced := Produced + Alloced}, buckets := Buckets2}.
-spec dispatch_burst_to_buckets( -spec dispatch_burst_to_buckets(
list(bucket()), list(bucket()),
@ -473,8 +473,8 @@ init_tree(Type, #{bucket := Buckets} = Cfg) ->
buckets => #{} buckets => #{}
}, },
{Factor, Root} = make_root(Cfg), Root = make_root(Cfg),
{CounterNum, DelayBuckets} = make_bucket(maps:to_list(Buckets), Type, Cfg, Factor, 1, []), {CounterNum, DelayBuckets} = make_bucket(maps:to_list(Buckets), Type, Cfg, 1, []),
State2 = State#{ State2 = State#{
root := Root, root := Root,
@ -483,25 +483,16 @@ init_tree(Type, #{bucket := Buckets} = Cfg) ->
lists:foldl(fun(F, Acc) -> F(Acc) end, State2, DelayBuckets). lists:foldl(fun(F, Acc) -> F(Acc) end, State2, DelayBuckets).
-spec make_root(hocons:confg()) -> {number(), root()}. -spec make_root(hocons:confg()) -> root().
make_root(#{rate := Rate, burst := Burst}) when Rate >= 1 -> make_root(#{rate := Rate, burst := Burst}) ->
{1, #{ #{
rate => Rate, rate => Rate,
burst => Burst, burst => Burst,
period => emqx_limiter_schema:minimum_period(), period => emqx_limiter_schema:default_period(),
consumed => 0 produced => 0.0
}}; }.
make_root(#{rate := Rate, burst := Burst}) ->
MiniPeriod = emqx_limiter_schema:minimum_period(),
Factor = 1 / Rate,
{Factor, #{
rate => 1,
burst => Burst * Factor,
period => erlang:floor(Factor * MiniPeriod),
consumed => 0
}}.
make_bucket([{Name, Conf} | T], Type, GlobalCfg, Factor, CounterNum, DelayBuckets) -> make_bucket([{Name, Conf} | T], Type, GlobalCfg, CounterNum, DelayBuckets) ->
Path = emqx_limiter_manager:make_path(Type, Name), Path = emqx_limiter_manager:make_path(Type, Name),
case get_counter_rate(Conf, GlobalCfg) of case get_counter_rate(Conf, GlobalCfg) of
infinity -> infinity ->
@ -514,13 +505,12 @@ make_bucket([{Name, Conf} | T], Type, GlobalCfg, Factor, CounterNum, DelayBucket
InitFun = fun(#{name := BucketName} = Bucket, #{buckets := Buckets} = State) -> InitFun = fun(#{name := BucketName} = Bucket, #{buckets := Buckets} = State) ->
State#{buckets := Buckets#{BucketName => Bucket}} State#{buckets := Buckets#{BucketName => Bucket}}
end; end;
RawRate -> Rate ->
#{capacity := Capacity} = Conf, #{capacity := Capacity} = Conf,
Initial = get_initial_val(Conf), Initial = get_initial_val(Conf),
Rate = mul(RawRate, Factor),
CounterNum2 = CounterNum + 1, CounterNum2 = CounterNum + 1,
InitFun = fun(#{name := BucketName} = Bucket, #{buckets := Buckets} = State) -> InitFun = fun(#{name := BucketName} = Bucket, #{buckets := Buckets} = State) ->
{Counter, Idx, State2} = alloc_counter(Path, RawRate, Initial, State), {Counter, Idx, State2} = alloc_counter(Path, Rate, Initial, State),
Bucket2 = Bucket#{counter := Counter, index := Idx}, Bucket2 = Bucket#{counter := Counter, index := Idx},
State2#{buckets := Buckets#{BucketName => Bucket2}} State2#{buckets := Buckets#{BucketName => Bucket2}}
end end
@ -542,11 +532,10 @@ make_bucket([{Name, Conf} | T], Type, GlobalCfg, Factor, CounterNum, DelayBucket
T, T,
Type, Type,
GlobalCfg, GlobalCfg,
Factor,
CounterNum2, CounterNum2,
[DelayInit | DelayBuckets] [DelayInit | DelayBuckets]
); );
make_bucket([], _Type, _Global, _Factor, CounterNum, DelayBuckets) -> make_bucket([], _Type, _Global, CounterNum, DelayBuckets) ->
{CounterNum, DelayBuckets}. {CounterNum, DelayBuckets}.
-spec alloc_counter(emqx_limiter_manager:path(), rate(), capacity(), state()) -> -spec alloc_counter(emqx_limiter_manager:path(), rate(), capacity(), state()) ->

View File

@ -646,7 +646,7 @@ client_loop(
} = State } = State
) -> ) ->
Now = ?NOW, Now = ?NOW,
Period = emqx_limiter_schema:minimum_period(), Period = emqx_limiter_schema:default_period(),
MinPeriod = erlang:ceil(0.25 * Period), MinPeriod = erlang:ceil(0.25 * Period),
if if
Now >= EndTime -> Now >= EndTime ->