ezmobius-redis-rb 0.0.3

Sign up to get free protection for your applications and to get access to all the features.
data/LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2009 Ezra Zygmuntowicz
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.markdown ADDED
@@ -0,0 +1,31 @@
1
+ # redis-rb
2
+
3
+ A ruby client library for the redis key value storage system.
4
+
5
+ ## Information about redis
6
+
7
+ Redis is a key value store with some interesting features:
8
+ 1. It's fast.
9
+ 2. Keys are strings but values can have types of "NONE", "STRING", "LIST", or "SET". List's can be atomically push'd, pop'd, lpush'd, lpop'd and indexed. This allows you to store things like lists of comments under one key while retaining the ability to append comments without reading and putting back the whole list.
10
+
11
+ See [redis on code.google.com](http://code.google.com/p/redis/wiki/README) for more information.
12
+
13
+ ## Dependencies
14
+
15
+ 1. redis -
16
+
17
+ rake redis:install
18
+
19
+ 2. dtach -
20
+
21
+ rake dtach:install
22
+
23
+ 3. svn - git is the new black, but we need it for the google codes.
24
+
25
+ ## Setup
26
+
27
+ Use the tasks mentioned above (in Dependencies) to get your machine setup.
28
+
29
+ ## Examples
30
+
31
+ Check the examples/ directory. *Note* you need to have redis-server running first.
data/Rakefile ADDED
@@ -0,0 +1,58 @@
1
+ require 'rubygems'
2
+ require 'rake/gempackagetask'
3
+ require 'rubygems/specification'
4
+ require 'date'
5
+ require 'spec/rake/spectask'
6
+ require 'tasks/redis.tasks'
7
+
8
+
9
+ GEM = 'redis'
10
+ GEM_VERSION = '0.0.3'
11
+ AUTHORS = ['Ezra Zygmuntowicz', 'Taylor Weibley']
12
+ EMAIL = "ez@engineyard.com"
13
+ HOMEPAGE = "http://github.com/ezmobius/redis-rb"
14
+ SUMMARY = "Ruby client library for redis key value storage server"
15
+
16
+ spec = Gem::Specification.new do |s|
17
+ s.name = GEM
18
+ s.version = GEM_VERSION
19
+ s.platform = Gem::Platform::RUBY
20
+ s.has_rdoc = true
21
+ s.extra_rdoc_files = ["LICENSE"]
22
+ s.summary = SUMMARY
23
+ s.description = s.summary
24
+ s.authors = AUTHORS
25
+ s.email = EMAIL
26
+ s.homepage = HOMEPAGE
27
+
28
+ # Uncomment this to add a dependency
29
+ # s.add_dependency "foo"
30
+
31
+ s.require_path = 'lib'
32
+ s.autorequire = GEM
33
+ s.files = %w(LICENSE README.markdown Rakefile) + Dir.glob("{lib,spec}/**/*")
34
+ end
35
+
36
+ task :default => :spec
37
+
38
+ desc "Run specs"
39
+ Spec::Rake::SpecTask.new do |t|
40
+ t.spec_files = FileList['spec/**/*_spec.rb']
41
+ t.spec_opts = %w(-fs --color)
42
+ end
43
+
44
+ Rake::GemPackageTask.new(spec) do |pkg|
45
+ pkg.gem_spec = spec
46
+ end
47
+
48
+ desc "install the gem locally"
49
+ task :install => [:package] do
50
+ sh %{sudo gem install pkg/#{GEM}-#{GEM_VERSION}}
51
+ end
52
+
53
+ desc "create a gemspec file"
54
+ task :make_spec do
55
+ File.open("#{GEM}.gemspec", "w") do |file|
56
+ file.puts spec.to_ruby
57
+ end
58
+ end
@@ -0,0 +1,191 @@
1
+ #--
2
+ # = timeout.rb
3
+ #
4
+ # execution timeout
5
+ #
6
+ # = Copyright
7
+ #
8
+ # Copyright - (C) 2008 Evan Phoenix
9
+ # Copyright:: (C) 2000 Network Applied Communication Laboratory, Inc.
10
+ # Copyright:: (C) 2000 Information-technology Promotion Agency, Japan
11
+ #
12
+ #++
13
+ #
14
+ # = Description
15
+ #
16
+ # A way of performing a potentially long-running operation in a thread, and
17
+ # terminating it's execution if it hasn't finished within fixed amount of
18
+ # time.
19
+ #
20
+ # Previous versions of timeout didn't use a module for namespace. This version
21
+ # provides both Timeout.timeout, and a backwards-compatible #timeout.
22
+ #
23
+ # = Synopsis
24
+ #
25
+ # require 'timeout'
26
+ # status = Timeout::timeout(5) {
27
+ # # Something that should be interrupted if it takes too much time...
28
+ # }
29
+ #
30
+
31
+ require 'thread'
32
+
33
+ module Timeout
34
+
35
+ ##
36
+ # Raised by Timeout#timeout when the block times out.
37
+
38
+ class Error<Interrupt
39
+ end
40
+
41
+ # A mutex to protect @requests
42
+ @mutex = Mutex.new
43
+
44
+ # All the outstanding TimeoutRequests
45
+ @requests = []
46
+
47
+ # Represents +thr+ asking for it to be timeout at in +secs+
48
+ # seconds. At timeout, raise +exc+.
49
+ class TimeoutRequest
50
+ def initialize(secs, thr, exc)
51
+ @left = secs
52
+ @thread = thr
53
+ @exception = exc
54
+ end
55
+
56
+ attr_reader :thread, :left
57
+
58
+ # Called because +time+ seconds have gone by. Returns
59
+ # true if the request has no more time left to run.
60
+ def elapsed(time)
61
+ @left -= time
62
+ @left <= 0
63
+ end
64
+
65
+ # Raise @exception if @thread.
66
+ def cancel
67
+ if @thread and @thread.alive?
68
+ @thread.raise @exception, "execution expired"
69
+ end
70
+
71
+ @left = 0
72
+ end
73
+
74
+ # Abort this request, ie, we don't care about tracking
75
+ # the thread anymore.
76
+ def abort
77
+ @thread = nil
78
+ @left = 0
79
+ end
80
+ end
81
+
82
+ def self.add_timeout(time, exc)
83
+
84
+ @controller ||= Thread.new do
85
+ while true
86
+ if @requests.empty?
87
+ sleep
88
+ next
89
+ end
90
+
91
+ min = nil
92
+
93
+ @mutex.synchronize do
94
+ min = @requests.min { |a,b| a.left <=> b.left }
95
+ end
96
+
97
+ slept_for = sleep(min.left)
98
+
99
+ @mutex.synchronize do
100
+ @requests.delete_if do |r|
101
+ if r.elapsed(slept_for)
102
+ r.cancel
103
+ true
104
+ else
105
+ false
106
+ end
107
+ end
108
+ end
109
+
110
+ end
111
+ end
112
+
113
+ req = TimeoutRequest.new(time, Thread.current, exc)
114
+
115
+ @mutex.synchronize do
116
+ @requests << req
117
+ end
118
+
119
+ @controller.run
120
+
121
+ return req
122
+ end
123
+
124
+ ##
125
+ # Executes the method's block. If the block execution terminates before +sec+
126
+ # seconds has passed, it returns true. If not, it terminates the execution
127
+ # and raises +exception+ (which defaults to Timeout::Error).
128
+ #
129
+ # Note that this is both a method of module Timeout, so you can 'include
130
+ # Timeout' into your classes so they have a #timeout method, as well as a
131
+ # module method, so you can call it directly as Timeout.timeout().
132
+
133
+ def timeout(sec, exception=Error)
134
+ return yield if sec == nil or sec.zero?
135
+ raise ThreadError, "timeout within critical session" if Thread.critical
136
+
137
+ req = Timeout.add_timeout sec, exception
138
+
139
+ begin
140
+ yield sec
141
+ ensure
142
+ req.abort
143
+ end
144
+ end
145
+
146
+ module_function :timeout
147
+
148
+ end
149
+
150
+ ##
151
+ # Identical to:
152
+ #
153
+ # Timeout::timeout(n, e, &block).
154
+ #
155
+ # Defined for backwards compatibility with earlier versions of timeout.rb, see
156
+ # Timeout#timeout.
157
+
158
+ def timeout(n, e=Timeout::Error, &block) # :nodoc:
159
+ Timeout::timeout(n, e, &block)
160
+ end
161
+
162
+ ##
163
+ # Another name for Timeout::Error, defined for backwards compatibility with
164
+ # earlier versions of timeout.rb.
165
+
166
+ class Object
167
+ remove_const(:TimeoutError) if const_defined?(:TimeoutError)
168
+ end
169
+ TimeoutError = Timeout::Error # :nodoc:
170
+
171
+ if __FILE__ == $0
172
+ p timeout(5) {
173
+ 45
174
+ }
175
+ p timeout(5, TimeoutError) {
176
+ 45
177
+ }
178
+ p timeout(nil) {
179
+ 54
180
+ }
181
+ p timeout(0) {
182
+ 54
183
+ }
184
+ p timeout(5) {
185
+ loop {
186
+ p 10
187
+ sleep 1
188
+ }
189
+ }
190
+ end
191
+
data/lib/dist_redis.rb ADDED
@@ -0,0 +1,111 @@
1
+ require 'redis'
2
+ require 'hash_ring'
3
+ class DistRedis
4
+ attr_reader :ring
5
+ def initialize(*servers)
6
+ srvs = []
7
+ servers.each do |s|
8
+ server, port = s.split(':')
9
+ srvs << Redis.new(:host => server, :port => port)
10
+ end
11
+ @ring = HashRing.new srvs
12
+ end
13
+
14
+ def node_for_key(key)
15
+ if key =~ /\{(.*)?\}/
16
+ key = $1
17
+ end
18
+ @ring.get_node(key)
19
+ end
20
+
21
+ def add_server(server)
22
+ server, port = server.split(':')
23
+ @ring.add_node Redis.new(:host => server, :port => port)
24
+ end
25
+
26
+ def method_missing(sym, *args, &blk)
27
+ if redis = node_for_key(args.first.to_s)
28
+ redis.send sym, *args, &blk
29
+ else
30
+ super
31
+ end
32
+ end
33
+
34
+ def keys(glob)
35
+ keyz = []
36
+ @ring.nodes.each do |red|
37
+ keyz.concat red.keys(glob)
38
+ end
39
+ keyz
40
+ end
41
+
42
+ def save
43
+ @ring.nodes.each do |red|
44
+ red.save
45
+ end
46
+ end
47
+
48
+ def bgsave
49
+ @ring.nodes.each do |red|
50
+ red.bgsave
51
+ end
52
+ end
53
+
54
+ def quit
55
+ @ring.nodes.each do |red|
56
+ red.quit
57
+ end
58
+ end
59
+
60
+ def delete_cloud!
61
+ @ring.nodes.each do |red|
62
+ red.keys("*").each do |key|
63
+ red.delete key
64
+ end
65
+ end
66
+ end
67
+
68
+ end
69
+
70
+
71
+ if __FILE__ == $0
72
+
73
+ r = DistRedis.new 'localhost:6379', 'localhost:6380', 'localhost:6381', 'localhost:6382'
74
+ r['urmom'] = 'urmom'
75
+ r['urdad'] = 'urdad'
76
+ r['urmom1'] = 'urmom1'
77
+ r['urdad1'] = 'urdad1'
78
+ r['urmom2'] = 'urmom2'
79
+ r['urdad2'] = 'urdad2'
80
+ r['urmom3'] = 'urmom3'
81
+ r['urdad3'] = 'urdad3'
82
+ p r['urmom']
83
+ p r['urdad']
84
+ p r['urmom1']
85
+ p r['urdad1']
86
+ p r['urmom2']
87
+ p r['urdad2']
88
+ p r['urmom3']
89
+ p r['urdad3']
90
+
91
+ r.push_tail 'listor', 'foo1'
92
+ r.push_tail 'listor', 'foo2'
93
+ r.push_tail 'listor', 'foo3'
94
+ r.push_tail 'listor', 'foo4'
95
+ r.push_tail 'listor', 'foo5'
96
+
97
+ p r.pop_tail('listor')
98
+ p r.pop_tail('listor')
99
+ p r.pop_tail('listor')
100
+ p r.pop_tail('listor')
101
+ p r.pop_tail('listor')
102
+
103
+ puts "key distribution:"
104
+
105
+ r.ring.nodes.each do |red|
106
+ p [red.port, red.keys("*")]
107
+ end
108
+ r.delete_cloud!
109
+ p r.keys('*')
110
+
111
+ end
data/lib/hash_ring.rb ADDED
@@ -0,0 +1,127 @@
1
+ require 'zlib'
2
+
3
+ class HashRing
4
+
5
+ POINTS_PER_SERVER = 160 # this is the default in libmemcached
6
+
7
+ attr_reader :ring, :sorted_keys, :replicas, :nodes
8
+
9
+ # nodes is a list of objects that have a proper to_s representation.
10
+ # replicas indicates how many virtual points should be used pr. node,
11
+ # replicas are required to improve the distribution.
12
+ def initialize(nodes=[], replicas=POINTS_PER_SERVER)
13
+ @replicas = replicas
14
+ @ring = {}
15
+ @nodes = []
16
+ @sorted_keys = []
17
+ nodes.each do |node|
18
+ add_node(node)
19
+ end
20
+ end
21
+
22
+ # Adds a `node` to the hash ring (including a number of replicas).
23
+ def add_node(node)
24
+ @nodes << node
25
+ @replicas.times do |i|
26
+ key = Zlib.crc32("#{node}:#{i}")
27
+ @ring[key] = node
28
+ @sorted_keys << key
29
+ end
30
+ @sorted_keys.sort!
31
+ end
32
+
33
+ def remove_node(node)
34
+ @replicas.times do |i|
35
+ key = Zlib.crc32("#{node}:#{count}")
36
+ @ring.delete(key)
37
+ @sorted_keys.reject! {|k| k == key}
38
+ end
39
+ end
40
+
41
+ # get the node in the hash ring for this key
42
+ def get_node(key)
43
+ get_node_pos(key)[0]
44
+ end
45
+
46
+ def get_node_pos(key)
47
+ return [nil,nil] if @ring.size == 0
48
+ crc = Zlib.crc32(key)
49
+ idx = HashRing.binary_search(@sorted_keys, crc)
50
+ return [@ring[@sorted_keys[idx]], idx]
51
+ end
52
+
53
+ def iter_nodes(key)
54
+ return [nil,nil] if @ring.size == 0
55
+ node, pos = get_node_pos(key)
56
+ @sorted_keys[pos..-1].each do |k|
57
+ yield @ring[k]
58
+ end
59
+ end
60
+
61
+ class << self
62
+
63
+ # gem install RubyInline to use this code
64
+ # Native extension to perform the binary search within the hashring.
65
+ # There's a pure ruby version below so this is purely optional
66
+ # for performance. In testing 20k gets and sets, the native
67
+ # binary search shaved about 12% off the runtime (9sec -> 8sec).
68
+ begin
69
+ require 'inline'
70
+ inline do |builder|
71
+ builder.c <<-EOM
72
+ int binary_search(VALUE ary, unsigned int r) {
73
+ int upper = RARRAY_LEN(ary) - 1;
74
+ int lower = 0;
75
+ int idx = 0;
76
+
77
+ while (lower <= upper) {
78
+ idx = (lower + upper) / 2;
79
+
80
+ VALUE continuumValue = RARRAY_PTR(ary)[idx];
81
+ unsigned int l = NUM2UINT(continuumValue);
82
+ if (l == r) {
83
+ return idx;
84
+ }
85
+ else if (l > r) {
86
+ upper = idx - 1;
87
+ }
88
+ else {
89
+ lower = idx + 1;
90
+ }
91
+ }
92
+ return upper;
93
+ }
94
+ EOM
95
+ end
96
+ rescue Exception => e
97
+ # Find the closest index in HashRing with value <= the given value
98
+ def binary_search(ary, value, &block)
99
+ upper = ary.size - 1
100
+ lower = 0
101
+ idx = 0
102
+
103
+ while(lower <= upper) do
104
+ idx = (lower + upper) / 2
105
+ comp = ary[idx] <=> value
106
+
107
+ if comp == 0
108
+ return idx
109
+ elsif comp > 0
110
+ upper = idx - 1
111
+ else
112
+ lower = idx + 1
113
+ end
114
+ end
115
+ return upper
116
+ end
117
+
118
+ end
119
+ end
120
+
121
+ end
122
+
123
+ # ring = HashRing.new ['server1', 'server2', 'server3']
124
+ # p ring
125
+ # #
126
+ # p ring.get_node "kjhjkjlkjlkkh"
127
+ #