load_balanced_rest_client 1.0.2

Sign up to get free protection for your applications and to get access to all the features.
@@ -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=
@@ -0,0 +1,2 @@
1
+ require_relative "algorithms/exponential_downtime"
2
+ require_relative "algorithms/linear_downtime"
@@ -0,0 +1,14 @@
1
+ class LoadBalancedRestClient
2
+ module Algorithms
3
+ class ExponentialDowntime
4
+ def initialize(exp = 2, factor = 60)
5
+ @e = exp
6
+ @f = factor
7
+ end
8
+
9
+ def call(x)
10
+ (x ** @e) * @f
11
+ end
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,13 @@
1
+ class LoadBalancedRestClient
2
+ module Algorithms
3
+ class LinearDowntime
4
+ def initialize(factor = 60)
5
+ @f = factor
6
+ end
7
+
8
+ def call(x)
9
+ x * @f
10
+ end
11
+ end
12
+ end
13
+ end
@@ -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
@@ -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
@@ -0,0 +1,3 @@
1
+ class LoadBalancedRestClient
2
+ VERSION = "1.0.2"
3
+ end
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: []