add out/2, plen/1

This commit is contained in:
Feng 2016-01-23 22:04:36 +08:00
parent 0728562733
commit 4ebdbd75a3
4 changed files with 174 additions and 86 deletions

View File

@ -23,6 +23,7 @@
%%% %%%
%%% @author Feng Lee <feng@emqtt.io> %%% @author Feng Lee <feng@emqtt.io>
%%%----------------------------------------------------------------------------- %%%-----------------------------------------------------------------------------
-module(emqttd). -module(emqttd).
-export([start/0, env/1, env/2, -export([start/0, env/1, env/2,

View File

@ -30,20 +30,21 @@
%%% %%%
%%% If the broker restarted or crashed, all the messages queued will be gone. %%% If the broker restarted or crashed, all the messages queued will be gone.
%%% %%%
%%% Desgin of The Queue: %%% Concept of Message Queue and Inflight Window:
%%%
%%% |<----------------- Max Len ----------------->| %%% |<----------------- Max Len ----------------->|
%%% ----------------------------------------------- %%% -----------------------------------------------
%%% IN -> | Pending Messages | Inflight Window | -> Out %%% IN -> | Messages Queue | Inflight Window | -> Out
%%% ----------------------------------------------- %%% -----------------------------------------------
%%% |<--- Win Size --->| %%% |<--- Win Size --->|
%%% %%%
%%% %%%
%%% 1. Inflight Window to store the messages awaiting for ack. %%% 1. Inflight Window to store the messages delivered and awaiting for puback.
%%% %%%
%%% 2. Suspend IN messages when the queue is deactive, or inflight windows is full. %%% 2. Enqueue messages when the inflight window is full.
%%% %%%
%%% 3. If the queue is full, dropped qos0 messages if store_qos0 is true, %%% 3. If the queue is full, dropped qos0 messages if store_qos0 is true,
%%% otherwise dropped the oldest pending one. %%% otherwise dropped the oldest one.
%%% %%%
%%% @end %%% @end
%%% %%%
@ -55,96 +56,158 @@
-include("emqttd_protocol.hrl"). -include("emqttd_protocol.hrl").
-export([new/3, name/1, -export([new/3, type/1, name/1, is_empty/1, len/1, max_len/1, in/2, out/1, stats/1]).
is_empty/1, is_full/1,
len/1, max_len/1,
in/2, out/1,
stats/1]).
-define(LOW_WM, 0.2). -define(LOW_WM, 0.2).
-define(HIGH_WM, 0.6). -define(HIGH_WM, 0.6).
-record(mqueue, {name, -type priority() :: {iolist(), pos_integer()}.
q = queue:new(), %% pending queue
len = 0, %% current queue len
low_wm = ?LOW_WM,
high_wm = ?HIGH_WM,
max_len = ?MAX_LEN,
qos0 = false,
dropped = 0,
alarm_fun}).
-type mqueue() :: #mqueue{}. -type option() :: {type, simple | priority}
| {max_length, pos_integer() | infinity}
| {priority, list(priority())}
| {low_watermark, float()} %% Low watermark
| {high_watermark, float()} %% High watermark
| {queue_qos0, boolean()}. %% Queue Qos0?
-type mqueue_option() :: {max_length, pos_integer()} %% Max queue length -type mqueue_option() :: {max_length, pos_integer()} %% Max queue length
| {low_watermark, float()} %% Low watermark | {low_watermark, float()} %% Low watermark
| {high_watermark, float()} %% High watermark | {high_watermark, float()} %% High watermark
| {queue_qos0, boolean()}. %% Queue Qos0 | {queue_qos0, boolean()}. %% Queue Qos0
-export_type([mqueue/0]). -type stat() :: {max_len, infinity | pos_integer()}
| {len, non_neg_integer()}
| {dropped, non_neg_integer()}.
-record(mqueue, {type :: simple | priority,
name, q :: queue:queue() | priority_queue:q(),
%% priority table
pseq = 0, priorities = [],
%% len of simple queue
len = 0, max_len = ?MAX_LEN,
low_wm = ?LOW_WM, high_wm = ?HIGH_WM,
qos0 = false, dropped = 0,
alarm_fun}).
-type mqueue() :: #mqueue{}.
-export_type([mqueue/0, priority/0, option/0]).
%%------------------------------------------------------------------------------
%% @doc New Queue. %% @doc New Queue.
%% @end -spec new(iolist(), list(mqueue_option()), fun()) -> mqueue().
%%------------------------------------------------------------------------------
-spec new(binary(), list(mqueue_option()), fun()) -> mqueue().
new(Name, Opts, AlarmFun) -> new(Name, Opts, AlarmFun) ->
MaxLen = emqttd_opts:g(max_length, Opts, 1000), Type = emqttd_opts:g(type, Opts, simple),
#mqueue{name = Name, MaxLen = emqttd_opts:g(max_length, Opts, infinity),
max_len = MaxLen, init_q(#mqueue{type = Type, name = Name, max_len = MaxLen,
low_wm = round(MaxLen * emqttd_opts:g(low_watermark, Opts, ?LOW_WM)), low_wm = low_wm(MaxLen, Opts),
high_wm = round(MaxLen * emqttd_opts:g(high_watermark, Opts, ?HIGH_WM)), high_wm = high_wm(MaxLen, Opts),
qos0 = emqttd_opts:g(queue_qos0, Opts, true), qos0 = emqttd_opts:g(queue_qos0, Opts, false),
alarm_fun = AlarmFun}. alarm_fun = AlarmFun}, Opts).
init_q(MQ = #mqueue{type = simple}, _Opts) ->
MQ#mqueue{q = queue:new()};
init_q(MQ = #mqueue{type = priority}, Opts) ->
Priorities = emqttd_opts:g(priority, Opts, []),
init_p(Priorities, MQ#mqueue{q = priority_queue:new()}).
init_p([], MQ) ->
MQ;
init_p([{Topic, P} | L], MQ) ->
{_, MQ1} = insert_p(iolist_to_binary(Topic), P, MQ),
init_p(L, MQ1).
insert_p(Topic, P, MQ = #mqueue{priorities = Tab, pseq = Seq}) ->
<<PInt:48>> = <<P:8, (erlang:phash2(Topic)):32, Seq:8>>,
{PInt, MQ#mqueue{priorities = [{Topic, PInt} | Tab], pseq = Seq + 1}}.
low_wm(infinity, _Opts) ->
infinity;
low_wm(MaxLen, Opts) ->
round(MaxLen * emqttd_opts:g(low_watermark, Opts, ?LOW_WM)).
high_wm(infinity, _Opts) ->
infinity;
high_wm(MaxLen, Opts) ->
round(MaxLen * emqttd_opts:g(high_watermark, Opts, ?HIGH_WM)).
-spec name(mqueue()) -> iolist().
name(#mqueue{name = Name}) -> name(#mqueue{name = Name}) ->
Name. Name.
is_empty(#mqueue{len = 0}) -> true; -spec type(mqueue()) -> atom().
is_empty(_MQ) -> false. type(#mqueue{type = Type}) ->
Type.
is_full(#mqueue{len = Len, max_len = MaxLen}) is_empty(#mqueue{type = simple, len = Len}) -> Len =:= 0;
when Len =:= MaxLen -> true; is_empty(#mqueue{type = priority, q = Q}) -> priority_queue:is_empty(Q).
is_full(_MQ) -> false.
len(#mqueue{len = Len}) -> Len. len(#mqueue{type = simple, len = Len}) -> Len;
len(#mqueue{type = priority, q = Q}) -> priority_queue:len(Q).
max_len(#mqueue{max_len= MaxLen}) -> MaxLen. max_len(#mqueue{max_len= MaxLen}) -> MaxLen.
stats(#mqueue{max_len = MaxLen, len = Len, dropped = Dropped}) -> stats(#mqueue{type = Type, q = Q, max_len = MaxLen, len = Len, dropped = Dropped}) ->
[{max_len, MaxLen}, {len, Len}, {dropped, Dropped}]. [{len, case Type of
simple -> Len;
%%------------------------------------------------------------------------------ priority -> priority_queue:len(Q)
%% @doc Queue one message. end} | [{max_len, MaxLen}, {dropped, Dropped}]].
%% @end
%%------------------------------------------------------------------------------
%% @doc Enqueue a message.
-spec in(mqtt_message(), mqueue()) -> mqueue(). -spec in(mqtt_message(), mqueue()) -> mqueue().
%% drop qos0
in(#mqtt_message{qos = ?QOS_0}, MQ = #mqueue{qos0 = false}) -> in(#mqtt_message{qos = ?QOS_0}, MQ = #mqueue{qos0 = false}) ->
MQ; MQ;
in(Msg, MQ = #mqueue{type = simple, q = Q, len = Len, max_len = infinity}) ->
%% simply drop the oldest one if queue is full, improve later MQ#mqueue{q = queue:in(Msg, Q), len = Len + 1};
in(Msg, MQ = #mqueue{q = Q, len = Len, max_len = MaxLen, dropped = Dropped}) in(Msg, MQ = #mqueue{type = simple, q = Q, len = Len, max_len = MaxLen, dropped = Dropped})
when Len =:= MaxLen -> when Len >= MaxLen ->
{{value, _OldMsg}, Q2} = queue:out(Q), {{value, _Old}, Q2} = queue:out(Q),
%lager:error("MQueue(~s) drop ~s", [Name, emqttd_message:format(OldMsg)]),
MQ#mqueue{q = queue:in(Msg, Q2), dropped = Dropped +1}; MQ#mqueue{q = queue:in(Msg, Q2), dropped = Dropped +1};
in(Msg, MQ = #mqueue{type = simple, q = Q, len = Len}) ->
maybe_set_alarm(MQ#mqueue{q = queue:in(Msg, Q), len = Len + 1});
in(Msg, MQ = #mqueue{q = Q, len = Len}) -> in(Msg = #mqtt_message{topic = Topic}, MQ = #mqueue{type = priority, q = Q,
maybe_set_alarm(MQ#mqueue{q = queue:in(Msg, Q), len = Len + 1}). priorities = Priorities,
max_len = infinity}) ->
case lists:keysearch(Topic, 1, Priorities) of
{value, {_, Pri}} ->
MQ#mqueue{q = priority_queue:in(Msg, Pri, Q)};
false ->
{Pri, MQ1} = insert_p(Topic, 0, MQ),
MQ1#mqueue{q = priority_queue:in(Msg, Pri, Q)}
end;
in(Msg = #mqtt_message{topic = Topic}, MQ = #mqueue{type = priority, q = Q,
priorities = Priorities,
max_len = MaxLen}) ->
case lists:keysearch(Topic, 1, Priorities) of
{value, {_, Pri}} ->
case priority_queue:plen(Pri, Q) >= MaxLen of
true ->
{_, Q1} = priority_queue:out(Pri, Q),
MQ#mqueue{q = priority_queue:in(Msg, Pri, Q1)};
false ->
MQ#mqueue{q = priority_queue:in(Msg, Pri, Q)}
end;
false ->
{Pri, MQ1} = insert_p(Topic, 0, MQ),
MQ1#mqueue{q = priority_queue:in(Msg, Pri, Q)}
end.
out(MQ = #mqueue{len = 0}) -> out(MQ = #mqueue{type = simple, len = 0}) ->
{empty, MQ}; {empty, MQ};
out(MQ = #mqueue{type = simple, q = Q, len = Len, max_len = infinity}) ->
out(MQ = #mqueue{q = Q, len = Len}) -> {R, Q2} = queue:out(Q),
{Result, Q2} = queue:out(Q), {R, MQ#mqueue{q = Q2, len = Len - 1}};
{Result, maybe_clear_alarm(MQ#mqueue{q = Q2, len = Len - 1})}. out(MQ = #mqueue{type = simple, q = Q, len = Len}) ->
{R, Q2} = queue:out(Q),
{R, maybe_clear_alarm(MQ#mqueue{q = Q2, len = Len - 1})};
out(MQ = #mqueue{type = priority, q = Q}) ->
{R, Q2} = priority_queue:out(Q),
{R, MQ#mqueue{q = Q2}}.
maybe_set_alarm(MQ = #mqueue{name = Name, len = Len, high_wm = HighWM, alarm_fun = AlarmFun}) maybe_set_alarm(MQ = #mqueue{name = Name, len = Len, high_wm = HighWM, alarm_fun = AlarmFun})
when Len > HighWM -> when Len > HighWM ->
Alarm = #mqtt_alarm{id = list_to_binary(["queue_high_watermark.", Name]), Alarm = #mqtt_alarm{id = iolist_to_binary(["queue_high_watermark.", Name]),
severity = warning, severity = warning,
title = io_lib:format("Queue ~s high-water mark", [Name]), title = io_lib:format("Queue ~s high-water mark", [Name]),
summary = io_lib:format("queue len ~p > high_watermark ~p", [Len, HighWM])}, summary = io_lib:format("queue len ~p > high_watermark ~p", [Len, HighWM])},

View File

@ -19,7 +19,7 @@
%%% OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE %%% OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
%%% SOFTWARE. %%% SOFTWARE.
%%%----------------------------------------------------------------------------- %%%-----------------------------------------------------------------------------
%%% @doc Message Router on Local Node. %%% @doc Message Router on local node.
%%% %%%
%%% @author Feng Lee <feng@emqtt.io> %%% @author Feng Lee <feng@emqtt.io>
%%%----------------------------------------------------------------------------- %%%-----------------------------------------------------------------------------
@ -52,8 +52,6 @@
-record(state, {pool, id, statsfun, aging :: #aging{}}). -record(state, {pool, id, statsfun, aging :: #aging{}}).
-type topic() :: binary().
%% @doc Start a local router. %% @doc Start a local router.
-spec start_link(atom(), pos_integer(), fun((atom()) -> ok), list()) -> {ok, pid()} | {error, any()}. -spec start_link(atom(), pos_integer(), fun((atom()) -> ok), list()) -> {ok, pid()} | {error, any()}.
start_link(Pool, Id, StatsFun, Env) -> start_link(Pool, Id, StatsFun, Env) ->
@ -61,7 +59,7 @@ start_link(Pool, Id, StatsFun, Env) ->
?MODULE, [Pool, Id, StatsFun, Env], []). ?MODULE, [Pool, Id, StatsFun, Env], []).
%% @doc Route Message on the local node. %% @doc Route Message on the local node.
-spec route(topic(), mqtt_message()) -> any(). -spec route(emqttd_topic:topic(), mqtt_message()) -> any().
route(Queue = <<"$Q/", _Q>>, Msg) -> route(Queue = <<"$Q/", _Q>>, Msg) ->
case lookup_routes(Queue) of case lookup_routes(Queue) of
[] -> [] ->
@ -87,12 +85,12 @@ route(Topic, Msg) ->
end. end.
%% @doc Has Route? %% @doc Has Route?
-spec has_route(topic()) -> boolean(). -spec has_route(emqttd_topic:topic()) -> boolean().
has_route(Topic) -> has_route(Topic) ->
ets:member(route, Topic). ets:member(route, Topic).
%% @doc Lookup Routes %% @doc Lookup Routes
-spec lookup_routes(topic()) -> list(pid()). -spec lookup_routes(emqttd_topic:topic()) -> list(pid()).
lookup_routes(Topic) when is_binary(Topic) -> lookup_routes(Topic) when is_binary(Topic) ->
case ets:member(route, Topic) of case ets:member(route, Topic) of
true -> true ->
@ -102,12 +100,12 @@ lookup_routes(Topic) when is_binary(Topic) ->
end. end.
%% @doc Add Route. %% @doc Add Route.
-spec add_route(topic(), pid()) -> ok. -spec add_route(emqttd_topic:topic(), pid()) -> ok.
add_route(Topic, Pid) when is_pid(Pid) -> add_route(Topic, Pid) when is_pid(Pid) ->
call(pick(Topic), {add_route, Topic, Pid}). call(pick(Topic), {add_route, Topic, Pid}).
%% @doc Add Routes. %% @doc Add Routes.
-spec add_routes(list(topic()), pid()) -> ok. -spec add_routes(list(emqttd_topic:topic()), pid()) -> ok.
add_routes([], _Pid) -> add_routes([], _Pid) ->
ok; ok;
add_routes([Topic], Pid) -> add_routes([Topic], Pid) ->
@ -119,12 +117,12 @@ add_routes(Topics, Pid) ->
end, slice(Topics)). end, slice(Topics)).
%% @doc Delete Route. %% @doc Delete Route.
-spec delete_route(topic(), pid()) -> ok. -spec delete_route(emqttd_topic:topic(), pid()) -> ok.
delete_route(Topic, Pid) -> delete_route(Topic, Pid) ->
cast(pick(Topic), {delete_route, Topic, Pid}). cast(pick(Topic), {delete_route, Topic, Pid}).
%% @doc Delete Routes. %% @doc Delete Routes.
-spec delete_routes(list(topic()), pid()) -> ok. -spec delete_routes(list(emqttd_topic:topic()), pid()) -> ok.
delete_routes([Topic], Pid) -> delete_routes([Topic], Pid) ->
delete_route(Topic, Pid); delete_route(Topic, Pid);
@ -136,13 +134,7 @@ delete_routes(Topics, Pid) ->
%% @private Slice topics. %% @private Slice topics.
slice(Topics) -> slice(Topics) ->
dict:to_list(lists:foldl(fun(Topic, Dict) -> dict:to_list(lists:foldl(fun(Topic, Dict) ->
Router = pick(Topic), dict:append(pick(Topic), Topic, Dict)
case dict:find(Router, Dict) of
{ok, L} ->
dict:store(Router, [Topic | L], Dict);
error ->
dict:store(Router, [Topic], Dict)
end
end, dict:new(), Topics)). end, dict:new(), Topics)).
%% @private Pick a router. %% @private Pick a router.
@ -162,7 +154,7 @@ init([Pool, Id, StatsFun, Opts]) ->
?GPROC_POOL(join, Pool, Id), ?GPROC_POOL(join, Pool, Id),
random:seed(os:timestamp()), random:seed(erlang:now()),
AgingSecs = proplists:get_value(route_aging, Opts, 5), AgingSecs = proplists:get_value(route_aging, Opts, 5),
@ -261,7 +253,7 @@ try_remove_topic(TopicR = #mqtt_topic{topic = Topic}) ->
%% Lock topic first %% Lock topic first
case mnesia:wread({topic, Topic}) of case mnesia:wread({topic, Topic}) of
[] -> [] ->
mnesia:abort(not_found); ok; %% mnesia:abort(not_found);
[TopicR] -> [TopicR] ->
%% Remove topic and trie %% Remove topic and trie
delete_topic(TopicR), delete_topic(TopicR),

View File

@ -37,11 +37,10 @@
%% calls into the same function knowing that ordinary queues represent %% calls into the same function knowing that ordinary queues represent
%% a base case. %% a base case.
-module(priority_queue). -module(priority_queue).
-export([new/0, is_queue/1, is_empty/1, len/1, to_list/1, from_list/1, -export([new/0, is_queue/1, is_empty/1, len/1, plen/2, to_list/1, from_list/1,
in/2, in/3, out/1, out_p/1, join/2, filter/2, fold/3, highest/1]). in/2, in/3, out/1, out/2, out_p/1, join/2, filter/2, fold/3, highest/1]).
%%---------------------------------------------------------------------------- %%----------------------------------------------------------------------------
@ -58,6 +57,7 @@
-spec(is_queue/1 :: (any()) -> boolean()). -spec(is_queue/1 :: (any()) -> boolean()).
-spec(is_empty/1 :: (pqueue()) -> boolean()). -spec(is_empty/1 :: (pqueue()) -> boolean()).
-spec(len/1 :: (pqueue()) -> non_neg_integer()). -spec(len/1 :: (pqueue()) -> non_neg_integer()).
-spec(len_p/2 :: (priority(), pqueue()) -> non_neg_integer()).
-spec(to_list/1 :: (pqueue()) -> [{priority(), any()}]). -spec(to_list/1 :: (pqueue()) -> [{priority(), any()}]).
-spec(from_list/1 :: ([{priority(), any()}]) -> pqueue()). -spec(from_list/1 :: ([{priority(), any()}]) -> pqueue()).
-spec(in/2 :: (any(), pqueue()) -> pqueue()). -spec(in/2 :: (any(), pqueue()) -> pqueue()).
@ -96,6 +96,16 @@ len({queue, _R, _F, L}) ->
len({pqueue, Queues}) -> len({pqueue, Queues}) ->
lists:sum([len(Q) || {_, Q} <- Queues]). lists:sum([len(Q) || {_, Q} <- Queues]).
plen(0, {queue, _R, _F, L}) ->
L;
plen(P, {queue, _R, _F, _}) ->
erlang:error(badarg, [P]);
plen(P, {pqueue, Queues}) ->
case lists:keysearch(maybe_negate_priority(P), 1, Queues) of
{value, {_, Q}} -> len(Q);
false -> 0
end.
to_list({queue, In, Out, _Len}) when is_list(In), is_list(Out) -> to_list({queue, In, Out, _Len}) when is_list(In), is_list(Out) ->
[{0, V} || V <- Out ++ lists:reverse(In, [])]; [{0, V} || V <- Out ++ lists:reverse(In, [])];
to_list({pqueue, Queues}) -> to_list({pqueue, Queues}) ->
@ -159,6 +169,28 @@ out({pqueue, [{P, Q} | Queues]}) ->
out_p({queue, _, _, _} = Q) -> add_p(out(Q), 0); out_p({queue, _, _, _} = Q) -> add_p(out(Q), 0);
out_p({pqueue, [{P, _} | _]} = Q) -> add_p(out(Q), maybe_negate_priority(P)). out_p({pqueue, [{P, _} | _]} = Q) -> add_p(out(Q), maybe_negate_priority(P)).
out(0, {queue, _, _, _} = Q) ->
out(Q);
out(Priority, {queue, _, _, _}) ->
erlang:error(badarg, [Priority]);
out(Priority, {pqueue, Queues}) ->
P = maybe_negate_priority(Priority),
case lists:keysearch(P, 1, Queues) of
{value, {_, Q}} ->
{R, Q1} = out(Q),
Queues1 = case is_empty(Q1) of
true -> lists:keydelete(P, 1, Queues);
false -> lists:keyreplace(P, 1, Queues, {P, Q1})
end,
{R, case Queues1 of
[] -> {queue, [], [], 0};
[{0, OnlyQ}] -> OnlyQ;
[_|_] -> {pqueue, Queues1}
end};
false ->
{empty, {pqueue, Queues}}
end.
add_p(R, P) -> case R of add_p(R, P) -> case R of
{empty, Q} -> {empty, Q}; {empty, Q} -> {empty, Q};
{{value, V}, Q} -> {{value, V, P}, Q} {{value, V}, Q} -> {{value, V, P}, Q}