diff --git a/src/bits.erl b/src/bits.erl old mode 100755 new mode 100644 diff --git a/src/gbt.erl b/src/gbt.erl old mode 100755 new mode 100644 diff --git a/src/hashmap.erl b/src/hashmap.erl old mode 100755 new mode 100644 diff --git a/src/ring.erl b/src/ring.erl new file mode 100644 index 0000000..ad52f44 --- /dev/null +++ b/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), + <> = Addr, + Int; + +addr_to_int(Addr, #ring{hash=md5}) -> + erlang:md5(term_to_binary(Addr)). diff --git a/test/ring_tests.erl b/test/ring_tests.erl new file mode 100644 index 0000000..8d01b3e --- /dev/null +++ b/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). \ No newline at end of file