redis-repeater 0.1.0 → 0.2.0
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/bin/redis-repeater +28 -3
- data/lib/redis_repeater.rb +7 -68
- data/lib/redis_repeater/configuration_error.rb +7 -0
- data/lib/redis_repeater/repeater.rb +66 -0
- data/lib/redis_repeater/transfer_scheduler_job.rb +17 -12
- data/lib/redis_repeater/version.rb +1 -1
- data/spec/spec_helper.rb +4 -36
- metadata +15 -4
- data/lib/redis_repeater/scheduler.rb +0 -19
- data/lib/redis_repeater/scheduler_job.rb +0 -24
data/bin/redis-repeater
CHANGED
@@ -1,9 +1,34 @@
|
|
1
1
|
#!/usr/bin/env ruby
|
2
2
|
|
3
|
+
require 'yaml'
|
3
4
|
require File.dirname(__FILE__) + '/../lib/redis_repeater'
|
5
|
+
require 'pidly'
|
4
6
|
|
5
|
-
|
6
|
-
|
7
|
+
class RedisRepeaterController < Pidly::Control
|
8
|
+
|
9
|
+
before_start do
|
10
|
+
if ARGV.size != 2
|
11
|
+
puts 'redis-repeater start /path/to/config.yml'
|
12
|
+
exit
|
13
|
+
else
|
14
|
+
configuration = YAML::load File.open(ARGV[1], 'r')
|
15
|
+
@repeater = RedisRepeater::Repeater.new(configuration)
|
16
|
+
puts self.pid
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
start do
|
21
|
+
@repeater.run_forever
|
22
|
+
end
|
23
|
+
|
24
|
+
end
|
25
|
+
|
26
|
+
@daemon = RedisRepeaterController.spawn :name => 'redis_repeater', :verbose => true
|
27
|
+
|
28
|
+
# Handle the command
|
29
|
+
commands = %w{start stop status restart clean! kill}
|
30
|
+
if commands.include?(ARGV.first)
|
31
|
+
@daemon.send ARGV.first
|
7
32
|
else
|
8
|
-
|
33
|
+
puts "No such command, try one of: #{commands.join(', ')}"
|
9
34
|
end
|
data/lib/redis_repeater.rb
CHANGED
@@ -1,75 +1,14 @@
|
|
1
|
-
require
|
2
|
-
require
|
3
|
-
require File.dirname(__FILE__) + '/redis_repeater/transfer_scheduler_job.rb'
|
4
|
-
|
5
|
-
require 'yaml'
|
1
|
+
require 'bundler/setup'
|
2
|
+
require 'redis'
|
6
3
|
require 'logger'
|
7
|
-
require 'fileutils'
|
8
|
-
|
9
4
|
require 'eventmachine'
|
10
|
-
require 'redis'
|
11
|
-
|
12
|
-
module RedisRepeater
|
13
|
-
|
14
|
-
DefaultRedisHost = 'localhost'
|
15
|
-
DefaultRedisPort = 6379
|
16
|
-
LogDefaultFilename = File.dirname(__FILE__) + '/../log/redis_repeater.log'
|
17
5
|
|
18
|
-
|
6
|
+
require File.dirname(__FILE__) + '/redis_repeater/configuration_error'
|
7
|
+
require File.dirname(__FILE__) + '/redis_repeater/repeater'
|
19
8
|
|
20
|
-
|
21
|
-
if File.directory?(config_path)
|
22
|
-
config = YAML::load File.open(File.join(config_path, 'config.yml'))
|
23
|
-
queues = YAML::load File.open(File.join(config_path, 'queues.yml'))
|
24
|
-
else
|
25
|
-
config = YAML::load File.open(config_path)
|
26
|
-
queues = config['queues']
|
27
|
-
end
|
28
|
-
|
29
|
-
# Connect to redis
|
30
|
-
redis_from = redis_configure(config, 'origin')
|
31
|
-
redis_to = redis_configure(config, 'destination')
|
32
|
-
|
33
|
-
# replace 'magic' resque:queues key with all resque queues, but don't overwrite otherwise configured queues
|
34
|
-
# warning: only resolves queue names on startup, if new queue is created it will not pick it up.
|
35
|
-
if queues.has_key?("resque:queues")
|
36
|
-
redis_from.smembers("resque:queues").each do |queue|
|
37
|
-
queues["resque:queue:#{queue}"] = queues["resque:queues"] unless queues.has_key?("resque:queue:#{queue}")
|
38
|
-
end
|
39
|
-
queues.delete("resque:queues")
|
40
|
-
end
|
41
|
-
|
42
|
-
# Logger
|
43
|
-
if config.has_key?('log')
|
44
|
-
log_filename = config['log']
|
45
|
-
FileUtils.mkdir_p(File.dirname(log_filename))
|
46
|
-
logger = Logger.new(log_filename)
|
47
|
-
else
|
48
|
-
logger = Logger.new(STDOUT)
|
49
|
-
end
|
50
|
-
|
51
|
-
# Load the queues into the scheduler
|
52
|
-
scheduler = Scheduler.new(logger)
|
53
|
-
EventMachine::run do
|
54
|
-
queues.each do |name, timeout|
|
55
|
-
puts "#{name} - #{timeout}"
|
56
|
-
scheduler << TransferSchedulerJob.new(name, timeout, logger, redis_from, redis_to, config['counter'])
|
57
|
-
end
|
58
|
-
end
|
59
|
-
|
60
|
-
# Perform forever
|
61
|
-
scheduler.perform # TODO make configurable
|
62
|
-
|
63
|
-
end
|
9
|
+
module RedisRepeater
|
64
10
|
|
65
|
-
|
66
|
-
|
67
|
-
port = DefaultRedisPort
|
68
|
-
if config.has_key?(name)
|
69
|
-
host = config[name]['host'] if config[name].has_key?('host')
|
70
|
-
port = config[name]['port'] if config[name].has_key?('port')
|
71
|
-
end
|
72
|
-
Redis.new(:host => host, :port => port)
|
73
|
-
end
|
11
|
+
autoload :VERSION, File.dirname(__FILE__) + '/redis_repeater/version'
|
12
|
+
autoload :TransferSchedulerJob, File.dirname(__FILE__) + '/redis_repeater/transfer_scheduler_job'
|
74
13
|
|
75
14
|
end
|
@@ -0,0 +1,66 @@
|
|
1
|
+
module RedisRepeater
|
2
|
+
|
3
|
+
class Repeater
|
4
|
+
|
5
|
+
attr_reader :servers, :repeats, :logger
|
6
|
+
|
7
|
+
def initialize(configuration, logger = nil)
|
8
|
+
raise ConfigurationError.new 'Configuration is empty' unless configuration
|
9
|
+
@servers = {}
|
10
|
+
@repeats = []
|
11
|
+
# Get the logger
|
12
|
+
@logger = logger
|
13
|
+
@logger ||= Logger.new(File.open(configuration['log'], 'a')) if configuration['log']
|
14
|
+
@logger ||= Logger.new(STDOUT)
|
15
|
+
# Load the configuration and start the server
|
16
|
+
load_hash_configuration configuration
|
17
|
+
end
|
18
|
+
|
19
|
+
def run_forever
|
20
|
+
raise ConfigurationError.new('No repeat jobs specified in configuration') if @repeats.empty?
|
21
|
+
# start the scheduler and run it forever
|
22
|
+
EventMachine::run do
|
23
|
+
@repeats.each do |job|
|
24
|
+
EventMachine::add_periodic_timer(job.timeout) { job.perform }
|
25
|
+
end
|
26
|
+
end
|
27
|
+
@logger.info "\nRepeating #{@repeats.count} #{@repeats.count == 1 ? 'queue' : 'queues'} forever!"
|
28
|
+
end
|
29
|
+
|
30
|
+
private
|
31
|
+
|
32
|
+
def load_hash_configuration(hash)
|
33
|
+
hash['servers'] && hash['servers'].each do |name, value|
|
34
|
+
options = {}
|
35
|
+
value.each { |k, v| options[k.to_sym] = v }
|
36
|
+
@servers[name] = Redis.new(options)
|
37
|
+
end
|
38
|
+
hash['repeats'] && hash['repeats'].each do |repeat|
|
39
|
+
options = {}
|
40
|
+
options[:source] = find_server_by_name repeat['source']
|
41
|
+
# Handle 'magic' queues
|
42
|
+
if repeat['queue'] == 'resque:queues' || repeat['queue'] == 'resque:queues:*'
|
43
|
+
options[:source].smembers('resque:queues').each do |queue|
|
44
|
+
repeat = repeat.dup
|
45
|
+
repeat['queue'] = "resque:queue:#{queue}"
|
46
|
+
hash['repeats'] << repeat
|
47
|
+
end
|
48
|
+
next
|
49
|
+
end
|
50
|
+
options[:destinations] = repeat['destinations'].map { |d| { :server => find_server_by_name(d['server']), :queue => d['queue'] || repeat['queue'] } }
|
51
|
+
options[:queue] = repeat['queue']
|
52
|
+
options[:timeout] = repeat['timeout'] || 0
|
53
|
+
options[:maintain_count] = !!repeat['maintain_count'] # default false
|
54
|
+
@repeats << TransferSchedulerJob.new(self, options)
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
def find_server_by_name(name)
|
59
|
+
server = @servers[name]
|
60
|
+
raise ConfigurationError.new("No such server found: #{name}") if server.nil?
|
61
|
+
server
|
62
|
+
end
|
63
|
+
|
64
|
+
end
|
65
|
+
|
66
|
+
end
|
@@ -1,25 +1,30 @@
|
|
1
1
|
module RedisRepeater
|
2
2
|
|
3
|
-
|
3
|
+
class TransferSchedulerJob
|
4
4
|
|
5
|
-
|
5
|
+
attr_reader :source, :destinations, :queue, :timeout, :maintain_count
|
6
6
|
|
7
|
-
def initialize(
|
8
|
-
@
|
9
|
-
@
|
10
|
-
@
|
11
|
-
|
7
|
+
def initialize(repeater, config)
|
8
|
+
@source = config[:source]
|
9
|
+
@destinations = config[:destinations]
|
10
|
+
@queue = config[:queue]
|
11
|
+
@timeout = config[:timeout]
|
12
|
+
@maintain_count = config[:maintain_count]
|
13
|
+
@logger = repeater.logger
|
12
14
|
end
|
13
15
|
|
14
16
|
def perform
|
17
|
+
# Transport everything we can
|
15
18
|
count = 0
|
16
|
-
while (item = @
|
17
|
-
|
18
|
-
|
19
|
+
while (item = @source.lpop @queue)
|
20
|
+
@destinations.each do |destination|
|
21
|
+
destination[:server].rpush destination[:queue], item
|
22
|
+
end
|
19
23
|
count += 1
|
20
24
|
end
|
21
|
-
|
22
|
-
@
|
25
|
+
# Keep the count and log a debug message
|
26
|
+
@source.incrby("redis-repeater:#{@queue}:count", count) if @maintain_count
|
27
|
+
@logger.debug "Transported #{count} items from #{@queue} to #{@destinations.count} #{@destinations.count == 1 ? 'destination' : 'destinations'}"
|
23
28
|
end
|
24
29
|
|
25
30
|
end
|
data/spec/spec_helper.rb
CHANGED
@@ -1,39 +1,7 @@
|
|
1
1
|
require File.dirname(__FILE__) + '/../lib/redis_repeater'
|
2
2
|
|
3
|
-
|
4
|
-
|
5
|
-
QN = 'queue-1234'
|
3
|
+
SERVER_ONE = { 'hostname' => 'localhost', 'port' => 6379 }
|
4
|
+
SERVER_TWO = { 'hostname' => 'localhost', 'port' => 6380 }
|
6
5
|
|
7
|
-
|
8
|
-
|
9
|
-
def start_servers
|
10
|
-
SERVERS.each do |s|
|
11
|
-
print "starting a #{s}.. "
|
12
|
-
`redis-server #{File.dirname(__FILE__) + "/support/#{s}.conf"}`
|
13
|
-
puts 'DONE'
|
14
|
-
end
|
15
|
-
end
|
16
|
-
|
17
|
-
def stop_servers
|
18
|
-
SERVERS.each do |s|
|
19
|
-
pid = `cat #{s}.pidfile`.chop
|
20
|
-
print "stopping #{s} (PID:#{pid}).. "
|
21
|
-
`kill #{pid}`
|
22
|
-
puts 'DONE'
|
23
|
-
end
|
24
|
-
end
|
25
|
-
|
26
|
-
def start_repeater(type = :default)
|
27
|
-
print 'starting the repeater.. '
|
28
|
-
# TODO deamonize
|
29
|
-
@rthread = Thread.start do
|
30
|
-
RedisRepeater.start(File.dirname(__FILE__) + "/support/config/#{type}")
|
31
|
-
end
|
32
|
-
puts 'DONE'
|
33
|
-
end
|
34
|
-
|
35
|
-
def stop_repeater
|
36
|
-
print 'stopping the repeater.. '
|
37
|
-
@rthread.kill
|
38
|
-
puts 'DONE'
|
39
|
-
end
|
6
|
+
REDIS_ONE = Redis.new SERVER_ONE
|
7
|
+
TEST_FILENAME = 'spec/test.log'
|
metadata
CHANGED
@@ -2,7 +2,7 @@
|
|
2
2
|
name: redis-repeater
|
3
3
|
version: !ruby/object:Gem::Version
|
4
4
|
prerelease:
|
5
|
-
version: 0.
|
5
|
+
version: 0.2.0
|
6
6
|
platform: ruby
|
7
7
|
authors:
|
8
8
|
- John Crepezzi
|
@@ -10,7 +10,7 @@ autorequire:
|
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
12
|
|
13
|
-
date: 2011-
|
13
|
+
date: 2011-04-20 00:00:00 -04:00
|
14
14
|
default_executable:
|
15
15
|
dependencies:
|
16
16
|
- !ruby/object:Gem::Dependency
|
@@ -46,6 +46,17 @@ dependencies:
|
|
46
46
|
version: "0"
|
47
47
|
type: :runtime
|
48
48
|
version_requirements: *id003
|
49
|
+
- !ruby/object:Gem::Dependency
|
50
|
+
name: pidly
|
51
|
+
prerelease: false
|
52
|
+
requirement: &id004 !ruby/object:Gem::Requirement
|
53
|
+
none: false
|
54
|
+
requirements:
|
55
|
+
- - ">="
|
56
|
+
- !ruby/object:Gem::Version
|
57
|
+
version: "0"
|
58
|
+
type: :runtime
|
59
|
+
version_requirements: *id004
|
49
60
|
description: Automatically move events from one redis queue to the same queue on another server
|
50
61
|
email: john@crepezzi.com
|
51
62
|
executables:
|
@@ -55,8 +66,8 @@ extensions: []
|
|
55
66
|
extra_rdoc_files: []
|
56
67
|
|
57
68
|
files:
|
58
|
-
- lib/redis_repeater/
|
59
|
-
- lib/redis_repeater/
|
69
|
+
- lib/redis_repeater/configuration_error.rb
|
70
|
+
- lib/redis_repeater/repeater.rb
|
60
71
|
- lib/redis_repeater/transfer_scheduler_job.rb
|
61
72
|
- lib/redis_repeater/version.rb
|
62
73
|
- lib/redis_repeater.rb
|
@@ -1,19 +0,0 @@
|
|
1
|
-
module RedisRepeater
|
2
|
-
|
3
|
-
class Scheduler
|
4
|
-
|
5
|
-
def initialize(logger = nil)
|
6
|
-
@logger = logger
|
7
|
-
@logger.info 'Running on schedule forever...'
|
8
|
-
end
|
9
|
-
|
10
|
-
# When adding, we move through the current queue, and
|
11
|
-
# find the first one that is performing after we want to
|
12
|
-
def <<(job)
|
13
|
-
scheduler = self
|
14
|
-
EventMachine::add_timer(job.timeout) { job.perform; scheduler << job }
|
15
|
-
end
|
16
|
-
|
17
|
-
end
|
18
|
-
|
19
|
-
end
|
@@ -1,24 +0,0 @@
|
|
1
|
-
module RedisRepeater
|
2
|
-
|
3
|
-
class SchedulerJob
|
4
|
-
|
5
|
-
def initialize(name, timeout, logger)
|
6
|
-
@name = name
|
7
|
-
@timeout = timeout
|
8
|
-
@logger = logger
|
9
|
-
@next_allowed_performance = Time.now + timeout
|
10
|
-
end
|
11
|
-
|
12
|
-
def perform
|
13
|
-
puts "Performing #{self.name}"
|
14
|
-
end
|
15
|
-
|
16
|
-
def adjust_next_allowed_performance
|
17
|
-
@next_allowed_performance = Time.now + timeout
|
18
|
-
end
|
19
|
-
|
20
|
-
attr_reader :name, :timeout, :next_allowed_performance
|
21
|
-
|
22
|
-
end
|
23
|
-
|
24
|
-
end
|