use std::collections::{BTreeMap, HashMap};
use serde::{Deserialize, Serialize};
use std::time::SystemTime;
use tokio::time::{Duration, Instant};
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct Request {
pub id: String,
pub request_type: String,
pub size_kb: f64,
pub cpu_requirement: f64,
pub memory_requirement: f64,
pub priority: RequestPriority,
pub created_at: Instant,
pub estimated_processing_time_ms: u64,
}
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)]
pub enum RequestPriority {
Critical,
High,
Medium,
Low,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct Backend {
pub id: String,
pub name: String,
pub address: String,
pub current_load: f64,
pub cpu_utilization: f64,
pub memory_utilization: f64,
pub response_time_ms: f64,
pub error_rate: f64,
pub max_connections: usize,
pub active_connections: usize,
pub healthy: bool,
pub weight: f64,
pub last_updated: Instant,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct LoadDistribution {
pub backend_id: String,
pub allocated_requests: usize,
pub allocation_percentage: f64,
pub load_score: f64,
pub predicted_response_time_ms: f64,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub enum BalanceStrategy {
RoundRobin,
WeightedRoundRobin,
LeastConnections,
FastestResponse,
IpHash,
IntelligentAI,
Adaptive,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct LoadBalanceResult {
pub success: bool,
pub selected_backend: Option<Backend>,
pub distribution: Vec<LoadDistribution>,
pub overall_load_score: f64,
pub resource_utilization: f64,
pub predicted_response_time_improvement: f64,
pub message: String,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct LoadMetrics {
pub time_window_ms: u64,
pub avg_response_time_ms: f64,
pub avg_cpu_utilization: f64,
pub avg_memory_utilization: f64,
pub avg_error_rate: f64,
pub requests_per_second: f64,
pub concurrent_connections: usize,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct LoadPattern {
pub pattern_type: PatternType,
pub peak_hours: Vec<u8>,
pub average_load: f64,
pub peak_load: f64,
pub variability_coefficient: f64,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub enum PatternType {
Stable,
Cyclic,
Burst,
GradualGrowth,
Random,
}
#[derive(Debug, Clone)]
pub struct LoadBalancer {
backends: HashMap<String, Backend>,
config: LoadBalancerConfig,
load_history: Vec<LoadMetrics>,
request_stats: RequestStatistics,
}
#[derive(Debug, Clone, Default)]
struct RequestStatistics {
total_requests: u64,
successful_requests: u64,
failed_requests: u64,
total_response_time_ms: u64,
}
#[derive(Debug, Clone)]
pub struct LoadBalancerConfig {
pub max_response_time_ms: f64,
pub max_error_rate: f64,
pub health_check_interval_ms: u64,
pub enable_ai_optimization: bool,
pub adaptive_adjustment_interval_ms: u64,
pub max_retry_attempts: usize,
pub connection_timeout_ms: u64,
}
impl Default for LoadBalancerConfig {
fn default() -> Self {
Self {
max_response_time_ms: 1000.0,
max_error_rate: 0.05,
health_check_interval_ms: 5000,
enable_ai_optimization: true,
adaptive_adjustment_interval_ms: 10000,
max_retry_attempts: 3,
connection_timeout_ms: 5000,
}
}
}
impl LoadBalancer {
pub fn new(config: LoadBalancerConfig) -> Self {
Self {
backends: HashMap::new(),
config,
load_history: Vec::new(),
request_stats: RequestStatistics::default(),
}
}
pub fn new_with_defaults() -> Self {
Self::new(LoadBalancerConfig::default())
}
pub async fn add_backend(&mut self, backend: Backend) -> bool {
if backend.healthy {
self.backends.insert(backend.id.clone(), backend);
true
} else {
false
}
}
pub async fn remove_backend(&mut self, backend_id: &str) -> bool {
self.backends.remove(backend_id).is_some()
}
pub async fn select_backend(
&mut self,
request: &Request,
strategy: BalanceStrategy,
) -> LoadBalanceResult {
if self.backends.is_empty() {
return LoadBalanceResult {
success: false,
selected_backend: None,
distribution: vec![],
overall_load_score: 0.0,
resource_utilization: 0.0,
predicted_response_time_improvement: 0.0,
message: "没有可用的后端服务器".to_string(),
};
}
let healthy_backends: Vec<&Backend> = self
.backends
.values()
.filter(|b| b.healthy && b.active_connections < b.max_connections)
.collect();
if healthy_backends.is_empty() {
return LoadBalanceResult {
success: false,
selected_backend: None,
distribution: vec![],
overall_load_score: 0.0,
resource_utilization: 0.0,
predicted_response_time_improvement: 0.0,
message: "没有健康的后端服务器".to_string(),
};
}
let selected_backend: _ =
self.select_backend_by_strategy(&healthy_backends, request, &strategy);
let distribution: _ = self.calculate_load_distribution(&healthy_backends);
let overall_load_score: _ = self.calculate_overall_load_score(&healthy_backends);
let resource_utilization: _ = self.calculate_resource_utilization(&healthy_backends);
let predicted_response_time_improvement =
self.predict_response_time_improvement(&healthy_backends);
self.update_request_stats();
LoadBalanceResult {
success: selected_backend.is_some(),
selected_backend: selected_backend.cloned(),
distribution,
overall_load_score,
resource_utilization,
predicted_response_time_improvement,
message: if selected_backend.is_some() {
format!(
"成功选择后端服务器: {}",
selected_backend.as_ref().unwrap().id
)
} else {
"无法选择合适的后端服务器".to_string()
},
}
}
pub async fn get_load_distribution(&self) -> Vec<LoadDistribution> {
let healthy_backends: Vec<&Backend> =
self.backends.values().filter(|b| b.healthy).collect();
self.calculate_load_distribution(&healthy_backends)
}
pub async fn get_statistics(&self) -> LoadBalancerStatistics {
let healthy_count: _ = self.backends.values().filter(|b| b.healthy).count();
let total_load: f64 = self.backends.values().map(|b| b.current_load).sum();
LoadBalancerStatistics {
total_backends: self.backends.len(),
healthy_backends: healthy_count,
unhealthy_backends: self.backends.len() - healthy_count,
avg_load: if healthy_count > 0 {
total_load / healthy_count as f64
} else {
0.0
},
total_requests: self.request_stats.total_requests,
success_rate: if self.request_stats.total_requests > 0 {
self.request_stats.successful_requests as f64
/ self.request_stats.total_requests as f64
} else {
0.0
},
avg_response_time_ms: if self.request_stats.total_requests > 0 {
self.request_stats.total_response_time_ms as f64
/ self.request_stats.total_requests as f64
} else {
0.0
},
}
}
pub async fn update_backend_status(
&mut self,
backend_id: &str,
cpu_utilization: f64,
memory_utilization: f64,
response_time_ms: f64,
error_rate: f64,
) -> bool {
if let Some(backend) = self.backends.get_mut(backend_id) {
backend.cpu_utilization = cpu_utilization;
backend.memory_utilization = memory_utilization;
backend.response_time_ms = response_time_ms;
backend.error_rate = error_rate;
backend.current_load = self.calculate_backend_load(backend);
backend.last_updated = Instant::now();
backend.healthy = self.is_backend_healthy(backend);
true
} else {
false
}
}
fn select_backend_by_strategy(
&self,
backends: &[&Backend],
request: &Request,
strategy: &BalanceStrategy,
) -> Option<&Backend> {
match strategy {
BalanceStrategy::RoundRobin => self.round_robin_select(backends),
BalanceStrategy::WeightedRoundRobin => self.weighted_round_robin_select(backends),
BalanceStrategy::LeastConnections => self.least_connections_select(backends),
BalanceStrategy::FastestResponse => self.fastest_response_select(backends),
BalanceStrategy::IpHash => {
Some(backends[0])
}
BalanceStrategy::IntelligentAI => self.ai_intelligent_select(backends, request),
BalanceStrategy::Adaptive => self.adaptive_select(backends, request),
}
}
fn round_robin_select(&self, backends: &[&Backend]) -> Option<&Backend> {
Some(backends[0])
}
fn weighted_round_robin_select(&self, backends: &[&Backend]) -> Option<&Backend> {
let total_weight: f64 = backends.iter().map(|b| b.weight).sum();
if total_weight <= 0.0 {
return Some(backends[0]);
}
let index: _ = (std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)
.unwrap()
.as_secs() as usize)
% backends.len();
Some(backends[index])
}
fn least_connections_select(&self, backends: &[&Backend]) -> Option<&Backend> {
backends
.iter()
.min_by_key(|b| b.active_connections)
.copied()
}
fn fastest_response_select(&self, backends: &[&Backend]) -> Option<&Backend> {
backends
.iter()
.min_by(|a, b| {
a.response_time_ms
.partial_cmp(&b.response_time_ms)
.unwrap_or(std::cmp::Ordering::Equal)
})
.copied()
}
fn ai_intelligent_select(&self, backends: &[&Backend], request: &Request) -> Option<&Backend> {
let mut best_backend = None;
let mut best_score = f64::MIN;
for backend in backends {
let score: _ = self.calculate_backend_score(backend, request);
if score > best_score {
best_score = score;
best_backend = Some(backend);
}
}
best_backend.copied()
}
fn adaptive_select(&self, backends: &[&Backend], request: &Request) -> Option<&Backend> {
let ai_score: _ = self
.ai_intelligent_select(backends, request)
.map(|b| self.calculate_backend_score(b, request))
.unwrap_or(0.0);
let least_conn: _ = self
.least_connections_select(backends)
.map(|b| 100.0 - (b.active_connections as f64 / b.max_connections as f64) * 100.0)
.unwrap_or(0.0);
let fastest_resp: _ = self
.fastest_response_select(backends)
.map(|b| 100.0 - (b.response_time_ms / self.config.max_response_time_ms) * 100.0)
.unwrap_or(0.0);
let combined_score: _ = ai_score * 0.5 + least_conn * 0.3 + fastest_resp * 0.2;
backends
.iter()
.max_by(|a, b| {
let score_a: _ = self.calculate_backend_score(a, request);
let score_b: _ = self.calculate_backend_score(b, request);
score_a
.partial_cmp(&score_b)
.unwrap_or(std::cmp::Ordering::Equal)
})
.copied()
}
fn calculate_backend_score(&self, backend: &Backend, request: &Request) -> f64 {
let load_score: _ = 100.0 - backend.current_load;
let response_score: _ =
100.0 - (backend.response_time_ms / self.config.max_response_time_ms) * 100.0;
let error_score: _ = 100.0 - (backend.error_rate / self.config.max_error_rate) * 100.0;
let resource_score: _ =
100.0 - ((backend.cpu_utilization + backend.memory_utilization) / 2.0);
let connection_score: _ =
100.0 - (backend.active_connections as f64 / backend.max_connections as f64) * 100.0;
let weighted_score: _ = load_score * 0.25
+ response_score * 0.25
+ error_score * 0.2
+ resource_score * 0.15
+ connection_score * 0.15;
weighted_score.max(0.0)
}
fn calculate_backend_load(&self, backend: &Backend) -> f64 {
let cpu_factor: _ = backend.cpu_utilization * 0.4;
let memory_factor: _ = backend.memory_utilization * 0.3;
let connection_factor =
(backend.active_connections as f64 / backend.max_connections as f64) * 100.0 * 0.3;
(cpu_factor + memory_factor + connection_factor).min(100.0)
}
fn is_backend_healthy(&self, backend: &Backend) -> bool {
backend.cpu_utilization < 95.0
&& backend.memory_utilization < 95.0
&& backend.response_time_ms < self.config.max_response_time_ms * 2.0
&& backend.error_rate < self.config.max_error_rate * 2.0
}
fn calculate_load_distribution(&self, backends: &[&Backend]) -> Vec<LoadDistribution> {
let total_backends: _ = backends.len() as f64;
if total_backends == 0.0 {
return vec![];
}
backends
.iter()
.map(|backend| {
let allocation_percentage: _ = 100.0 / total_backends;
let load_score: _ = 100.0 - backend.current_load;
let predicted_response_time: _ =
backend.response_time_ms * (1.0 + backend.current_load / 100.0);
LoadDistribution {
backend_id: backend.id.clone(),
allocated_requests: backend.active_connections,
allocation_percentage,
load_score,
predicted_response_time_ms: predicted_response_time,
}
})
.collect()
}
fn calculate_overall_load_score(&self, backends: &[&Backend]) -> f64 {
if backends.is_empty() {
return 0.0;
}
let total_score: f64 = backends.iter().map(|b| 100.0 - b.current_load).sum();
total_score / backends.len() as f64
}
fn calculate_resource_utilization(&self, backends: &[&Backend]) -> f64 {
if backends.is_empty() {
return 0.0;
}
let total_cpu: f64 = backends.iter().map(|b| b.cpu_utilization).sum();
let total_memory: f64 = backends.iter().map(|b| b.memory_utilization).sum();
(total_cpu + total_memory) / (2.0 * backends.len() as f64)
}
fn predict_response_time_improvement(&self, backends: &[&Backend]) -> f64 {
let avg_response_time: f64 =
backends.iter().map(|b| b.response_time_ms).sum::<f64>() / backends.len() as f64;
avg_response_time * 0.15 }
fn update_request_stats(&mut self) {
self.request_stats.total_requests += 1;
self.request_stats.successful_requests += 1;
self.request_stats.total_response_time_ms += 100; }
pub async fn detect_load_pattern(&self) -> Option<LoadPattern> {
if self.load_history.len() < 10 {
return None;
}
let recent_loads: Vec<f64> = self
.load_history
.iter()
.rev()
.take(10)
.map(|m| m.avg_cpu_utilization)
.collect();
let avg_load: _ = recent_loads.iter().sum::<f64>() / recent_loads.len() as f64;
let variance: _ = recent_loads
.iter()
.map(|&l| (l - avg_load).powi(2))
.sum::<f64>()
/ recent_loads.len() as f64;
let std_dev: _ = variance.sqrt();
let variability_coefficient: _ = if avg_load > 0.0 {
std_dev / avg_load
} else {
0.0
};
let pattern_type: _ = if variability_coefficient < 0.1 {
PatternType::Stable
} else if variability_coefficient < 0.3 {
PatternType::Cyclic
} else {
PatternType::Burst
};
Some(LoadPattern {
pattern_type,
peak_hours: vec![9, 14, 20], average_load: avg_load,
peak_load: recent_loads.iter().max().copied().unwrap_or(avg_load),
variability_coefficient,
})
}
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct LoadBalancerStatistics {
pub total_backends: usize,
pub healthy_backends: usize,
pub unhealthy_backends: usize,
pub avg_load: f64,
pub total_requests: u64,
pub success_rate: f64,
pub avg_response_time_ms: f64,
}
#[cfg(test)]
mod tests {
use super::*;
use std::sync::atomic::Ordering;
use std::time::{Duration, Instant};
#[tokio::test]
async fn test_add_backend() {
let mut lb = LoadBalancer::new_with_defaults();
let backend: _ = Backend {
id: "backend-1".to_string(),
name: "Backend 1".to_string(),
address: "192.168.1.1".to_string(),
current_load: 50.0,
cpu_utilization: 50.0,
memory_utilization: 50.0,
response_time_ms: 100.0,
error_rate: 0.01,
max_connections: 1000,
active_connections: 100,
healthy: true,
weight: 1.0,
last_updated: Instant::now(),
};
let result: _ = lb.add_backend(backend).await;
assert!(result);
assert_eq!(lb.backends.len(), 1);
}
#[tokio::test]
async fn test_select_backend_round_robin() {
let mut lb = LoadBalancer::new_with_defaults();
let backend: _ = Backend {
id: "backend-1".to_string(),
name: "Backend 1".to_string(),
address: "192.168.1.1".to_string(),
current_load: 50.0,
cpu_utilization: 50.0,
memory_utilization: 50.0,
response_time_ms: 100.0,
error_rate: 0.01,
max_connections: 1000,
active_connections: 100,
healthy: true,
weight: 1.0,
last_updated: Instant::now(),
};
lb.add_backend(backend).await;
let request: _ = Request {
id: "req-1".to_string(),
request_type: "GET".to_string(),
size_kb: 10.0,
cpu_requirement: 10.0,
memory_requirement: 10.0,
priority: RequestPriority::High,
created_at: Instant::now(),
estimated_processing_time_ms: 100,
};
let result: _ = lb
.select_backend(&request, BalanceStrategy::RoundRobin)
.await;
assert!(result.success);
assert!(result.selected_backend.is_some());
}
#[tokio::test]
async fn test_backend_score_calculation() {
let lb: _ = LoadBalancer::new_with_defaults();
let backend: _ = Backend {
id: "backend-1".to_string(),
name: "Backend 1".to_string(),
address: "192.168.1.1".to_string(),
current_load: 50.0,
cpu_utilization: 50.0,
memory_utilization: 50.0,
response_time_ms: 100.0,
error_rate: 0.01,
max_connections: 1000,
active_connections: 100,
healthy: true,
weight: 1.0,
last_updated: Instant::now(),
};
let request: _ = Request {
id: "req-1".to_string(),
request_type: "GET".to_string(),
size_kb: 10.0,
cpu_requirement: 10.0,
memory_requirement: 10.0,
priority: RequestPriority::High,
created_at: Instant::now(),
estimated_processing_time_ms: 100,
};
let score: _ = lb.calculate_backend_score(&backend, &request);
assert!(score > 0.0);
assert!(score <= 100.0);
}
}