ezmobius-redis-rb 0.0.3
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.
- data/LICENSE +20 -0
- data/README.markdown +31 -0
- data/Rakefile +58 -0
- data/lib/better_timeout.rb +191 -0
- data/lib/dist_redis.rb +111 -0
- data/lib/hash_ring.rb +127 -0
- data/lib/redis.rb +472 -0
- data/lib/server.rb +128 -0
- data/spec/redis_spec.rb +322 -0
- data/spec/spec_helper.rb +4 -0
- metadata +63 -0
data/lib/redis.rb
ADDED
@@ -0,0 +1,472 @@
|
|
1
|
+
require 'socket'
|
2
|
+
require 'set'
|
3
|
+
require File.join(File.dirname(__FILE__),'server')
|
4
|
+
|
5
|
+
|
6
|
+
class RedisError < StandardError
|
7
|
+
end
|
8
|
+
class RedisRenameError < StandardError
|
9
|
+
end
|
10
|
+
class Redis
|
11
|
+
ERR = "-".freeze
|
12
|
+
OK = 'OK'.freeze
|
13
|
+
SINGLE = '+'.freeze
|
14
|
+
BULK = '$'.freeze
|
15
|
+
MULTI = '*'.freeze
|
16
|
+
INT = ':'.freeze
|
17
|
+
|
18
|
+
attr_reader :server
|
19
|
+
|
20
|
+
|
21
|
+
def initialize(opts={})
|
22
|
+
@opts = {:host => 'localhost', :port => '6379', :db => 0}.merge(opts)
|
23
|
+
$debug = @opts[:debug]
|
24
|
+
@db = @opts[:db]
|
25
|
+
@server = Server.new(@opts[:host], @opts[:port])
|
26
|
+
end
|
27
|
+
|
28
|
+
def to_s
|
29
|
+
"#{host}:#{port}"
|
30
|
+
end
|
31
|
+
|
32
|
+
def port
|
33
|
+
@opts[:port]
|
34
|
+
end
|
35
|
+
|
36
|
+
def host
|
37
|
+
@opts[:host]
|
38
|
+
end
|
39
|
+
|
40
|
+
def with_socket_management(server, &block)
|
41
|
+
begin
|
42
|
+
block.call(server.socket)
|
43
|
+
#Timeout or server down
|
44
|
+
rescue Errno::ECONNRESET, Errno::EPIPE, Errno::ECONNREFUSED => e
|
45
|
+
server.close
|
46
|
+
puts "Client (#{server.inspect}) disconnected from server: #{e.inspect}\n" if $debug
|
47
|
+
retry
|
48
|
+
#Server down
|
49
|
+
rescue NoMethodError => e
|
50
|
+
puts "Client (#{server.inspect}) tryin server that is down: #{e.inspect}\n Dying!" if $debug
|
51
|
+
raise Errno::ECONNREFUSED
|
52
|
+
#exit
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
def monitor
|
57
|
+
with_socket_management(@server) do |socket|
|
58
|
+
trap("INT") { puts "\nGot ^C! Dying!"; exit }
|
59
|
+
write "MONITOR\r\n"
|
60
|
+
puts "Now Monitoring..."
|
61
|
+
socket.read(12)
|
62
|
+
loop do
|
63
|
+
x = socket.gets
|
64
|
+
puts x unless x.nil?
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
def quit
|
70
|
+
write "QUIT\r\n"
|
71
|
+
end
|
72
|
+
|
73
|
+
def select_db(index)
|
74
|
+
@db = index
|
75
|
+
write "SELECT #{index}\r\n"
|
76
|
+
get_response
|
77
|
+
end
|
78
|
+
|
79
|
+
def flush_db
|
80
|
+
write "FLUSHDB\r\n"
|
81
|
+
get_response == OK
|
82
|
+
end
|
83
|
+
|
84
|
+
def flush_all
|
85
|
+
ensure_retry do
|
86
|
+
puts "Warning!\nFlushing *ALL* databases!\n5 Seconds to Hit ^C!"
|
87
|
+
trap('INT') {quit; return false}
|
88
|
+
sleep 5
|
89
|
+
write "FLUSHALL\r\n"
|
90
|
+
get_response == OK
|
91
|
+
end
|
92
|
+
end
|
93
|
+
|
94
|
+
def last_save
|
95
|
+
write "LASTSAVE\r\n"
|
96
|
+
get_response.to_i
|
97
|
+
end
|
98
|
+
|
99
|
+
def bgsave
|
100
|
+
write "BGSAVE\r\n"
|
101
|
+
get_response == OK
|
102
|
+
end
|
103
|
+
|
104
|
+
def info
|
105
|
+
info = {}
|
106
|
+
write("INFO\r\n")
|
107
|
+
x = get_response
|
108
|
+
x.each do |kv|
|
109
|
+
k,v = kv.split(':', 2)
|
110
|
+
k,v = k.chomp, v = v.chomp
|
111
|
+
info[k.to_sym] = v
|
112
|
+
end
|
113
|
+
info
|
114
|
+
end
|
115
|
+
|
116
|
+
|
117
|
+
def bulk_reply
|
118
|
+
begin
|
119
|
+
x = read.chomp
|
120
|
+
puts "bulk_reply read value is #{x.inspect}" if $debug
|
121
|
+
return x
|
122
|
+
rescue => e
|
123
|
+
puts "error in bulk_reply #{e}" if $debug
|
124
|
+
nil
|
125
|
+
end
|
126
|
+
end
|
127
|
+
|
128
|
+
def write(data)
|
129
|
+
with_socket_management(@server) do |socket|
|
130
|
+
puts "writing: #{data}" if $debug
|
131
|
+
socket.write(data)
|
132
|
+
end
|
133
|
+
end
|
134
|
+
|
135
|
+
def fetch(len)
|
136
|
+
with_socket_management(@server) do |socket|
|
137
|
+
len = [0, len.to_i].max
|
138
|
+
res = socket.read(len + 2)
|
139
|
+
res = res.chomp if res
|
140
|
+
res
|
141
|
+
end
|
142
|
+
end
|
143
|
+
|
144
|
+
def read(length = read_proto)
|
145
|
+
with_socket_management(@server) do |socket|
|
146
|
+
res = socket.read(length)
|
147
|
+
puts "read is #{res.inspect}" if $debug
|
148
|
+
res
|
149
|
+
end
|
150
|
+
end
|
151
|
+
|
152
|
+
def keys(glob)
|
153
|
+
write "KEYS #{glob}\r\n"
|
154
|
+
get_response.split(' ')
|
155
|
+
end
|
156
|
+
|
157
|
+
def rename!(oldkey, newkey)
|
158
|
+
write "RENAME #{oldkey} #{newkey}\r\n"
|
159
|
+
get_response
|
160
|
+
end
|
161
|
+
|
162
|
+
def rename(oldkey, newkey)
|
163
|
+
write "RENAMENX #{oldkey} #{newkey}\r\n"
|
164
|
+
case get_response
|
165
|
+
when -1
|
166
|
+
raise RedisRenameError, "source key: #{oldkey} does not exist"
|
167
|
+
when 0
|
168
|
+
raise RedisRenameError, "target key: #{oldkey} already exists"
|
169
|
+
when -3
|
170
|
+
raise RedisRenameError, "source and destination keys are the same"
|
171
|
+
when 1
|
172
|
+
true
|
173
|
+
end
|
174
|
+
end
|
175
|
+
|
176
|
+
def key?(key)
|
177
|
+
write "EXISTS #{key}\r\n"
|
178
|
+
get_response == 1
|
179
|
+
end
|
180
|
+
|
181
|
+
def delete(key)
|
182
|
+
write "DEL #{key}\r\n"
|
183
|
+
get_response == 1
|
184
|
+
end
|
185
|
+
|
186
|
+
def [](key)
|
187
|
+
get(key)
|
188
|
+
end
|
189
|
+
|
190
|
+
def get(key)
|
191
|
+
write "GET #{key}\r\n"
|
192
|
+
get_response
|
193
|
+
end
|
194
|
+
|
195
|
+
def mget(*keys)
|
196
|
+
write "MGET #{keys.join(' ')}\r\n"
|
197
|
+
get_response
|
198
|
+
end
|
199
|
+
|
200
|
+
def incr(key, increment=nil)
|
201
|
+
if increment
|
202
|
+
write "INCRBY #{key} #{increment}\r\n"
|
203
|
+
else
|
204
|
+
write "INCR #{key}\r\n"
|
205
|
+
end
|
206
|
+
get_response
|
207
|
+
end
|
208
|
+
|
209
|
+
def decr(key, decrement=nil)
|
210
|
+
if decrement
|
211
|
+
write "DECRBY #{key} #{decrement}\r\n"
|
212
|
+
else
|
213
|
+
write "DECR #{key}\r\n"
|
214
|
+
end
|
215
|
+
get_response
|
216
|
+
end
|
217
|
+
|
218
|
+
def randkey
|
219
|
+
write "RANDOMKEY\r\n"
|
220
|
+
get_response
|
221
|
+
end
|
222
|
+
|
223
|
+
def list_length(key)
|
224
|
+
write "LLEN #{key}\r\n"
|
225
|
+
case i = get_response
|
226
|
+
when -2
|
227
|
+
raise RedisError, "key: #{key} does not hold a list value"
|
228
|
+
else
|
229
|
+
i
|
230
|
+
end
|
231
|
+
end
|
232
|
+
|
233
|
+
def type?(key)
|
234
|
+
write "TYPE #{key}\r\n"
|
235
|
+
get_response
|
236
|
+
end
|
237
|
+
|
238
|
+
def push_tail(key, string)
|
239
|
+
write "RPUSH #{key} #{string.to_s.size}\r\n#{string.to_s}\r\n"
|
240
|
+
get_response
|
241
|
+
end
|
242
|
+
|
243
|
+
def push_head(key, string)
|
244
|
+
write "LPUSH #{key} #{string.to_s.size}\r\n#{string.to_s}\r\n"
|
245
|
+
get_response
|
246
|
+
end
|
247
|
+
|
248
|
+
def pop_head(key)
|
249
|
+
write "LPOP #{key}\r\n"
|
250
|
+
get_response
|
251
|
+
end
|
252
|
+
|
253
|
+
def pop_tail(key)
|
254
|
+
write "RPOP #{key}\r\n"
|
255
|
+
get_response
|
256
|
+
end
|
257
|
+
|
258
|
+
def list_set(key, index, val)
|
259
|
+
write "LSET #{key} #{index} #{val.to_s.size}\r\n#{val}\r\n"
|
260
|
+
get_response == OK
|
261
|
+
end
|
262
|
+
|
263
|
+
def list_length(key)
|
264
|
+
write "LLEN #{key}\r\n"
|
265
|
+
case i = get_response
|
266
|
+
when -2
|
267
|
+
raise RedisError, "key: #{key} does not hold a list value"
|
268
|
+
else
|
269
|
+
i
|
270
|
+
end
|
271
|
+
end
|
272
|
+
|
273
|
+
def list_range(key, start, ending)
|
274
|
+
write "LRANGE #{key} #{start} #{ending}\r\n"
|
275
|
+
get_response
|
276
|
+
end
|
277
|
+
|
278
|
+
def list_trim(key, start, ending)
|
279
|
+
write "LTRIM #{key} #{start} #{ending}\r\n"
|
280
|
+
get_response
|
281
|
+
end
|
282
|
+
|
283
|
+
def list_index(key, index)
|
284
|
+
write "LINDEX #{key} #{index}\r\n"
|
285
|
+
get_response
|
286
|
+
end
|
287
|
+
|
288
|
+
def list_rm(key, count, value)
|
289
|
+
write "LREM #{key} #{count} #{value.to_s.size}\r\n#{value}\r\n"
|
290
|
+
case num = get_response
|
291
|
+
when -1
|
292
|
+
raise RedisError, "key: #{key} does not exist"
|
293
|
+
when -2
|
294
|
+
raise RedisError, "key: #{key} does not hold a list value"
|
295
|
+
else
|
296
|
+
num
|
297
|
+
end
|
298
|
+
end
|
299
|
+
|
300
|
+
def set_add(key, member)
|
301
|
+
write "SADD #{key} #{member.to_s.size}\r\n#{member}\r\n"
|
302
|
+
case get_response
|
303
|
+
when 1
|
304
|
+
true
|
305
|
+
when 0
|
306
|
+
false
|
307
|
+
when -2
|
308
|
+
raise RedisError, "key: #{key} contains a non set value"
|
309
|
+
end
|
310
|
+
end
|
311
|
+
|
312
|
+
def set_delete(key, member)
|
313
|
+
write "SREM #{key} #{member.to_s.size}\r\n#{member}\r\n"
|
314
|
+
case get_response
|
315
|
+
when 1
|
316
|
+
true
|
317
|
+
when 0
|
318
|
+
false
|
319
|
+
when -2
|
320
|
+
raise RedisError, "key: #{key} contains a non set value"
|
321
|
+
end
|
322
|
+
end
|
323
|
+
|
324
|
+
def set_count(key)
|
325
|
+
write "SCARD #{key}\r\n"
|
326
|
+
case i = get_response
|
327
|
+
when -2
|
328
|
+
raise RedisError, "key: #{key} contains a non set value"
|
329
|
+
else
|
330
|
+
i
|
331
|
+
end
|
332
|
+
end
|
333
|
+
|
334
|
+
def set_member?(key, member)
|
335
|
+
write "SISMEMBER #{key} #{member.to_s.size}\r\n#{member}\r\n"
|
336
|
+
case get_response
|
337
|
+
when 1
|
338
|
+
true
|
339
|
+
when 0
|
340
|
+
false
|
341
|
+
when -2
|
342
|
+
raise RedisError, "key: #{key} contains a non set value"
|
343
|
+
end
|
344
|
+
end
|
345
|
+
|
346
|
+
def set_members(key)
|
347
|
+
write "SMEMBERS #{key}\r\n"
|
348
|
+
Set.new(get_response)
|
349
|
+
end
|
350
|
+
|
351
|
+
def set_intersect(*keys)
|
352
|
+
write "SINTER #{keys.join(' ')}\r\n"
|
353
|
+
Set.new(get_response)
|
354
|
+
end
|
355
|
+
|
356
|
+
def set_inter_store(destkey, *keys)
|
357
|
+
write "SINTERSTORE #{destkey} #{keys.join(' ')}\r\n"
|
358
|
+
get_response
|
359
|
+
end
|
360
|
+
|
361
|
+
def sort(key, opts={})
|
362
|
+
cmd = "SORT #{key}"
|
363
|
+
cmd << " BY #{opts[:by]}" if opts[:by]
|
364
|
+
cmd << " GET #{opts[:get]}" if opts[:get]
|
365
|
+
cmd << " INCR #{opts[:incr]}" if opts[:incr]
|
366
|
+
cmd << " DEL #{opts[:del]}" if opts[:del]
|
367
|
+
cmd << " DECR #{opts[:decr]}" if opts[:decr]
|
368
|
+
cmd << " #{opts[:order]}" if opts[:order]
|
369
|
+
cmd << " LIMIT #{opts[:limit].join(' ')}" if opts[:limit]
|
370
|
+
cmd << "\r\n"
|
371
|
+
write(cmd)
|
372
|
+
get_response
|
373
|
+
end
|
374
|
+
|
375
|
+
def multi_bulk
|
376
|
+
res = read_proto
|
377
|
+
puts "mb res is #{res.inspect}" if $debug
|
378
|
+
list = []
|
379
|
+
Integer(res).times do
|
380
|
+
vf = get_response
|
381
|
+
puts "curren vf is #{vf.inspect}" if $debug
|
382
|
+
list << vf
|
383
|
+
puts "current list is #{list.inspect}" if $debug
|
384
|
+
end
|
385
|
+
list
|
386
|
+
end
|
387
|
+
|
388
|
+
def get_reply
|
389
|
+
begin
|
390
|
+
r = read(1)
|
391
|
+
raise RedisError if (r == "\r" || r == "\n")
|
392
|
+
rescue RedisError
|
393
|
+
retry
|
394
|
+
end
|
395
|
+
r
|
396
|
+
end
|
397
|
+
|
398
|
+
def []=(key, val)
|
399
|
+
set(key,val)
|
400
|
+
end
|
401
|
+
|
402
|
+
|
403
|
+
def set(key, val, expiry=nil)
|
404
|
+
write("SET #{key} #{val.to_s.size}\r\n#{val}\r\n")
|
405
|
+
get_response == OK
|
406
|
+
end
|
407
|
+
|
408
|
+
def set_unless_exists(key, val)
|
409
|
+
write "SETNX #{key} #{val.to_s.size}\r\n#{val}\r\n"
|
410
|
+
get_response == 1
|
411
|
+
end
|
412
|
+
|
413
|
+
def status_code_reply
|
414
|
+
begin
|
415
|
+
res = read_proto
|
416
|
+
if res.index('-') == 0
|
417
|
+
raise RedisError, res
|
418
|
+
else
|
419
|
+
true
|
420
|
+
end
|
421
|
+
rescue RedisError
|
422
|
+
raise RedisError
|
423
|
+
end
|
424
|
+
end
|
425
|
+
|
426
|
+
def get_response
|
427
|
+
begin
|
428
|
+
rtype = get_reply
|
429
|
+
rescue => e
|
430
|
+
raise RedisError, e.inspect
|
431
|
+
end
|
432
|
+
puts "reply_type is #{rtype.inspect}" if $debug
|
433
|
+
case rtype
|
434
|
+
when SINGLE
|
435
|
+
single_line
|
436
|
+
when BULK
|
437
|
+
bulk_reply
|
438
|
+
when MULTI
|
439
|
+
multi_bulk
|
440
|
+
when INT
|
441
|
+
integer_reply
|
442
|
+
when ERR
|
443
|
+
raise RedisError, single_line
|
444
|
+
else
|
445
|
+
raise RedisError, "Unknown response.."
|
446
|
+
end
|
447
|
+
end
|
448
|
+
|
449
|
+
def integer_reply
|
450
|
+
Integer(read_proto)
|
451
|
+
end
|
452
|
+
|
453
|
+
def single_line
|
454
|
+
buff = ""
|
455
|
+
while buff[-2..-1] != "\r\n"
|
456
|
+
buff << read(1)
|
457
|
+
end
|
458
|
+
puts "single_line value is #{buff[0..-3].inspect}" if $debug
|
459
|
+
buff[0..-3]
|
460
|
+
end
|
461
|
+
|
462
|
+
def read_proto
|
463
|
+
with_socket_management(@server) do |socket|
|
464
|
+
if res = socket.gets
|
465
|
+
x = res.chomp
|
466
|
+
puts "read_proto is #{x.inspect}\n\n" if $debug
|
467
|
+
x.to_i
|
468
|
+
end
|
469
|
+
end
|
470
|
+
end
|
471
|
+
|
472
|
+
end
|