hawatel_tlb 0.1.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.
- checksums.yaml +7 -0
- data/.gitignore +10 -0
- data/.rspec +2 -0
- data/.travis.yml +4 -0
- data/CONTRIBUTING.md +70 -0
- data/Gemfile +4 -0
- data/LICENSE.txt +21 -0
- data/README.md +65 -0
- data/Rakefile +6 -0
- data/bin/console +14 -0
- data/bin/setup +7 -0
- data/hawatel_tlb.gemspec +27 -0
- data/lib/hawatel_tlb.rb +4 -0
- data/lib/hawatel_tlb/client.rb +207 -0
- data/lib/hawatel_tlb/mode.rb +5 -0
- data/lib/hawatel_tlb/mode/dynamicratio.rb +75 -0
- data/lib/hawatel_tlb/mode/fastest.rb +111 -0
- data/lib/hawatel_tlb/mode/ratio.rb +101 -0
- data/lib/hawatel_tlb/mode/roundrobin.rb +50 -0
- data/lib/hawatel_tlb/mode/weighted.rb +102 -0
- data/lib/hawatel_tlb/version.rb +3 -0
- data/lib/hawatel_tlb/watchdog.rb +38 -0
- data/spec/client_spec.rb +76 -0
- data/spec/modes/dynamicratio_spec.rb +43 -0
- data/spec/modes/fastest_spec.rb +46 -0
- data/spec/modes/ratio_spec.rb +74 -0
- data/spec/modes/roundrobin_spec.rb +42 -0
- data/spec/modes/weighted_spec.rb +40 -0
- data/spec/spec_helper.rb +2 -0
- metadata +127 -0
@@ -0,0 +1,75 @@
|
|
1
|
+
module HawatelTlb::Mode
|
2
|
+
##
|
3
|
+
# = Dynamic Ratio algorithm
|
4
|
+
#
|
5
|
+
# Thease are dynamic load balancing algorithm based on dynamic ratio weights.
|
6
|
+
# Algorithm explanation:
|
7
|
+
# 1. Weight for each node is setting up by following formula:
|
8
|
+
# 100 - (respond_time / sum_respond_time) * 100
|
9
|
+
# Weights are refreshing once per minute
|
10
|
+
# 2. divide the amount of traffic (requests) sent to each server by weight (configured in the Client)
|
11
|
+
# 3. Sort by ratio (result from 1. point) from smallest to largest
|
12
|
+
# 4. Get node with smallest ratio
|
13
|
+
# If you want to see how it works you can set client.mode.debug to 1 and run dynamicratio_spec.rb.
|
14
|
+
# @!attribute [rw] debug
|
15
|
+
# 0: enable, 1: disable
|
16
|
+
class DynamicRatio < Ratio
|
17
|
+
attr_accessor :recalc_interval
|
18
|
+
|
19
|
+
RECALC_WEIGHT_INTERVAL = 10
|
20
|
+
|
21
|
+
def initialize(group)
|
22
|
+
super(group)
|
23
|
+
|
24
|
+
@recalc_interval = RECALC_WEIGHT_INTERVAL
|
25
|
+
@dynamic_weight = 1
|
26
|
+
set_weights
|
27
|
+
thread_start
|
28
|
+
end
|
29
|
+
|
30
|
+
# Destroy thread
|
31
|
+
def destroy
|
32
|
+
@dynamic_weight = 0
|
33
|
+
end
|
34
|
+
|
35
|
+
private
|
36
|
+
|
37
|
+
def sum_respond_time
|
38
|
+
sum = 0
|
39
|
+
@group.each do |node|
|
40
|
+
if node.status[:state] == 'online' && node.state == 'enable'
|
41
|
+
sum += node.status[:respond_time]
|
42
|
+
end
|
43
|
+
end
|
44
|
+
sum
|
45
|
+
end
|
46
|
+
|
47
|
+
def set_weights
|
48
|
+
sum = sum_respond_time
|
49
|
+
@group.each do |node|
|
50
|
+
if node.status[:state] == 'online' && node.state == 'enable'
|
51
|
+
node.weight = 100 - ((node.status[:respond_time] / sum) * 100).to_i if !sum.zero?
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
def thread_start
|
57
|
+
@thr = Thread.new{
|
58
|
+
while(@dynamic_weight == 1)
|
59
|
+
sleep(RECALC_WEIGHT_INTERVAL)
|
60
|
+
thread_heartbeat
|
61
|
+
set_weights
|
62
|
+
end
|
63
|
+
}
|
64
|
+
rescue => e
|
65
|
+
puts "Exception: #{e.inspect}"
|
66
|
+
end
|
67
|
+
|
68
|
+
def thread_heartbeat
|
69
|
+
if @debug > 0
|
70
|
+
puts "#{Time.now} thread hearbeat #{Thread.current.object_id}"
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
end
|
75
|
+
end
|
@@ -0,0 +1,111 @@
|
|
1
|
+
module HawatelTlb::Mode
|
2
|
+
class Fastest
|
3
|
+
|
4
|
+
# number of elements stored in history array
|
5
|
+
HISTORY_SIZE = 3
|
6
|
+
|
7
|
+
def initialize(group)
|
8
|
+
@group = group
|
9
|
+
@history = Array.new
|
10
|
+
@counter = 0
|
11
|
+
end
|
12
|
+
|
13
|
+
# Refresh group table after delete or add host
|
14
|
+
#
|
15
|
+
# @param group [Array<Hash>]
|
16
|
+
def refresh(group)
|
17
|
+
@group = group
|
18
|
+
end
|
19
|
+
|
20
|
+
# Calculate base on fastest algorithm
|
21
|
+
# Algorithm based on history statistics about respond time and 'online' states (flapping detection)
|
22
|
+
#
|
23
|
+
# @return [Hash] hostname/ip address and port number
|
24
|
+
def node
|
25
|
+
# push nodes statistics to history array
|
26
|
+
if @counter < HISTORY_SIZE
|
27
|
+
@history[@counter] = @group.dup
|
28
|
+
@counter += 1
|
29
|
+
else
|
30
|
+
@counter = 0
|
31
|
+
@history[@counter] = @group.dup
|
32
|
+
end
|
33
|
+
|
34
|
+
current_online = Array.new
|
35
|
+
@group.each do |node|
|
36
|
+
current_online.push(node.id) if node.state == 'enable' && node.status[:state] == 'online'
|
37
|
+
end
|
38
|
+
|
39
|
+
# there is no any available node
|
40
|
+
return false if current_online.empty?
|
41
|
+
|
42
|
+
# there is only one available node
|
43
|
+
return find_node(current_online[0]) if current_online.size == 1
|
44
|
+
|
45
|
+
# there are more that one online nodes
|
46
|
+
nodes_sorted_stats = sort_by_state(average_respond_time(current_online))
|
47
|
+
|
48
|
+
# choose only top nodes with equal online state
|
49
|
+
top_nodes_by_state = Array.new
|
50
|
+
top_value = 0
|
51
|
+
nodes_sorted_stats.each do |node|
|
52
|
+
top_value = node[:online_count] if top_value == 0
|
53
|
+
if node[:online_count] == top_value
|
54
|
+
top_nodes_by_state.push(node)
|
55
|
+
else
|
56
|
+
break
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
# there is only one node with highest online count
|
61
|
+
return find_node(top_nodes_by_state[0][:id]) if top_nodes_by_state.size == 1
|
62
|
+
|
63
|
+
# there are more with top
|
64
|
+
top_nodes_by_respond = sort_by_respond(top_nodes_by_state)
|
65
|
+
find_node(top_nodes_by_respond[0][:id])
|
66
|
+
end
|
67
|
+
|
68
|
+
private
|
69
|
+
# sort decedent nodes by online counter
|
70
|
+
def sort_by_state(nodes)
|
71
|
+
sorted_nodes = Array.new
|
72
|
+
nodes.each_with_index do |node, index|
|
73
|
+
next if node.nil?
|
74
|
+
node[:id] = index
|
75
|
+
sorted_nodes.push(node)
|
76
|
+
end
|
77
|
+
sorted_nodes.sort_by { |node| node[:online_count]}.reverse!
|
78
|
+
end
|
79
|
+
|
80
|
+
# sort by average respond time
|
81
|
+
def sort_by_respond(nodes)
|
82
|
+
nodes.sort_by { |node| node[:avg_respond_time]}
|
83
|
+
end
|
84
|
+
|
85
|
+
# return node hostname and port based on node id
|
86
|
+
def find_node(node_id)
|
87
|
+
node_param = @group.select {|node| node[:id] == node_id}
|
88
|
+
{:host => node_param[0].host, :port => node_param[0].port}
|
89
|
+
end
|
90
|
+
|
91
|
+
# calculate average respond time time for online nodes
|
92
|
+
def average_respond_time(current_online)
|
93
|
+
stats = Array.new
|
94
|
+
@history.each do |round|
|
95
|
+
round.each do |node|
|
96
|
+
if node.status[:state] == 'online' && current_online.include?(node.id)
|
97
|
+
stats[node.id] = Hash.new if !stats[node.id].is_a?(Hash)
|
98
|
+
!stats[node.id][:online_count] ? stats[node.id][:online_count] = 1 : stats[node.id][:online_count] += 1
|
99
|
+
!stats[node.id][:sum_respond_time] ?
|
100
|
+
stats[node.id][:sum_respond_time] = node.status[:respond_time] :
|
101
|
+
stats[node.id][:sum_respond_time] += node.status[:respond_time]
|
102
|
+
!stats[node.id][:avg_respond_time] ?
|
103
|
+
stats[node.id][:avg_respond_time] = node.status[:respond_time] :
|
104
|
+
stats[node.id][:avg_respond_time] = stats[node.id][:sum_respond_time] / stats[node.id][:online_count]
|
105
|
+
end
|
106
|
+
end
|
107
|
+
end
|
108
|
+
return stats
|
109
|
+
end
|
110
|
+
end
|
111
|
+
end
|
@@ -0,0 +1,101 @@
|
|
1
|
+
module HawatelTlb::Mode
|
2
|
+
##
|
3
|
+
# = Ratio algorithm
|
4
|
+
#
|
5
|
+
# Thease are static load balancing algorithm based on ratio weights.
|
6
|
+
# Algorithm explanation:
|
7
|
+
# 1. divide the amount of traffic (requests) sent to each server by weight (configured in the Client)
|
8
|
+
# 2. Sort by ratio (result from 1. point) from smallest to largest
|
9
|
+
# 3. Get node with smallest ratio
|
10
|
+
# If you want to see how it works you can set client.mode.debug to 1 and run ratio_spec.rb.
|
11
|
+
# @!attribute [rw] debug
|
12
|
+
class Ratio
|
13
|
+
attr_accessor :debug
|
14
|
+
|
15
|
+
def initialize(group)
|
16
|
+
@traffic = 0
|
17
|
+
@debug = 0
|
18
|
+
|
19
|
+
group.each do |node|
|
20
|
+
node.ratio = Hash.new
|
21
|
+
node.ratio[:traffic] = 0
|
22
|
+
node.ratio[:value] = 0
|
23
|
+
|
24
|
+
node.weight = 1 if node.weight.to_i < 1
|
25
|
+
end
|
26
|
+
|
27
|
+
@group = group
|
28
|
+
end
|
29
|
+
|
30
|
+
# Refresh group table after delete or add host
|
31
|
+
#
|
32
|
+
# @param group [Array<Hash>]
|
33
|
+
def refresh(group)
|
34
|
+
@group = group
|
35
|
+
end
|
36
|
+
|
37
|
+
# Description
|
38
|
+
# @param name [Type] description
|
39
|
+
# @option name [Type] :opt description
|
40
|
+
# @example
|
41
|
+
# mode = Ratio.new(nodes)
|
42
|
+
# p mode.node
|
43
|
+
# @return [Hash] :host and :port
|
44
|
+
def node
|
45
|
+
node = get_right_node
|
46
|
+
return {:host => node.host, :port => node.port} if node
|
47
|
+
false
|
48
|
+
end
|
49
|
+
|
50
|
+
private
|
51
|
+
|
52
|
+
def get_right_node
|
53
|
+
nodes = sort_nodes_by_ratio_asc
|
54
|
+
node = get_first_online_node(nodes)
|
55
|
+
debug_log(nodes)
|
56
|
+
|
57
|
+
if node
|
58
|
+
set_ratio(node)
|
59
|
+
return node
|
60
|
+
else
|
61
|
+
false
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
def get_first_online_node(nodes)
|
66
|
+
nodes.each do |node|
|
67
|
+
return node if node.status[:state] == 'online' && node.state == 'enable'
|
68
|
+
end
|
69
|
+
false
|
70
|
+
end
|
71
|
+
|
72
|
+
def sort_nodes_by_ratio_asc
|
73
|
+
@group.sort_by {|node| node.ratio[:value]}
|
74
|
+
end
|
75
|
+
|
76
|
+
def set_ratio(node)
|
77
|
+
@traffic += 1
|
78
|
+
node.ratio[:traffic] += 1
|
79
|
+
node.ratio[:value] = calc_ratio(node.ratio[:traffic], node.weight)
|
80
|
+
end
|
81
|
+
|
82
|
+
def calc_ratio(traffic, weight)
|
83
|
+
if weight.zero?
|
84
|
+
0
|
85
|
+
else
|
86
|
+
traffic.to_f / weight.to_f
|
87
|
+
end
|
88
|
+
end
|
89
|
+
|
90
|
+
def debug_log(nodes)
|
91
|
+
if @debug > 0
|
92
|
+
puts ">> request: #{@traffic} | selected #{nodes[0].host}"
|
93
|
+
nodes.each do |node|
|
94
|
+
puts "\t" "#{node.host} - ratio: #{node.ratio}, weight: #{node.weight}\n"
|
95
|
+
end
|
96
|
+
puts "------------------------------------------------------------"
|
97
|
+
end
|
98
|
+
end
|
99
|
+
|
100
|
+
end
|
101
|
+
end
|
@@ -0,0 +1,50 @@
|
|
1
|
+
module HawatelTlb
|
2
|
+
module Mode
|
3
|
+
|
4
|
+
##
|
5
|
+
# = Overview
|
6
|
+
#
|
7
|
+
# Details
|
8
|
+
#
|
9
|
+
# @!attribute [rw] name
|
10
|
+
# @return [Type] description
|
11
|
+
class RoundRobin
|
12
|
+
|
13
|
+
def initialize(group)
|
14
|
+
@group = group
|
15
|
+
@round = Array.new
|
16
|
+
end
|
17
|
+
|
18
|
+
# Refresh group table after delete or add host
|
19
|
+
#
|
20
|
+
# @param group [Array<Hash>]
|
21
|
+
def refresh(group)
|
22
|
+
@group = group
|
23
|
+
end
|
24
|
+
|
25
|
+
# Return ip address based on Round Robin algorithm
|
26
|
+
#
|
27
|
+
# @return [Hash] hostname/ip address and port number
|
28
|
+
def node
|
29
|
+
counter = 0
|
30
|
+
first_item = ''
|
31
|
+
@group.each do |node|
|
32
|
+
if !node.status.empty?
|
33
|
+
if node.status[:state] == 'online' && node.state == 'enable'
|
34
|
+
first_item = {:host => node.host, :port => node.port} if counter == 0
|
35
|
+
if !@round.include?(node.id)
|
36
|
+
@round.push(node.id)
|
37
|
+
return {:host => node.host, :port => node.port}
|
38
|
+
end
|
39
|
+
counter += 1
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
43
|
+
@round = Array.new
|
44
|
+
return first_item if !first_item.empty?
|
45
|
+
false
|
46
|
+
end
|
47
|
+
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
@@ -0,0 +1,102 @@
|
|
1
|
+
module HawatelTlb::Mode
|
2
|
+
class Weighted
|
3
|
+
|
4
|
+
# number of elements stored in history array
|
5
|
+
HISTORY_SIZE = 3
|
6
|
+
|
7
|
+
def initialize(group)
|
8
|
+
@group = group
|
9
|
+
@history = Array.new
|
10
|
+
@counter = 0
|
11
|
+
end
|
12
|
+
|
13
|
+
# Refresh group table after delete or add host
|
14
|
+
#
|
15
|
+
# @param group [Array<Hash>]
|
16
|
+
def refresh(group)
|
17
|
+
@group = group
|
18
|
+
end
|
19
|
+
|
20
|
+
#
|
21
|
+
# @return [Hash] hostname/ip address and port number
|
22
|
+
def node
|
23
|
+
|
24
|
+
# push nodes statistics to history array
|
25
|
+
if @counter < HISTORY_SIZE
|
26
|
+
@history[@counter] = @group.dup
|
27
|
+
@counter += 1
|
28
|
+
else
|
29
|
+
@counter = 0
|
30
|
+
@history[@counter] = @group.dup
|
31
|
+
end
|
32
|
+
|
33
|
+
# build array with only
|
34
|
+
current_online = Array.new
|
35
|
+
@group.each do |node|
|
36
|
+
current_online.push(node.id) if node.state == 'enable' && node.status[:state] == 'online'
|
37
|
+
end
|
38
|
+
|
39
|
+
# there is no any available node
|
40
|
+
return false if current_online.empty?
|
41
|
+
|
42
|
+
# there is only one available node
|
43
|
+
return find_node(current_online[0]) if current_online.size == 1
|
44
|
+
|
45
|
+
# there is more that one nodes - choose the most reliable node
|
46
|
+
nodes_reliability = Array.new
|
47
|
+
@history.each do |round|
|
48
|
+
round.each do |node|
|
49
|
+
if node.state == 'enable' && node.status[:state] == 'online'
|
50
|
+
nodes_reliability[node.id] = Hash.new if nodes_reliability[node.id].nil?
|
51
|
+
nodes_reliability[node.id][:online_count] = 0 if nodes_reliability[node.id][:online_count].nil?
|
52
|
+
nodes_reliability[node.id][:online_count] += 1
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
# choose only top nodes with equal online state
|
58
|
+
top_nodes_by_state = Array.new
|
59
|
+
top_value = 0
|
60
|
+
sort_by_state(nodes_reliability).each do |node|
|
61
|
+
top_value = node[:online_count] if top_value == 0
|
62
|
+
if node[:online_count] == top_value
|
63
|
+
node_param = @group.select {|item| item[:id] == node[:hostid]}
|
64
|
+
top_nodes_by_state << node_param
|
65
|
+
else
|
66
|
+
break
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
# there is only one node with highest online count
|
71
|
+
return {:host => top_nodes_by_state[0][:host], port => top_nodes_by_state[0][:port]} if top_nodes_by_state.size == 1
|
72
|
+
|
73
|
+
# find node with highest weight
|
74
|
+
nodes = sort_by_weight(top_nodes_by_state)
|
75
|
+
return {:host => nodes[0][0].host, :port => nodes[0][0].port}
|
76
|
+
end
|
77
|
+
|
78
|
+
private
|
79
|
+
# return node hostname and port based on node id
|
80
|
+
def find_node(node_id)
|
81
|
+
node_param = @group.select {|node| node[:id] == node_id}
|
82
|
+
{:host => node_param[0].host, :port => node_param[0].port}
|
83
|
+
end
|
84
|
+
|
85
|
+
# sort nodes by online counter
|
86
|
+
def sort_by_state(nodes)
|
87
|
+
sorted_nodes = Array.new
|
88
|
+
nodes.each_with_index do |node, index|
|
89
|
+
next if node.nil?
|
90
|
+
node[:hostid] = index
|
91
|
+
sorted_nodes.push(node)
|
92
|
+
end
|
93
|
+
sorted_nodes.sort_by { |node| node[:online_count]}.reverse!
|
94
|
+
end
|
95
|
+
|
96
|
+
# sort by average weight
|
97
|
+
def sort_by_weight(nodes)
|
98
|
+
nodes.sort_by { |node| node[0].weight }.reverse!
|
99
|
+
end
|
100
|
+
|
101
|
+
end
|
102
|
+
end
|