Skip to content

Instantly share code, notes, and snippets.

@roowe
Created July 27, 2014 13:58
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save roowe/a1c0aeabda42f53db608 to your computer and use it in GitHub Desktop.
Save roowe/a1c0aeabda42f53db608 to your computer and use it in GitHub Desktop.
%% 排行榜引擎是从小到大排序
%% 清理要从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)).
-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