roma-client 0.3.7
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 +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
|