redi 0.0.1
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/Gemfile +4 -0
- data/Gemfile.lock +25 -0
- data/README.md +49 -0
- data/init.rb +3 -0
- data/lib/redi.rb +200 -0
- data/lib/redi/version.rb +3 -0
- data/redi.gemspec +28 -0
- metadata +129 -0
data/Gemfile
ADDED
data/Gemfile.lock
ADDED
@@ -0,0 +1,25 @@
|
|
1
|
+
PATH
|
2
|
+
remote: .
|
3
|
+
specs:
|
4
|
+
redis-ring (0.0.1)
|
5
|
+
active_support
|
6
|
+
json
|
7
|
+
redis
|
8
|
+
redis-namespace
|
9
|
+
|
10
|
+
GEM
|
11
|
+
remote: http://rubygems.org/
|
12
|
+
specs:
|
13
|
+
active_support (3.0.0)
|
14
|
+
activesupport (= 3.0.0)
|
15
|
+
activesupport (3.0.0)
|
16
|
+
json (1.6.1)
|
17
|
+
redis (2.2.2)
|
18
|
+
redis-namespace (1.1.0)
|
19
|
+
redis (< 3.0.0)
|
20
|
+
|
21
|
+
PLATFORMS
|
22
|
+
ruby
|
23
|
+
|
24
|
+
DEPENDENCIES
|
25
|
+
redis-ring!
|
data/README.md
ADDED
@@ -0,0 +1,49 @@
|
|
1
|
+
Redi
|
2
|
+
----------
|
3
|
+
|
4
|
+
Pooled redis puts a layer of indirection between server pool and key ring
|
5
|
+
|
6
|
+
|
7
|
+
The idea comes from http://blog.zawodny.com/2011/02/26/redis-sharding-at-craigslist/
|
8
|
+
|
9
|
+
gem install redi
|
10
|
+
|
11
|
+
|
12
|
+
Create a configuration file:
|
13
|
+
|
14
|
+
development:
|
15
|
+
:keyspace:
|
16
|
+
- :n0: 0
|
17
|
+
- :n1: 1
|
18
|
+
- :n2: 0
|
19
|
+
- :n3: 1
|
20
|
+
- :n4: 0
|
21
|
+
- :n5: 1
|
22
|
+
- :n6: 0
|
23
|
+
- :n7: 1
|
24
|
+
- :n8: 0
|
25
|
+
- :n9: 1
|
26
|
+
- :n10: 0
|
27
|
+
- :n11: 1
|
28
|
+
- :n12: 0
|
29
|
+
- :n13: 1
|
30
|
+
- :n14: 0
|
31
|
+
- :n15: 1
|
32
|
+
- :n16: 0
|
33
|
+
:servers:
|
34
|
+
- :host: 192.168.0.10
|
35
|
+
:port: 6379
|
36
|
+
:db: 0
|
37
|
+
- :host: 192.168.0.11
|
38
|
+
:port: 6380
|
39
|
+
:db: 0
|
40
|
+
|
41
|
+
The keyspace mapping is very important. It helps you manage the exact mapping of buckets to servers and is how you can scale from 2 severs to 16.
|
42
|
+
In the example, you have 16 buckets that map evenly to 2 servers. If you decide you need more memory (more severs) you can choose a segment of your
|
43
|
+
buckets to replicate to a new server.
|
44
|
+
|
45
|
+
1. create new server
|
46
|
+
2. identify buckets to move to new server
|
47
|
+
3. setup new server as slave to replicate
|
48
|
+
4. update configuration to point buckets to new server
|
49
|
+
5. prune old keys from old server freeing up more space on old server using a keys nsxx* query
|
data/init.rb
ADDED
data/lib/redi.rb
ADDED
@@ -0,0 +1,200 @@
|
|
1
|
+
require 'rubygems' if __FILE__ == $0
|
2
|
+
require 'zlib'
|
3
|
+
require 'yaml'
|
4
|
+
require 'redis'
|
5
|
+
require 'redis/hash_ring'
|
6
|
+
require 'redis/namespace'
|
7
|
+
|
8
|
+
class Redi
|
9
|
+
|
10
|
+
def self.get(key)
|
11
|
+
pool.redis_by_key(key).get(key)
|
12
|
+
end
|
13
|
+
|
14
|
+
def self.set(key, val)
|
15
|
+
pool.redis_by_key(key).set(key, val)
|
16
|
+
end
|
17
|
+
|
18
|
+
def self.pool
|
19
|
+
@pool ||= Pool.new(self.config)
|
20
|
+
end
|
21
|
+
|
22
|
+
def self.config=(config)
|
23
|
+
@config = config
|
24
|
+
end
|
25
|
+
|
26
|
+
def self.config
|
27
|
+
@config
|
28
|
+
end
|
29
|
+
|
30
|
+
# provide a key to name to host:port mapping
|
31
|
+
#
|
32
|
+
# should have a larger keyspace than servers, this allows scaling up the servers without changing the keyspace mapping
|
33
|
+
#
|
34
|
+
# sample configuration:
|
35
|
+
#
|
36
|
+
# :keyspace:
|
37
|
+
# - :n0: 0
|
38
|
+
# - :n1: 1
|
39
|
+
# - :n2: 0
|
40
|
+
# - :n3: 1
|
41
|
+
# - :n4: 0
|
42
|
+
# :servers:
|
43
|
+
# - :host:
|
44
|
+
# :port:
|
45
|
+
# :db:
|
46
|
+
# - :host:
|
47
|
+
# :port:
|
48
|
+
# :db:
|
49
|
+
#
|
50
|
+
class Pool
|
51
|
+
attr_reader :keyspace, :servers
|
52
|
+
def initialize(config)
|
53
|
+
@key_type = Struct.new(:id, :to_s)
|
54
|
+
keyspace = config[:keyspace]
|
55
|
+
|
56
|
+
# create bucket set
|
57
|
+
@buckets = keyspace.each_with_index.map {|k,i| @key_type.new(i, k.keys.first) }
|
58
|
+
|
59
|
+
# build server pool
|
60
|
+
@servers = config[:servers].map {|cfg| Redis.new(cfg) }
|
61
|
+
|
62
|
+
# create the mapping from bucket to server
|
63
|
+
@bucket2server = {}
|
64
|
+
keyspace.each do|ks|
|
65
|
+
@bucket2server[ks.keys.first] = @servers[ks.values.first]
|
66
|
+
end
|
67
|
+
|
68
|
+
# create the keyring to map redis keys to buckets
|
69
|
+
@keyring = Redis::HashRing.new(@buckets)
|
70
|
+
end
|
71
|
+
|
72
|
+
def redis_by_key(key)
|
73
|
+
bucket = @keyring.get_node(key)
|
74
|
+
Redis::Namespace.new(bucket.to_s, :redis => @bucket2server[bucket.to_s])
|
75
|
+
end
|
76
|
+
|
77
|
+
end
|
78
|
+
|
79
|
+
class Mock
|
80
|
+
def initialize
|
81
|
+
@store = {}
|
82
|
+
end
|
83
|
+
|
84
|
+
def get(key)
|
85
|
+
@store[key]
|
86
|
+
end
|
87
|
+
|
88
|
+
def set(key, val)
|
89
|
+
@store[key] = val
|
90
|
+
end
|
91
|
+
|
92
|
+
def mget(*keys)
|
93
|
+
keys.map {|k| get(k) }
|
94
|
+
end
|
95
|
+
|
96
|
+
def flushall
|
97
|
+
@store = {}
|
98
|
+
end
|
99
|
+
|
100
|
+
end
|
101
|
+
|
102
|
+
end
|
103
|
+
|
104
|
+
if __FILE__ == $0
|
105
|
+
require 'rubygems'
|
106
|
+
require 'test/unit'
|
107
|
+
|
108
|
+
TEST_CONFIG = %(
|
109
|
+
:keyspace:
|
110
|
+
- :n0: 0
|
111
|
+
- :n1: 1
|
112
|
+
- :n2: 0
|
113
|
+
- :n3: 1
|
114
|
+
- :n4: 0
|
115
|
+
- :n5: 1
|
116
|
+
- :n6: 0
|
117
|
+
- :n7: 1
|
118
|
+
- :n8: 0
|
119
|
+
- :n9: 1
|
120
|
+
- :n10: 0
|
121
|
+
- :n11: 1
|
122
|
+
- :n12: 0
|
123
|
+
- :n13: 1
|
124
|
+
- :n14: 0
|
125
|
+
- :n15: 1
|
126
|
+
- :n16: 0
|
127
|
+
- :n17: 1
|
128
|
+
- :n18: 0
|
129
|
+
- :n19: 1
|
130
|
+
- :n20: 0
|
131
|
+
- :n21: 1
|
132
|
+
- :n22: 0
|
133
|
+
- :n23: 1
|
134
|
+
- :n24: 0
|
135
|
+
- :n25: 1
|
136
|
+
- :n26: 0
|
137
|
+
- :n27: 1
|
138
|
+
- :n28: 0
|
139
|
+
- :n29: 1
|
140
|
+
- :n30: 0
|
141
|
+
- :n31: 1
|
142
|
+
- :n32: 0
|
143
|
+
:servers:
|
144
|
+
- :host: 127.0.0.1
|
145
|
+
:port: 6379
|
146
|
+
:db: 0
|
147
|
+
- :host: 127.0.0.1
|
148
|
+
:port: 6379
|
149
|
+
:db: 1
|
150
|
+
)
|
151
|
+
class TestIt < Test::Unit::TestCase
|
152
|
+
|
153
|
+
def test_redis_pool
|
154
|
+
pool = Redi::Pool.new(YAML.load(TEST_CONFIG))
|
155
|
+
redis = pool.redis_by_key('me:foo:1')
|
156
|
+
assert_equal :n25, redis.namespace
|
157
|
+
redis.flushall
|
158
|
+
redis.set("me:foo:1", "hello")
|
159
|
+
assert_equal "hello", redis.get("me:foo:1")
|
160
|
+
end
|
161
|
+
|
162
|
+
def test_using_hash_ring_strategy
|
163
|
+
node_class = Struct.new(:id, :to_s)
|
164
|
+
buckets = []
|
165
|
+
128.times do|i|
|
166
|
+
buckets << node_class.new(i, "n#{i}")
|
167
|
+
end
|
168
|
+
|
169
|
+
servers = []
|
170
|
+
2.times do|i|
|
171
|
+
servers << node_class.new(i, "s#{i}")
|
172
|
+
end
|
173
|
+
|
174
|
+
ring1 = Redis::HashRing.new(buckets)
|
175
|
+
|
176
|
+
bucket2server = {}
|
177
|
+
buckets.each do|bucket|
|
178
|
+
bucket2server[bucket.to_s] = servers[bucket.id % servers.size].to_s
|
179
|
+
end
|
180
|
+
|
181
|
+
puts bucket2server.inspect
|
182
|
+
|
183
|
+
servers_used = {}
|
184
|
+
|
185
|
+
buckets_used = {}
|
186
|
+
6400.times do|i|
|
187
|
+
key = "me:foo#{i}"
|
188
|
+
bucket = ring1.get_node(key)
|
189
|
+
server = bucket2server[bucket.to_s]#ring2.get_node(name.to_s)
|
190
|
+
#puts "#{key} -> #{name} -> #{server}"
|
191
|
+
servers_used[ server.to_s ] ||= 0
|
192
|
+
servers_used[ server.to_s ] += 1
|
193
|
+
buckets_used[ bucket.to_s ] ||= 0
|
194
|
+
buckets_used[ bucket.to_s ] += 1
|
195
|
+
end
|
196
|
+
puts servers_used.inspect
|
197
|
+
puts buckets_used.inspect
|
198
|
+
end
|
199
|
+
end
|
200
|
+
end
|
data/lib/redi/version.rb
ADDED
data/redi.gemspec
ADDED
@@ -0,0 +1,28 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
$:.push File.expand_path("../lib", __FILE__)
|
3
|
+
require "redi/version"
|
4
|
+
|
5
|
+
Gem::Specification.new do |s|
|
6
|
+
s.name = "redi"
|
7
|
+
s.version = RedisRing::VERSION
|
8
|
+
s.authors = ["Todd Fisher"]
|
9
|
+
s.email = ["todd.fisher@livingsocial.com"]
|
10
|
+
s.homepage = "http://livingsocial.com/"
|
11
|
+
s.summary = %q{Redi multi redis scaling "to infinity and beyond!"}
|
12
|
+
s.description = %q{hash keys to intermediate buckets allowing you to more easily scale out to more severs later}
|
13
|
+
|
14
|
+
s.rubyforge_project = "redi"
|
15
|
+
|
16
|
+
s.files = `git ls-files`.split("\n")
|
17
|
+
s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
|
18
|
+
s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
|
19
|
+
s.require_paths = ["lib"]
|
20
|
+
|
21
|
+
# specify any dependencies here; for example:
|
22
|
+
# s.add_development_dependency "rails"
|
23
|
+
|
24
|
+
s.add_runtime_dependency "json"
|
25
|
+
s.add_runtime_dependency "active_support"
|
26
|
+
s.add_runtime_dependency "redis"
|
27
|
+
s.add_runtime_dependency "redis-namespace"
|
28
|
+
end
|
metadata
ADDED
@@ -0,0 +1,129 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: redi
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
hash: 29
|
5
|
+
prerelease:
|
6
|
+
segments:
|
7
|
+
- 0
|
8
|
+
- 0
|
9
|
+
- 1
|
10
|
+
version: 0.0.1
|
11
|
+
platform: ruby
|
12
|
+
authors:
|
13
|
+
- Todd Fisher
|
14
|
+
autorequire:
|
15
|
+
bindir: bin
|
16
|
+
cert_chain: []
|
17
|
+
|
18
|
+
date: 2011-10-12 00:00:00 -04:00
|
19
|
+
default_executable:
|
20
|
+
dependencies:
|
21
|
+
- !ruby/object:Gem::Dependency
|
22
|
+
name: json
|
23
|
+
prerelease: false
|
24
|
+
requirement: &id001 !ruby/object:Gem::Requirement
|
25
|
+
none: false
|
26
|
+
requirements:
|
27
|
+
- - ">="
|
28
|
+
- !ruby/object:Gem::Version
|
29
|
+
hash: 3
|
30
|
+
segments:
|
31
|
+
- 0
|
32
|
+
version: "0"
|
33
|
+
type: :runtime
|
34
|
+
version_requirements: *id001
|
35
|
+
- !ruby/object:Gem::Dependency
|
36
|
+
name: active_support
|
37
|
+
prerelease: false
|
38
|
+
requirement: &id002 !ruby/object:Gem::Requirement
|
39
|
+
none: false
|
40
|
+
requirements:
|
41
|
+
- - ">="
|
42
|
+
- !ruby/object:Gem::Version
|
43
|
+
hash: 3
|
44
|
+
segments:
|
45
|
+
- 0
|
46
|
+
version: "0"
|
47
|
+
type: :runtime
|
48
|
+
version_requirements: *id002
|
49
|
+
- !ruby/object:Gem::Dependency
|
50
|
+
name: redis
|
51
|
+
prerelease: false
|
52
|
+
requirement: &id003 !ruby/object:Gem::Requirement
|
53
|
+
none: false
|
54
|
+
requirements:
|
55
|
+
- - ">="
|
56
|
+
- !ruby/object:Gem::Version
|
57
|
+
hash: 3
|
58
|
+
segments:
|
59
|
+
- 0
|
60
|
+
version: "0"
|
61
|
+
type: :runtime
|
62
|
+
version_requirements: *id003
|
63
|
+
- !ruby/object:Gem::Dependency
|
64
|
+
name: redis-namespace
|
65
|
+
prerelease: false
|
66
|
+
requirement: &id004 !ruby/object:Gem::Requirement
|
67
|
+
none: false
|
68
|
+
requirements:
|
69
|
+
- - ">="
|
70
|
+
- !ruby/object:Gem::Version
|
71
|
+
hash: 3
|
72
|
+
segments:
|
73
|
+
- 0
|
74
|
+
version: "0"
|
75
|
+
type: :runtime
|
76
|
+
version_requirements: *id004
|
77
|
+
description: hash keys to intermediate buckets allowing you to more easily scale out to more severs later
|
78
|
+
email:
|
79
|
+
- todd.fisher@livingsocial.com
|
80
|
+
executables: []
|
81
|
+
|
82
|
+
extensions: []
|
83
|
+
|
84
|
+
extra_rdoc_files: []
|
85
|
+
|
86
|
+
files:
|
87
|
+
- Gemfile
|
88
|
+
- Gemfile.lock
|
89
|
+
- README.md
|
90
|
+
- init.rb
|
91
|
+
- lib/redi.rb
|
92
|
+
- lib/redi/version.rb
|
93
|
+
- redi.gemspec
|
94
|
+
has_rdoc: true
|
95
|
+
homepage: http://livingsocial.com/
|
96
|
+
licenses: []
|
97
|
+
|
98
|
+
post_install_message:
|
99
|
+
rdoc_options: []
|
100
|
+
|
101
|
+
require_paths:
|
102
|
+
- lib
|
103
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
104
|
+
none: false
|
105
|
+
requirements:
|
106
|
+
- - ">="
|
107
|
+
- !ruby/object:Gem::Version
|
108
|
+
hash: 3
|
109
|
+
segments:
|
110
|
+
- 0
|
111
|
+
version: "0"
|
112
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
113
|
+
none: false
|
114
|
+
requirements:
|
115
|
+
- - ">="
|
116
|
+
- !ruby/object:Gem::Version
|
117
|
+
hash: 3
|
118
|
+
segments:
|
119
|
+
- 0
|
120
|
+
version: "0"
|
121
|
+
requirements: []
|
122
|
+
|
123
|
+
rubyforge_project: redi
|
124
|
+
rubygems_version: 1.5.2
|
125
|
+
signing_key:
|
126
|
+
specification_version: 3
|
127
|
+
summary: Redi multi redis scaling "to infinity and beyond!"
|
128
|
+
test_files: []
|
129
|
+
|