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