adamwiggins-redis-rb 0.1.1

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,34 @@
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. rspec -
16
+ sudo gem install rspec
17
+
18
+ 2. redis -
19
+
20
+ rake redis:install
21
+
22
+ 2. dtach -
23
+
24
+ rake dtach:install
25
+
26
+ 3. git - git is the new black.
27
+
28
+ ## Setup
29
+
30
+ Use the tasks mentioned above (in Dependencies) to get your machine setup.
31
+
32
+ ## Examples
33
+
34
+ Check the examples/ directory. *Note* you need to have redis-server running first.
data/Rakefile ADDED
@@ -0,0 +1,43 @@
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
+ begin
9
+ require 'jeweler'
10
+
11
+ Jeweler::Tasks.new do |s|
12
+ s.name = "redis-rb"
13
+ s.summary = "Ruby client library for redis key value storage server"
14
+ s.description = s.summary
15
+ s.authors = ['Ezra Zygmuntowicz', 'Taylor Weibley', 'Matthew Clark', 'Brian McKinney', 'Salvatore Sanfilippo', 'Luca Guidi']
16
+ s.email = "ez@engineyard.com"
17
+ s.homepage = "http://github.com/ezmobius/redis-rb"
18
+ s.has_rdoc = true
19
+ s.extra_rdoc_files = ["LICENSE"]
20
+ s.require_path = 'lib'
21
+ s.autorequire = 'redis'
22
+ s.files = %w(LICENSE README.markdown Rakefile) + Dir.glob("{lib,tasks,spec}/**/*")
23
+ s.add_dependency "rspec"
24
+ end
25
+ rescue LoadError
26
+ puts "Jewler is not installed, gem publishing tasks will not be available"
27
+ end
28
+
29
+ ###############################
30
+
31
+ task :default => :spec
32
+
33
+ desc "Run specs"
34
+ Spec::Rake::SpecTask.new do |t|
35
+ t.spec_files = FileList['spec/**/*_spec.rb']
36
+ t.spec_opts = %w(-fs --color)
37
+ end
38
+
39
+ desc "Run all examples with RCov"
40
+ Spec::Rake::SpecTask.new(:rcov) do |t|
41
+ t.spec_files = FileList['spec/**/*_spec.rb']
42
+ t.rcov = true
43
+ end
data/bin/distredis ADDED
@@ -0,0 +1,33 @@
1
+ require 'fileutils'
2
+
3
+ class RedisCluster
4
+
5
+ def initialize(opts={})
6
+ opts = {:port => 6379, :host => 'localhost', :basedir => "#{Dir.pwd}/rdsrv" }.merge(opts)
7
+ FileUtils.mkdir_p opts[:basedir]
8
+ opts[:size].times do |i|
9
+ port = opts[:port] + i
10
+ FileUtils.mkdir_p "#{opts[:basedir]}/#{port}"
11
+ File.open("#{opts[:basedir]}/#{port}.conf", 'w'){|f| f.write(make_config(port, "#{opts[:basedir]}/#{port}", "#{opts[:basedir]}/#{port}.log"))}
12
+ system(%Q{#{File.join(File.expand_path(File.dirname(__FILE__)), "../redis/redis-server #{opts[:basedir]}/#{port}.conf &" )}})
13
+ end
14
+ end
15
+
16
+ def make_config(port=6379, data=port, logfile='stdout', loglevel='debug')
17
+ config = %Q{
18
+ timeout 300
19
+ save 900 1
20
+ save 300 10
21
+ save 60 10000
22
+ dir #{data}
23
+ loglevel #{loglevel}
24
+ logfile #{logfile}
25
+ databases 16
26
+ port #{port}
27
+ }
28
+ end
29
+
30
+ end
31
+
32
+
33
+ RedisCluster.new :size => 4
data/examples/basic.rb ADDED
@@ -0,0 +1,16 @@
1
+ require 'rubygems'
2
+ require 'redis'
3
+
4
+ r = Redis.new
5
+
6
+ r.delete('foo')
7
+
8
+ puts
9
+
10
+ p'set foo to "bar"'
11
+ r['foo'] = 'bar'
12
+
13
+ puts
14
+
15
+ p 'value of foo'
16
+ p r['foo']
@@ -0,0 +1,18 @@
1
+ require 'rubygems'
2
+ require 'redis'
3
+
4
+ r = Redis.new
5
+
6
+ puts
7
+ p 'incr'
8
+ r.delete 'counter'
9
+
10
+ p r.incr('counter')
11
+ p r.incr('counter')
12
+ p r.incr('counter')
13
+
14
+ puts
15
+ p 'decr'
16
+ p r.decr('counter')
17
+ p r.decr('counter')
18
+ p r.decr('counter')
data/examples/list.rb ADDED
@@ -0,0 +1,26 @@
1
+ require 'rubygems'
2
+ require 'redis'
3
+
4
+ r = Redis.new
5
+
6
+ r.delete 'logs'
7
+
8
+ puts
9
+
10
+ p "pushing log messages into a LIST"
11
+ r.push_tail 'logs', 'some log message'
12
+ r.push_tail 'logs', 'another log message'
13
+ r.push_tail 'logs', 'yet another log message'
14
+ r.push_tail 'logs', 'also another log message'
15
+
16
+ puts
17
+ p 'contents of logs LIST'
18
+
19
+ p r.list_range('logs', 0, -1)
20
+
21
+ puts
22
+ p 'Trim logs LIST to last 2 elements(easy circular buffer)'
23
+
24
+ r.list_trim('logs', -2, -1)
25
+
26
+ p r.list_range('logs', 0, -1)
data/examples/sets.rb ADDED
@@ -0,0 +1,36 @@
1
+ require 'rubygems'
2
+ require 'redis'
3
+
4
+ r = Redis.new
5
+
6
+ r.delete 'foo-tags'
7
+ r.delete 'bar-tags'
8
+
9
+ puts
10
+ p "create a set of tags on foo-tags"
11
+
12
+ r.set_add 'foo-tags', 'one'
13
+ r.set_add 'foo-tags', 'two'
14
+ r.set_add 'foo-tags', 'three'
15
+
16
+ puts
17
+ p "create a set of tags on bar-tags"
18
+
19
+ r.set_add 'bar-tags', 'three'
20
+ r.set_add 'bar-tags', 'four'
21
+ r.set_add 'bar-tags', 'five'
22
+
23
+ puts
24
+ p 'foo-tags'
25
+
26
+ p r.set_members('foo-tags')
27
+
28
+ puts
29
+ p 'bar-tags'
30
+
31
+ p r.set_members('bar-tags')
32
+
33
+ puts
34
+ p 'intersection of foo-tags and bar-tags'
35
+
36
+ p r.set_intersect('foo-tags', 'bar-tags')
@@ -0,0 +1,13 @@
1
+ require 'socket'
2
+ require 'pp'
3
+ require File.join(File.dirname(__FILE__), '../lib/redis')
4
+
5
+ #require File.join(File.dirname(__FILE__), '../lib/server')
6
+
7
+
8
+ #r = Redis.new
9
+ #loop do
10
+
11
+ # puts "--------------------------------------"
12
+ # sleep 12
13
+ #end
data/lib/dist_redis.rb ADDED
@@ -0,0 +1,118 @@
1
+ require 'redis'
2
+ require 'hash_ring'
3
+ class DistRedis
4
+ attr_reader :ring
5
+ def initialize(opts={})
6
+ hosts = []
7
+
8
+ db = opts[:db] || nil
9
+ timeout = opts[:timeout] || nil
10
+
11
+ raise Error, "No hosts given" unless opts[:hosts]
12
+
13
+ opts[:hosts].each do |h|
14
+ host, port = h.split(':')
15
+ hosts << Redis.new(:host => host, :port => port, :db => db, :timeout => timeout, :db => db)
16
+ end
17
+
18
+ @ring = HashRing.new hosts
19
+ end
20
+
21
+ def node_for_key(key)
22
+ if key =~ /\{(.*)?\}/
23
+ key = $1
24
+ end
25
+ @ring.get_node(key)
26
+ end
27
+
28
+ def add_server(server)
29
+ server, port = server.split(':')
30
+ @ring.add_node Redis.new(:host => server, :port => port)
31
+ end
32
+
33
+ def method_missing(sym, *args, &blk)
34
+ if redis = node_for_key(args.first.to_s)
35
+ redis.send sym, *args, &blk
36
+ else
37
+ super
38
+ end
39
+ end
40
+
41
+ def keys(glob)
42
+ keyz = []
43
+ @ring.nodes.each do |red|
44
+ keyz.concat red.keys(glob)
45
+ end
46
+ keyz
47
+ end
48
+
49
+ def save
50
+ @ring.nodes.each do |red|
51
+ red.save
52
+ end
53
+ end
54
+
55
+ def bgsave
56
+ @ring.nodes.each do |red|
57
+ red.bgsave
58
+ end
59
+ end
60
+
61
+ def quit
62
+ @ring.nodes.each do |red|
63
+ red.quit
64
+ end
65
+ end
66
+
67
+ def delete_cloud!
68
+ @ring.nodes.each do |red|
69
+ red.keys("*").each do |key|
70
+ red.delete key
71
+ end
72
+ end
73
+ end
74
+
75
+ end
76
+
77
+
78
+ if __FILE__ == $0
79
+
80
+ r = DistRedis.new 'localhost:6379', 'localhost:6380', 'localhost:6381', 'localhost:6382'
81
+ r['urmom'] = 'urmom'
82
+ r['urdad'] = 'urdad'
83
+ r['urmom1'] = 'urmom1'
84
+ r['urdad1'] = 'urdad1'
85
+ r['urmom2'] = 'urmom2'
86
+ r['urdad2'] = 'urdad2'
87
+ r['urmom3'] = 'urmom3'
88
+ r['urdad3'] = 'urdad3'
89
+ p r['urmom']
90
+ p r['urdad']
91
+ p r['urmom1']
92
+ p r['urdad1']
93
+ p r['urmom2']
94
+ p r['urdad2']
95
+ p r['urmom3']
96
+ p r['urdad3']
97
+
98
+ r.push_tail 'listor', 'foo1'
99
+ r.push_tail 'listor', 'foo2'
100
+ r.push_tail 'listor', 'foo3'
101
+ r.push_tail 'listor', 'foo4'
102
+ r.push_tail 'listor', 'foo5'
103
+
104
+ p r.pop_tail('listor')
105
+ p r.pop_tail('listor')
106
+ p r.pop_tail('listor')
107
+ p r.pop_tail('listor')
108
+ p r.pop_tail('listor')
109
+
110
+ puts "key distribution:"
111
+
112
+ r.ring.nodes.each do |red|
113
+ p [red.port, red.keys("*")]
114
+ end
115
+ r.delete_cloud!
116
+ p r.keys('*')
117
+
118
+ 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
+ #