ch 0.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/.gitignore +14 -0
- data/Gemfile +10 -0
- data/Guardfile +7 -0
- data/LICENSE.txt +22 -0
- data/README.md +62 -0
- data/Rakefile +2 -0
- data/ch.gemspec +24 -0
- data/lib/ch.rb +2 -0
- data/lib/ch/avl_tree.rb +50 -0
- data/lib/ch/ring.rb +45 -0
- data/lib/ch/version.rb +3 -0
- data/spec/ch/ring_spec.rb +86 -0
- data/spec/spec_helper.rb +1 -0
- metadata +101 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 1eae3db0f2ca5c86f6a00459af2765dbe0663cc6
|
4
|
+
data.tar.gz: 43915f85e350d3af504c1ab7d65f542d40df5e84
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 2a9a5db6026a3b10a7446ff0f75cefc00ad134e12e2ce44b72a5447e3dfba2e1ea3d90c2843107f6560e22354cde0b9599fa327edc907f7a2e780d6d7fe65a9b
|
7
|
+
data.tar.gz: 19d53368cfd2c2b77806317610d0c2a7462ec00763a0688883730b5dabed6e69e21afd0f7a3d83ad00c58c700dc716bed6e53daba6130bde1569e4640641d5ea
|
data/.gitignore
ADDED
data/Gemfile
ADDED
data/Guardfile
ADDED
data/LICENSE.txt
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
Copyright (c) 2014 Forrest Ye
|
2
|
+
|
3
|
+
MIT License
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
6
|
+
a copy of this software and associated documentation files (the
|
7
|
+
"Software"), to deal in the Software without restriction, including
|
8
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
9
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
10
|
+
permit persons to whom the Software is furnished to do so, subject to
|
11
|
+
the following conditions:
|
12
|
+
|
13
|
+
The above copyright notice and this permission notice shall be
|
14
|
+
included in all copies or substantial portions of the Software.
|
15
|
+
|
16
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
17
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
18
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
19
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
20
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
21
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
22
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,62 @@
|
|
1
|
+
# ch gem
|
2
|
+
|
3
|
+
yet another consistent hashing library
|
4
|
+
|
5
|
+
Implemented using AVLTree.
|
6
|
+
|
7
|
+
Allow nodes to be inserted into multiple positions.
|
8
|
+
|
9
|
+
Allow nodes to be inserted into specific positions for more granular control.
|
10
|
+
|
11
|
+
## Installation
|
12
|
+
|
13
|
+
Add this line to your application's Gemfile:
|
14
|
+
|
15
|
+
```ruby
|
16
|
+
gem 'ch'
|
17
|
+
```
|
18
|
+
|
19
|
+
And then execute:
|
20
|
+
|
21
|
+
$ bundle
|
22
|
+
|
23
|
+
Or install it yourself as:
|
24
|
+
|
25
|
+
$ gem install ch
|
26
|
+
|
27
|
+
## Usage
|
28
|
+
|
29
|
+
```ruby
|
30
|
+
require 'ch'
|
31
|
+
ring = ConsistentHashing::Ring.new
|
32
|
+
|
33
|
+
# add nodes
|
34
|
+
ring.add_node('127.0.0.1:6379') # key 102336333644841978549106395032298540172546507605
|
35
|
+
ring.add_node('127.0.0.1:6380') # key 455838294994277962587720662485947692006035699684
|
36
|
+
|
37
|
+
# between nodes
|
38
|
+
ring.node_for_key(1).should == '127.0.0.1:6380' # key 304942582444936629325699363757435820077590259883
|
39
|
+
|
40
|
+
# larger than both nodes
|
41
|
+
ring.node_for_key(2).should == '127.0.0.1:6379' # key 1246245281287062843477446394631337292330716631216
|
42
|
+
|
43
|
+
# pin point!
|
44
|
+
ring.add_node('127.0.0.1:6381', 304942582444936629325699363757435820077590259883 + 1)
|
45
|
+
ring.node_for_key(1).should == '127.0.0.1:6381'
|
46
|
+
|
47
|
+
|
48
|
+
# key is simply computed with:
|
49
|
+
Digest::SHA1.hexdigest(key.to_s).hex
|
50
|
+
```
|
51
|
+
|
52
|
+
## Credits
|
53
|
+
|
54
|
+
- [https://github.com/domnikl/consistent-hashing](https://github.com/domnikl/consistent-hashing)
|
55
|
+
|
56
|
+
## Contributing
|
57
|
+
|
58
|
+
1. Fork it ( https://github.com/forresty/ch/fork )
|
59
|
+
2. Create your feature branch (`git checkout -b my-new-feature`)
|
60
|
+
3. Commit your changes (`git commit -am 'Add some feature'`)
|
61
|
+
4. Push to the branch (`git push origin my-new-feature`)
|
62
|
+
5. Create a new Pull Request
|
data/Rakefile
ADDED
data/ch.gemspec
ADDED
@@ -0,0 +1,24 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
lib = File.expand_path('../lib', __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
require 'ch/version'
|
5
|
+
|
6
|
+
Gem::Specification.new do |spec|
|
7
|
+
spec.name = "ch"
|
8
|
+
spec.version = ConsistentHashing::VERSION
|
9
|
+
spec.authors = ["Forrest Ye"]
|
10
|
+
spec.email = ["afu@forresty.com"]
|
11
|
+
spec.summary = %q{ yet another consistent hashing library }
|
12
|
+
spec.homepage = "https://github.com/forresty/ch"
|
13
|
+
spec.license = "MIT"
|
14
|
+
|
15
|
+
spec.files = `git ls-files -z`.split("\x0")
|
16
|
+
spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
|
17
|
+
spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
|
18
|
+
spec.require_paths = ["lib"]
|
19
|
+
|
20
|
+
spec.add_development_dependency "bundler", "~> 1.7"
|
21
|
+
spec.add_development_dependency "rake", "~> 10.0"
|
22
|
+
|
23
|
+
spec.add_runtime_dependency 'avl_tree', '1.1.3'
|
24
|
+
end
|
data/lib/ch.rb
ADDED
data/lib/ch/avl_tree.rb
ADDED
@@ -0,0 +1,50 @@
|
|
1
|
+
# originally from https://github.com/domnikl/consistent-hashing
|
2
|
+
|
3
|
+
require 'avl_tree'
|
4
|
+
|
5
|
+
module ConsistentHashing
|
6
|
+
class AVLTree < ::AVLTree
|
7
|
+
def minimum_pair()
|
8
|
+
# Return the key with the smallest key value.
|
9
|
+
return nil if @root.empty?
|
10
|
+
|
11
|
+
current_node = @root
|
12
|
+
while not current_node.left.empty?
|
13
|
+
current_node = current_node.left
|
14
|
+
end
|
15
|
+
|
16
|
+
[current_node.key, current_node.value]
|
17
|
+
end
|
18
|
+
|
19
|
+
def next_gte_pair(key)
|
20
|
+
# Returns the key/value pair with a key that follows the provided key in
|
21
|
+
# sorted order.
|
22
|
+
node = next_gte_node(@root, key)
|
23
|
+
[node.key, node.value] if not node.empty?
|
24
|
+
end
|
25
|
+
|
26
|
+
protected
|
27
|
+
|
28
|
+
def next_gte_node(node, key)
|
29
|
+
return AVLTree::Node::EMPTY if node.empty?
|
30
|
+
|
31
|
+
if key < node.key
|
32
|
+
# The current key qualifies as after the provided key. However, we need
|
33
|
+
# to check the tree on the left to see if there's a key in there also
|
34
|
+
# greater than the provided key but less than the current key.
|
35
|
+
after = next_gte_node(node.left, key)
|
36
|
+
after = node if after.empty?
|
37
|
+
elsif key > node.key
|
38
|
+
# The current key will not be after the provided key, but something
|
39
|
+
# in the right branch maybe. Check the right branch for the first key
|
40
|
+
# larger than our value.
|
41
|
+
after = next_gte_node(node.right, key)
|
42
|
+
elsif node.key == key
|
43
|
+
# An exact match qualifies as the next largest node.
|
44
|
+
after = node
|
45
|
+
end
|
46
|
+
|
47
|
+
return after
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
data/lib/ch/ring.rb
ADDED
@@ -0,0 +1,45 @@
|
|
1
|
+
require "digest/sha1"
|
2
|
+
require_relative 'avl_tree'
|
3
|
+
|
4
|
+
module ConsistentHashing
|
5
|
+
class Ring
|
6
|
+
def initialize
|
7
|
+
@tree = AVLTree.new
|
8
|
+
end
|
9
|
+
|
10
|
+
# a node can be inserted into multiple positions
|
11
|
+
def add_node(node, position=nil)
|
12
|
+
position ||= hash_for_key(node)
|
13
|
+
|
14
|
+
@tree[position] = node
|
15
|
+
end
|
16
|
+
|
17
|
+
def delete_node_at_position(position)
|
18
|
+
@tree.delete(position)
|
19
|
+
end
|
20
|
+
|
21
|
+
def delete_node(node)
|
22
|
+
@tree.each_key { |key| @tree.delete(key) if @tree[key] == node }
|
23
|
+
end
|
24
|
+
|
25
|
+
def node_for_key(key)
|
26
|
+
return nil if @tree.empty?
|
27
|
+
key = hash_for_key(key)
|
28
|
+
_, value = @tree.next_gte_pair(key)
|
29
|
+
_, value = @tree.minimum_pair unless value
|
30
|
+
value
|
31
|
+
end
|
32
|
+
|
33
|
+
def nodes
|
34
|
+
@tree.values.uniq
|
35
|
+
end
|
36
|
+
|
37
|
+
def positions
|
38
|
+
@tree.keys
|
39
|
+
end
|
40
|
+
|
41
|
+
def hash_for_key(key)
|
42
|
+
Digest::SHA1.hexdigest(key.to_s).hex
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
data/lib/ch/version.rb
ADDED
@@ -0,0 +1,86 @@
|
|
1
|
+
require "spec_helper"
|
2
|
+
|
3
|
+
module ConsistentHashing
|
4
|
+
describe Ring do
|
5
|
+
it { should respond_to :add_node }
|
6
|
+
it { should respond_to :nodes }
|
7
|
+
it { should respond_to :hash_for_key }
|
8
|
+
it { should respond_to :node_for_key }
|
9
|
+
it { should respond_to :delete_node_at_position }
|
10
|
+
it { should respond_to :delete_node }
|
11
|
+
|
12
|
+
describe '#add_node' do
|
13
|
+
it 'adds' do
|
14
|
+
subject.add_node('127.0.0.1:6379')
|
15
|
+
|
16
|
+
subject.nodes.should == %w{ 127.0.0.1:6379 }
|
17
|
+
|
18
|
+
subject.add_node('127.0.0.1:6379')
|
19
|
+
subject.add_node('127.0.0.1:6380')
|
20
|
+
|
21
|
+
subject.nodes.should == %w{ 127.0.0.1:6379 127.0.0.1:6380 }
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
describe '#node_for_key' do
|
26
|
+
it 'finds' do
|
27
|
+
# show hash keys
|
28
|
+
subject.hash_for_key('127.0.0.1:6379').should == 102336333644841978549106395032298540172546507605
|
29
|
+
subject.hash_for_key('127.0.0.1:6380').should == 455838294994277962587720662485947692006035699684
|
30
|
+
|
31
|
+
subject.hash_for_key(1).should == 304942582444936629325699363757435820077590259883
|
32
|
+
subject.hash_for_key(2).should == 1246245281287062843477446394631337292330716631216
|
33
|
+
subject.hash_for_key(3).should == 684329801336223661356952546078269889038938702779
|
34
|
+
subject.hash_for_key(4).should == 156380102318965990264666286018191900590658905210
|
35
|
+
|
36
|
+
|
37
|
+
# add nodes
|
38
|
+
subject.add_node('127.0.0.1:6379')
|
39
|
+
subject.add_node('127.0.0.1:6380')
|
40
|
+
|
41
|
+
# between nodes
|
42
|
+
subject.node_for_key(1).should == '127.0.0.1:6380'
|
43
|
+
|
44
|
+
# larger than both nodes
|
45
|
+
subject.node_for_key(2).should == '127.0.0.1:6379'
|
46
|
+
|
47
|
+
# pin point!
|
48
|
+
subject.add_node('127.0.0.1:6381', 304942582444936629325699363757435820077590259883 + 1)
|
49
|
+
subject.node_for_key(1).should == '127.0.0.1:6381'
|
50
|
+
|
51
|
+
# same point works as well
|
52
|
+
subject.add_node('127.0.0.1:6382', 1246245281287062843477446394631337292330716631216)
|
53
|
+
subject.node_for_key(2).should == '127.0.0.1:6382'
|
54
|
+
|
55
|
+
# same point can be inserted into multiple positions
|
56
|
+
subject.node_for_key(4).should == '127.0.0.1:6381'
|
57
|
+
subject.add_node('127.0.0.1:6379', 156380102318965990264666286018191900590658905210)
|
58
|
+
subject.node_for_key(4).should == '127.0.0.1:6379'
|
59
|
+
# remove pinned point
|
60
|
+
subject.delete_node_at_position(1246245281287062843477446394631337292330716631216)
|
61
|
+
subject.node_for_key(2).should == '127.0.0.1:6379'
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
describe '#delete_node' do
|
66
|
+
it 'deletes all positions' do
|
67
|
+
subject.add_node('127.0.0.1:6379', 1)
|
68
|
+
subject.add_node('127.0.0.1:6379', 2)
|
69
|
+
|
70
|
+
subject.positions.should == [1, 2]
|
71
|
+
subject.nodes.count.should == 1
|
72
|
+
|
73
|
+
subject.delete_node('127.0.0.1:6379')
|
74
|
+
|
75
|
+
subject.positions.should == []
|
76
|
+
subject.nodes.count.should == 0
|
77
|
+
end
|
78
|
+
end
|
79
|
+
|
80
|
+
describe '#hash_for_key' do
|
81
|
+
it 'generates different hash' do
|
82
|
+
subject.hash_for_key(1).should_not == subject.hash_for_key(2)
|
83
|
+
end
|
84
|
+
end
|
85
|
+
end
|
86
|
+
end
|
data/spec/spec_helper.rb
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
require "ch"
|
metadata
ADDED
@@ -0,0 +1,101 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: ch
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.1
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Forrest Ye
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2014-12-26 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: bundler
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - ~>
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '1.7'
|
20
|
+
type: :development
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - ~>
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '1.7'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: rake
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - ~>
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '10.0'
|
34
|
+
type: :development
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - ~>
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '10.0'
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: avl_tree
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - '='
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: 1.1.3
|
48
|
+
type: :runtime
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - '='
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: 1.1.3
|
55
|
+
description:
|
56
|
+
email:
|
57
|
+
- afu@forresty.com
|
58
|
+
executables: []
|
59
|
+
extensions: []
|
60
|
+
extra_rdoc_files: []
|
61
|
+
files:
|
62
|
+
- .gitignore
|
63
|
+
- Gemfile
|
64
|
+
- Guardfile
|
65
|
+
- LICENSE.txt
|
66
|
+
- README.md
|
67
|
+
- Rakefile
|
68
|
+
- ch.gemspec
|
69
|
+
- lib/ch.rb
|
70
|
+
- lib/ch/avl_tree.rb
|
71
|
+
- lib/ch/ring.rb
|
72
|
+
- lib/ch/version.rb
|
73
|
+
- spec/ch/ring_spec.rb
|
74
|
+
- spec/spec_helper.rb
|
75
|
+
homepage: https://github.com/forresty/ch
|
76
|
+
licenses:
|
77
|
+
- MIT
|
78
|
+
metadata: {}
|
79
|
+
post_install_message:
|
80
|
+
rdoc_options: []
|
81
|
+
require_paths:
|
82
|
+
- lib
|
83
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
84
|
+
requirements:
|
85
|
+
- - '>='
|
86
|
+
- !ruby/object:Gem::Version
|
87
|
+
version: '0'
|
88
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
89
|
+
requirements:
|
90
|
+
- - '>='
|
91
|
+
- !ruby/object:Gem::Version
|
92
|
+
version: '0'
|
93
|
+
requirements: []
|
94
|
+
rubyforge_project:
|
95
|
+
rubygems_version: 2.4.4
|
96
|
+
signing_key:
|
97
|
+
specification_version: 4
|
98
|
+
summary: yet another consistent hashing library
|
99
|
+
test_files:
|
100
|
+
- spec/ch/ring_spec.rb
|
101
|
+
- spec/spec_helper.rb
|