Skip to content

Commit b2fdded

Browse files
committed
Add least connections load balancing algorithm
1 parent b521575 commit b2fdded

4 files changed

Lines changed: 83 additions & 8 deletions

File tree

AGENTS.md

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -270,6 +270,16 @@ error_page = { status = 404, page = "/404.html" }
270270
make test # 或 cargo test
271271
```
272272

273+
5. **配置文件示例**:每次新增功能时,如果需要配置文件支持,则必须在 examples/config.example_full.toml 文件中添加相应的 example 配置,同时更新 config.schema.json 文件以提供验证支持。
274+
275+
```bash
276+
# 查看 examples/config.example_full.toml 中的示例配置
277+
cat examples/config.example_full.toml
278+
279+
# 查看 config.schema.json 中的验证规则
280+
cat config.schema.json
281+
```
282+
273283
### 函数注释要求
274284

275285
所有 Rust 函数必须遵循以下注释格式:
@@ -398,4 +408,3 @@ make release
398408
## 许可证
399409

400410
MIT 许可证 - 详见 LICENSE 文件
401-

config.schema.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,7 @@
5353
"method": {
5454
"description": "Load balancing algorithm type",
5555
"type": "string",
56-
"enum": ["roundrobin", "weightedroundrobin", "iphash"],
56+
"enum": ["roundrobin", "weightedroundrobin", "iphash", "leastconn"],
5757
"default": "weightedroundrobin"
5858
}
5959
},

examples/config.example_full.toml

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -164,3 +164,23 @@ server = [
164164
{ server = "192.168.1.104:8080", weight = 1 }
165165
]
166166

167+
# Example 9: Load balancing with Least Connections (least conn) algorithm
168+
[[host]]
169+
ip = "0.0.0.0"
170+
port = 8086
171+
server_name = "leastconn.example.com"
172+
173+
[[host.route]]
174+
location = "/api"
175+
upstream = "leastconn_servers" # Reference to upstream server group with least connections
176+
proxy_timeout = 30 # Proxy timeout in seconds (default: 5)
177+
max_body_size = 1048576 # 1MB max body size
178+
179+
[[upstream]]
180+
name = "leastconn_servers"
181+
method = "least_conn" # Use least connections load balancing algorithm
182+
server = [
183+
{ server = "192.168.1.105:8080", weight = 1 }, # Server 1: weight 1
184+
{ server = "192.168.1.106:8080", weight = 2 }, # Server 2: weight 2 (handles more connections)
185+
{ server = "192.168.1.107:8080", weight = 1 } # Server 3: weight 1
186+
]

src/http/reverse_proxy.rs

Lines changed: 52 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,12 @@ static LEAST_CONN_COUNTERS: LazyLock<DashMap<String, DashMap<usize, AtomicUsize>
3434
static CLIENT: OnceLock<Client> = OnceLock::new();
3535

3636
/// 获取全局 reqwest 客户端实例
37+
///
38+
/// 该函数使用 OnceLock 确保客户端只初始化一次,提供一个静态引用以实现连接池复用。
39+
///
40+
/// # 返回值
41+
///
42+
/// 返回静态的 reqwest 客户端引用,用于发送 HTTP 请求
3743
fn get_client() -> &'static Client {
3844
CLIENT.get_or_init(|| {
3945
Client::builder()
@@ -346,6 +352,19 @@ pub async fn serve(
346352
}
347353

348354
/// 包装响应体,在响应完成后自动减少连接计数
355+
///
356+
/// 该函数用于处理最少连接数负载均衡算法的连接计数管理。它会读取整个响应体,
357+
/// 确保在响应处理完成后正确减少服务器的连接计数。
358+
///
359+
/// # 参数
360+
///
361+
/// * `response` - 要包装的原始响应
362+
/// * `upstream_name` - 上游服务器组名称
363+
/// * `server_index` - 服务器在 upstream 中的索引
364+
///
365+
/// # 返回值
366+
///
367+
/// 返回包装后的响应,确保在响应完成后正确更新连接计数
349368
async fn wrap_response_body(
350369
response: Response<Body>,
351370
upstream_name: String,
@@ -386,8 +405,18 @@ async fn wrap_response_body(
386405
Response::from_parts(parts, Body::from(body_bytes))
387406
}
388407

389-
/// 检查给定的头部是否应该在反向代理中被排除转发。
390-
/// 像 "host"、"connection" 等头部通常会被排除,以避免冲突或安全问题。
408+
/// 检查给定的头部是否应该在反向代理中被排除转发
409+
///
410+
/// 某些 HTTP 头部(如 "host"、"connection" 等)在代理过程中可能会导致冲突或安全问题,
411+
/// 因此需要被排除在转发的头部列表之外。
412+
///
413+
/// # 参数
414+
///
415+
/// * `name` - 要检查的 HTTP 头部名称
416+
///
417+
/// # 返回值
418+
///
419+
/// 如果头部应该被排除则返回 `true`,否则返回 `false`
391420
fn is_exclude_header(name: &HeaderName) -> bool {
392421
matches!(
393422
name.as_str(),
@@ -402,9 +431,18 @@ fn is_exclude_header(name: &HeaderName) -> bool {
402431
)
403432
}
404433

405-
/// 将头部从一个 `HeaderMap` 复制到另一个,排除在 `is_exclude_header` 中指定的头部。
406-
/// 这确保只转发相关的头部,避免冲突或安全问题。
407-
/// 计算 IP 地址的哈希值,用于 IP 哈希负载均衡
434+
/// 计算 IP 地址的哈希值,用于 IP 哈希负载均衡算法
435+
///
436+
/// 该函数实现了一个简单但有效的字符串哈希算法,将 IP 地址字符串转换为哈希值,
437+
/// 用于在 IP 哈希负载均衡算法中选择服务器。
438+
///
439+
/// # 参数
440+
///
441+
/// * `ip` - 要计算哈希值的 IP 地址字符串(支持 IPv4 和 IPv6)
442+
///
443+
/// # 返回值
444+
///
445+
/// 返回 IP 地址的哈希值(使用 wrapping 运算防止溢出)
408446
fn ip_hash(ip: &str) -> usize {
409447
let mut hash: usize = 5381;
410448

@@ -419,6 +457,15 @@ fn ip_hash(ip: &str) -> usize {
419457
hash
420458
}
421459

460+
/// 将头部从一个 `HeaderMap` 复制到另一个,排除指定的头部
461+
///
462+
/// 该函数负责在代理请求和响应时复制 HTTP 头部,但会排除在 `is_exclude_header` 中
463+
/// 定义的头部,以避免冲突或安全问题。
464+
///
465+
/// # 参数
466+
///
467+
/// * `from` - 源头部映射
468+
/// * `to` - 目标头部映射
422469
fn copy_headers(from: &http::HeaderMap, to: &mut http::HeaderMap) {
423470
for (name, value) in from.iter() {
424471
if !is_exclude_header(name) {
@@ -621,4 +668,3 @@ mod tests {
621668
assert_eq!(selected_index, 2);
622669
}
623670
}
624-

0 commit comments

Comments
 (0)