-
-
Save roowe/a1c0aeabda42f53db608 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
%% 排行榜引擎是从小到大排序 | |
%% 清理要从first开始,然后next | |
%% top要从last开始取,然后prev | |
%% rank值计算,获取当前的key之后,next到last,计算有多少个位置 | |
%% 加了个CountTable维护主值的计数器,减少遍历排行榜。 | |
-module(lib_big_rank). | |
-include("common.hrl"). | |
-include("define_rank.hrl"). | |
-include("define_mnesia.hrl"). | |
-export([insert/7, top/2, rank/4]). | |
-export([clean_over_flow/2]). | |
insert(Table, MaxSize, Value, OldValue, | |
CountTable, MasterValue, OldMasterValue) -> | |
hdb:transaction(fun() -> | |
delete(Table, OldValue, CountTable, OldMasterValue) | |
end), | |
hdb:transaction(fun() -> | |
case check_insert(Table, MaxSize, Value) of | |
false -> | |
update_counter(CountTable, MasterValue, 1); | |
true -> | |
update_counter(CountTable, MasterValue, 1), | |
mnesia:write(Table, #big_rank{ | |
value = Value, | |
master_value = MasterValue | |
}, write) | |
end | |
end). | |
check_insert(Table, MaxSize, Value) -> | |
Size = mnesia:table_info(Table, size), | |
LowValue = mnesia:first(Table), | |
Size =< MaxSize orelse | |
Value > LowValue. | |
delete(Table, Value, CountTable, MasterValue) -> | |
mnesia:delete({Table, Value}), | |
update_counter(CountTable, MasterValue, -1). | |
clean_over_flow(Table, MaxSize) -> | |
hdb:transaction(fun() -> | |
?DEBUG("clean_over_flow"), | |
Size = mnesia:table_info(Table, size), | |
Num = Size - MaxSize, | |
if | |
Num > 0 -> | |
?DEBUG("run ~p ~n", [Num]), | |
mnesia:lock_table(Table, write), | |
clean_over_flow2(Table, mnesia:first(Table), Num), | |
?DEBUG("run end ~n", []); | |
true -> | |
ignore | |
end | |
end). | |
clean_over_flow2(_, '$end_of_table', _) -> | |
ok; | |
clean_over_flow2(Table, Key, Num) | |
when Num > 0 -> | |
%delete(Table, Key, CountTable), | |
mnesia:delete({Table, Key}), | |
clean_over_flow2(Table, mnesia:next(Table, Key), Num-1); | |
clean_over_flow2(_, _, _) -> | |
ok. | |
%% 不用dirty_update_counter的原因是因为从dets加载到ets的时候,数据会乱掉,原因未明,估计是log的问题 | |
%% 但是用id自增是没有问题,因为保证唯一即可,奇怪自增一些并不是什么大问题 | |
update_counter(CountTable, MasterValue, Incr) -> | |
mnesia:write(CountTable, | |
case mnesia:wread({CountTable, MasterValue}) of | |
[] -> | |
#counter{ | |
type = MasterValue, | |
counter = erlang:max(0, Incr) | |
}; | |
[Counter] -> | |
Counter#counter{ | |
counter = erlang:max(0, Counter#counter.counter + Incr) | |
} | |
end, write). | |
top(Table, Len) -> | |
top2(Table, hdb:dirty_last(Table), Len). | |
top2(_, '$end_of_table', _) -> | |
[]; | |
top2(_, _, 0) -> | |
[]; | |
top2(Table, Key, Num) -> | |
[hdb:dirty_read(Table, Key)|top2(Table, hdb:dirty_prev(Table, Key), Num - 1)]. | |
rank(Table, Value, MasterValue, CountTable) -> | |
case hdb:dirty_read(Table, Value) of | |
[] -> | |
1 + counter(CountTable, MasterValue); | |
#big_rank{ | |
master_value = MasterValue | |
} -> | |
rank2(Table, hdb:dirty_next(Table, Value), CountTable, MasterValue, 0) + 1 | |
end. | |
%%算出前面还有几个人 | |
rank2(_, '$end_of_table', CountTable, MasterValue, Num) -> | |
Num + counter(CountTable, MasterValue); | |
rank2(Table, Key, CountTable, MasterValue, Num) -> | |
case hdb:dirty_read(Table, Key) of | |
#big_rank{ | |
master_value = MasterValue | |
} -> | |
rank2(Table, hdb:dirty_next(Table, Key), CountTable, MasterValue, Num + 1); | |
_ -> | |
Num + counter(CountTable, MasterValue) | |
end. | |
counter(CountTable, MasterValue) -> | |
lists:sum(hdb:async_dirty(fun() -> | |
QH = qlc:q([ Counter || #counter{ | |
type = Value, | |
counter = Counter | |
} <- mnesia:table(CountTable), | |
Value > MasterValue]), | |
qlc:e(QH) | |
end)). | |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
-module(mod_big_rank). | |
-behaviour(gen_server). | |
%% API | |
-export([start_link/1]). | |
-export([insert/5]). | |
%% gen_server callbacks | |
-export([init/1, handle_call/3, handle_cast/2, handle_info/2, | |
terminate/2, code_change/3]). | |
-include("common.hrl"). | |
-define(SERVER, ?MODULE). | |
-record(state, { | |
table, | |
count_table, | |
max_size | |
}). | |
%%%=================================================================== | |
%%% API | |
%%%=================================================================== | |
insert(Process, Value, OldValue, MasterValue, OldMasterValue) -> | |
gen_server:cast(Process, {insert, Value, OldValue, MasterValue, OldMasterValue}). | |
%%-------------------------------------------------------------------- | |
%% @doc | |
%% Starts the server | |
%% | |
%% @spec start_link() -> {ok, Pid} | ignore | {error, Error} | |
%% @end | |
%%-------------------------------------------------------------------- | |
start_link([Process, Table, CountTable, MaxSize]) -> | |
gen_server:start_link({local, Process}, ?MODULE, [Table, CountTable, MaxSize], []). | |
%%%=================================================================== | |
%%% gen_server callbacks | |
%%%=================================================================== | |
%%-------------------------------------------------------------------- | |
%% @private | |
%% @doc | |
%% Initializes the server | |
%% | |
%% @spec init(Args) -> {ok, State} | | |
%% {ok, State, Timeout} | | |
%% ignore | | |
%% {stop, Reason} | |
%% @end | |
%%-------------------------------------------------------------------- | |
init([Table, CountTable, MaxSize]) -> | |
{ok, #state{ | |
table = Table, | |
count_table = CountTable, | |
max_size = MaxSize | |
}}. | |
%%-------------------------------------------------------------------- | |
%% @private | |
%% @doc | |
%% Handling call messages | |
%% | |
%% @spec handle_call(Request, From, State) -> | |
%% {reply, Reply, State} | | |
%% {reply, Reply, State, Timeout} | | |
%% {noreply, State} | | |
%% {noreply, State, Timeout} | | |
%% {stop, Reason, Reply, State} | | |
%% {stop, Reason, State} | |
%% @end | |
%%-------------------------------------------------------------------- | |
handle_call(_Request, _From, State) -> | |
?WARNING_MSG("unknow request: ~p~n", [_Request]), | |
Reply = ok, | |
{reply, Reply, State}. | |
%%-------------------------------------------------------------------- | |
%% @private | |
%% @doc | |
%% Handling cast messages | |
%% | |
%% @spec handle_cast(Msg, State) -> {noreply, State} | | |
%% {noreply, State, Timeout} | | |
%% {stop, Reason, State} | |
%% @end | |
%%-------------------------------------------------------------------- | |
handle_cast({insert, Value, OldValue, MasterValue, OldMasterValue}, | |
#state{ | |
table = Table, | |
count_table = CountTable, | |
max_size = MaxSize | |
} = State) -> | |
lib_big_rank:insert(Table, MaxSize, Value, OldValue, | |
CountTable, MasterValue, OldMasterValue), | |
{noreply, State, 0}; | |
handle_cast(_Msg, State) -> | |
?WARNING_MSG("unknow cast: ~p~n", [_Msg]), | |
{noreply, State}. | |
%%-------------------------------------------------------------------- | |
%% @private | |
%% @doc | |
%% Handling all non call/cast messages | |
%% | |
%% @spec handle_info(Info, State) -> {noreply, State} | | |
%% {noreply, State, Timeout} | | |
%% {stop, Reason, State} | |
%% @end | |
%%-------------------------------------------------------------------- | |
handle_info(timeout, #state{ | |
table = Table, | |
max_size = MaxSize | |
} = State) -> | |
lib_big_rank:clean_over_flow(Table, MaxSize), | |
{noreply, State}; | |
handle_info(_Info, State) -> | |
?WARNING_MSG("unknow info: ~p~n", [_Info]), | |
{noreply, State}. | |
%%-------------------------------------------------------------------- | |
%% @private | |
%% @doc | |
%% This function is called by a gen_server when it is about to | |
%% terminate. It should be the opposite of Module:init/1 and do any | |
%% necessary cleaning up. When it returns, the gen_server terminates | |
%% with Reason. The return value is ignored. | |
%% | |
%% @spec terminate(Reason, State) -> void() | |
%% @end | |
%%-------------------------------------------------------------------- | |
terminate(_Reason, _State) -> | |
ok. | |
%%-------------------------------------------------------------------- | |
%% @private | |
%% @doc | |
%% Convert process state when code is changed | |
%% | |
%% @spec code_change(OldVsn, State, Extra) -> {ok, NewState} | |
%% @end | |
%%-------------------------------------------------------------------- | |
code_change(_OldVsn, State, _Extra) -> | |
{ok, State}. | |
%%%=================================================================== | |
%%% Internal functions | |
%%%=================================================================== |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment