Merge pull request #12739 from ieQu1/dev/zone-durability
feat(sessds): Add zone overrides for session durability settings
This commit is contained in:
commit
8b963d5960
|
@ -19,9 +19,10 @@
|
||||||
-behaviour(emqx_config_handler).
|
-behaviour(emqx_config_handler).
|
||||||
|
|
||||||
-include("emqx.hrl").
|
-include("emqx.hrl").
|
||||||
|
-include_lib("emqx/include/logger.hrl").
|
||||||
|
|
||||||
-export([init/0]).
|
-export([init/0]).
|
||||||
-export([is_persistence_enabled/0, force_ds/0]).
|
-export([is_persistence_enabled/0, is_persistence_enabled/1, force_ds/1]).
|
||||||
|
|
||||||
%% Config handler
|
%% Config handler
|
||||||
-export([add_handler/0, pre_config_update/3]).
|
-export([add_handler/0, pre_config_update/3]).
|
||||||
|
@ -32,6 +33,7 @@
|
||||||
]).
|
]).
|
||||||
|
|
||||||
-define(PERSISTENT_MESSAGE_DB, emqx_persistent_message).
|
-define(PERSISTENT_MESSAGE_DB, emqx_persistent_message).
|
||||||
|
-define(PERSISTENCE_ENABLED, emqx_message_persistence_enabled).
|
||||||
|
|
||||||
-define(WHEN_ENABLED(DO),
|
-define(WHEN_ENABLED(DO),
|
||||||
case is_persistence_enabled() of
|
case is_persistence_enabled() of
|
||||||
|
@ -43,7 +45,14 @@
|
||||||
%%--------------------------------------------------------------------
|
%%--------------------------------------------------------------------
|
||||||
|
|
||||||
init() ->
|
init() ->
|
||||||
|
%% Note: currently persistence can't be enabled or disabled in the
|
||||||
|
%% runtime. If persistence is enabled for any of the zones, we
|
||||||
|
%% consider durability feature to be on:
|
||||||
|
Zones = maps:keys(emqx_config:get([zones])),
|
||||||
|
IsEnabled = lists:any(fun is_persistence_enabled/1, Zones),
|
||||||
|
persistent_term:put(?PERSISTENCE_ENABLED, IsEnabled),
|
||||||
?WHEN_ENABLED(begin
|
?WHEN_ENABLED(begin
|
||||||
|
?SLOG(notice, #{msg => "Session durability is enabled"}),
|
||||||
Backend = storage_backend(),
|
Backend = storage_backend(),
|
||||||
ok = emqx_ds:open_db(?PERSISTENT_MESSAGE_DB, Backend),
|
ok = emqx_ds:open_db(?PERSISTENT_MESSAGE_DB, Backend),
|
||||||
ok = emqx_persistent_session_ds_router:init_tables(),
|
ok = emqx_persistent_session_ds_router:init_tables(),
|
||||||
|
@ -53,7 +62,11 @@ init() ->
|
||||||
|
|
||||||
-spec is_persistence_enabled() -> boolean().
|
-spec is_persistence_enabled() -> boolean().
|
||||||
is_persistence_enabled() ->
|
is_persistence_enabled() ->
|
||||||
emqx_config:get([session_persistence, enable]).
|
persistent_term:get(?PERSISTENCE_ENABLED).
|
||||||
|
|
||||||
|
-spec is_persistence_enabled(emqx_types:zone()) -> boolean().
|
||||||
|
is_persistence_enabled(Zone) ->
|
||||||
|
emqx_config:get_zone_conf(Zone, [session_persistence, enable]).
|
||||||
|
|
||||||
-spec storage_backend() -> emqx_ds:create_db_opts().
|
-spec storage_backend() -> emqx_ds:create_db_opts().
|
||||||
storage_backend() ->
|
storage_backend() ->
|
||||||
|
@ -61,9 +74,9 @@ storage_backend() ->
|
||||||
|
|
||||||
%% Dev-only option: force all messages to go through
|
%% Dev-only option: force all messages to go through
|
||||||
%% `emqx_persistent_session_ds':
|
%% `emqx_persistent_session_ds':
|
||||||
-spec force_ds() -> boolean().
|
-spec force_ds(emqx_types:zone()) -> boolean().
|
||||||
force_ds() ->
|
force_ds(Zone) ->
|
||||||
emqx_config:get([session_persistence, force_persistence]).
|
emqx_config:get_zone_conf(Zone, [session_persistence, force_persistence]).
|
||||||
|
|
||||||
storage_backend(Path) ->
|
storage_backend(Path) ->
|
||||||
ConfigTree = #{'_config_handler' := {Module, Function}} = emqx_config:get(Path),
|
ConfigTree = #{'_config_handler' := {Module, Function}} = emqx_config:get(Path),
|
||||||
|
|
|
@ -502,7 +502,7 @@ handle_timeout(ClientInfo, ?TIMER_PULL, Session0) ->
|
||||||
Timeout =
|
Timeout =
|
||||||
case Publishes of
|
case Publishes of
|
||||||
[] ->
|
[] ->
|
||||||
emqx_config:get([session_persistence, idle_poll_interval]);
|
get_config(ClientInfo, [idle_poll_interval]);
|
||||||
[_ | _] ->
|
[_ | _] ->
|
||||||
0
|
0
|
||||||
end,
|
end,
|
||||||
|
@ -511,10 +511,10 @@ handle_timeout(ClientInfo, ?TIMER_PULL, Session0) ->
|
||||||
handle_timeout(ClientInfo, ?TIMER_RETRY_REPLAY, Session0) ->
|
handle_timeout(ClientInfo, ?TIMER_RETRY_REPLAY, Session0) ->
|
||||||
Session = replay_streams(Session0, ClientInfo),
|
Session = replay_streams(Session0, ClientInfo),
|
||||||
{ok, [], Session};
|
{ok, [], Session};
|
||||||
handle_timeout(_ClientInfo, ?TIMER_GET_STREAMS, Session0 = #{s := S0}) ->
|
handle_timeout(ClientInfo, ?TIMER_GET_STREAMS, Session0 = #{s := S0}) ->
|
||||||
S1 = emqx_persistent_session_ds_subs:gc(S0),
|
S1 = emqx_persistent_session_ds_subs:gc(S0),
|
||||||
S = emqx_persistent_session_ds_stream_scheduler:renew_streams(S1),
|
S = emqx_persistent_session_ds_stream_scheduler:renew_streams(S1),
|
||||||
Interval = emqx_config:get([session_persistence, renew_streams_interval]),
|
Interval = get_config(ClientInfo, [renew_streams_interval]),
|
||||||
Session = emqx_session:ensure_timer(
|
Session = emqx_session:ensure_timer(
|
||||||
?TIMER_GET_STREAMS,
|
?TIMER_GET_STREAMS,
|
||||||
Interval,
|
Interval,
|
||||||
|
@ -799,7 +799,7 @@ fetch_new_messages(Session = #{s := S}, ClientInfo) ->
|
||||||
fetch_new_messages([], Session, _ClientInfo) ->
|
fetch_new_messages([], Session, _ClientInfo) ->
|
||||||
Session;
|
Session;
|
||||||
fetch_new_messages([I | Streams], Session0 = #{inflight := Inflight}, ClientInfo) ->
|
fetch_new_messages([I | Streams], Session0 = #{inflight := Inflight}, ClientInfo) ->
|
||||||
BatchSize = emqx_config:get([session_persistence, batch_size]),
|
BatchSize = get_config(ClientInfo, [batch_size]),
|
||||||
case emqx_persistent_session_ds_inflight:n_buffered(all, Inflight) >= BatchSize of
|
case emqx_persistent_session_ds_inflight:n_buffered(all, Inflight) >= BatchSize of
|
||||||
true ->
|
true ->
|
||||||
%% Buffer is full:
|
%% Buffer is full:
|
||||||
|
@ -1055,9 +1055,15 @@ receive_maximum(ConnInfo) ->
|
||||||
expiry_interval(ConnInfo) ->
|
expiry_interval(ConnInfo) ->
|
||||||
maps:get(expiry_interval, ConnInfo, 0).
|
maps:get(expiry_interval, ConnInfo, 0).
|
||||||
|
|
||||||
|
%% Note: we don't allow overriding `last_alive_update_interval' per
|
||||||
|
%% zone, since the GC process is responsible for all sessions
|
||||||
|
%% regardless of the zone.
|
||||||
bump_interval() ->
|
bump_interval() ->
|
||||||
emqx_config:get([session_persistence, last_alive_update_interval]).
|
emqx_config:get([session_persistence, last_alive_update_interval]).
|
||||||
|
|
||||||
|
get_config(#{zone := Zone}, Key) ->
|
||||||
|
emqx_config:get_zone_conf(Zone, [session_persistence | Key]).
|
||||||
|
|
||||||
-spec try_get_live_session(emqx_types:clientid()) ->
|
-spec try_get_live_session(emqx_types:clientid()) ->
|
||||||
{pid(), session()} | not_found | not_persistent.
|
{pid(), session()} | not_found | not_persistent.
|
||||||
try_get_live_session(ClientId) ->
|
try_get_live_session(ClientId) ->
|
||||||
|
|
|
@ -192,7 +192,7 @@ create(ClientInfo, ConnInfo) ->
|
||||||
|
|
||||||
create(ClientInfo, ConnInfo, Conf) ->
|
create(ClientInfo, ConnInfo, Conf) ->
|
||||||
% FIXME error conditions
|
% FIXME error conditions
|
||||||
create(choose_impl_mod(ConnInfo), ClientInfo, ConnInfo, Conf).
|
create(hd(choose_impl_candidates(ClientInfo, ConnInfo)), ClientInfo, ConnInfo, Conf).
|
||||||
|
|
||||||
create(Mod, ClientInfo, ConnInfo, Conf) ->
|
create(Mod, ClientInfo, ConnInfo, Conf) ->
|
||||||
% FIXME error conditions
|
% FIXME error conditions
|
||||||
|
@ -205,7 +205,7 @@ create(Mod, ClientInfo, ConnInfo, Conf) ->
|
||||||
{_IsPresent :: true, t(), _ReplayContext} | {_IsPresent :: false, t()}.
|
{_IsPresent :: true, t(), _ReplayContext} | {_IsPresent :: false, t()}.
|
||||||
open(ClientInfo, ConnInfo) ->
|
open(ClientInfo, ConnInfo) ->
|
||||||
Conf = get_session_conf(ClientInfo),
|
Conf = get_session_conf(ClientInfo),
|
||||||
Mods = [Default | _] = choose_impl_candidates(ConnInfo),
|
Mods = [Default | _] = choose_impl_candidates(ClientInfo, ConnInfo),
|
||||||
%% NOTE
|
%% NOTE
|
||||||
%% Try to look the existing session up in session stores corresponding to the given
|
%% Try to look the existing session up in session stores corresponding to the given
|
||||||
%% `Mods` in order, starting from the last one.
|
%% `Mods` in order, starting from the last one.
|
||||||
|
@ -253,7 +253,7 @@ destroy(ClientInfo, ConnInfo) ->
|
||||||
%% 0, and later reconnects with `Session-Expiry-Interval' = 0 and `clean_start' =
|
%% 0, and later reconnects with `Session-Expiry-Interval' = 0 and `clean_start' =
|
||||||
%% true. So we may simply destroy sessions from all implementations, since the key
|
%% true. So we may simply destroy sessions from all implementations, since the key
|
||||||
%% (ClientID) is the same.
|
%% (ClientID) is the same.
|
||||||
Mods = choose_impl_candidates(ConnInfo),
|
Mods = choose_impl_candidates(ClientInfo, ConnInfo),
|
||||||
lists:foreach(fun(Mod) -> Mod:destroy(ClientInfo) end, Mods).
|
lists:foreach(fun(Mod) -> Mod:destroy(ClientInfo) end, Mods).
|
||||||
|
|
||||||
-spec destroy(t()) -> ok.
|
-spec destroy(t()) -> ok.
|
||||||
|
@ -610,31 +610,26 @@ maybe_mock_impl_mod(Session) ->
|
||||||
error(noimpl, [Session]).
|
error(noimpl, [Session]).
|
||||||
-endif.
|
-endif.
|
||||||
|
|
||||||
-spec choose_impl_mod(conninfo()) -> module().
|
choose_impl_candidates(#{zone := Zone}, #{expiry_interval := EI}) ->
|
||||||
choose_impl_mod(#{expiry_interval := EI}) ->
|
case emqx_persistent_message:is_persistence_enabled(Zone) of
|
||||||
hd(choose_impl_candidates(EI, emqx_persistent_message:is_persistence_enabled())).
|
|
||||||
|
|
||||||
-spec choose_impl_candidates(conninfo()) -> [module()].
|
|
||||||
choose_impl_candidates(#{expiry_interval := EI}) ->
|
|
||||||
choose_impl_candidates(EI, emqx_persistent_message:is_persistence_enabled()).
|
|
||||||
|
|
||||||
choose_impl_candidates(_, _IsPSStoreEnabled = false) ->
|
|
||||||
[emqx_session_mem];
|
|
||||||
choose_impl_candidates(0, _IsPSStoreEnabled = true) ->
|
|
||||||
case emqx_persistent_message:force_ds() of
|
|
||||||
false ->
|
false ->
|
||||||
%% NOTE
|
[emqx_session_mem];
|
||||||
%% If ExpiryInterval is 0, the natural choice is
|
|
||||||
%% `emqx_session_mem'. Yet we still need to look the
|
|
||||||
%% existing session up in the `emqx_persistent_session_ds'
|
|
||||||
%% store first, because previous connection may have set
|
|
||||||
%% ExpiryInterval to a non-zero value.
|
|
||||||
[emqx_session_mem, emqx_persistent_session_ds];
|
|
||||||
true ->
|
true ->
|
||||||
[emqx_persistent_session_ds]
|
Force = emqx_persistent_message:force_ds(Zone),
|
||||||
end;
|
case EI of
|
||||||
choose_impl_candidates(EI, _IsPSStoreEnabled = true) when EI > 0 ->
|
0 when not Force ->
|
||||||
[emqx_persistent_session_ds].
|
%% NOTE
|
||||||
|
%% If ExpiryInterval is 0, the natural choice is
|
||||||
|
%% `emqx_session_mem'. Yet we still need to look
|
||||||
|
%% the existing session up in the
|
||||||
|
%% `emqx_persistent_session_ds' store first,
|
||||||
|
%% because previous connection may have set
|
||||||
|
%% ExpiryInterval to a non-zero value.
|
||||||
|
[emqx_session_mem, emqx_persistent_session_ds];
|
||||||
|
_ ->
|
||||||
|
[emqx_persistent_session_ds]
|
||||||
|
end
|
||||||
|
end.
|
||||||
|
|
||||||
-compile({inline, [run_hook/2]}).
|
-compile({inline, [run_hook/2]}).
|
||||||
run_hook(Name, Args) ->
|
run_hook(Name, Args) ->
|
||||||
|
|
|
@ -33,7 +33,8 @@ roots() ->
|
||||||
force_shutdown,
|
force_shutdown,
|
||||||
conn_congestion,
|
conn_congestion,
|
||||||
force_gc,
|
force_gc,
|
||||||
overload_protection
|
overload_protection,
|
||||||
|
session_persistence
|
||||||
].
|
].
|
||||||
|
|
||||||
zones_without_default() ->
|
zones_without_default() ->
|
||||||
|
|
|
@ -463,5 +463,17 @@ zone_global_defaults() ->
|
||||||
backoff_new_conn => true,
|
backoff_new_conn => true,
|
||||||
enable => false
|
enable => false
|
||||||
},
|
},
|
||||||
stats => #{enable => true}
|
stats => #{enable => true},
|
||||||
|
session_persistence =>
|
||||||
|
#{
|
||||||
|
enable => false,
|
||||||
|
batch_size => 100,
|
||||||
|
force_persistence => false,
|
||||||
|
idle_poll_interval => 100,
|
||||||
|
last_alive_update_interval => 5000,
|
||||||
|
message_retention_period => 86400000,
|
||||||
|
renew_streams_interval => 5000,
|
||||||
|
session_gc_batch_size => 100,
|
||||||
|
session_gc_interval => 600000
|
||||||
|
}
|
||||||
}.
|
}.
|
||||||
|
|
|
@ -0,0 +1,2 @@
|
||||||
|
Make it possible to override `session_persistence` settings per zone.
|
||||||
|
Since durable sessions are inherently more expensive to maintain than the regular sessions, it's desirable to grant the operator finer control of session durability for different classes of clients.
|
Loading…
Reference in New Issue