roma-client 0.3.7
Sign up to get free protection for your applications and to get access to all the features.
- data/LICENSE +674 -0
- data/README +17 -0
- data/Rakefile +59 -0
- data/bin/rcdaemon +6 -0
- data/bin/sample +23 -0
- data/bin/showbalance +6 -0
- data/lib/roma/client/client_rttable.rb +77 -0
- data/lib/roma/client/con_pool.rb +76 -0
- data/lib/roma/client/proxy/daemon.rb +281 -0
- data/lib/roma/client/proxy/version.rb +25 -0
- data/lib/roma/client/rclient.rb +409 -0
- data/lib/roma/client/rlogger.rb +148 -0
- data/lib/roma/client/routing/routing_data.rb +241 -0
- data/lib/roma/client/sender.rb +155 -0
- data/lib/roma/client/tools/showbalance.rb +33 -0
- data/lib/roma/client/version.rb +25 -0
- data/lib/roma/client.rb +22 -0
- metadata +75 -0
data/README
ADDED
@@ -0,0 +1,17 @@
|
|
1
|
+
================================================================
|
2
|
+
ROMA: A Distributed Key-Value Store in Ruby
|
3
|
+
Copyright (C) 2009 Rakuten, Inc.
|
4
|
+
================================================================
|
5
|
+
|
6
|
+
ROMA is one of the data storing systems for distributed key-value
|
7
|
+
stores. It is a completely decentralized distributed system that
|
8
|
+
consists of multiple processes, called nodes, on several machines. It
|
9
|
+
is based on pure P2P architecture like a distributed hash table, thus
|
10
|
+
it provides high availability and scalability.
|
11
|
+
|
12
|
+
ROMA is written in Ruby. However, following choices are available to
|
13
|
+
access to ROMA.
|
14
|
+
|
15
|
+
* Client libraries of Ruby and Java are available.
|
16
|
+
* ROMA protocol is compatible with memcached text-based one so that
|
17
|
+
any memcached client libraries allows users to interact with ROMA.
|
data/Rakefile
ADDED
@@ -0,0 +1,59 @@
|
|
1
|
+
# -*- coding: utf-8 -*-
|
2
|
+
require 'rubygems'
|
3
|
+
require 'rake'
|
4
|
+
require 'rake/gempackagetask'
|
5
|
+
require 'rake/rdoctask'
|
6
|
+
|
7
|
+
RDOC_OPTIONS = [
|
8
|
+
'--line-numbers',
|
9
|
+
'--inline-source',
|
10
|
+
"--main", "README.rdoc",
|
11
|
+
"-c UTF-8",
|
12
|
+
]
|
13
|
+
|
14
|
+
# gem tasks
|
15
|
+
PKG_FILES = FileList[
|
16
|
+
'[A-Z]*',
|
17
|
+
'bin/**/*',
|
18
|
+
'lib/**/*.rb',
|
19
|
+
'test/**/*.rb',
|
20
|
+
'spec/**/*.rb',
|
21
|
+
'doc/**/*',
|
22
|
+
'examples/**/*',
|
23
|
+
]
|
24
|
+
|
25
|
+
VER_NUM = `ruby -Ilib -e 'require "roma/client/version"; puts Roma::Client::VERSION::STRING'`
|
26
|
+
|
27
|
+
if VER_NUM =~ /([0-9.]+)$/
|
28
|
+
CURRENT_VERSION = $1
|
29
|
+
else
|
30
|
+
CURRENT_VERSION = "0.0.0"
|
31
|
+
end
|
32
|
+
|
33
|
+
SPEC = Gem::Specification.new do |s|
|
34
|
+
s.name = "roma-client"
|
35
|
+
s.version = CURRENT_VERSION
|
36
|
+
s.summary = "ROMA client library"
|
37
|
+
s.description = <<-EOF
|
38
|
+
ROMA client library
|
39
|
+
EOF
|
40
|
+
s.files = PKG_FILES.to_a
|
41
|
+
|
42
|
+
s.require_path = 'lib' # Use these for libraries.
|
43
|
+
|
44
|
+
s.has_rdoc = true
|
45
|
+
s.rdoc_options.concat RDOC_OPTIONS
|
46
|
+
s.extra_rdoc_files = ["README"]
|
47
|
+
end
|
48
|
+
|
49
|
+
package_task = Rake::GemPackageTask.new(SPEC) do |pkg|
|
50
|
+
end
|
51
|
+
|
52
|
+
|
53
|
+
Rake::RDocTask.new("doc") { |rdoc|
|
54
|
+
rdoc.rdoc_dir = 'doc'
|
55
|
+
rdoc.title = "ROMA documents"
|
56
|
+
rdoc.options.concat RDOC_OPTIONS
|
57
|
+
rdoc.rdoc_files.include('lib/**/*.rb')
|
58
|
+
rdoc.rdoc_files.include("README.rdoc")
|
59
|
+
}
|
data/bin/rcdaemon
ADDED
data/bin/sample
ADDED
@@ -0,0 +1,23 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
path = File.dirname(File.expand_path($PROGRAM_NAME))
|
4
|
+
$LOAD_PATH << path + "/../lib"
|
5
|
+
|
6
|
+
if ARGV.length < 1
|
7
|
+
puts "usage:sample address:port"
|
8
|
+
exit
|
9
|
+
end
|
10
|
+
|
11
|
+
require 'roma/client'
|
12
|
+
|
13
|
+
rc = Roma::Client::RomaClient.new(ARGV)
|
14
|
+
|
15
|
+
n = 10
|
16
|
+
|
17
|
+
n.times{|i|
|
18
|
+
rc["key-#{i}"]="value-#{i}"
|
19
|
+
}
|
20
|
+
|
21
|
+
n.times{|i|
|
22
|
+
puts rc["key-#{i}"]
|
23
|
+
}
|
data/bin/showbalance
ADDED
@@ -0,0 +1,77 @@
|
|
1
|
+
require 'yaml'
|
2
|
+
require 'digest/sha1'
|
3
|
+
require 'roma/client/routing/routing_data'
|
4
|
+
|
5
|
+
module Roma
|
6
|
+
module Client
|
7
|
+
|
8
|
+
class ClientRoutingTable
|
9
|
+
|
10
|
+
attr :rd
|
11
|
+
attr :search_mask
|
12
|
+
attr :fail_cnt
|
13
|
+
attr :hbits
|
14
|
+
attr :rn
|
15
|
+
attr :div_bits
|
16
|
+
attr_accessor :mklhash
|
17
|
+
|
18
|
+
def initialize(rd)
|
19
|
+
@rd = rd
|
20
|
+
@rn = @rd.rn
|
21
|
+
@div_bits=@rd.div_bits
|
22
|
+
@hbits = 2**@rd.dgst_bits
|
23
|
+
@search_mask = @rd.search_mask
|
24
|
+
@fail_cnt = Hash.new(0)
|
25
|
+
@mklhash = nil
|
26
|
+
end
|
27
|
+
|
28
|
+
def nodes
|
29
|
+
@rd.nodes.clone
|
30
|
+
end
|
31
|
+
|
32
|
+
def vnodes
|
33
|
+
@rd.v_idx.keys
|
34
|
+
end
|
35
|
+
|
36
|
+
# Returns a vnode-id from digest.
|
37
|
+
def get_vnode_id(d)
|
38
|
+
d & @search_mask
|
39
|
+
end
|
40
|
+
|
41
|
+
# Returns a node-is list at the vnode.
|
42
|
+
# +vn+: vnode-id
|
43
|
+
def search_nodes(vn)
|
44
|
+
@rd.v_idx[vn].clone
|
45
|
+
rescue
|
46
|
+
nil
|
47
|
+
end
|
48
|
+
|
49
|
+
def search_node(key)
|
50
|
+
d = Digest::SHA1.hexdigest(key).hex % @hbits
|
51
|
+
nodes = @rd.v_idx[d & @search_mask]
|
52
|
+
nodes.each_index { |i|
|
53
|
+
return [nodes[i], d] if @fail_cnt[nodes[i]] == 0
|
54
|
+
}
|
55
|
+
# for expecting an auto assign process
|
56
|
+
svn = vn = d & @search_mask
|
57
|
+
while( (vn = @rd.next_vnode(vn)) != svn )
|
58
|
+
nodes = @rd.v_idx[vn]
|
59
|
+
nodes.each_index { |i|
|
60
|
+
return [nodes[i], d] if @fail_cnt[nodes[i]] == 0
|
61
|
+
}
|
62
|
+
end
|
63
|
+
nil
|
64
|
+
rescue => e
|
65
|
+
p e
|
66
|
+
nil
|
67
|
+
end
|
68
|
+
|
69
|
+
def proc_failed(nid)
|
70
|
+
@fail_cnt[nid] += 1
|
71
|
+
@mklhash = 0
|
72
|
+
end
|
73
|
+
|
74
|
+
end # class ClientRoutingTable
|
75
|
+
|
76
|
+
end # module Client
|
77
|
+
end # module Roma
|
@@ -0,0 +1,76 @@
|
|
1
|
+
require 'thread'
|
2
|
+
require 'socket'
|
3
|
+
require 'singleton'
|
4
|
+
|
5
|
+
module Roma
|
6
|
+
module Client
|
7
|
+
|
8
|
+
class ConPool
|
9
|
+
include Singleton
|
10
|
+
|
11
|
+
attr_accessor :maxlength
|
12
|
+
|
13
|
+
def initialize(maxlength = 10)
|
14
|
+
@pool = {}
|
15
|
+
@maxlength = maxlength
|
16
|
+
@lock = Mutex.new
|
17
|
+
end
|
18
|
+
|
19
|
+
def get_connection(ap)
|
20
|
+
ret = @pool[ap].shift if @pool.key?(ap) && @pool[ap].length > 0
|
21
|
+
ret = create_connection(ap) unless ret
|
22
|
+
ret
|
23
|
+
rescue
|
24
|
+
nil
|
25
|
+
end
|
26
|
+
|
27
|
+
def return_connection(ap, con)
|
28
|
+
if @pool.key?(ap) && @pool[ap].length > 0
|
29
|
+
if @pool[ap].length > @maxlength
|
30
|
+
con.close
|
31
|
+
else
|
32
|
+
@pool[ap] << con
|
33
|
+
end
|
34
|
+
else
|
35
|
+
@pool[ap] = [con]
|
36
|
+
end
|
37
|
+
rescue
|
38
|
+
end
|
39
|
+
|
40
|
+
def create_connection(ap)
|
41
|
+
addr, port = ap.split(/[:_]/)
|
42
|
+
TCPSocket.new(addr, port)
|
43
|
+
end
|
44
|
+
|
45
|
+
def delete_connection(ap)
|
46
|
+
@pool.delete(ap)
|
47
|
+
end
|
48
|
+
|
49
|
+
def close_all
|
50
|
+
@pool.each_key{|ap| close_at(ap) }
|
51
|
+
end
|
52
|
+
|
53
|
+
def close_same_host(ap)
|
54
|
+
host,port = ap.split(/[:_]/)
|
55
|
+
@pool.each_key{|eap|
|
56
|
+
close_at(eap) if eap.split(/[:_]/)[0] == host
|
57
|
+
}
|
58
|
+
end
|
59
|
+
|
60
|
+
def close_at(ap)
|
61
|
+
return unless @pool.key?(ap)
|
62
|
+
@lock.synchronize {
|
63
|
+
while(@pool[ap].length > 0)
|
64
|
+
begin
|
65
|
+
@pool[ap].shift.close
|
66
|
+
rescue =>e
|
67
|
+
end
|
68
|
+
end
|
69
|
+
@pool.delete(ap)
|
70
|
+
}
|
71
|
+
end
|
72
|
+
|
73
|
+
end # class ConPool
|
74
|
+
|
75
|
+
end # module Client
|
76
|
+
end # module Roma
|
@@ -0,0 +1,281 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
#
|
3
|
+
# client proxy daemon
|
4
|
+
#
|
5
|
+
require 'optparse'
|
6
|
+
require 'eventmachine'
|
7
|
+
require 'timeout'
|
8
|
+
require 'singleton'
|
9
|
+
require 'roma/client/sender'
|
10
|
+
require 'roma/client/rlogger'
|
11
|
+
require 'roma/client/client_rttable'
|
12
|
+
|
13
|
+
module Roma
|
14
|
+
module Client
|
15
|
+
module Proxy
|
16
|
+
|
17
|
+
module RomaHandler
|
18
|
+
attr_accessor :ap
|
19
|
+
attr_reader :connected
|
20
|
+
|
21
|
+
def post_init
|
22
|
+
$log.info("Connected to roma")
|
23
|
+
@connected = true
|
24
|
+
end
|
25
|
+
|
26
|
+
def unbind
|
27
|
+
$log.info("Disconnected from roma")
|
28
|
+
@connected = nil
|
29
|
+
end
|
30
|
+
end # module RomaHandler
|
31
|
+
|
32
|
+
module ClientHandler
|
33
|
+
|
34
|
+
def post_init
|
35
|
+
$log.info("Connected from client")
|
36
|
+
@cmd = ''
|
37
|
+
end
|
38
|
+
|
39
|
+
def receive_data(data)
|
40
|
+
@cmd << data
|
41
|
+
if @cmd.index("\n")
|
42
|
+
@roma_h = get_roma_handler(@cmd)
|
43
|
+
@roma_h.send_data(@cmd)
|
44
|
+
@cmd = ''
|
45
|
+
EM::enable_proxy(self, @roma_h)
|
46
|
+
EM::enable_proxy(@roma_h, self)
|
47
|
+
end
|
48
|
+
rescue =>e
|
49
|
+
$log.error("#{e} #{$@}")
|
50
|
+
end
|
51
|
+
|
52
|
+
def unbind
|
53
|
+
$log.info("Disconnected from client")
|
54
|
+
EM::disable_proxy(self)
|
55
|
+
if @roma_h
|
56
|
+
EM::disable_proxy(@roma_h)
|
57
|
+
Conpool::instance.return_connection(@roma_h)
|
58
|
+
end
|
59
|
+
rescue =>e
|
60
|
+
$log.error("#{e} #{$@}")
|
61
|
+
end
|
62
|
+
|
63
|
+
def get_roma_handler(cmd_line)
|
64
|
+
cmd, key_hname = cmd_line.split(' ')
|
65
|
+
key, hname = key_hname.split("\e")
|
66
|
+
nid, d = Daemon::rttable.search_node(key)
|
67
|
+
Conpool::instance.get_connection(nid, RomaHandler)
|
68
|
+
rescue =>e
|
69
|
+
$log.error("#{e} #{$@}")
|
70
|
+
end
|
71
|
+
|
72
|
+
end # module ClientHandler
|
73
|
+
|
74
|
+
class Conpool
|
75
|
+
include Singleton
|
76
|
+
|
77
|
+
attr_accessor :maxlength
|
78
|
+
|
79
|
+
def initialize
|
80
|
+
@pool = {}
|
81
|
+
@maxlength = 10
|
82
|
+
@lock = Mutex.new
|
83
|
+
end
|
84
|
+
|
85
|
+
def get_connection(ap, handler)
|
86
|
+
ret = @pool[ap].shift if @pool.key?(ap) && @pool[ap].length > 0
|
87
|
+
ret = create_connection(ap, handler) if ret == nil
|
88
|
+
ret
|
89
|
+
end
|
90
|
+
|
91
|
+
def return_connection(con)
|
92
|
+
return unless con.connected
|
93
|
+
if @pool.key?(con.ap) && @pool[con.ap].length > 0
|
94
|
+
if @pool[con.ap].length > @maxlength
|
95
|
+
con.close_connection
|
96
|
+
else
|
97
|
+
@pool[con.ap] << con
|
98
|
+
end
|
99
|
+
else
|
100
|
+
@pool[con.ap] = [con]
|
101
|
+
end
|
102
|
+
end
|
103
|
+
|
104
|
+
def create_connection(ap, handler)
|
105
|
+
addr,port = ap.split('_')
|
106
|
+
con = EventMachine::connect(addr, port, handler)
|
107
|
+
con.ap = ap
|
108
|
+
con
|
109
|
+
end
|
110
|
+
|
111
|
+
def close_all
|
112
|
+
@pool.each_key{|ap| close_at(ap) }
|
113
|
+
end
|
114
|
+
|
115
|
+
def close_at(ap)
|
116
|
+
return unless @pool.key?(ap)
|
117
|
+
@lock.synchronize {
|
118
|
+
while(@pool[ap].length > 0)
|
119
|
+
begin
|
120
|
+
@pool[ap].shift.close_connection
|
121
|
+
rescue =>e
|
122
|
+
$log.error("#{e} #{$@}")
|
123
|
+
end
|
124
|
+
end
|
125
|
+
@pool.delete(ap)
|
126
|
+
}
|
127
|
+
end
|
128
|
+
end # class Conpool
|
129
|
+
|
130
|
+
class Daemon
|
131
|
+
attr_reader :daemon
|
132
|
+
|
133
|
+
@@rttable = nil
|
134
|
+
|
135
|
+
def self.rttable
|
136
|
+
@@rttable
|
137
|
+
end
|
138
|
+
|
139
|
+
def initialize(argv = nil)
|
140
|
+
options(argv)
|
141
|
+
initialize_logger
|
142
|
+
@sender = Roma::Client::Sender.new
|
143
|
+
update_rttable(@init_nodes)
|
144
|
+
end
|
145
|
+
|
146
|
+
def initialize_logger
|
147
|
+
Roma::Logging::RLogger.create_singleton_instance(@log_path,
|
148
|
+
@log_age,
|
149
|
+
@log_size)
|
150
|
+
end
|
151
|
+
|
152
|
+
def start
|
153
|
+
|
154
|
+
timer
|
155
|
+
|
156
|
+
loop do
|
157
|
+
begin
|
158
|
+
EventMachine::run do
|
159
|
+
EventMachine.start_server('0.0.0.0', @port, ClientHandler)
|
160
|
+
EventMachine.start_unix_domain_server("/tmp/#{@uds_name}", ClientHandler)
|
161
|
+
end
|
162
|
+
rescue =>e
|
163
|
+
$log.error("#{e} #{$@}")
|
164
|
+
retry
|
165
|
+
end
|
166
|
+
end
|
167
|
+
end
|
168
|
+
|
169
|
+
private
|
170
|
+
|
171
|
+
def update_rttable(nodes)
|
172
|
+
raise RuntimeError.new("nodes must not be nil.") unless nodes
|
173
|
+
|
174
|
+
nodes.each { |node|
|
175
|
+
rt = make_rttable(node)
|
176
|
+
if rt != nil
|
177
|
+
@@rttable = rt
|
178
|
+
return
|
179
|
+
end
|
180
|
+
}
|
181
|
+
raise RuntimeError.new("fatal error")
|
182
|
+
end
|
183
|
+
|
184
|
+
def make_rttable(node)
|
185
|
+
mklhash = @sender.send_route_mklhash_command(node)
|
186
|
+
return nil unless mklhash
|
187
|
+
|
188
|
+
if @@rttable && @@rttable.mklhash == mklhash
|
189
|
+
return @@rttable
|
190
|
+
end
|
191
|
+
|
192
|
+
rd = @sender.send_routedump_command(node)
|
193
|
+
if rd
|
194
|
+
ret = Roma::Client::ClientRoutingTable.new(rd)
|
195
|
+
ret.mklhash = mklhash
|
196
|
+
return ret
|
197
|
+
end
|
198
|
+
nil
|
199
|
+
rescue =>e
|
200
|
+
$log.error("#{e} #{$@}")
|
201
|
+
nil
|
202
|
+
end
|
203
|
+
|
204
|
+
def options(argv)
|
205
|
+
opts = OptionParser.new
|
206
|
+
opts.banner="usage:#{File.basename($0)} [options] addr_port"
|
207
|
+
|
208
|
+
@uds_name = 'roma'
|
209
|
+
opts.on("-n", "--name [name]","Unix domain socket name.default=roma") { |v| @uds_name = v }
|
210
|
+
@port = 12345
|
211
|
+
opts.on("-p", "--port [port number]","default=12345"){ |v| @port = v.to_i }
|
212
|
+
@log_path = "./rcdaemon.log"
|
213
|
+
opts.on("-l", "--log [path]","default=./"){ |v|
|
214
|
+
@log_path = v
|
215
|
+
@log_path << "/" if @log_path[-1] != "/"
|
216
|
+
@log_path << "rcdaemon.log"
|
217
|
+
}
|
218
|
+
|
219
|
+
@daemon = true
|
220
|
+
opts.on(nil, "--debug"){ @daemon = false }
|
221
|
+
@log_age = 10
|
222
|
+
@log_size = 1024 * 1024
|
223
|
+
|
224
|
+
opts.parse!(argv)
|
225
|
+
raise OptionParser::ParseError.new if argv.length < 1
|
226
|
+
@init_nodes = argv
|
227
|
+
rescue OptionParser::ParseError => e
|
228
|
+
$stderr.puts e.message
|
229
|
+
$stderr.puts opts.help
|
230
|
+
exit 1
|
231
|
+
end
|
232
|
+
|
233
|
+
def timer
|
234
|
+
Thread.new do
|
235
|
+
loop do
|
236
|
+
sleep 10
|
237
|
+
timer_event_10sec
|
238
|
+
end
|
239
|
+
end
|
240
|
+
end
|
241
|
+
|
242
|
+
def timer_event_10sec
|
243
|
+
update_rttable(@@rttable.nodes)
|
244
|
+
end
|
245
|
+
|
246
|
+
def self.daemon
|
247
|
+
p = Process.fork {
|
248
|
+
pid=Process.setsid
|
249
|
+
Signal.trap(:INT){
|
250
|
+
exit! 0
|
251
|
+
}
|
252
|
+
Signal.trap(:TERM){
|
253
|
+
exit! 0
|
254
|
+
}
|
255
|
+
Signal.trap(:HUP){
|
256
|
+
exit! 0
|
257
|
+
}
|
258
|
+
File.open("/dev/null","r+"){|f|
|
259
|
+
STDIN.reopen f
|
260
|
+
STDOUT.reopen f
|
261
|
+
STDERR.reopen f
|
262
|
+
}
|
263
|
+
yield
|
264
|
+
}
|
265
|
+
$stderr.puts p
|
266
|
+
exit! 0
|
267
|
+
end
|
268
|
+
|
269
|
+
end # class Daemon
|
270
|
+
|
271
|
+
end # module Proxy
|
272
|
+
end # module Client
|
273
|
+
end # module Roma
|
274
|
+
|
275
|
+
d = Roma::Client::Proxy::Daemon.new(ARGV)
|
276
|
+
$log = Roma::Logging::RLogger.instance
|
277
|
+
if d.daemon
|
278
|
+
Roma::Client::Proxy::Daemon.daemon{ d.start }
|
279
|
+
else
|
280
|
+
d.start
|
281
|
+
end
|