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.
@@ -0,0 +1,5 @@
1
+ require 'hawatel_tlb/mode/ratio'
2
+ require 'hawatel_tlb/mode/dynamicratio'
3
+ require 'hawatel_tlb/mode/roundrobin'
4
+ require 'hawatel_tlb/mode/fastest'
5
+ require 'hawatel_tlb/mode/weighted'
@@ -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