chimera 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (56) hide show
  1. data/History.txt +4 -0
  2. data/Manifest.txt +57 -0
  3. data/PostInstall.txt +7 -0
  4. data/README.rdoc +114 -0
  5. data/Rakefile +30 -0
  6. data/doc/NOTES +11 -0
  7. data/doc/examples/config.yml +16 -0
  8. data/doc/redis6379.conf +132 -0
  9. data/lib/chimera.rb +33 -0
  10. data/lib/chimera/associations.rb +146 -0
  11. data/lib/chimera/attributes.rb +52 -0
  12. data/lib/chimera/base.rb +95 -0
  13. data/lib/chimera/config.rb +9 -0
  14. data/lib/chimera/error.rb +12 -0
  15. data/lib/chimera/finders.rb +49 -0
  16. data/lib/chimera/geo_indexes.rb +76 -0
  17. data/lib/chimera/indexes.rb +177 -0
  18. data/lib/chimera/persistence.rb +70 -0
  19. data/lib/chimera/redis_objects.rb +345 -0
  20. data/lib/redis.rb +373 -0
  21. data/lib/redis/counter.rb +94 -0
  22. data/lib/redis/dist_redis.rb +149 -0
  23. data/lib/redis/hash_ring.rb +135 -0
  24. data/lib/redis/helpers/core_commands.rb +46 -0
  25. data/lib/redis/helpers/serialize.rb +25 -0
  26. data/lib/redis/list.rb +122 -0
  27. data/lib/redis/lock.rb +83 -0
  28. data/lib/redis/objects.rb +100 -0
  29. data/lib/redis/objects/counters.rb +132 -0
  30. data/lib/redis/objects/lists.rb +45 -0
  31. data/lib/redis/objects/locks.rb +71 -0
  32. data/lib/redis/objects/sets.rb +46 -0
  33. data/lib/redis/objects/values.rb +56 -0
  34. data/lib/redis/pipeline.rb +21 -0
  35. data/lib/redis/set.rb +156 -0
  36. data/lib/redis/value.rb +35 -0
  37. data/lib/riak_raw.rb +100 -0
  38. data/lib/typhoeus.rb +55 -0
  39. data/lib/typhoeus/.gitignore +1 -0
  40. data/lib/typhoeus/easy.rb +253 -0
  41. data/lib/typhoeus/filter.rb +28 -0
  42. data/lib/typhoeus/hydra.rb +210 -0
  43. data/lib/typhoeus/multi.rb +34 -0
  44. data/lib/typhoeus/remote.rb +306 -0
  45. data/lib/typhoeus/remote_method.rb +108 -0
  46. data/lib/typhoeus/remote_proxy_object.rb +48 -0
  47. data/lib/typhoeus/request.rb +124 -0
  48. data/lib/typhoeus/response.rb +39 -0
  49. data/lib/typhoeus/service.rb +20 -0
  50. data/script/console +10 -0
  51. data/script/destroy +14 -0
  52. data/script/generate +14 -0
  53. data/test/models.rb +49 -0
  54. data/test/test_chimera.rb +238 -0
  55. data/test/test_helper.rb +7 -0
  56. 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