chimera 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- data/History.txt +4 -0
- data/Manifest.txt +57 -0
- data/PostInstall.txt +7 -0
- data/README.rdoc +114 -0
- data/Rakefile +30 -0
- data/doc/NOTES +11 -0
- data/doc/examples/config.yml +16 -0
- data/doc/redis6379.conf +132 -0
- data/lib/chimera.rb +33 -0
- data/lib/chimera/associations.rb +146 -0
- data/lib/chimera/attributes.rb +52 -0
- data/lib/chimera/base.rb +95 -0
- data/lib/chimera/config.rb +9 -0
- data/lib/chimera/error.rb +12 -0
- data/lib/chimera/finders.rb +49 -0
- data/lib/chimera/geo_indexes.rb +76 -0
- data/lib/chimera/indexes.rb +177 -0
- data/lib/chimera/persistence.rb +70 -0
- data/lib/chimera/redis_objects.rb +345 -0
- data/lib/redis.rb +373 -0
- data/lib/redis/counter.rb +94 -0
- data/lib/redis/dist_redis.rb +149 -0
- data/lib/redis/hash_ring.rb +135 -0
- data/lib/redis/helpers/core_commands.rb +46 -0
- data/lib/redis/helpers/serialize.rb +25 -0
- data/lib/redis/list.rb +122 -0
- data/lib/redis/lock.rb +83 -0
- data/lib/redis/objects.rb +100 -0
- data/lib/redis/objects/counters.rb +132 -0
- data/lib/redis/objects/lists.rb +45 -0
- data/lib/redis/objects/locks.rb +71 -0
- data/lib/redis/objects/sets.rb +46 -0
- data/lib/redis/objects/values.rb +56 -0
- data/lib/redis/pipeline.rb +21 -0
- data/lib/redis/set.rb +156 -0
- data/lib/redis/value.rb +35 -0
- data/lib/riak_raw.rb +100 -0
- data/lib/typhoeus.rb +55 -0
- data/lib/typhoeus/.gitignore +1 -0
- data/lib/typhoeus/easy.rb +253 -0
- data/lib/typhoeus/filter.rb +28 -0
- data/lib/typhoeus/hydra.rb +210 -0
- data/lib/typhoeus/multi.rb +34 -0
- data/lib/typhoeus/remote.rb +306 -0
- data/lib/typhoeus/remote_method.rb +108 -0
- data/lib/typhoeus/remote_proxy_object.rb +48 -0
- data/lib/typhoeus/request.rb +124 -0
- data/lib/typhoeus/response.rb +39 -0
- data/lib/typhoeus/service.rb +20 -0
- data/script/console +10 -0
- data/script/destroy +14 -0
- data/script/generate +14 -0
- data/test/models.rb +49 -0
- data/test/test_chimera.rb +238 -0
- data/test/test_helper.rb +7 -0
- metadata +243 -0
@@ -0,0 +1,94 @@
|
|
1
|
+
class Redis
|
2
|
+
#
|
3
|
+
# Class representing a Redis counter. This functions like a proxy class, in
|
4
|
+
# that you can say @object.counter_name to get the value and then
|
5
|
+
# @object.counter_name.increment to operate on it. You can use this
|
6
|
+
# directly, or you can use the counter :foo class method in your
|
7
|
+
# class to define a counter.
|
8
|
+
#
|
9
|
+
class Counter
|
10
|
+
require 'redis/helpers/core_commands'
|
11
|
+
include Redis::Helpers::CoreCommands
|
12
|
+
|
13
|
+
attr_reader :key, :options, :redis
|
14
|
+
def initialize(key, redis=$redis, options={})
|
15
|
+
@key = key
|
16
|
+
@redis = redis
|
17
|
+
@options = options
|
18
|
+
@options[:start] ||= 0
|
19
|
+
@redis.setnx(key, @options[:start]) unless @options[:start] == 0 || @options[:init] === false
|
20
|
+
end
|
21
|
+
|
22
|
+
# Reset the counter to its starting value. Not atomic, so use with care.
|
23
|
+
# Normally only useful if you're discarding all sub-records associated
|
24
|
+
# with a parent and starting over (for example, restarting a game and
|
25
|
+
# disconnecting all players).
|
26
|
+
def reset(to=options[:start])
|
27
|
+
redis.set key, to.to_i
|
28
|
+
end
|
29
|
+
|
30
|
+
# Returns the current value of the counter. Normally just calling the
|
31
|
+
# counter will lazily fetch the value, and only update it if increment
|
32
|
+
# or decrement is called. This forces a network call to redis-server
|
33
|
+
# to get the current value.
|
34
|
+
def value
|
35
|
+
redis.get(key).to_i
|
36
|
+
end
|
37
|
+
alias_method :get, :value
|
38
|
+
|
39
|
+
# Increment the counter atomically and return the new value. If passed
|
40
|
+
# a block, that block will be evaluated with the new value of the counter
|
41
|
+
# as an argument. If the block returns nil or throws an exception, the
|
42
|
+
# counter will automatically be decremented to its previous value. This
|
43
|
+
# method is aliased as incr() for brevity.
|
44
|
+
def increment(by=1, &block)
|
45
|
+
val = redis.incr(key, by).to_i
|
46
|
+
block_given? ? rewindable_block(:decrement, val, &block) : val
|
47
|
+
end
|
48
|
+
alias_method :incr, :increment
|
49
|
+
|
50
|
+
# Decrement the counter atomically and return the new value. If passed
|
51
|
+
# a block, that block will be evaluated with the new value of the counter
|
52
|
+
# as an argument. If the block returns nil or throws an exception, the
|
53
|
+
# counter will automatically be incremented to its previous value. This
|
54
|
+
# method is aliased as incr() for brevity.
|
55
|
+
def decrement(by=1, &block)
|
56
|
+
val = redis.decr(key, by).to_i
|
57
|
+
block_given? ? rewindable_block(:increment, val, &block) : val
|
58
|
+
end
|
59
|
+
alias_method :decr, :decrement
|
60
|
+
|
61
|
+
##
|
62
|
+
# Proxy methods to help make @object.counter == 10 work
|
63
|
+
def to_s; value.to_s; end
|
64
|
+
alias_method :to_str, :to_s
|
65
|
+
alias_method :to_i, :value
|
66
|
+
def nil?; value.nil? end
|
67
|
+
|
68
|
+
# Math ops
|
69
|
+
%w(== < > <= >=).each do |m|
|
70
|
+
class_eval <<-EndOverload
|
71
|
+
def #{m}(x)
|
72
|
+
value #{m} x
|
73
|
+
end
|
74
|
+
EndOverload
|
75
|
+
end
|
76
|
+
|
77
|
+
private
|
78
|
+
|
79
|
+
# Implements atomic increment/decrement blocks
|
80
|
+
def rewindable_block(rewind, value, &block)
|
81
|
+
raise ArgumentError, "Missing block to rewindable_block somehow" unless block_given?
|
82
|
+
ret = nil
|
83
|
+
begin
|
84
|
+
ret = yield value
|
85
|
+
rescue
|
86
|
+
send(rewind)
|
87
|
+
raise
|
88
|
+
end
|
89
|
+
send(rewind) if ret.nil?
|
90
|
+
ret
|
91
|
+
end
|
92
|
+
end
|
93
|
+
end
|
94
|
+
|
@@ -0,0 +1,149 @@
|
|
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)
|
16
|
+
end
|
17
|
+
|
18
|
+
@ring = HashRing.new hosts
|
19
|
+
end
|
20
|
+
|
21
|
+
def node_for_key(key)
|
22
|
+
key = $1 if key =~ /\{(.*)?\}/
|
23
|
+
@ring.get_node(key)
|
24
|
+
end
|
25
|
+
|
26
|
+
def add_server(server)
|
27
|
+
server, port = server.split(':')
|
28
|
+
@ring.add_node Redis.new(:host => server, :port => port)
|
29
|
+
end
|
30
|
+
|
31
|
+
def method_missing(sym, *args, &blk)
|
32
|
+
if redis = node_for_key(args.first.to_s)
|
33
|
+
redis.send sym, *args, &blk
|
34
|
+
else
|
35
|
+
super
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
def keys(glob)
|
40
|
+
@ring.nodes.map do |red|
|
41
|
+
red.keys(glob)
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
def save
|
46
|
+
on_each_node :save
|
47
|
+
end
|
48
|
+
|
49
|
+
def bgsave
|
50
|
+
on_each_node :bgsave
|
51
|
+
end
|
52
|
+
|
53
|
+
def quit
|
54
|
+
on_each_node :quit
|
55
|
+
end
|
56
|
+
|
57
|
+
def flush_all
|
58
|
+
on_each_node :flush_all
|
59
|
+
end
|
60
|
+
alias_method :flushall, :flush_all
|
61
|
+
|
62
|
+
def flush_db
|
63
|
+
on_each_node :flush_db
|
64
|
+
end
|
65
|
+
alias_method :flushdb, :flush_db
|
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
|
+
def on_each_node(command, *args)
|
76
|
+
@ring.nodes.each do |red|
|
77
|
+
red.send(command, *args)
|
78
|
+
end
|
79
|
+
end
|
80
|
+
|
81
|
+
def mset()
|
82
|
+
|
83
|
+
end
|
84
|
+
|
85
|
+
def mget(*keyz)
|
86
|
+
results = {}
|
87
|
+
kbn = keys_by_node(keyz)
|
88
|
+
kbn.each do |node, node_keyz|
|
89
|
+
node.mapped_mget(*node_keyz).each do |k, v|
|
90
|
+
results[k] = v
|
91
|
+
end
|
92
|
+
end
|
93
|
+
keyz.flatten.map { |k| results[k] }
|
94
|
+
end
|
95
|
+
|
96
|
+
def keys_by_node(*keyz)
|
97
|
+
keyz.flatten.inject({}) do |kbn, k|
|
98
|
+
node = node_for_key(k)
|
99
|
+
next if kbn[node] && kbn[node].include?(k)
|
100
|
+
kbn[node] ||= []
|
101
|
+
kbn[node] << k
|
102
|
+
kbn
|
103
|
+
end
|
104
|
+
end
|
105
|
+
|
106
|
+
end
|
107
|
+
|
108
|
+
|
109
|
+
if __FILE__ == $0
|
110
|
+
|
111
|
+
r = DistRedis.new 'localhost:6379', 'localhost:6380', 'localhost:6381', 'localhost:6382'
|
112
|
+
r['urmom'] = 'urmom'
|
113
|
+
r['urdad'] = 'urdad'
|
114
|
+
r['urmom1'] = 'urmom1'
|
115
|
+
r['urdad1'] = 'urdad1'
|
116
|
+
r['urmom2'] = 'urmom2'
|
117
|
+
r['urdad2'] = 'urdad2'
|
118
|
+
r['urmom3'] = 'urmom3'
|
119
|
+
r['urdad3'] = 'urdad3'
|
120
|
+
p r['urmom']
|
121
|
+
p r['urdad']
|
122
|
+
p r['urmom1']
|
123
|
+
p r['urdad1']
|
124
|
+
p r['urmom2']
|
125
|
+
p r['urdad2']
|
126
|
+
p r['urmom3']
|
127
|
+
p r['urdad3']
|
128
|
+
|
129
|
+
r.push_tail 'listor', 'foo1'
|
130
|
+
r.push_tail 'listor', 'foo2'
|
131
|
+
r.push_tail 'listor', 'foo3'
|
132
|
+
r.push_tail 'listor', 'foo4'
|
133
|
+
r.push_tail 'listor', 'foo5'
|
134
|
+
|
135
|
+
p r.pop_tail('listor')
|
136
|
+
p r.pop_tail('listor')
|
137
|
+
p r.pop_tail('listor')
|
138
|
+
p r.pop_tail('listor')
|
139
|
+
p r.pop_tail('listor')
|
140
|
+
|
141
|
+
puts "key distribution:"
|
142
|
+
|
143
|
+
r.ring.nodes.each do |red|
|
144
|
+
p [red.port, red.keys("*")]
|
145
|
+
end
|
146
|
+
r.delete_cloud!
|
147
|
+
p r.keys('*')
|
148
|
+
|
149
|
+
end
|
@@ -0,0 +1,135 @@
|
|
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
|
+
@nodes.reject!{|n| n.to_s == node.to_s}
|
35
|
+
@replicas.times do |i|
|
36
|
+
key = Zlib.crc32("#{node}:#{i}")
|
37
|
+
@ring.delete(key)
|
38
|
+
@sorted_keys.reject! {|k| k == key}
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
# get the node in the hash ring for this key
|
43
|
+
def get_node(key)
|
44
|
+
get_node_pos(key)[0]
|
45
|
+
end
|
46
|
+
|
47
|
+
def get_node_pos(key)
|
48
|
+
return [nil,nil] if @ring.size == 0
|
49
|
+
crc = Zlib.crc32(key)
|
50
|
+
idx = HashRing.binary_search(@sorted_keys, crc)
|
51
|
+
return [@ring[@sorted_keys[idx]], idx]
|
52
|
+
end
|
53
|
+
|
54
|
+
def iter_nodes(key)
|
55
|
+
return [nil,nil] if @ring.size == 0
|
56
|
+
node, pos = get_node_pos(key)
|
57
|
+
@sorted_keys[pos..-1].each do |k|
|
58
|
+
yield @ring[k]
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
class << self
|
63
|
+
|
64
|
+
# gem install RubyInline to use this code
|
65
|
+
# Native extension to perform the binary search within the hashring.
|
66
|
+
# There's a pure ruby version below so this is purely optional
|
67
|
+
# for performance. In testing 20k gets and sets, the native
|
68
|
+
# binary search shaved about 12% off the runtime (9sec -> 8sec).
|
69
|
+
begin
|
70
|
+
require 'inline'
|
71
|
+
inline do |builder|
|
72
|
+
builder.c <<-EOM
|
73
|
+
int binary_search(VALUE ary, unsigned int r) {
|
74
|
+
int upper = RARRAY_LEN(ary) - 1;
|
75
|
+
int lower = 0;
|
76
|
+
int idx = 0;
|
77
|
+
|
78
|
+
while (lower <= upper) {
|
79
|
+
idx = (lower + upper) / 2;
|
80
|
+
|
81
|
+
VALUE continuumValue = RARRAY_PTR(ary)[idx];
|
82
|
+
unsigned int l = NUM2UINT(continuumValue);
|
83
|
+
if (l == r) {
|
84
|
+
return idx;
|
85
|
+
}
|
86
|
+
else if (l > r) {
|
87
|
+
upper = idx - 1;
|
88
|
+
}
|
89
|
+
else {
|
90
|
+
lower = idx + 1;
|
91
|
+
}
|
92
|
+
}
|
93
|
+
if (upper < 0) {
|
94
|
+
upper = RARRAY_LEN(ary) - 1;
|
95
|
+
}
|
96
|
+
return upper;
|
97
|
+
}
|
98
|
+
EOM
|
99
|
+
end
|
100
|
+
rescue Exception => e
|
101
|
+
# Find the closest index in HashRing with value <= the given value
|
102
|
+
def binary_search(ary, value, &block)
|
103
|
+
upper = ary.size - 1
|
104
|
+
lower = 0
|
105
|
+
idx = 0
|
106
|
+
|
107
|
+
while(lower <= upper) do
|
108
|
+
idx = (lower + upper) / 2
|
109
|
+
comp = ary[idx] <=> value
|
110
|
+
|
111
|
+
if comp == 0
|
112
|
+
return idx
|
113
|
+
elsif comp > 0
|
114
|
+
upper = idx - 1
|
115
|
+
else
|
116
|
+
lower = idx + 1
|
117
|
+
end
|
118
|
+
end
|
119
|
+
|
120
|
+
if upper < 0
|
121
|
+
upper = ary.size - 1
|
122
|
+
end
|
123
|
+
return upper
|
124
|
+
end
|
125
|
+
|
126
|
+
end
|
127
|
+
end
|
128
|
+
|
129
|
+
end
|
130
|
+
|
131
|
+
# ring = HashRing.new ['server1', 'server2', 'server3']
|
132
|
+
# p ring
|
133
|
+
# #
|
134
|
+
# p ring.get_node "kjhjkjlkjlkkh"
|
135
|
+
#
|
@@ -0,0 +1,46 @@
|
|
1
|
+
class Redis
|
2
|
+
module Helpers
|
3
|
+
# These are core commands that all types share (rename, etc)
|
4
|
+
module CoreCommands
|
5
|
+
def exists?
|
6
|
+
redis.exists key
|
7
|
+
end
|
8
|
+
|
9
|
+
def delete
|
10
|
+
redis.del key
|
11
|
+
end
|
12
|
+
alias_method :del, :delete
|
13
|
+
alias_method :clear, :delete
|
14
|
+
|
15
|
+
def type
|
16
|
+
redis.type key
|
17
|
+
end
|
18
|
+
|
19
|
+
def rename(name, setkey=true)
|
20
|
+
dest = name.is_a?(self.class) ? name.key : name
|
21
|
+
ret = redis.rename key, dest
|
22
|
+
@key = dest if ret && setkey
|
23
|
+
ret
|
24
|
+
end
|
25
|
+
|
26
|
+
def renamenx(name, setkey=true)
|
27
|
+
dest = name.is_a?(self.class) ? name.key : name
|
28
|
+
ret = redis.renamenx key, dest
|
29
|
+
@key = dest if ret && setkey
|
30
|
+
ret
|
31
|
+
end
|
32
|
+
|
33
|
+
def expire(seconds)
|
34
|
+
redis.expire key, seconds
|
35
|
+
end
|
36
|
+
|
37
|
+
def expireat(unixtime)
|
38
|
+
redis.expire key, unixtime
|
39
|
+
end
|
40
|
+
|
41
|
+
def move(dbindex)
|
42
|
+
redis.move key, dbindex
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
class Redis
|
2
|
+
module Helpers
|
3
|
+
module Serialize
|
4
|
+
include Marshal
|
5
|
+
|
6
|
+
def to_redis(value)
|
7
|
+
case value
|
8
|
+
when String, Fixnum, Bignum, Float
|
9
|
+
value
|
10
|
+
else
|
11
|
+
dump(value)
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
def from_redis(value)
|
16
|
+
case value
|
17
|
+
when Array
|
18
|
+
value.collect{|v| from_redis(v)}
|
19
|
+
else
|
20
|
+
restore(value) rescue value
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|