feat(cluster-link): preserve replication actor state in pdict

This commit is contained in:
Andrew Mayorov 2024-05-20 11:55:56 +02:00 committed by Serge Tupchii
parent 5771a41a32
commit 43d114546c
2 changed files with 82 additions and 66 deletions

View File

@ -86,14 +86,15 @@ should_route_to_external_dests(_Msg) ->
on_message_publish( on_message_publish(
#message{topic = <<?ROUTE_TOPIC_PREFIX, ClusterName/binary>>, payload = Payload} = Msg #message{topic = <<?ROUTE_TOPIC_PREFIX, ClusterName/binary>>, payload = Payload} = Msg
) -> ) ->
_ =
case emqx_cluster_link_mqtt:decode_route_op(Payload) of case emqx_cluster_link_mqtt:decode_route_op(Payload) of
{actor_init, InitInfoMap} -> {actor_init, Actor, InitInfo} ->
actor_init(ClusterName, emqx_message:get_header(properties, Msg), InitInfoMap); Result = actor_init(ClusterName, Actor, InitInfo),
{route_updates, #{actor := Actor, incarnation := Incr}, RouteOps} -> _ = actor_init_ack(Actor, Result, Msg),
update_routes(ClusterName, Actor, Incr, RouteOps); ok;
{heartbeat, #{actor := Actor, incarnation := Incr}} -> {route_updates, #{actor := Actor}, RouteOps} ->
actor_heartbeat(ClusterName, Actor, Incr) ok = update_routes(ClusterName, Actor, RouteOps);
{heartbeat, #{actor := Actor}} ->
ok = actor_heartbeat(ClusterName, Actor)
end, end,
{stop, []}; {stop, []};
on_message_publish(#message{topic = <<?MSG_TOPIC_PREFIX, ClusterName/binary>>, payload = Payload}) -> on_message_publish(#message{topic = <<?MSG_TOPIC_PREFIX, ClusterName/binary>>, payload = Payload}) ->
@ -117,6 +118,9 @@ delete_hook() ->
%% Internal functions %% Internal functions
%%-------------------------------------------------------------------- %%--------------------------------------------------------------------
-define(PD_EXTROUTER_ACTOR, '$clink_extrouter_actor').
-define(PD_EXTROUTER_ACTOR_STATE, '$clink_extrouter_actor_state').
maybe_push_route_op(Op, Topic, RouteID) -> maybe_push_route_op(Op, Topic, RouteID) ->
maybe_push_route_op(Op, Topic, RouteID, push). maybe_push_route_op(Op, Topic, RouteID, push).
@ -143,24 +147,18 @@ topic_intersect_any(_Topic, []) ->
actor_init( actor_init(
ClusterName, ClusterName,
#{'Correlation-Data' := ReqId, 'Response-Topic' := RespTopic}, #{actor := Actor, incarnation := Incr},
#{ #{
actor := Actor, target_cluster := TargetCluster,
incarnation := Incr,
cluster := TargetCluster,
proto_ver := _ proto_ver := _
} }
) -> ) ->
Res =
case emqx_cluster_link_config:link(ClusterName) of case emqx_cluster_link_config:link(ClusterName) of
undefined -> undefined ->
?SLOG( ?SLOG(error, #{
error,
#{
msg => "init_link_request_from_unknown_cluster", msg => "init_link_request_from_unknown_cluster",
link_name => ClusterName link_name => ClusterName
} }),
),
%% Avoid atom error reasons, since they can be sent to the remote cluster, %% Avoid atom error reasons, since they can be sent to the remote cluster,
%% which will use safe binary_to_term decoding %% which will use safe binary_to_term decoding
%% TODO: add error details? %% TODO: add error details?
@ -172,7 +170,11 @@ actor_init(
case MyClusterName of case MyClusterName of
TargetCluster -> TargetCluster ->
Env = #{timestamp => erlang:system_time(millisecond)}, Env = #{timestamp => erlang:system_time(millisecond)},
emqx_cluster_link_extrouter:actor_init(ClusterName, Actor, Incr, Env); {ok, ActorSt} = emqx_cluster_link_extrouter:actor_init(
ClusterName, Actor, Incr, Env
),
undefined = set_actor_state(ClusterName, Actor, ActorSt),
ok;
_ -> _ ->
%% The remote cluster uses a different name to refer to this cluster %% The remote cluster uses a different name to refer to this cluster
?SLOG(error, #{ ?SLOG(error, #{
@ -186,27 +188,38 @@ actor_init(
}), }),
{error, <<"bad_remote_cluster_link_name">>} {error, <<"bad_remote_cluster_link_name">>}
end end
end, end.
_ = actor_init_ack(Actor, Res, ReqId, RespTopic),
{stop, []}.
actor_init_ack(Actor, Res, ReqId, RespTopic) -> actor_init_ack(#{actor := Actor}, Res, MsgIn) ->
RespMsg = emqx_cluster_link_mqtt:actor_init_ack_resp_msg(Actor, Res, ReqId, RespTopic), RespMsg = emqx_cluster_link_mqtt:actor_init_ack_resp_msg(Actor, Res, MsgIn),
emqx_broker:publish(RespMsg). emqx_broker:publish(RespMsg).
update_routes(ClusterName, Actor, Incarnation, RouteOps) -> update_routes(ClusterName, Actor, RouteOps) ->
ActorState = emqx_cluster_link_extrouter:actor_state(ClusterName, Actor, Incarnation), ActorSt = get_actor_state(ClusterName, Actor),
lists:foreach( lists:foreach(
fun(RouteOp) -> fun(RouteOp) ->
emqx_cluster_link_extrouter:actor_apply_operation(RouteOp, ActorState) emqx_cluster_link_extrouter:actor_apply_operation(RouteOp, ActorSt)
end, end,
RouteOps RouteOps
). ).
actor_heartbeat(ClusterName, Actor, Incarnation) -> actor_heartbeat(ClusterName, Actor) ->
Env = #{timestamp => erlang:system_time(millisecond)}, Env = #{timestamp => erlang:system_time(millisecond)},
ActorState = emqx_cluster_link_extrouter:actor_state(ClusterName, Actor, Incarnation), ActorSt0 = get_actor_state(ClusterName, Actor),
_State = emqx_cluster_link_extrouter:actor_apply_operation(heartbeat, ActorState, Env). ActorSt = emqx_cluster_link_extrouter:actor_apply_operation(heartbeat, ActorSt0, Env),
_ = update_actor_state(ActorSt),
ok.
get_actor_state(ClusterName, Actor) ->
{ClusterName, Actor} = erlang:get(?PD_EXTROUTER_ACTOR),
erlang:get(?PD_EXTROUTER_ACTOR_STATE).
set_actor_state(ClusterName, Actor, ActorSt) ->
undefined = erlang:put(?PD_EXTROUTER_ACTOR, {ClusterName, Actor}),
update_actor_state(ActorSt).
update_actor_state(ActorSt) ->
erlang:put(?PD_EXTROUTER_ACTOR_STATE, ActorSt).
%% let it crash if extra is not a map, %% let it crash if extra is not a map,
%% we don't expect the message to be forwarded from an older EMQX release, %% we don't expect the message to be forwarded from an older EMQX release,

View File

@ -35,7 +35,7 @@
-export([ -export([
publish_actor_init_sync/6, publish_actor_init_sync/6,
actor_init_ack_resp_msg/4, actor_init_ack_resp_msg/3,
publish_route_sync/4, publish_route_sync/4,
publish_heartbeat/3, publish_heartbeat/3,
encode_field/2 encode_field/2
@ -277,13 +277,17 @@ publish_actor_init_sync(ClientPid, ReqId, RespTopic, TargetCluster, Actor, Incar
}, },
emqtt:publish(ClientPid, ?ROUTE_TOPIC, Properties, ?ENCODE(Payload), [{qos, ?QOS_1}]). emqtt:publish(ClientPid, ?ROUTE_TOPIC, Properties, ?ENCODE(Payload), [{qos, ?QOS_1}]).
actor_init_ack_resp_msg(Actor, InitRes, ReqId, RespTopic) -> actor_init_ack_resp_msg(Actor, InitRes, MsgIn) ->
Payload = #{ Payload = #{
?F_OPERATION => ?OP_ACTOR_INIT_ACK, ?F_OPERATION => ?OP_ACTOR_INIT_ACK,
?F_PROTO_VER => ?PROTO_VER, ?F_PROTO_VER => ?PROTO_VER,
?F_ACTOR => Actor ?F_ACTOR => Actor
}, },
Payload1 = with_res_and_bootstrap(Payload, InitRes), Payload1 = with_res_and_bootstrap(Payload, InitRes),
#{
'Response-Topic' := RespTopic,
'Correlation-Data' := ReqId
} = emqx_message:get_header(properties, MsgIn),
emqx_message:make( emqx_message:make(
undefined, undefined,
?QOS_1, ?QOS_1,
@ -334,12 +338,11 @@ decode_route_op1(#{
?F_ACTOR := Actor, ?F_ACTOR := Actor,
?F_INCARNATION := Incr ?F_INCARNATION := Incr
}) -> }) ->
{actor_init, #{ Info = #{
actor => Actor, target_cluster => TargetCluster,
incarnation => Incr,
cluster => TargetCluster,
proto_ver => ProtoVer proto_ver => ProtoVer
}}; },
{actor_init, #{actor => Actor, incarnation => Incr}, Info};
decode_route_op1(#{ decode_route_op1(#{
?F_OPERATION := ?OP_ROUTE, ?F_OPERATION := ?OP_ROUTE,
?F_ACTOR := Actor, ?F_ACTOR := Actor,