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.
@@ -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: []