dsander-redis 1.0.6

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,118 @@
1
+ require 'redis/hash_ring'
2
+
3
+ class Redis
4
+ class DistRedis
5
+ attr_reader :ring
6
+ def initialize(opts={})
7
+ hosts = []
8
+
9
+ db = opts[:db] || nil
10
+ timeout = opts[:timeout] || nil
11
+
12
+ raise "No hosts given" unless opts[:hosts]
13
+
14
+ opts[:hosts].each do |h|
15
+ host, port = h.split(':')
16
+ hosts << Client.new(:host => host, :port => port, :db => db, :timeout => timeout)
17
+ end
18
+
19
+ @ring = HashRing.new hosts
20
+ end
21
+
22
+ def node_for_key(key)
23
+ key = $1 if key =~ /\{(.*)?\}/
24
+ @ring.get_node(key)
25
+ end
26
+
27
+ def add_server(server)
28
+ server, port = server.split(':')
29
+ @ring.add_node Client.new(:host => server, :port => port)
30
+ end
31
+
32
+ def method_missing(sym, *args, &blk)
33
+ if redis = node_for_key(args.first.to_s)
34
+ redis.send sym, *args, &blk
35
+ else
36
+ super
37
+ end
38
+ end
39
+
40
+ def node_keys(glob)
41
+ @ring.nodes.map do |red|
42
+ red.keys(glob)
43
+ end
44
+ end
45
+
46
+ def keys(glob)
47
+ node_keys(glob).flatten
48
+ end
49
+
50
+ def save
51
+ on_each_node :save
52
+ end
53
+
54
+ def bgsave
55
+ on_each_node :bgsave
56
+ end
57
+
58
+ def quit
59
+ on_each_node :quit
60
+ end
61
+
62
+ def flush_all
63
+ on_each_node :flush_all
64
+ end
65
+ alias_method :flushall, :flush_all
66
+
67
+ def flush_db
68
+ on_each_node :flush_db
69
+ end
70
+ alias_method :flushdb, :flush_db
71
+
72
+ def delete_cloud!
73
+ @ring.nodes.each do |red|
74
+ red.keys("*").each do |key|
75
+ red.del key
76
+ end
77
+ end
78
+ end
79
+
80
+ def on_each_node(command, *args)
81
+ @ring.nodes.each do |red|
82
+ red.send(command, *args)
83
+ end
84
+ end
85
+
86
+ def mset()
87
+
88
+ end
89
+
90
+ def mget(*keyz)
91
+ results = {}
92
+ kbn = keys_by_node(keyz)
93
+ kbn.each do |node, node_keyz|
94
+ node.mapped_mget(*node_keyz).each do |k, v|
95
+ results[k] = v
96
+ end
97
+ end
98
+ keyz.flatten.map { |k| results[k] }
99
+ end
100
+
101
+ def keys_by_node(*keyz)
102
+ keyz.flatten.inject({}) do |kbn, k|
103
+ node = node_for_key(k)
104
+ next if kbn[node] && kbn[node].include?(k)
105
+ kbn[node] ||= []
106
+ kbn[node] << k
107
+ kbn
108
+ end
109
+ end
110
+
111
+ def type(key)
112
+ method_missing(:type, key)
113
+ end
114
+ end
115
+ end
116
+
117
+ # For backwards compatibility
118
+ DistRedis = Redis::DistRedis
@@ -0,0 +1,181 @@
1
+ # Extensions for using memcache-client with EventMachine
2
+
3
+ raise "redis/event_machine requires Ruby 1.9" if RUBY_VERSION < '1.9'
4
+
5
+ require 'redis'
6
+ require 'eventmachine'
7
+ require 'fiber'
8
+
9
+ class Redis
10
+
11
+ # Since we are working in a single Thread, multiple Fiber environment,
12
+ # disable the multithread Mutex as it will not work.
13
+ # DEFAULT_OPTIONS[:multithread] = false
14
+
15
+ module EventedClient
16
+
17
+ def fiber_key
18
+ @fiber_key ||= "redis-#{@host}-#{@port}"
19
+ end
20
+
21
+ def connect_to(host,port)
22
+ log("Redis >> Using EM connection")
23
+ sock = Thread.current[fiber_key]
24
+ return @sock if @sock and not @sock.closed?
25
+
26
+ Thread.current[fiber_key] = nil
27
+
28
+ # If the host was dead, don't retry for a while.
29
+ #return if @retry and @retry > Time.now
30
+
31
+ Thread.current[fiber_key] ||= begin
32
+ @sock = EM::SocketConnection.connect(host, port, @timeout)
33
+ yielding = true
34
+ fiber = Fiber.current
35
+ @sock.callback do
36
+ log("Redis >> Connected")
37
+ @status = 'CONNECTED'
38
+ @retry = nil
39
+ yielding = false
40
+ fiber.resume if Fiber.current != fiber
41
+ log("Redis >> Done with callback")
42
+ end
43
+ @sock.errback do
44
+ @sock = nil
45
+ yielding = false
46
+ fiber.resume if Fiber.current != fiber
47
+ end
48
+ Fiber.yield if yielding
49
+ @sock
50
+ end
51
+ end
52
+
53
+ def close
54
+ sock = Thread.current[fiber_key]
55
+ if sock
56
+ sock.close if !sock.closed?
57
+ Thread.current[fiber_key] = nil
58
+ end
59
+ @retry = nil
60
+ @status = "NOT CONNECTED"
61
+ end
62
+
63
+ end
64
+ end
65
+
66
+ module EM
67
+ module SocketConnection
68
+ include EM::Deferrable
69
+
70
+ def self.connect(host, port, timeout)
71
+ EM.connect(host, port, self) do |conn|
72
+ conn.pending_connect_timeout = timeout
73
+ end
74
+ end
75
+
76
+ def initialize
77
+ @connected = false
78
+ @index = 0
79
+ @buf = ''
80
+ end
81
+
82
+ def closed?
83
+ !@connected
84
+ end
85
+
86
+ def close
87
+ @connected = false
88
+ close_connection(true)
89
+ end
90
+
91
+ def write(buf)
92
+ send_data(buf)
93
+ end
94
+
95
+ def read(size)
96
+ if can_read?(size)
97
+ #puts("Redis >> can read")
98
+ yank(size)
99
+ else
100
+ #puts("Redis >> cant read")
101
+ fiber = Fiber.current
102
+ @size = size
103
+ @callback = proc { |data|
104
+ fiber.resume(data)
105
+ }
106
+ #puts @callback
107
+ # TODO Can leak fiber if the connection dies while
108
+ # this fiber is yielded, waiting for data
109
+ Fiber.yield
110
+ end
111
+ end
112
+
113
+ SEP = "\r\n"
114
+
115
+ def gets
116
+ #puts "Gets #{@buf.inspect} #{@index}"
117
+ while true
118
+ # Read to ensure we have some data in the buffer
119
+ line = read(1)
120
+ # Reset the buffer index to zero
121
+ @buf = @buf.slice(@index..-1)
122
+ #puts "#{@buf.inspect}"
123
+ @index = 0
124
+ if eol = @buf.index(SEP)
125
+ line << yank(eol + SEP.size)
126
+ break
127
+ else
128
+ # EOL not in the current buffer
129
+ line << yank(@buf.size)
130
+ end
131
+ end
132
+ line
133
+ end
134
+
135
+ def can_read?(size)
136
+ @buf.size >= @index + size
137
+ end
138
+
139
+ # EM callbacks
140
+
141
+ def receive_data(data)
142
+ @buf << data
143
+ if @callback and can_read?(@size)
144
+ callback = @callback
145
+ #puts "Now can read #{@size} for\n#{@callback}"
146
+ data = yank(@size)
147
+ @callback = @size = nil
148
+ callback.call(data)
149
+ end
150
+ end
151
+
152
+ def post_init
153
+ @connected = true
154
+ succeed
155
+ end
156
+
157
+ def unbind
158
+ if @connected
159
+ @connected = false
160
+ else
161
+ fail
162
+ end
163
+ end
164
+
165
+ private
166
+
167
+ BUFFER_SIZE = 4096
168
+
169
+ def yank(len)
170
+ data = @buf.slice(@index, len)
171
+ @index += len
172
+ @index = @buf.size if @index > @buf.size
173
+ if @index >= BUFFER_SIZE
174
+ @buf = @buf.slice(@index..-1)
175
+ @index = 0
176
+ end
177
+ data
178
+ end
179
+
180
+ end
181
+ end
@@ -0,0 +1,131 @@
1
+ require 'zlib'
2
+
3
+ class Redis
4
+ class HashRing
5
+
6
+ POINTS_PER_SERVER = 160 # this is the default in libmemcached
7
+
8
+ attr_reader :ring, :sorted_keys, :replicas, :nodes
9
+
10
+ # nodes is a list of objects that have a proper to_s representation.
11
+ # replicas indicates how many virtual points should be used pr. node,
12
+ # replicas are required to improve the distribution.
13
+ def initialize(nodes=[], replicas=POINTS_PER_SERVER)
14
+ @replicas = replicas
15
+ @ring = {}
16
+ @nodes = []
17
+ @sorted_keys = []
18
+ nodes.each do |node|
19
+ add_node(node)
20
+ end
21
+ end
22
+
23
+ # Adds a `node` to the hash ring (including a number of replicas).
24
+ def add_node(node)
25
+ @nodes << node
26
+ @replicas.times do |i|
27
+ key = Zlib.crc32("#{node}:#{i}")
28
+ @ring[key] = node
29
+ @sorted_keys << key
30
+ end
31
+ @sorted_keys.sort!
32
+ end
33
+
34
+ def remove_node(node)
35
+ @nodes.reject!{|n| n.to_s == node.to_s}
36
+ @replicas.times do |i|
37
+ key = Zlib.crc32("#{node}:#{i}")
38
+ @ring.delete(key)
39
+ @sorted_keys.reject! {|k| k == key}
40
+ end
41
+ end
42
+
43
+ # get the node in the hash ring for this key
44
+ def get_node(key)
45
+ get_node_pos(key)[0]
46
+ end
47
+
48
+ def get_node_pos(key)
49
+ return [nil,nil] if @ring.size == 0
50
+ crc = Zlib.crc32(key)
51
+ idx = HashRing.binary_search(@sorted_keys, crc)
52
+ return [@ring[@sorted_keys[idx]], idx]
53
+ end
54
+
55
+ def iter_nodes(key)
56
+ return [nil,nil] if @ring.size == 0
57
+ node, pos = get_node_pos(key)
58
+ @sorted_keys[pos..-1].each do |k|
59
+ yield @ring[k]
60
+ end
61
+ end
62
+
63
+ class << self
64
+
65
+ # gem install RubyInline to use this code
66
+ # Native extension to perform the binary search within the hashring.
67
+ # There's a pure ruby version below so this is purely optional
68
+ # for performance. In testing 20k gets and sets, the native
69
+ # binary search shaved about 12% off the runtime (9sec -> 8sec).
70
+ begin
71
+ require 'inline'
72
+ inline do |builder|
73
+ builder.c <<-EOM
74
+ int binary_search(VALUE ary, unsigned int r) {
75
+ int upper = RARRAY_LEN(ary) - 1;
76
+ int lower = 0;
77
+ int idx = 0;
78
+
79
+ while (lower <= upper) {
80
+ idx = (lower + upper) / 2;
81
+
82
+ VALUE continuumValue = RARRAY_PTR(ary)[idx];
83
+ unsigned int l = NUM2UINT(continuumValue);
84
+ if (l == r) {
85
+ return idx;
86
+ }
87
+ else if (l > r) {
88
+ upper = idx - 1;
89
+ }
90
+ else {
91
+ lower = idx + 1;
92
+ }
93
+ }
94
+ if (upper < 0) {
95
+ upper = RARRAY_LEN(ary) - 1;
96
+ }
97
+ return upper;
98
+ }
99
+ EOM
100
+ end
101
+ rescue Exception => e
102
+ # Find the closest index in HashRing with value <= the given value
103
+ def binary_search(ary, value, &block)
104
+ upper = ary.size - 1
105
+ lower = 0
106
+ idx = 0
107
+
108
+ while(lower <= upper) do
109
+ idx = (lower + upper) / 2
110
+ comp = ary[idx] <=> value
111
+
112
+ if comp == 0
113
+ return idx
114
+ elsif comp > 0
115
+ upper = idx - 1
116
+ else
117
+ lower = idx + 1
118
+ end
119
+ end
120
+
121
+ if upper < 0
122
+ upper = ary.size - 1
123
+ end
124
+ return upper
125
+ end
126
+
127
+ end
128
+ end
129
+
130
+ end
131
+ end
@@ -0,0 +1,19 @@
1
+ class Redis
2
+ class Pipeline < Client
3
+ BUFFER_SIZE = 50_000
4
+
5
+ def initialize(redis)
6
+ @redis = redis
7
+ @commands = []
8
+ end
9
+
10
+ def call_command(command)
11
+ @commands << command
12
+ end
13
+
14
+ def execute
15
+ return if @commands.empty?
16
+ @redis.call_command(@commands)
17
+ end
18
+ end
19
+ end