Skip to content

Commit

Permalink
consisten hashing, ring
Browse files Browse the repository at this point in the history
  • Loading branch information
fogfish committed Sep 17, 2012
1 parent 943ea9b commit 986ca0a
Show file tree
Hide file tree
Showing 5 changed files with 140 additions and 0 deletions.
Empty file modified src/bits.erl 100755 → 100644
Empty file.
Empty file modified src/gbt.erl 100755 → 100644
Empty file.
Empty file modified src/hashmap.erl 100755 → 100644
Empty file.
101 changes: 101 additions & 0 deletions src/ring.erl
@@ -0,0 +1,101 @@
%%
%% Copyright 2012 Dmitry Kolesnikov, All Rights Reserved
%%
%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License.
%% You may obtain a copy of the License at
%%
%% http://www.apache.org/licenses/LICENSE-2.0
%%
%% Unless required by applicable law or agreed to in writing, software
%% distributed under the License is distributed on an "AS IS" BASIS,
%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
%% See the License for the specific language governing permissions and
%% limitations under the License.
%%
%% @description
%% consistent hash
-module(ring).

-export([new/0, new/1, join/3, leave/2]).
-export([whereis/2, members/1, size/1]).

%%
-record(ring, {
keylen = 128, % length of key
hash = md5, % hashing
replica= 1, % number of replica
nodes = [] % nodes
}).


%%
%% create new ring
new() ->
#ring{}.
new(Opts) ->
new(Opts, #ring{}).

new([{hash, X} | Opts], R) ->
new(Opts, R#ring{hash=X});
new([{keylen, X} | Opts], R) ->
new(Opts, R#ring{keylen=X});
new([{replica, X} | Opts], R) ->
new(Opts, R#ring{replica=X});
new([], R) ->
R.

%%
%% join node to the ring
join(Key, Node, #ring{nodes=Nodes}=R) ->
Addr = addr_to_int(Key, R),
List = case lists:keytake(Addr, 1, Nodes) of
false -> Nodes;
{value, _, L} -> L
end,
R#ring{nodes=[{Addr, Node} | List]}.

%%
%% leave node
leave(Key, #ring{nodes=Nodes}=R) ->
Addr = addr_to_int(Key, R),
case lists:keytake(Addr, 1, Nodes) of
false -> R;
{value, _, NN} -> R#ring{nodes=NN}
end.

%%
%% return list of nodes for the key
whereis(Key, #ring{replica=N, nodes=Nodes}=R) ->
Addr = addr_to_int(Key, R),
{T, H} = lists:partition(
fun({X, _}) -> X >= Addr end,
lists:usort(Nodes)
),
List = case length(T) of
Len when Len >= N -> lists:sublist(T, N);
Len -> T ++ lists:sublist(H, N - Len)
end,
lists:map(fun({_, Peer}) -> Peer end, List).

%%
%% return list of ring members
members(#ring{nodes=Nodes}) ->
lists:map(fun({_, Peer}) -> Peer end, Nodes).

%%
%% number of members
size(#ring{nodes=Nodes}) ->
length(Nodes).


%%
%%
addr_to_int(Addr, #ring{keylen=Len})
when is_binary(Addr) ->
Size = erlang:min(bit_size(Addr), Len),
<<Int:Size, _/bits>> = Addr,
Int;

addr_to_int(Addr, #ring{hash=md5}) ->
erlang:md5(term_to_binary(Addr)).
39 changes: 39 additions & 0 deletions test/ring_tests.erl
@@ -0,0 +1,39 @@
%%
%% Copyright 2012 Dmitry Kolesnikov, All Rights Reserved
%%
%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License.
%% You may obtain a copy of the License at
%%
%% http://www.apache.org/licenses/LICENSE-2.0
%%
%% Unless required by applicable law or agreed to in writing, software
%% distributed under the License is distributed on an "AS IS" BASIS,
%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
%% See the License for the specific language governing permissions and
%% limitations under the License.
%%
%% @description
%%
-module(ring_tests).
-include_lib("eunit/include/eunit.hrl").

join_leave_by_addr_test() ->
R0 = ring:new(),
R1 = ring:join(<<0,0,0>>, localhost, R0),
[localhost] = ring:members(R1),
R2 = ring:leave(<<0,0,0>>, R1),
[] = ring:members(R2).

key_test() ->
Nodes = [
{<<16#0,0>>, node0},
{<<16#4,0>>, node1},
{<<16#8,0>>, node2},
{<<16#b,0>>, node3},
{<<16#f,0>>, node4}
],
R = lists:foldl(fun({Key, Node}, R) -> ring:join(Key, Node, R) end, ring:new(), Nodes),
[node1] = ring:whereis(<<16#3, 0>>, R),
[node2] = ring:whereis(<<16#5, 0>>, R),
[node0] = ring:whereis(<<16#f, 1>>, R).

0 comments on commit 986ca0a

Please sign in to comment.