load_balanced_rest_client 1.0.2
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.
- checksums.yaml +15 -0
- data/lib/algorithms.rb +2 -0
- data/lib/algorithms/exponential_downtime.rb +14 -0
- data/lib/algorithms/linear_downtime.rb +13 -0
- data/lib/cluster.rb +32 -0
- data/lib/load_balanced_rest_client.rb +18 -0
- data/lib/load_balancer.rb +61 -0
- data/lib/rest_client_proxy.rb +24 -0
- data/lib/server.rb +61 -0
- data/lib/version.rb +3 -0
- metadata +66 -0
checksums.yaml
ADDED
@@ -0,0 +1,15 @@
|
|
1
|
+
---
|
2
|
+
!binary "U0hBMQ==":
|
3
|
+
metadata.gz: !binary |-
|
4
|
+
NTNiMDJjMWFjYzkwYWQ0ZmU1ZmQzOWQ3Y2Y5NGY1ZTBlNmYzZTgyNQ==
|
5
|
+
data.tar.gz: !binary |-
|
6
|
+
OTQxNzA3NWIyNDBjY2YyMzljNzY4MmMyYWQ2ZjgyZGRhNzljNTFlNQ==
|
7
|
+
!binary "U0hBNTEy":
|
8
|
+
metadata.gz: !binary |-
|
9
|
+
NWYwN2E0YmMzMGEyYTU3MTZiMzc5YzYxMDY2N2QxNjUwZGE0MzI1ZTk0YWU2
|
10
|
+
ZjQ0ODc3NjM4NmU0NjBjODI0NzVmMzhjZGUxMTNhZDRlYzZiYTUzOTgxMTE5
|
11
|
+
NTA2MmNkYzJkYWVhMWJlN2M4Mjc1YWM0NzMxZGFjYTNlOTk4YjQ=
|
12
|
+
data.tar.gz: !binary |-
|
13
|
+
MjhmMzJiM2YyZWQ2ZmRmZjFhNGY5MzBiZTI2ZjIyNWQzYTQ3Y2QxYzU2OGVj
|
14
|
+
MmM3OTQ1MTM5MDVlYzllZjRhYmQ3YmQyNTcxNGE3YTBmNGIzZDRmN2MxYjgx
|
15
|
+
MmExN2ZjNzYxZDMwMzI3ZWU3OGIyMDU0OTBkZDA3M2M2MmUxZmM=
|
data/lib/algorithms.rb
ADDED
data/lib/cluster.rb
ADDED
@@ -0,0 +1,32 @@
|
|
1
|
+
class LoadBalancedRestClient
|
2
|
+
class Cluster
|
3
|
+
class NoServers < StandardError; end
|
4
|
+
attr_accessor :servers
|
5
|
+
|
6
|
+
def initialize(servers, opts={})
|
7
|
+
server_klass = opts[:server_klass] || Server
|
8
|
+
|
9
|
+
if servers.first.class.to_s != "LoadBalancedRestClient::Server"
|
10
|
+
@servers = servers.map {|server| server_klass.new(server, opts)}
|
11
|
+
else
|
12
|
+
@servers = servers
|
13
|
+
end
|
14
|
+
|
15
|
+
if servers.empty?
|
16
|
+
raise NoServers.new("No servers in cluster")
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
def balance!
|
21
|
+
@servers.push(@servers.delete(next_server))
|
22
|
+
end
|
23
|
+
|
24
|
+
def next_server
|
25
|
+
servers_to_try.first || @servers.first
|
26
|
+
end
|
27
|
+
|
28
|
+
def servers_to_try
|
29
|
+
@servers.select {|server| server.should_try? }
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
require 'rest-client'
|
2
|
+
require 'logger'
|
3
|
+
require_relative 'version'
|
4
|
+
require_relative 'server'
|
5
|
+
require_relative 'cluster'
|
6
|
+
require_relative 'algorithms'
|
7
|
+
require_relative 'load_balancer'
|
8
|
+
require_relative 'rest_client_proxy'
|
9
|
+
|
10
|
+
class LoadBalancedRestClient
|
11
|
+
attr_accessor :cluster, :load_balancer
|
12
|
+
include RestClientProxy
|
13
|
+
|
14
|
+
def initialize(servers, opts={})
|
15
|
+
@cluster = Cluster.new(servers, opts)
|
16
|
+
@load_balancer = LoadBalancer.new(@cluster, opts)
|
17
|
+
end
|
18
|
+
end
|
@@ -0,0 +1,61 @@
|
|
1
|
+
class LoadBalancedRestClient
|
2
|
+
class LoadBalancer
|
3
|
+
class MaxTriesReached < StandardError; end
|
4
|
+
attr_accessor :cluster
|
5
|
+
|
6
|
+
def initialize(cluster, options = {})
|
7
|
+
@cluster = cluster
|
8
|
+
@human_readable_cluster = cluster.servers_to_try.join(", ")
|
9
|
+
@exceptions_to_catch = options[:catch] || [Errno::ECONNREFUSED, Errno::EHOSTUNREACH,
|
10
|
+
RestClient::ServerBrokeConnection, RestClient::RequestTimeout]
|
11
|
+
@max_tries = options[:max_tries] || 4
|
12
|
+
@logger = options[:logger] || Logger.new(STDOUT)
|
13
|
+
|
14
|
+
@logger.info "Setting up load balancing: #{@human_readable_cluster}"
|
15
|
+
end
|
16
|
+
|
17
|
+
def with_server(&req_blk)
|
18
|
+
@try_counter = 0
|
19
|
+
result = false
|
20
|
+
|
21
|
+
until result or @try_counter == @max_tries do
|
22
|
+
@try_counter += 1
|
23
|
+
@server = @cluster.next_server
|
24
|
+
|
25
|
+
@cluster.balance!
|
26
|
+
@logger.info "#{human_readable_request_counter}: Trying #{@server}"
|
27
|
+
result = try_request(&req_blk)
|
28
|
+
end
|
29
|
+
|
30
|
+
if result
|
31
|
+
result
|
32
|
+
else
|
33
|
+
@logger.error "Max tries reached"
|
34
|
+
raise MaxTriesReached.new("Max tries reached")
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
private
|
39
|
+
def human_readable_request_counter
|
40
|
+
"Request #{@try_counter}/#{@max_tries}"
|
41
|
+
end
|
42
|
+
|
43
|
+
def try_request(&req_blk)
|
44
|
+
begin
|
45
|
+
result = yield @server
|
46
|
+
@logger.info "#{human_readable_request_counter}: #{@server} successful"
|
47
|
+
@server.stop_marking_down!
|
48
|
+
|
49
|
+
result
|
50
|
+
rescue *@exceptions_to_catch => e
|
51
|
+
@logger.info "#{human_readable_request_counter}: #{@server} threw \"#{e}\""
|
52
|
+
if @server.should_try?
|
53
|
+
@logger.warn "Marking server down: #{@server} for #{@server.next_downtime} seconds"
|
54
|
+
@server.mark_down!
|
55
|
+
end
|
56
|
+
|
57
|
+
nil
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
class LoadBalancedRestClient
|
2
|
+
class RestClientProxyCall
|
3
|
+
def initialize(load_balancer, uri = nil)
|
4
|
+
@load_balancer = load_balancer
|
5
|
+
@uri = uri
|
6
|
+
end
|
7
|
+
|
8
|
+
def method_missing(method_name, *args, &blk)
|
9
|
+
@load_balancer.with_server do |server|
|
10
|
+
server.client[@uri].send(method_name, *args, &blk)
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
module RestClientProxy
|
16
|
+
def [](uri)
|
17
|
+
RestClientProxyCall.new(@load_balancer, uri)
|
18
|
+
end
|
19
|
+
|
20
|
+
def method_missing(method_name, *args, &blk)
|
21
|
+
RestClientProxyCall.new(@load_balancer).send(method_name, *args, &blk)
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
data/lib/server.rb
ADDED
@@ -0,0 +1,61 @@
|
|
1
|
+
class LoadBalancedRestClient
|
2
|
+
class Server
|
3
|
+
attr_accessor :client, :max_downtime, :downtime_counter
|
4
|
+
|
5
|
+
def initialize(url, opts={})
|
6
|
+
@url = url
|
7
|
+
@client = opts[:client_klass] || ::RestClient::Resource.new(url, opts)
|
8
|
+
@max_downtime = opts[:max_downtime] || 60 * 60 #= 1 hour
|
9
|
+
@downtime_algorithm = opts[:downtime_algorithm] || ::LoadBalancedRestClient::Algorithms::ExponentialDowntime.new
|
10
|
+
@downtime_counter = 0
|
11
|
+
end
|
12
|
+
|
13
|
+
def mark_down_for!(time_amount)
|
14
|
+
@downtime_counter += 1
|
15
|
+
@marked_down_until = Time.now + with_max_downtime { time_amount }
|
16
|
+
|
17
|
+
true
|
18
|
+
end
|
19
|
+
|
20
|
+
def mark_down!
|
21
|
+
if should_try?
|
22
|
+
downtime = next_downtime
|
23
|
+
mark_down_for! downtime
|
24
|
+
|
25
|
+
downtime
|
26
|
+
else
|
27
|
+
false
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
def stop_marking_down!
|
32
|
+
@marked_down_until = nil
|
33
|
+
@downtime_counter = 0
|
34
|
+
true
|
35
|
+
end
|
36
|
+
|
37
|
+
def should_try?
|
38
|
+
!marked_down?
|
39
|
+
end
|
40
|
+
|
41
|
+
def marked_down?
|
42
|
+
!@marked_down_until.nil? && @marked_down_until > Time.now
|
43
|
+
end
|
44
|
+
|
45
|
+
def next_downtime
|
46
|
+
with_max_downtime do
|
47
|
+
@downtime_algorithm.call(@downtime_counter + 1)
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
def to_s
|
52
|
+
@url
|
53
|
+
end
|
54
|
+
|
55
|
+
private
|
56
|
+
def with_max_downtime(&block)
|
57
|
+
time_amount = yield
|
58
|
+
time_amount > @max_downtime ? @max_downtime : time_amount
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
data/lib/version.rb
ADDED
metadata
ADDED
@@ -0,0 +1,66 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: load_balanced_rest_client
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 1.0.2
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Eric Rafaloff
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2013-08-13 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: rest-client
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - ! '>='
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '0'
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - ! '>='
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '0'
|
27
|
+
description: Automatically load balances a Rest Client, allowing you to enjoy the
|
28
|
+
benefits of redundancy without any additional points of failure.
|
29
|
+
email: hello@eric.rafaloff.com
|
30
|
+
executables: []
|
31
|
+
extensions: []
|
32
|
+
extra_rdoc_files: []
|
33
|
+
files:
|
34
|
+
- lib/algorithms/exponential_downtime.rb
|
35
|
+
- lib/algorithms/linear_downtime.rb
|
36
|
+
- lib/algorithms.rb
|
37
|
+
- lib/cluster.rb
|
38
|
+
- lib/load_balanced_rest_client.rb
|
39
|
+
- lib/load_balancer.rb
|
40
|
+
- lib/rest_client_proxy.rb
|
41
|
+
- lib/server.rb
|
42
|
+
- lib/version.rb
|
43
|
+
homepage: http://github.com/bitlove/load_balanced_rest_client
|
44
|
+
licenses: []
|
45
|
+
metadata: {}
|
46
|
+
post_install_message:
|
47
|
+
rdoc_options: []
|
48
|
+
require_paths:
|
49
|
+
- lib/
|
50
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - ! '>='
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '0'
|
55
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
56
|
+
requirements:
|
57
|
+
- - ! '>='
|
58
|
+
- !ruby/object:Gem::Version
|
59
|
+
version: '0'
|
60
|
+
requirements: []
|
61
|
+
rubyforge_project:
|
62
|
+
rubygems_version: 2.0.6
|
63
|
+
signing_key:
|
64
|
+
specification_version: 4
|
65
|
+
summary: A client-side load balancer for RestClient
|
66
|
+
test_files: []
|