458 lines
11 KiB
Go
458 lines
11 KiB
Go
package performance
|
|
|
|
import (
|
|
"fmt"
|
|
"math/rand"
|
|
"sort"
|
|
"sync"
|
|
"time"
|
|
)
|
|
|
|
// RoundRobinStrategy implements round-robin load balancing
|
|
type RoundRobinStrategy struct {
|
|
currentIndex int
|
|
mu sync.Mutex
|
|
}
|
|
|
|
// NewRoundRobinStrategy creates a new round-robin strategy
|
|
func NewRoundRobinStrategy() *RoundRobinStrategy {
|
|
return &RoundRobinStrategy{
|
|
currentIndex: 0,
|
|
}
|
|
}
|
|
|
|
// SelectNode selects a node using round-robin algorithm
|
|
func (rr *RoundRobinStrategy) SelectNode(nodes map[string]*Node, request *LoadRequest) (*Node, error) {
|
|
if len(nodes) == 0 {
|
|
return nil, fmt.Errorf("no nodes available")
|
|
}
|
|
|
|
rr.mu.Lock()
|
|
defer rr.mu.Unlock()
|
|
|
|
// Convert map to slice for indexing
|
|
nodeSlice := make([]*Node, 0, len(nodes))
|
|
for _, node := range nodes {
|
|
nodeSlice = append(nodeSlice, node)
|
|
}
|
|
|
|
// Sort by ID for consistent ordering
|
|
sort.Slice(nodeSlice, func(i, j int) bool {
|
|
return nodeSlice[i].ID < nodeSlice[j].ID
|
|
})
|
|
|
|
// Select next node in round-robin fashion
|
|
selectedNode := nodeSlice[rr.currentIndex%len(nodeSlice)]
|
|
rr.currentIndex++
|
|
|
|
return selectedNode, nil
|
|
}
|
|
|
|
// GetName returns the strategy name
|
|
func (rr *RoundRobinStrategy) GetName() string {
|
|
return "round_robin"
|
|
}
|
|
|
|
// LeastConnectionsStrategy implements least connections load balancing
|
|
type LeastConnectionsStrategy struct{}
|
|
|
|
// NewLeastConnectionsStrategy creates a new least connections strategy
|
|
func NewLeastConnectionsStrategy() *LeastConnectionsStrategy {
|
|
return &LeastConnectionsStrategy{}
|
|
}
|
|
|
|
// SelectNode selects a node with the least active connections
|
|
func (lc *LeastConnectionsStrategy) SelectNode(nodes map[string]*Node, request *LoadRequest) (*Node, error) {
|
|
if len(nodes) == 0 {
|
|
return nil, fmt.Errorf("no nodes available")
|
|
}
|
|
|
|
var selectedNode *Node
|
|
minConnections := int(^uint(0) >> 1) // Max int
|
|
|
|
for _, node := range nodes {
|
|
if node.Metrics == nil {
|
|
continue
|
|
}
|
|
|
|
activeJobs := node.Metrics.ActiveJobs
|
|
if activeJobs < minConnections {
|
|
minConnections = activeJobs
|
|
selectedNode = node
|
|
}
|
|
}
|
|
|
|
if selectedNode == nil {
|
|
return nil, fmt.Errorf("no suitable node found")
|
|
}
|
|
|
|
return selectedNode, nil
|
|
}
|
|
|
|
// GetName returns the strategy name
|
|
func (lc *LeastConnectionsStrategy) GetName() string {
|
|
return "least_connections"
|
|
}
|
|
|
|
// WeightedRoundRobinStrategy implements weighted round-robin load balancing
|
|
type WeightedRoundRobinStrategy struct {
|
|
currentIndex int
|
|
mu sync.Mutex
|
|
}
|
|
|
|
// NewWeightedRoundRobinStrategy creates a new weighted round-robin strategy
|
|
func NewWeightedRoundRobinStrategy() *WeightedRoundRobinStrategy {
|
|
return &WeightedRoundRobinStrategy{
|
|
currentIndex: 0,
|
|
}
|
|
}
|
|
|
|
// SelectNode selects a node using weighted round-robin algorithm
|
|
func (wr *WeightedRoundRobinStrategy) SelectNode(nodes map[string]*Node, request *LoadRequest) (*Node, error) {
|
|
if len(nodes) == 0 {
|
|
return nil, fmt.Errorf("no nodes available")
|
|
}
|
|
|
|
wr.mu.Lock()
|
|
defer wr.mu.Unlock()
|
|
|
|
// Convert map to slice and calculate weights
|
|
type weightedNode struct {
|
|
node *Node
|
|
weight int
|
|
}
|
|
|
|
var weightedNodes []weightedNode
|
|
for _, node := range nodes {
|
|
weight := 1 // Default weight
|
|
|
|
// Calculate weight based on node capabilities and current load
|
|
if node.Metrics != nil {
|
|
// Higher weight for nodes with more capacity
|
|
availableCapacity := node.Metrics.MaxJobs - node.Metrics.ActiveJobs
|
|
if availableCapacity > 0 {
|
|
weight = availableCapacity
|
|
}
|
|
}
|
|
|
|
// Apply tags-based weight adjustments
|
|
if node.Tags != nil {
|
|
if priority, ok := node.Tags["priority"]; ok {
|
|
switch priority {
|
|
case "high":
|
|
weight *= 2
|
|
case "low":
|
|
weight /= 2
|
|
}
|
|
}
|
|
}
|
|
|
|
weightedNodes = append(weightedNodes, weightedNode{node: node, weight: weight})
|
|
}
|
|
|
|
// Sort by weight (descending)
|
|
sort.Slice(weightedNodes, func(i, j int) bool {
|
|
return weightedNodes[i].weight > weightedNodes[j].weight
|
|
})
|
|
|
|
// Select next node in weighted round-robin fashion
|
|
selectedNode := weightedNodes[wr.currentIndex%len(weightedNodes)].node
|
|
wr.currentIndex++
|
|
|
|
return selectedNode, nil
|
|
}
|
|
|
|
// GetName returns the strategy name
|
|
func (wr *WeightedRoundRobinStrategy) GetName() string {
|
|
return "weighted_round_robin"
|
|
}
|
|
|
|
// RandomStrategy implements random load balancing
|
|
type RandomStrategy struct {
|
|
rand *rand.Rand
|
|
mu sync.Mutex
|
|
}
|
|
|
|
// NewRandomStrategy creates a new random strategy
|
|
func NewRandomStrategy() *RandomStrategy {
|
|
return &RandomStrategy{
|
|
rand: rand.New(rand.NewSource(time.Now().UnixNano())),
|
|
}
|
|
}
|
|
|
|
// SelectNode selects a random node
|
|
func (r *RandomStrategy) SelectNode(nodes map[string]*Node, request *LoadRequest) (*Node, error) {
|
|
if len(nodes) == 0 {
|
|
return nil, fmt.Errorf("no nodes available")
|
|
}
|
|
|
|
r.mu.Lock()
|
|
defer r.mu.Unlock()
|
|
|
|
// Convert map to slice
|
|
nodeSlice := make([]*Node, 0, len(nodes))
|
|
for _, node := range nodes {
|
|
nodeSlice = append(nodeSlice, node)
|
|
}
|
|
|
|
// Select random node
|
|
randomIndex := r.rand.Intn(len(nodeSlice))
|
|
return nodeSlice[randomIndex], nil
|
|
}
|
|
|
|
// GetName returns the strategy name
|
|
func (r *RandomStrategy) GetName() string {
|
|
return "random"
|
|
}
|
|
|
|
// LeastResponseTimeStrategy implements least response time load balancing
|
|
type LeastResponseTimeStrategy struct{}
|
|
|
|
// NewLeastResponseTimeStrategy creates a new least response time strategy
|
|
func NewLeastResponseTimeStrategy() *LeastResponseTimeStrategy {
|
|
return &LeastResponseTimeStrategy{}
|
|
}
|
|
|
|
// SelectNode selects a node with the least response time
|
|
func (lrt *LeastResponseTimeStrategy) SelectNode(nodes map[string]*Node, request *LoadRequest) (*Node, error) {
|
|
if len(nodes) == 0 {
|
|
return nil, fmt.Errorf("no nodes available")
|
|
}
|
|
|
|
var selectedNode *Node
|
|
minResponseTime := float64(^uint(0) >> 1) // Max float64
|
|
|
|
for _, node := range nodes {
|
|
if node.Metrics == nil {
|
|
continue
|
|
}
|
|
|
|
// Use load average as a proxy for response time
|
|
// In a real implementation, you'd have actual response time metrics
|
|
responseTime := node.Metrics.LoadAverage
|
|
if responseTime < minResponseTime {
|
|
minResponseTime = responseTime
|
|
selectedNode = node
|
|
}
|
|
}
|
|
|
|
if selectedNode == nil {
|
|
return nil, fmt.Errorf("no suitable node found")
|
|
}
|
|
|
|
return selectedNode, nil
|
|
}
|
|
|
|
// GetName returns the strategy name
|
|
func (lrt *LeastResponseTimeStrategy) GetName() string {
|
|
return "least_response_time"
|
|
}
|
|
|
|
// IPHashStrategy implements IP hash load balancing
|
|
type IPHashStrategy struct{}
|
|
|
|
// NewIPHashStrategy creates a new IP hash strategy
|
|
func NewIPHashStrategy() *IPHashStrategy {
|
|
return &IPHashStrategy{}
|
|
}
|
|
|
|
// SelectNode selects a node using IP hash algorithm
|
|
func (ih *IPHashStrategy) SelectNode(nodes map[string]*Node, request *LoadRequest) (*Node, error) {
|
|
if len(nodes) == 0 {
|
|
return nil, fmt.Errorf("no nodes available")
|
|
}
|
|
|
|
// Extract client IP from request metadata
|
|
clientIP := "127.0.0.1" // Default IP
|
|
if request.Metadata != nil {
|
|
if ip, ok := request.Metadata["client_ip"].(string); ok {
|
|
clientIP = ip
|
|
}
|
|
}
|
|
|
|
// Calculate hash of client IP
|
|
hash := hashString(clientIP)
|
|
|
|
// Convert map to slice for indexing
|
|
nodeSlice := make([]*Node, 0, len(nodes))
|
|
for _, node := range nodes {
|
|
nodeSlice = append(nodeSlice, node)
|
|
}
|
|
|
|
// Sort by ID for consistent ordering
|
|
sort.Slice(nodeSlice, func(i, j int) bool {
|
|
return nodeSlice[i].ID < nodeSlice[j].ID
|
|
})
|
|
|
|
// Select node based on hash
|
|
selectedIndex := hash % uint32(len(nodeSlice))
|
|
return nodeSlice[selectedIndex], nil
|
|
}
|
|
|
|
// GetName returns the strategy name
|
|
func (ih *IPHashStrategy) GetName() string {
|
|
return "ip_hash"
|
|
}
|
|
|
|
// hashString calculates a simple hash of a string
|
|
func hashString(s string) uint32 {
|
|
var hash uint32
|
|
for _, char := range s {
|
|
hash = ((hash << 5) + hash) + uint32(char)
|
|
}
|
|
return hash
|
|
}
|
|
|
|
// AdaptiveStrategy implements adaptive load balancing based on multiple factors
|
|
type AdaptiveStrategy struct {
|
|
mu sync.Mutex
|
|
}
|
|
|
|
// NewAdaptiveStrategy creates a new adaptive strategy
|
|
func NewAdaptiveStrategy() *AdaptiveStrategy {
|
|
return &AdaptiveStrategy{}
|
|
}
|
|
|
|
// SelectNode selects a node using adaptive algorithm
|
|
func (a *AdaptiveStrategy) SelectNode(nodes map[string]*Node, request *LoadRequest) (*Node, error) {
|
|
if len(nodes) == 0 {
|
|
return nil, fmt.Errorf("no nodes available")
|
|
}
|
|
|
|
a.mu.Lock()
|
|
defer a.mu.Unlock()
|
|
|
|
// Score each node based on multiple factors
|
|
type scoredNode struct {
|
|
node *Node
|
|
score float64
|
|
}
|
|
|
|
var scoredNodes []scoredNode
|
|
|
|
for _, node := range nodes {
|
|
if node.Metrics == nil {
|
|
continue
|
|
}
|
|
|
|
score := a.calculateNodeScore(node, request)
|
|
scoredNodes = append(scoredNodes, scoredNode{node: node, score: score})
|
|
}
|
|
|
|
if len(scoredNodes) == 0 {
|
|
return nil, fmt.Errorf("no suitable node found")
|
|
}
|
|
|
|
// Sort by score (descending)
|
|
sort.Slice(scoredNodes, func(i, j int) bool {
|
|
return scoredNodes[i].score > scoredNodes[j].score
|
|
})
|
|
|
|
// Return the highest scoring node
|
|
return scoredNodes[0].node, nil
|
|
}
|
|
|
|
// calculateNodeScore calculates a score for a node based on multiple factors
|
|
func (a *AdaptiveStrategy) calculateNodeScore(node *Node, request *LoadRequest) float64 {
|
|
score := 100.0
|
|
|
|
if node.Metrics == nil {
|
|
return score
|
|
}
|
|
|
|
// Factor 1: Available capacity (higher is better)
|
|
availableCapacity := float64(node.Metrics.MaxJobs - node.Metrics.ActiveJobs)
|
|
if node.Metrics.MaxJobs > 0 {
|
|
capacityRatio := availableCapacity / float64(node.Metrics.MaxJobs)
|
|
score += capacityRatio * 50 // Up to 50 points for capacity
|
|
}
|
|
|
|
// Factor 2: System load (lower is better)
|
|
if node.Metrics.LoadAverage > 0 {
|
|
loadScore := 100.0 - (node.Metrics.LoadAverage * 10)
|
|
if loadScore < 0 {
|
|
loadScore = 0
|
|
}
|
|
score += loadScore * 0.3 // Up to 30 points for load
|
|
}
|
|
|
|
// Factor 3: Resource usage (lower is better)
|
|
cpuScore := 100.0 - node.Metrics.CPUUsage
|
|
memoryScore := 100.0 - node.Metrics.MemoryUsage
|
|
|
|
score += cpuScore * 0.1 // Up to 10 points for CPU
|
|
score += memoryScore * 0.1 // Up to 10 points for memory
|
|
|
|
// Factor 4: Priority-based adjustments
|
|
if node.Tags != nil {
|
|
if priority, ok := node.Tags["priority"]; ok {
|
|
switch priority {
|
|
case "high":
|
|
score += 20
|
|
case "low":
|
|
score -= 20
|
|
}
|
|
}
|
|
}
|
|
|
|
// Factor 5: Request-specific requirements
|
|
if request.Requirements != nil {
|
|
if arch, ok := request.Requirements["architecture"].(string); ok {
|
|
if nodeArch, ok := node.Capabilities["architecture"].(string); ok {
|
|
if arch == nodeArch {
|
|
score += 25 // Bonus for architecture match
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return score
|
|
}
|
|
|
|
// GetName returns the strategy name
|
|
func (a *AdaptiveStrategy) GetName() string {
|
|
return "adaptive"
|
|
}
|
|
|
|
// StrategyFactory creates load balancing strategies
|
|
type StrategyFactory struct{}
|
|
|
|
// NewStrategyFactory creates a new strategy factory
|
|
func NewStrategyFactory() *StrategyFactory {
|
|
return &StrategyFactory{}
|
|
}
|
|
|
|
// CreateStrategy creates a strategy by name
|
|
func (sf *StrategyFactory) CreateStrategy(name string) (LoadBalancingStrategy, error) {
|
|
switch name {
|
|
case "round_robin":
|
|
return NewRoundRobinStrategy(), nil
|
|
case "least_connections":
|
|
return NewLeastConnectionsStrategy(), nil
|
|
case "weighted_round_robin":
|
|
return NewWeightedRoundRobinStrategy(), nil
|
|
case "random":
|
|
return NewRandomStrategy(), nil
|
|
case "least_response_time":
|
|
return NewLeastResponseTimeStrategy(), nil
|
|
case "ip_hash":
|
|
return NewIPHashStrategy(), nil
|
|
case "adaptive":
|
|
return NewAdaptiveStrategy(), nil
|
|
default:
|
|
return nil, fmt.Errorf("unknown strategy: %s", name)
|
|
}
|
|
}
|
|
|
|
// GetAvailableStrategies returns all available strategy names
|
|
func (sf *StrategyFactory) GetAvailableStrategies() []string {
|
|
return []string{
|
|
"round_robin",
|
|
"least_connections",
|
|
"weighted_round_robin",
|
|
"random",
|
|
"least_response_time",
|
|
"ip_hash",
|
|
"adaptive",
|
|
}
|
|
}
|