beetle 0.1 → 0.2.1
Sign up to get free protection for your applications and to get access to all the features.
- data/README.rdoc +18 -8
- data/beetle.gemspec +37 -121
- data/bin/beetle +9 -0
- data/examples/README.rdoc +0 -2
- data/examples/rpc.rb +3 -2
- data/ext/mkrf_conf.rb +19 -0
- data/lib/beetle.rb +2 -2
- data/lib/beetle/base.rb +1 -8
- data/lib/beetle/client.rb +16 -14
- data/lib/beetle/commands.rb +30 -0
- data/lib/beetle/commands/configuration_client.rb +73 -0
- data/lib/beetle/commands/configuration_server.rb +85 -0
- data/lib/beetle/configuration.rb +70 -7
- data/lib/beetle/deduplication_store.rb +50 -38
- data/lib/beetle/handler.rb +2 -5
- data/lib/beetle/logging.rb +7 -0
- data/lib/beetle/message.rb +11 -13
- data/lib/beetle/publisher.rb +12 -4
- data/lib/beetle/r_c.rb +2 -1
- data/lib/beetle/redis_configuration_client.rb +136 -0
- data/lib/beetle/redis_configuration_server.rb +301 -0
- data/lib/beetle/redis_ext.rb +79 -0
- data/lib/beetle/redis_master_file.rb +35 -0
- data/lib/beetle/redis_server_info.rb +65 -0
- data/lib/beetle/subscriber.rb +4 -1
- data/test/beetle/configuration_test.rb +14 -2
- data/test/beetle/deduplication_store_test.rb +61 -43
- data/test/beetle/message_test.rb +28 -4
- data/test/beetle/publisher_test.rb +17 -3
- data/test/beetle/redis_configuration_client_test.rb +97 -0
- data/test/beetle/redis_configuration_server_test.rb +278 -0
- data/test/beetle/redis_ext_test.rb +71 -0
- data/test/beetle/redis_master_file_test.rb +39 -0
- data/test/test_helper.rb +13 -1
- metadata +162 -69
- data/.gitignore +0 -5
- data/MIT-LICENSE +0 -20
- data/Rakefile +0 -114
- data/TODO +0 -7
- data/etc/redis-master.conf +0 -189
- data/etc/redis-slave.conf +0 -189
- data/examples/redis_failover.rb +0 -65
- data/script/start_rabbit +0 -29
- data/snafu.rb +0 -55
- data/test/beetle.yml +0 -81
- data/test/beetle/bla.rb +0 -0
- data/tmp/master/.gitignore +0 -2
- data/tmp/slave/.gitignore +0 -3
@@ -0,0 +1,85 @@
|
|
1
|
+
require 'optparse'
|
2
|
+
require 'daemons'
|
3
|
+
require 'beetle'
|
4
|
+
|
5
|
+
module Beetle
|
6
|
+
module Commands
|
7
|
+
# Command to start a RedisConfigurationServer daemon.
|
8
|
+
#
|
9
|
+
# Usage: beetle configuration_server [options] -- [server options]
|
10
|
+
#
|
11
|
+
# server options:
|
12
|
+
# --redis-servers LIST Required for start command (e.g. 192.168.0.1:6379,192.168.0.2:6379)
|
13
|
+
# --client-ids LIST Clients that have to acknowledge on master switch (e.g. client-id1,client-id2)
|
14
|
+
# --redis-master-file FILE Write redis master server string to FILE
|
15
|
+
# --redis-retry-interval SEC Number of seconds to wait between master checks
|
16
|
+
# --amqp-servers LIST AMQP server list (e.g. 192.168.0.1:5672,192.168.0.2:5672)
|
17
|
+
# --config-file PATH Path to an external yaml config file
|
18
|
+
# --pid-dir DIR Write pid and log to DIR
|
19
|
+
# -v, --verbose
|
20
|
+
# -h, --help Show this message
|
21
|
+
#
|
22
|
+
class ConfigurationServer
|
23
|
+
# parses command line options and starts Beetle::RedisConfigurationServer as a daemon
|
24
|
+
def self.execute
|
25
|
+
command, controller_options, app_options = Daemons::Controller.split_argv(ARGV)
|
26
|
+
|
27
|
+
opts = OptionParser.new
|
28
|
+
opts.banner = "Usage: beetle configuration_server #{command} [options] -- [server options]"
|
29
|
+
opts.separator ""
|
30
|
+
opts.separator "server options:"
|
31
|
+
|
32
|
+
opts.on("--redis-servers LIST", Array, "Required for start command (e.g. 192.168.0.1:6379,192.168.0.2:6379)") do |val|
|
33
|
+
Beetle.config.redis_servers = val.join(",")
|
34
|
+
end
|
35
|
+
|
36
|
+
opts.on("--client-ids LIST", "Clients that have to acknowledge on master switch (e.g. client-id1,client-id2)") do |val|
|
37
|
+
Beetle.config.redis_configuration_client_ids = val
|
38
|
+
end
|
39
|
+
|
40
|
+
opts.on("--redis-master-file FILE", String, "Write redis master server string to FILE") do |val|
|
41
|
+
Beetle.config.redis_server = val
|
42
|
+
end
|
43
|
+
|
44
|
+
opts.on("--redis-retry-interval SEC", Integer, "Number of seconds to wait between master checks") do |val|
|
45
|
+
Beetle.config.redis_configuration_master_retry_interval = val
|
46
|
+
end
|
47
|
+
|
48
|
+
opts.on("--amqp-servers LIST", String, "AMQP server list (e.g. 192.168.0.1:5672,192.168.0.2:5672)") do |val|
|
49
|
+
Beetle.config.servers = val
|
50
|
+
end
|
51
|
+
|
52
|
+
opts.on("--config-file PATH", String, "Path to an external yaml config file") do |val|
|
53
|
+
Beetle.config.config_file = val
|
54
|
+
end
|
55
|
+
|
56
|
+
dir_mode = nil
|
57
|
+
dir = nil
|
58
|
+
opts.on("--pid-dir DIR", String, "Write pid and log to DIR") do |val|
|
59
|
+
dir_mode = :normal
|
60
|
+
dir = val
|
61
|
+
end
|
62
|
+
|
63
|
+
opts.on("-v", "--verbose") do |val|
|
64
|
+
Beetle.config.logger.level = Logger::DEBUG
|
65
|
+
end
|
66
|
+
|
67
|
+
opts.on_tail("-h", "--help", "Show this message") do
|
68
|
+
puts opts
|
69
|
+
exit
|
70
|
+
end
|
71
|
+
|
72
|
+
opts.parse!(app_options)
|
73
|
+
|
74
|
+
if command =~ /start|run/ && Beetle.config.redis_servers.blank?
|
75
|
+
puts opts
|
76
|
+
exit
|
77
|
+
end
|
78
|
+
|
79
|
+
Daemons.run_proc("redis_configuration_server", :log_output => true, :dir_mode => dir_mode, :dir => dir) do
|
80
|
+
Beetle::RedisConfigurationServer.new.start
|
81
|
+
end
|
82
|
+
end
|
83
|
+
end
|
84
|
+
end
|
85
|
+
end
|
data/lib/beetle/configuration.rb
CHANGED
@@ -1,31 +1,94 @@
|
|
1
|
+
require 'erb'
|
2
|
+
|
1
3
|
module Beetle
|
2
4
|
class Configuration
|
5
|
+
# system name (used for redis cluster partitioning) (defaults to <tt>system</tt>)
|
6
|
+
attr_accessor :system_name
|
3
7
|
# default logger (defaults to <tt>Logger.new(STDOUT)</tt>)
|
4
8
|
attr_accessor :logger
|
5
9
|
# number of seconds after which keys are removed form the message deduplication store (defaults to <tt>3.days</tt>)
|
6
10
|
attr_accessor :gc_threshold
|
7
|
-
# the
|
8
|
-
|
11
|
+
# the redis server to use for deduplication
|
12
|
+
# either a string like <tt>"localhost:6379"</tt> (default) or a file that contains the string.
|
13
|
+
# use a file if you are using a beetle configuration_client process to update it for automatic redis failover.
|
14
|
+
attr_accessor :redis_server
|
15
|
+
# comma separated list of redis servers available for master/slave switching
|
16
|
+
# e.g. "192.168.1.2:6379,192.168.1.3:6379"
|
17
|
+
attr_accessor :redis_servers
|
9
18
|
# redis database number to use for the message deduplication store (defaults to <tt>4</tt>)
|
10
19
|
attr_accessor :redis_db
|
20
|
+
|
21
|
+
# how long we should repeatedly retry a redis operation before giving up, with a one
|
22
|
+
# second sleep between retries (defaults to <tt>180.seconds</tt>). this value needs to be
|
23
|
+
# somewehere between the maximum time it takes to auto-switch redis and the smallest
|
24
|
+
# handler timeout.
|
25
|
+
attr_accessor :redis_failover_timeout
|
26
|
+
|
27
|
+
## redis configuration server options
|
28
|
+
# how often should the redis configuration server try to reach the redis master before nominating a new one (defaults to <tt>3</tt>)
|
29
|
+
attr_accessor :redis_configuration_master_retries
|
30
|
+
# number of seconds to wait between retries (defaults to <tt>10</tt>)
|
31
|
+
attr_accessor :redis_configuration_master_retry_interval
|
32
|
+
# number of seconds the redis configuration server waits for answers from clients (defaults to <tt>5</tt>)
|
33
|
+
attr_accessor :redis_configuration_client_timeout
|
34
|
+
# the redis configuration client ids living on the worker machines taking part in the redis failover, separated by comma (defaults to <tt>""</tt>)
|
35
|
+
attr_accessor :redis_configuration_client_ids
|
36
|
+
|
11
37
|
# list of amqp servers to use (defaults to <tt>"localhost:5672"</tt>)
|
12
38
|
attr_accessor :servers
|
13
|
-
# the virtual host to use on the AMQP servers
|
39
|
+
# the virtual host to use on the AMQP servers (defaults to <tt>"/"</tt>)
|
14
40
|
attr_accessor :vhost
|
15
|
-
# the AMQP user to use when connecting to the AMQP servers
|
41
|
+
# the AMQP user to use when connecting to the AMQP servers (defaults to <tt>"guest"</tt>)
|
16
42
|
attr_accessor :user
|
17
|
-
# the password to use when connectiong to the AMQP servers
|
43
|
+
# the password to use when connectiong to the AMQP servers (defaults to <tt>"guest"</tt>)
|
18
44
|
attr_accessor :password
|
19
45
|
|
46
|
+
# external config file (defaults to <tt>no file</tt>)
|
47
|
+
attr_reader :config_file
|
48
|
+
|
20
49
|
def initialize #:nodoc:
|
21
|
-
self.
|
50
|
+
self.system_name = "system"
|
51
|
+
|
52
|
+
self.logger = begin
|
53
|
+
logger = Logger.new(STDOUT)
|
54
|
+
logger.formatter = Logger::Formatter.new
|
55
|
+
logger.level = Logger::INFO
|
56
|
+
logger.datetime_format = "%Y-%m-%d %H:%M:%S"
|
57
|
+
logger
|
58
|
+
end
|
59
|
+
|
22
60
|
self.gc_threshold = 3.days
|
23
|
-
self.
|
61
|
+
self.redis_server = "localhost:6379"
|
62
|
+
self.redis_servers = ""
|
24
63
|
self.redis_db = 4
|
64
|
+
self.redis_failover_timeout = 180.seconds
|
65
|
+
|
66
|
+
self.redis_configuration_master_retries = 3
|
67
|
+
self.redis_configuration_master_retry_interval = 10.seconds
|
68
|
+
self.redis_configuration_client_timeout = 5.seconds
|
69
|
+
self.redis_configuration_client_ids = ""
|
70
|
+
|
25
71
|
self.servers = "localhost:5672"
|
26
72
|
self.vhost = "/"
|
27
73
|
self.user = "guest"
|
28
74
|
self.password = "guest"
|
29
75
|
end
|
76
|
+
|
77
|
+
# setting the external config file will load it on assignment
|
78
|
+
def config_file=(file_name) #:nodoc:
|
79
|
+
@config_file = file_name
|
80
|
+
load_config
|
81
|
+
end
|
82
|
+
|
83
|
+
private
|
84
|
+
def load_config
|
85
|
+
hash = YAML::load(ERB.new(IO.read(config_file)).result)
|
86
|
+
hash.each do |key, value|
|
87
|
+
send("#{key}=", value)
|
88
|
+
end
|
89
|
+
rescue Exception
|
90
|
+
logger.error "Error loading beetle config file '#{config_file}': #{$!}"
|
91
|
+
raise
|
92
|
+
end
|
30
93
|
end
|
31
94
|
end
|
@@ -11,15 +11,23 @@ module Beetle
|
|
11
11
|
#
|
12
12
|
# It also provides a method to garbage collect keys for expired messages.
|
13
13
|
class DeduplicationStore
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
@
|
14
|
+
include Logging
|
15
|
+
|
16
|
+
def initialize(config = Beetle.config)
|
17
|
+
@config = config
|
18
|
+
@current_master = nil
|
19
|
+
@last_time_master_file_changed = nil
|
18
20
|
end
|
19
21
|
|
20
22
|
# get the Redis instance
|
21
23
|
def redis
|
22
|
-
@
|
24
|
+
redis_master_source = @config.redis_server =~ /^\S+\:\d+$/ ? "server_string" : "master_file"
|
25
|
+
_eigenclass_.class_eval <<-EVALS, __FILE__, __LINE__
|
26
|
+
def redis
|
27
|
+
redis_master_from_#{redis_master_source}
|
28
|
+
end
|
29
|
+
EVALS
|
30
|
+
redis
|
23
31
|
end
|
24
32
|
|
25
33
|
# list of key suffixes to use for storing values in Redis.
|
@@ -43,7 +51,7 @@ module Beetle
|
|
43
51
|
# garbage collect keys in Redis (always assume the worst!)
|
44
52
|
def garbage_collect_keys(now = Time.now.to_i)
|
45
53
|
keys = redis.keys("msgid:*:expires")
|
46
|
-
threshold = now +
|
54
|
+
threshold = now + @config.gc_threshold
|
47
55
|
keys.each do |key|
|
48
56
|
expires_at = redis.get key
|
49
57
|
if expires_at && expires_at.to_i < threshold
|
@@ -53,20 +61,20 @@ module Beetle
|
|
53
61
|
end
|
54
62
|
end
|
55
63
|
|
56
|
-
# unconditionally store a key
|
64
|
+
# unconditionally store a (key,value) pair with given <tt>suffix</tt> for given <tt>msg_id</tt>.
|
57
65
|
def set(msg_id, suffix, value)
|
58
66
|
with_failover { redis.set(key(msg_id, suffix), value) }
|
59
67
|
end
|
60
68
|
|
61
|
-
# store a key
|
69
|
+
# store a (key,value) pair with given <tt>suffix</tt> for given <tt>msg_id</tt> if it doesn't exists yet.
|
62
70
|
def setnx(msg_id, suffix, value)
|
63
71
|
with_failover { redis.setnx(key(msg_id, suffix), value) }
|
64
72
|
end
|
65
73
|
|
66
74
|
# store some key/value pairs if none of the given keys exist.
|
67
75
|
def msetnx(msg_id, values)
|
68
|
-
values = values.inject(
|
69
|
-
with_failover { redis.msetnx(values) }
|
76
|
+
values = values.inject([]){|a,(k,v)| a.concat([key(msg_id, k), v])}
|
77
|
+
with_failover { redis.msetnx(*values) }
|
70
78
|
end
|
71
79
|
|
72
80
|
# increment counter for key with given <tt>suffix</tt> for given <tt>msg_id</tt>. returns an integer.
|
@@ -86,7 +94,7 @@ module Beetle
|
|
86
94
|
|
87
95
|
# delete all keys associated with the given <tt>msg_id</tt>.
|
88
96
|
def del_keys(msg_id)
|
89
|
-
with_failover { redis.del(keys(msg_id)) }
|
97
|
+
with_failover { redis.del(*keys(msg_id)) }
|
90
98
|
end
|
91
99
|
|
92
100
|
# check whether key with given suffix exists for a given <tt>msg_id</tt>.
|
@@ -101,16 +109,15 @@ module Beetle
|
|
101
109
|
|
102
110
|
# performs redis operations by yielding a passed in block, waiting for a new master to
|
103
111
|
# show up on the network if the operation throws an exception. if a new master doesn't
|
104
|
-
# appear after
|
112
|
+
# appear after the configured timeout interval, we raise an exception.
|
105
113
|
def with_failover #:nodoc:
|
106
|
-
|
114
|
+
end_time = Time.now.to_i + @config.redis_failover_timeout.to_i
|
107
115
|
begin
|
108
116
|
yield
|
109
117
|
rescue Exception => e
|
110
118
|
Beetle::reraise_expectation_errors!
|
111
|
-
logger.error "Beetle: redis connection error
|
112
|
-
if
|
113
|
-
@redis = nil
|
119
|
+
logger.error "Beetle: redis connection error #{e} #{@config.redis_server} (#{e.backtrace[0]})"
|
120
|
+
if Time.now.to_i < end_time
|
114
121
|
sleep 1
|
115
122
|
logger.info "Beetle: retrying redis operation"
|
116
123
|
retry
|
@@ -120,33 +127,38 @@ module Beetle
|
|
120
127
|
end
|
121
128
|
end
|
122
129
|
|
123
|
-
#
|
124
|
-
def
|
125
|
-
|
126
|
-
redis_instances.each do |redis|
|
127
|
-
begin
|
128
|
-
masters << redis if redis.info[:role] == "master"
|
129
|
-
rescue Exception => e
|
130
|
-
logger.error "Beetle: could not determine status of redis instance #{redis.server}"
|
131
|
-
end
|
132
|
-
end
|
133
|
-
raise NoRedisMaster.new("unable to determine a new master redis instance") if masters.empty?
|
134
|
-
raise TwoRedisMasters.new("more than one redis master instances") if masters.size > 1
|
135
|
-
logger.info "Beetle: configured new redis master #{masters.first.server}"
|
136
|
-
masters.first
|
130
|
+
# set current redis master instance (as specified in the Beetle::Configuration)
|
131
|
+
def redis_master_from_server_string
|
132
|
+
@current_master ||= Redis.from_server_string(@config.redis_server, :db => @config.redis_db)
|
137
133
|
end
|
138
134
|
|
139
|
-
#
|
140
|
-
def
|
141
|
-
|
142
|
-
|
143
|
-
|
135
|
+
# set current redis master from master file
|
136
|
+
def redis_master_from_master_file
|
137
|
+
set_current_redis_master_from_master_file if redis_master_file_changed?
|
138
|
+
@current_master
|
139
|
+
rescue Errno::ENOENT
|
140
|
+
nil
|
144
141
|
end
|
145
142
|
|
146
|
-
#
|
147
|
-
def
|
148
|
-
|
143
|
+
# redis master file changed outside the running process?
|
144
|
+
def redis_master_file_changed?
|
145
|
+
@last_time_master_file_changed != File.mtime(@config.redis_server)
|
149
146
|
end
|
150
147
|
|
148
|
+
# set current redis master from server:port string contained in the redis master file
|
149
|
+
def set_current_redis_master_from_master_file
|
150
|
+
@last_time_master_file_changed = File.mtime(@config.redis_server)
|
151
|
+
server_string = read_master_file
|
152
|
+
@current_master = !server_string.blank? ? Redis.from_server_string(server_string, :db => @config.redis_db) : nil
|
153
|
+
end
|
154
|
+
|
155
|
+
# server:port string from the redis master file
|
156
|
+
def read_master_file
|
157
|
+
File.read(@config.redis_server).chomp
|
158
|
+
end
|
159
|
+
|
160
|
+
def _eigenclass_ #:nodoc:
|
161
|
+
class << self; self; end
|
162
|
+
end
|
151
163
|
end
|
152
164
|
end
|
data/lib/beetle/handler.rb
CHANGED
@@ -6,6 +6,8 @@ module Beetle
|
|
6
6
|
# Most applications will define Handler subclasses and override the process, error and
|
7
7
|
# failure methods.
|
8
8
|
class Handler
|
9
|
+
include Logging
|
10
|
+
|
9
11
|
# the Message instance which caused the handler to be created
|
10
12
|
attr_reader :message
|
11
13
|
|
@@ -81,11 +83,6 @@ module Beetle
|
|
81
83
|
logger.error "Beetle: handler has finally failed"
|
82
84
|
end
|
83
85
|
|
84
|
-
# returns the configured Beetle logger
|
85
|
-
def logger
|
86
|
-
Beetle.config.logger
|
87
|
-
end
|
88
|
-
|
89
86
|
# returns the configured Beetle logger
|
90
87
|
def self.logger
|
91
88
|
Beetle.config.logger
|
data/lib/beetle/message.rb
CHANGED
@@ -6,6 +6,8 @@ module Beetle
|
|
6
6
|
# should retry executing the message handler after a handler has crashed (or forcefully
|
7
7
|
# aborted).
|
8
8
|
class Message
|
9
|
+
include Logging
|
10
|
+
|
9
11
|
# current message format version
|
10
12
|
FORMAT_VERSION = 1
|
11
13
|
# flag for encoding redundant messages
|
@@ -14,7 +16,7 @@ module Beetle
|
|
14
16
|
DEFAULT_TTL = 1.day
|
15
17
|
# forcefully abort a running handler after this many seconds.
|
16
18
|
# can be overriden when registering a handler.
|
17
|
-
DEFAULT_HANDLER_TIMEOUT =
|
19
|
+
DEFAULT_HANDLER_TIMEOUT = 600.seconds
|
18
20
|
# how many times we should try to run a handler before giving up
|
19
21
|
DEFAULT_HANDLER_EXECUTION_ATTEMPTS = 1
|
20
22
|
# how many seconds we should wait before retrying handler execution
|
@@ -77,6 +79,8 @@ module Beetle
|
|
77
79
|
@format_version = headers[:format_version].to_i
|
78
80
|
@flags = headers[:flags].to_i
|
79
81
|
@expires_at = headers[:expires_at].to_i
|
82
|
+
rescue Exception => @exception
|
83
|
+
logger.error "Could not decode message. #{self.inspect}"
|
80
84
|
end
|
81
85
|
|
82
86
|
# build hash with options for the publisher
|
@@ -193,7 +197,7 @@ module Beetle
|
|
193
197
|
# have we already seen this message? if not, set the status to "incomplete" and store
|
194
198
|
# the message exipration timestamp in the deduplication store.
|
195
199
|
def key_exists?
|
196
|
-
old_message = 0 == @store.msetnx(msg_id, :status =>"incomplete", :expires => @expires_at)
|
200
|
+
old_message = 0 == @store.msetnx(msg_id, :status =>"incomplete", :expires => @expires_at, :timeout => now + timeout)
|
197
201
|
if old_message
|
198
202
|
logger.debug "Beetle: received duplicate message: #{msg_id} on queue: #{@queue}"
|
199
203
|
end
|
@@ -236,7 +240,10 @@ module Beetle
|
|
236
240
|
private
|
237
241
|
|
238
242
|
def process_internal(handler)
|
239
|
-
if
|
243
|
+
if @exception
|
244
|
+
ack!
|
245
|
+
RC::DecodingError
|
246
|
+
elsif expired?
|
240
247
|
logger.warn "Beetle: ignored expired message (#{msg_id})!"
|
241
248
|
ack!
|
242
249
|
RC::Ancient
|
@@ -244,7 +251,6 @@ module Beetle
|
|
244
251
|
ack!
|
245
252
|
run_handler(handler) == RC::HandlerCrash ? RC::AttemptsLimitReached : RC::OK
|
246
253
|
elsif !key_exists?
|
247
|
-
set_timeout!
|
248
254
|
run_handler!(handler)
|
249
255
|
elsif completed?
|
250
256
|
ack!
|
@@ -273,7 +279,7 @@ module Beetle
|
|
273
279
|
end
|
274
280
|
|
275
281
|
def run_handler(handler)
|
276
|
-
|
282
|
+
Redis::Timer.timeout(@timeout.to_f) { @handler_result = handler.call(self) }
|
277
283
|
RC::OK
|
278
284
|
rescue Exception => @exception
|
279
285
|
Beetle::reraise_expectation_errors!
|
@@ -313,14 +319,6 @@ module Beetle
|
|
313
319
|
end
|
314
320
|
end
|
315
321
|
|
316
|
-
def logger
|
317
|
-
@logger ||= self.class.logger
|
318
|
-
end
|
319
|
-
|
320
|
-
def self.logger
|
321
|
-
Beetle.config.logger
|
322
|
-
end
|
323
|
-
|
324
322
|
# ack the message for rabbit. deletes all keys associated with this message in the
|
325
323
|
# deduplication store if we are sure this is the last message with the given msg_id.
|
326
324
|
def ack!
|
data/lib/beetle/publisher.rb
CHANGED
@@ -132,11 +132,16 @@ module Beetle
|
|
132
132
|
b
|
133
133
|
end
|
134
134
|
|
135
|
+
# retry dead servers after ignoring them for 10.seconds
|
136
|
+
# if all servers are dead, retry the one which has been dead for the longest time
|
135
137
|
def recycle_dead_servers
|
136
138
|
recycle = []
|
137
139
|
@dead_servers.each do |s, dead_since|
|
138
140
|
recycle << s if dead_since < 10.seconds.ago
|
139
141
|
end
|
142
|
+
if recycle.empty? && @servers.empty?
|
143
|
+
recycle << @dead_servers.keys.sort_by{|k| @dead_servers[k]}.first
|
144
|
+
end
|
140
145
|
@servers.concat recycle
|
141
146
|
recycle.each {|s| @dead_servers.delete(s)}
|
142
147
|
end
|
@@ -149,8 +154,11 @@ module Beetle
|
|
149
154
|
end
|
150
155
|
|
151
156
|
def select_next_server
|
152
|
-
|
153
|
-
|
157
|
+
if @servers.empty?
|
158
|
+
logger.error("Beetle: no server available")
|
159
|
+
else
|
160
|
+
set_current_server(@servers[((@servers.index(@server) || 0)+1) % @servers.size])
|
161
|
+
end
|
154
162
|
end
|
155
163
|
|
156
164
|
def create_exchange!(name, opts)
|
@@ -165,9 +173,9 @@ module Beetle
|
|
165
173
|
|
166
174
|
# TODO: Refactor, fethch the keys and stuff itself
|
167
175
|
def bind_queue!(queue_name, creation_keys, exchange_name, binding_keys)
|
168
|
-
logger.debug("
|
176
|
+
logger.debug("Beetle: creating queue with opts: #{creation_keys.inspect}")
|
169
177
|
queue = bunny.queue(queue_name, creation_keys)
|
170
|
-
logger.debug("
|
178
|
+
logger.debug("Beetle: binding queue #{queue_name} to #{exchange_name} with opts: #{binding_keys.inspect}")
|
171
179
|
queue.bind(exchange(exchange_name), binding_keys)
|
172
180
|
queue
|
173
181
|
end
|