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.
@@ -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
@@ -0,0 +1,14 @@
1
+ /.bundle/
2
+ /.yardoc
3
+ /Gemfile.lock
4
+ /_yardoc/
5
+ /coverage/
6
+ /doc/
7
+ /pkg/
8
+ /spec/reports/
9
+ /tmp/
10
+ *.bundle
11
+ *.so
12
+ *.o
13
+ *.a
14
+ mkmf.log
data/Gemfile ADDED
@@ -0,0 +1,10 @@
1
+ source 'https://ruby.taobao.org'
2
+
3
+ group :development, :test do
4
+ # rspec and guard
5
+ gem 'rspec', '~> 2.12.0'
6
+ gem 'guard', '~> 2.8.2'
7
+ gem 'guard-rspec'
8
+ end
9
+
10
+ gemspec
@@ -0,0 +1,7 @@
1
+ ENV['GUARD_GEM_SILENCE_DEPRECATIONS'] = '1'
2
+
3
+ guard 'rspec' do
4
+ watch(%r{^spec/.+_spec\.rb$})
5
+ watch(%r{^lib/(.+)\.rb$}) { |m| "spec/#{m[1]}_spec.rb" }
6
+ watch('spec/spec_helper.rb') { "spec" }
7
+ end
@@ -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.
@@ -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
@@ -0,0 +1,2 @@
1
+ require "bundler/gem_tasks"
2
+
@@ -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
@@ -0,0 +1,2 @@
1
+ require_relative "ch/version"
2
+ require_relative "ch/ring"
@@ -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
@@ -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
@@ -0,0 +1,3 @@
1
+ module ConsistentHashing
2
+ VERSION = "0.0.1"
3
+ end
@@ -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
@@ -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