迁移到ets

This commit is contained in:
anlicheng 2026-04-22 10:53:49 +08:00
parent 852823e686
commit 45a2fe5300

View File

@ -20,6 +20,7 @@
-export([init/1, handle_call/3, handle_cast/2, handle_info/2, terminate/2, code_change/3]).
-define(SERVER, ?MODULE).
-define(SUBSCRIBER_TAB, endpoint_subscription_subscribers).
%%
-record(subscriber, {
@ -35,7 +36,7 @@
}).
-record(state, {
subscribers = []
tid :: ets:tid()
}).
%%%===================================================================
@ -56,7 +57,18 @@ get_subscribers() ->
-spec publish(RouteKey :: binary(), Content :: binary()) -> no_return().
publish(RouteKey, Content) when is_binary(RouteKey), is_binary(Content) ->
gen_server:cast(?SERVER, {publish, RouteKey, Content}).
case ets:info(?SUBSCRIBER_TAB) of
undefined ->
ok;
_ ->
Subscribers = ets:tab2list(?SUBSCRIBER_TAB),
MatchedSubscribers = match_subscribers(Subscribers, RouteKey),
lists:foreach(fun(#subscriber{subscriber_pid = SubscriberPid}) ->
endpoint:forward(SubscriberPid, Content)
end, MatchedSubscribers),
logger:debug("[efka_subscription] route_key: ~p, metric: ~p, match subscribers: ~p", [RouteKey, Content, MatchedSubscribers]),
ok
end.
%% @doc Spawns the server and registers the local name (unique)
-spec(start_link() ->
@ -75,7 +87,8 @@ start_link() ->
{stop, Reason :: term()} | ignore).
init([]) ->
ok = iot_log:set_metadata(),
{ok, #state{}}.
Tid = ets:new(?SUBSCRIBER_TAB, [named_table, protected, bag, {keypos, 2}]),
{ok, #state{tid = Tid}}.
%% @private
%% @doc Handling call messages
@ -88,14 +101,15 @@ init([]) ->
{stop, Reason :: term(), Reply :: term(), NewState :: #state{}} |
{stop, Reason :: term(), NewState :: #state{}}).
%% SubscriberPid只能订阅同一个topic一次
handle_call(get_subscribers, _From, State = #state{subscribers = Subscribers}) ->
handle_call(get_subscribers, _From, State = #state{tid = Tid}) ->
Subscribers = ets:tab2list(Tid),
{reply, {ok, Subscribers}, State};
handle_call({subscribe, Topic, SubscriberPid}, _From, State = #state{subscribers = Subscribers}) ->
handle_call({subscribe, Topic, SubscriberPid}, _From, State = #state{tid = Tid}) ->
Components = of_components(Topic),
case is_valid_components(Components) of
true ->
case has_subscription(Topic, SubscriberPid, Subscribers) of
case has_subscription(Tid, Topic, SubscriberPid) of
true ->
{reply, ok, State};
false ->
@ -108,22 +122,23 @@ handle_call({subscribe, Topic, SubscriberPid}, _From, State = #state{subscribers
monitor_ref = MonitorRef,
order = order_num(Components)
},
{reply, ok, State#state{subscribers = [Sub | Subscribers]}}
true = ets:insert(Tid, Sub),
{reply, ok, State}
end;
false ->
{reply, {error, <<"invalid topic name">>}, State}
end;
handle_call({unsubscribe, Topic, SubscriberPid}, _From, State = #state{subscribers = Subscribers}) ->
{Removed, Reserved} = lists:partition(fun(#subscriber{topic = Topic0, subscriber_pid = SubscriberPid0}) ->
Topic =:= Topic0 andalso SubscriberPid =:= SubscriberPid0
end, Subscribers),
handle_call({unsubscribe, Topic, SubscriberPid}, _From, State = #state{tid = Tid}) ->
Removed = [Sub || Sub = #subscriber{subscriber_pid = SubscriberPid0} <- ets:lookup(Tid, Topic),
SubscriberPid =:= SubscriberPid0],
lists:foreach(fun(#subscriber{monitor_ref = MonitorRef}) when is_reference(MonitorRef) ->
erlang:demonitor(MonitorRef, [flush]);
(_) ->
ok
end, Removed),
{reply, ok, State#state{subscribers = Reserved}}.
lists:foreach(fun(Sub) -> true = ets:delete_object(Tid, Sub) end, Removed),
{reply, ok, State}.
%% @private
%% @doc Handling cast messages
@ -131,13 +146,7 @@ handle_call({unsubscribe, Topic, SubscriberPid}, _From, State = #state{subscribe
{noreply, NewState :: #state{}} |
{noreply, NewState :: #state{}, timeout() | hibernate} |
{stop, Reason :: term(), NewState :: #state{}}).
%%
handle_cast({publish, RouteKey, Metric}, State = #state{subscribers = Subscribers}) ->
MatchedSubscribers = match_subscribers(Subscribers, RouteKey),
lists:foreach(fun(#subscriber{subscriber_pid = SubscriberPid}) ->
endpoint:forward(SubscriberPid, Metric)
end, MatchedSubscribers),
logger:debug("[efka_subscription] route_key: ~p, metric: ~p, match subscribers: ~p", [RouteKey, Metric, MatchedSubscribers]),
handle_cast(_Request, State = #state{}) ->
{noreply, State}.
%% @private
@ -146,10 +155,15 @@ handle_cast({publish, RouteKey, Metric}, State = #state{subscribers = Subscriber
{noreply, NewState :: #state{}} |
{noreply, NewState :: #state{}, timeout() | hibernate} |
{stop, Reason :: term(), NewState :: #state{}}).
handle_info({'DOWN', _Ref, process, SubscriberPid, Reason}, State = #state{subscribers = Subscribers}) ->
handle_info({'DOWN', _Ref, process, SubscriberPid, Reason}, State = #state{tid = Tid}) ->
logger:debug("[efka_subscription] subscriber: ~p, down with reason: ~p", [SubscriberPid, Reason]),
NSubscribers = lists:filter(fun(#subscriber{subscriber_pid = Pid0}) -> SubscriberPid /= Pid0 end, Subscribers),
{noreply, State#state{subscribers = NSubscribers}};
Subscribers = ets:tab2list(Tid),
lists:foreach(fun(Sub = #subscriber{subscriber_pid = Pid0}) when Pid0 =:= SubscriberPid ->
true = ets:delete_object(Tid, Sub);
(_) ->
ok
end, Subscribers),
{noreply, State};
handle_info(Info, State = #state{}) ->
logger:debug("[efka_subscription] get unknown info: ~p", [Info]),
@ -226,8 +240,9 @@ order_num([<<$+>>|_]) ->
order_num([_|Tail]) ->
order_num(Tail).
-spec has_subscription(binary(), pid(), [#subscriber{}]) -> boolean().
has_subscription(Topic, SubscriberPid, Subscribers) ->
-spec has_subscription(ets:tid(), binary(), pid()) -> boolean().
has_subscription(Tid, Topic, SubscriberPid) ->
Subscribers = ets:lookup(Tid, Topic),
lists:any(fun(#subscriber{topic = Topic0, subscriber_pid = SubscriberPid0}) ->
Topic =:= Topic0 andalso SubscriberPid =:= SubscriberPid0
end, Subscribers).