fix(shared_sub): Dispatch shared messages via gen_rpc
This commit is contained in:
parent
03766d54cc
commit
9dfa82b448
|
@ -26,6 +26,7 @@
|
||||||
{emqx_resource,1}.
|
{emqx_resource,1}.
|
||||||
{emqx_retainer,1}.
|
{emqx_retainer,1}.
|
||||||
{emqx_rule_engine,1}.
|
{emqx_rule_engine,1}.
|
||||||
|
{emqx_shared_sub,1}.
|
||||||
{emqx_slow_subs,1}.
|
{emqx_slow_subs,1}.
|
||||||
{emqx_statsd,1}.
|
{emqx_statsd,1}.
|
||||||
{emqx_telemetry,1}.
|
{emqx_telemetry,1}.
|
||||||
|
|
|
@ -38,7 +38,8 @@
|
||||||
|
|
||||||
-export([
|
-export([
|
||||||
dispatch/3,
|
dispatch/3,
|
||||||
dispatch/4
|
dispatch/4,
|
||||||
|
do_dispatch_with_ack/4
|
||||||
]).
|
]).
|
||||||
|
|
||||||
-export([
|
-export([
|
||||||
|
@ -170,30 +171,31 @@ do_dispatch(SubPid, _Group, Topic, Msg, _Type) when SubPid =:= self() ->
|
||||||
%% return either 'ok' (when everything is fine) or 'error'
|
%% return either 'ok' (when everything is fine) or 'error'
|
||||||
do_dispatch(SubPid, _Group, Topic, #message{qos = ?QOS_0} = Msg, _Type) ->
|
do_dispatch(SubPid, _Group, Topic, #message{qos = ?QOS_0} = Msg, _Type) ->
|
||||||
%% For QoS 0 message, send it as regular dispatch
|
%% For QoS 0 message, send it as regular dispatch
|
||||||
SubPid ! {deliver, Topic, Msg},
|
send(SubPid, Topic, {deliver, Topic, Msg});
|
||||||
ok;
|
|
||||||
do_dispatch(SubPid, _Group, Topic, Msg, retry) ->
|
do_dispatch(SubPid, _Group, Topic, Msg, retry) ->
|
||||||
%% Retry implies all subscribers nack:ed, send again without ack
|
%% Retry implies all subscribers nack:ed, send again without ack
|
||||||
SubPid ! {deliver, Topic, Msg},
|
send(SubPid, Topic, {deliver, Topic, Msg});
|
||||||
ok;
|
|
||||||
do_dispatch(SubPid, Group, Topic, Msg, fresh) ->
|
do_dispatch(SubPid, Group, Topic, Msg, fresh) ->
|
||||||
case ack_enabled() of
|
case ack_enabled() of
|
||||||
true ->
|
true ->
|
||||||
dispatch_with_ack(SubPid, Group, Topic, Msg);
|
%% FIXME: replace with `emqx_shared_sub_proto:dispatch_with_ack' in 5.2
|
||||||
|
do_dispatch_with_ack(SubPid, Group, Topic, Msg);
|
||||||
false ->
|
false ->
|
||||||
SubPid ! {deliver, Topic, Msg},
|
send(SubPid, Topic, {deliver, Topic, Msg})
|
||||||
ok
|
|
||||||
end.
|
end.
|
||||||
|
|
||||||
dispatch_with_ack(SubPid, Group, Topic, Msg) ->
|
-spec do_dispatch_with_ack(pid(), emqx_types:group(), emqx_types:topic(), emqx_types:message()) ->
|
||||||
|
ok | {error, _}.
|
||||||
|
do_dispatch_with_ack(SubPid, Group, Topic, Msg) ->
|
||||||
%% For QoS 1/2 message, expect an ack
|
%% For QoS 1/2 message, expect an ack
|
||||||
Ref = erlang:monitor(process, SubPid),
|
Ref = erlang:monitor(process, SubPid),
|
||||||
Sender = self(),
|
Sender = self(),
|
||||||
SubPid ! {deliver, Topic, with_group_ack(Msg, Group, Sender, Ref)},
|
%% FIXME: replace with regular send in 5.2
|
||||||
|
send(SubPid, Topic, {deliver, Topic, with_group_ack(Msg, Group, Sender, Ref)}),
|
||||||
Timeout =
|
Timeout =
|
||||||
case Msg#message.qos of
|
case Msg#message.qos of
|
||||||
?QOS_1 -> timer:seconds(?SHARED_SUB_QOS1_DISPATCH_TIMEOUT_SECONDS);
|
?QOS_2 -> infinity;
|
||||||
?QOS_2 -> infinity
|
_ -> timer:seconds(?SHARED_SUB_QOS1_DISPATCH_TIMEOUT_SECONDS)
|
||||||
end,
|
end,
|
||||||
try
|
try
|
||||||
receive
|
receive
|
||||||
|
@ -412,6 +414,17 @@ code_change(_OldVsn, State, _Extra) ->
|
||||||
%% Internal functions
|
%% Internal functions
|
||||||
%%--------------------------------------------------------------------
|
%%--------------------------------------------------------------------
|
||||||
|
|
||||||
|
send(Pid, Topic, Msg) ->
|
||||||
|
Node = node(Pid),
|
||||||
|
_ =
|
||||||
|
case Node =:= node() of
|
||||||
|
true ->
|
||||||
|
Pid ! Msg;
|
||||||
|
false ->
|
||||||
|
emqx_shared_sub_proto_v1:send(Node, Pid, Topic, Msg)
|
||||||
|
end,
|
||||||
|
ok.
|
||||||
|
|
||||||
maybe_insert_round_robin_count({Group, _Topic} = GroupTopic) ->
|
maybe_insert_round_robin_count({Group, _Topic} = GroupTopic) ->
|
||||||
strategy(Group) =:= round_robin_per_group andalso
|
strategy(Group) =:= round_robin_per_group andalso
|
||||||
ets:insert(?SHARED_SUBS_ROUND_ROBIN_COUNTER, {GroupTopic, 0}),
|
ets:insert(?SHARED_SUBS_ROUND_ROBIN_COUNTER, {GroupTopic, 0}),
|
||||||
|
|
|
@ -0,0 +1,50 @@
|
||||||
|
%%--------------------------------------------------------------------
|
||||||
|
%% Copyright (c) 2022 EMQ Technologies Co., Ltd. All Rights Reserved.
|
||||||
|
%%
|
||||||
|
%% Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
%% you may not use this file except in compliance with the License.
|
||||||
|
%% You may obtain a copy of the License at
|
||||||
|
%%
|
||||||
|
%% http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
%%
|
||||||
|
%% Unless required by applicable law or agreed to in writing, software
|
||||||
|
%% distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
%% See the License for the specific language governing permissions and
|
||||||
|
%% limitations under the License.
|
||||||
|
%%--------------------------------------------------------------------
|
||||||
|
-module(emqx_shared_sub_proto_v1).
|
||||||
|
|
||||||
|
-behaviour(emqx_bpapi).
|
||||||
|
|
||||||
|
-export([
|
||||||
|
introduced_in/0,
|
||||||
|
send/4,
|
||||||
|
dispatch_with_ack/5
|
||||||
|
]).
|
||||||
|
|
||||||
|
-include("bpapi.hrl").
|
||||||
|
|
||||||
|
%%================================================================================
|
||||||
|
%% behavior callbacks
|
||||||
|
%%================================================================================
|
||||||
|
|
||||||
|
introduced_in() ->
|
||||||
|
"5.0.8".
|
||||||
|
|
||||||
|
%%================================================================================
|
||||||
|
%% API funcions
|
||||||
|
%%================================================================================
|
||||||
|
|
||||||
|
-spec send(node(), pid(), emqx_types:topic(), term()) -> true.
|
||||||
|
send(Node, Pid, Topic, Msg) ->
|
||||||
|
emqx_rpc:cast(Topic, Node, erlang, send, [Pid, Msg]).
|
||||||
|
|
||||||
|
-spec dispatch_with_ack(
|
||||||
|
pid(), emqx_types:group(), emqx_types:topic(), emqx_types:message(), timeout()
|
||||||
|
) ->
|
||||||
|
ok | {error, _}.
|
||||||
|
dispatch_with_ack(Pid, Group, Topic, Msg, Timeout) ->
|
||||||
|
emqx_rpc:call(
|
||||||
|
Topic, node(Pid), emqx_shared_sub, do_dispatch_with_ack, [Pid, Group, Topic, Msg], Timeout
|
||||||
|
).
|
|
@ -567,6 +567,59 @@ t_local(_) ->
|
||||||
?assertNotEqual(UsedSubPid1, UsedSubPid2),
|
?assertNotEqual(UsedSubPid1, UsedSubPid2),
|
||||||
ok.
|
ok.
|
||||||
|
|
||||||
|
t_remote(_) ->
|
||||||
|
%% This testcase verifies dispatching of shared messages to the remote nodes via backplane API.
|
||||||
|
%%
|
||||||
|
%% In this testcase we start two EMQX nodes: local and remote.
|
||||||
|
%% A subscriber connects to the remote node.
|
||||||
|
%% A publisher connects to the local node and sends three messages with different QoS.
|
||||||
|
%% The test verifies that the remote side received all three messages.
|
||||||
|
ok = ensure_config(sticky, true),
|
||||||
|
GroupConfig = #{
|
||||||
|
<<"local_group">> => local,
|
||||||
|
<<"round_robin_group">> => round_robin,
|
||||||
|
<<"sticky_group">> => sticky
|
||||||
|
},
|
||||||
|
|
||||||
|
Node = start_slave('remote_shared_sub_testtesttest', 21999),
|
||||||
|
ok = ensure_group_config(GroupConfig),
|
||||||
|
ok = ensure_group_config(Node, GroupConfig),
|
||||||
|
|
||||||
|
Topic = <<"foo/bar">>,
|
||||||
|
ClientIdLocal = <<"ClientId1">>,
|
||||||
|
ClientIdRemote = <<"ClientId2">>,
|
||||||
|
|
||||||
|
{ok, ConnPidLocal} = emqtt:start_link([{clientid, ClientIdLocal}]),
|
||||||
|
{ok, ConnPidRemote} = emqtt:start_link([{clientid, ClientIdRemote}, {port, 21999}]),
|
||||||
|
|
||||||
|
try
|
||||||
|
{ok, ClientPidLocal} = emqtt:connect(ConnPidLocal),
|
||||||
|
{ok, ClientPidRemote} = emqtt:connect(ConnPidRemote),
|
||||||
|
|
||||||
|
emqtt:subscribe(ConnPidRemote, {<<"$share/remote_group/", Topic/binary>>, 0}),
|
||||||
|
|
||||||
|
ct:sleep(100),
|
||||||
|
|
||||||
|
Message1 = emqx_message:make(ClientPidLocal, 0, Topic, <<"hello1">>),
|
||||||
|
Message2 = emqx_message:make(ClientPidLocal, 1, Topic, <<"hello2">>),
|
||||||
|
Message3 = emqx_message:make(ClientPidLocal, 2, Topic, <<"hello3">>),
|
||||||
|
|
||||||
|
emqx:publish(Message1),
|
||||||
|
{true, UsedSubPid1} = last_message(<<"hello1">>, [ConnPidRemote]),
|
||||||
|
|
||||||
|
emqx:publish(Message2),
|
||||||
|
{true, UsedSubPid1} = last_message(<<"hello2">>, [ConnPidRemote]),
|
||||||
|
|
||||||
|
emqx:publish(Message3),
|
||||||
|
{true, UsedSubPid1} = last_message(<<"hello3">>, [ConnPidRemote]),
|
||||||
|
|
||||||
|
ok
|
||||||
|
after
|
||||||
|
emqtt:stop(ConnPidLocal),
|
||||||
|
emqtt:stop(ConnPidRemote),
|
||||||
|
stop_slave(Node)
|
||||||
|
end.
|
||||||
|
|
||||||
t_local_fallback(_) ->
|
t_local_fallback(_) ->
|
||||||
ok = ensure_group_config(#{
|
ok = ensure_group_config(#{
|
||||||
<<"local_group">> => local,
|
<<"local_group">> => local,
|
||||||
|
|
Loading…
Reference in New Issue