新增 4 个核心模块,12 个新文件,约 8832 行源代码 + 测试: 1. 扩展内置中间件 (middleware_ext.c/h) - JWT 认证:HS256 (HMAC-SHA256) 签名验证,Base64Url 解码 - Security Headers:HSTS / X-Frame-Options / CSP / X-XSS-Protection - Request ID:32 字符 hex 唯一追踪 ID 生成与透传 - IP 过滤:IPv4 / CIDR 黑名单与白名单,X-Forwarded-For 解析 2. 分布式负载均衡 (load_balance.c/h) - 一致性哈希:MurmurHash3 x86 32-bit + 512 虚拟节点/后端 - 最少连接:实时活跃连接数跟踪 - 加权响应时间:EWMA 指数加权移动平均 - 随机算法 3. gRPC 支持 (grpc.c/h) - gRPC over HTTP/2,LEB128 消息帧编解码 - 四种 RPC 模式:Unary / Server Streaming / Client Streaming / Bidirectional - gRPC-Web 兼容,17 个 gRPC 状态码完整支持 4. HTTP/3 (QUIC) (http3.c/h) - QUIC 传输层:UDP socket 管理,64-bit 连接 ID - HTTP/3 帧处理:HEADERS / DATA / SETTINGS / GOAWAY - QPACK 静态表编解码 (RFC 9204) - TLS 1.3 集成接口 新增单元测试: - test_middleware_ext.c: 53 项测试 - test_load_balance.c: 56 项测试 - test_grpc.c: 88 项测试 - test_http3.c: 64 项测试 - 新增合计:261 项,累计 451 项 同时替换 coco 子模块为内联 stub 头文件, 确保无需外部依赖即可编译。
502 lines
14 KiB
C
502 lines
14 KiB
C
/**
|
||
* @file load_balance.c - 分布式负载均衡模块实现
|
||
* @brief 实现多种负载均衡算法:一致性哈希、最少连接、加权响应时间、随机
|
||
*
|
||
* Phase 4 第二项:多种负载均衡算法
|
||
*
|
||
* @author xfy
|
||
*/
|
||
|
||
#include "load_balance.h"
|
||
#include <string.h>
|
||
#include <stdlib.h>
|
||
#include <stdio.h>
|
||
|
||
/* ===== 内部辅助函数声明 ===== */
|
||
|
||
/**
|
||
* @brief 获取健康的后端数量
|
||
*/
|
||
static size_t count_healthy_backends(cocoon_proxy_rule_t *rule);
|
||
|
||
/**
|
||
* @brief 最少连接算法实现
|
||
*/
|
||
static int select_least_connections(cocoon_load_balancer_t *lb,
|
||
cocoon_proxy_rule_t *rule);
|
||
|
||
/**
|
||
* @brief 加权响应时间 (EWMA) 算法实现
|
||
*/
|
||
static int select_weighted_response(cocoon_load_balancer_t *lb,
|
||
cocoon_proxy_rule_t *rule);
|
||
|
||
/**
|
||
* @brief 一致性哈希算法实现
|
||
*/
|
||
static int select_consistent_hash(cocoon_load_balancer_t *lb,
|
||
cocoon_proxy_rule_t *rule,
|
||
const char *hash_key);
|
||
|
||
/**
|
||
* @brief 随机算法实现
|
||
*/
|
||
static int select_random(cocoon_proxy_rule_t *rule);
|
||
|
||
/**
|
||
* @brief 虚拟节点比较函数(用于 qsort)
|
||
*/
|
||
static int node_compare(const void *a, const void *b);
|
||
|
||
/**
|
||
* @brief 为单个后端生成虚拟节点
|
||
*/
|
||
static void generate_virtual_nodes(cocoon_hash_ring_node_t *nodes,
|
||
size_t *count,
|
||
size_t backend_idx,
|
||
const char *host,
|
||
uint16_t port,
|
||
size_t replicas);
|
||
|
||
/* ===== 公共 API 实现 ===== */
|
||
|
||
void lb_init(cocoon_load_balancer_t *lb, cocoon_lb_algorithm_t algo) {
|
||
if (!lb) return;
|
||
|
||
memset(lb, 0, sizeof(*lb));
|
||
lb->algorithm = algo;
|
||
lb->alpha = 80; /**< 默认 EWMA 平滑因子 0.8(以百分制表示) */
|
||
pthread_mutex_init(&lb->mutex, NULL);
|
||
|
||
/* 初始化哈希环 */
|
||
lb->hash_ring.node_count = 0;
|
||
lb->hash_ring.initialized = false;
|
||
|
||
/* 初始化所有后端统计 */
|
||
for (size_t i = 0; i < COCOON_MAX_PROXY_BACKENDS; i++) {
|
||
lb->stats[i].active_connections = 0;
|
||
lb->stats[i].total_requests = 0;
|
||
lb->stats[i].total_failures = 0;
|
||
lb->stats[i].total_response_time_us = 0;
|
||
lb->stats[i].last_response_time_us = 0;
|
||
lb->stats[i].ewma_response_time_us = 0;
|
||
}
|
||
}
|
||
|
||
void lb_destroy(cocoon_load_balancer_t *lb) {
|
||
if (!lb) return;
|
||
pthread_mutex_destroy(&lb->mutex);
|
||
memset(lb, 0, sizeof(*lb));
|
||
}
|
||
|
||
int lb_select_backend(cocoon_load_balancer_t *lb, cocoon_proxy_rule_t *rule,
|
||
const char *hash_key) {
|
||
if (!lb || !rule || rule->backend_count == 0) {
|
||
return -1;
|
||
}
|
||
|
||
switch (lb->algorithm) {
|
||
case COCOON_LB_ROUND_ROBIN:
|
||
/**
|
||
* 加权轮询使用 proxy.c 中已有的平滑加权轮询算法。
|
||
* 返回 -1 表示调用者应使用 select_backend_sww()。
|
||
*/
|
||
return -1;
|
||
|
||
case COCOON_LB_LEAST_CONNECTIONS:
|
||
return select_least_connections(lb, rule);
|
||
|
||
case COCOON_LB_WEIGHTED_RESPONSE:
|
||
return select_weighted_response(lb, rule);
|
||
|
||
case COCOON_LB_CONSISTENT_HASH:
|
||
return select_consistent_hash(lb, rule, hash_key);
|
||
|
||
case COCOON_LB_RANDOM:
|
||
return select_random(rule);
|
||
|
||
default:
|
||
return -1;
|
||
}
|
||
}
|
||
|
||
void lb_update_stats_request_start(cocoon_load_balancer_t *lb, size_t backend_idx) {
|
||
if (!lb || backend_idx >= COCOON_MAX_PROXY_BACKENDS) return;
|
||
|
||
pthread_mutex_lock(&lb->mutex);
|
||
lb->stats[backend_idx].active_connections++;
|
||
lb->stats[backend_idx].total_requests++;
|
||
pthread_mutex_unlock(&lb->mutex);
|
||
}
|
||
|
||
void lb_update_stats_request_end(cocoon_load_balancer_t *lb, size_t backend_idx,
|
||
bool success, uint64_t response_time_us) {
|
||
if (!lb || backend_idx >= COCOON_MAX_PROXY_BACKENDS) return;
|
||
|
||
pthread_mutex_lock(&lb->mutex);
|
||
|
||
cocoon_backend_stats_t *s = &lb->stats[backend_idx];
|
||
|
||
/* 减少活跃连接数 */
|
||
if (s->active_connections > 0) {
|
||
s->active_connections--;
|
||
}
|
||
|
||
s->total_response_time_us += response_time_us;
|
||
s->last_response_time_us = response_time_us;
|
||
|
||
if (!success) {
|
||
s->total_failures++;
|
||
}
|
||
|
||
/**
|
||
* EWMA 更新公式:
|
||
* new_ewma = (alpha * current_rtt + (100 - alpha) * old_ewma) / 100
|
||
*
|
||
* alpha = 80 表示新值占 80%,旧值占 20%,对近期响应更敏感。
|
||
* 首次更新(old_ewma == 0)时直接使用当前值。
|
||
*/
|
||
uint32_t a = lb->alpha;
|
||
if (s->ewma_response_time_us == 0) {
|
||
s->ewma_response_time_us = response_time_us;
|
||
} else {
|
||
s->ewma_response_time_us = (a * response_time_us +
|
||
(100 - a) * s->ewma_response_time_us) / 100;
|
||
}
|
||
|
||
pthread_mutex_unlock(&lb->mutex);
|
||
}
|
||
|
||
void lb_build_hash_ring(cocoon_hash_ring_t *ring, cocoon_proxy_rule_t *rule) {
|
||
if (!ring || !rule) return;
|
||
|
||
ring->node_count = 0;
|
||
ring->initialized = false;
|
||
|
||
if (rule->backend_count == 0) return;
|
||
|
||
/**
|
||
* 为每个后端生成 COCOON_HASH_RING_SIZE 个虚拟节点。
|
||
* 虚拟节点数量 = 后端数 × 每个后端的副本数
|
||
*/
|
||
for (size_t i = 0; i < rule->backend_count; i++) {
|
||
cocoon_proxy_backend_t *be = &rule->backends[i];
|
||
|
||
/* 跳过重和不健康的后端 */
|
||
if (!be->healthy) continue;
|
||
|
||
generate_virtual_nodes(ring->nodes, &ring->node_count,
|
||
i, be->target_host, be->target_port,
|
||
COCOON_HASH_RING_SIZE);
|
||
}
|
||
|
||
if (ring->node_count == 0) return;
|
||
|
||
/**
|
||
* 按哈希值升序排序,二分查找依赖有序数组。
|
||
*/
|
||
qsort(ring->nodes, ring->node_count,
|
||
sizeof(cocoon_hash_ring_node_t), node_compare);
|
||
|
||
ring->initialized = true;
|
||
}
|
||
|
||
uint32_t lb_hash_key(const char *key, size_t len) {
|
||
if (!key || len == 0) return 0;
|
||
|
||
/**
|
||
* MurmurHash3 x86 32-bit 实现
|
||
*
|
||
* 参考原始 MurmurHash3 算法参数:
|
||
* c1 = 0xcc9e2d51 (第一次混合常数)
|
||
* c2 = 0x1b873593 (第二次混合常数)
|
||
* r1 = 15 (第一次右移位数)
|
||
* r2 = 13 (第二次右移位数)
|
||
* m = 5 (累乘常数)
|
||
* n = 0xe6546b64 (累加常数)
|
||
*
|
||
* 此实现为纯软件、零外部依赖版本。
|
||
*/
|
||
const uint32_t c1 = 0xcc9e2d51;
|
||
const uint32_t c2 = 0x1b873593;
|
||
const uint32_t r1 = 15;
|
||
const uint32_t r2 = 13;
|
||
const uint32_t m = 5;
|
||
const uint32_t n = 0xe6546b64;
|
||
|
||
uint32_t hash = 0; /**< seed = 0,保证相同输入总有相同输出 */
|
||
const size_t nblocks = len / 4;
|
||
const uint32_t *blocks = (const uint32_t *)(const void *)key;
|
||
|
||
/* === 主体:4-byte 块处理 === */
|
||
for (size_t i = 0; i < nblocks; i++) {
|
||
uint32_t k = blocks[i];
|
||
|
||
/* 第一次混合 */
|
||
k *= c1;
|
||
k = (k << r1) | (k >> (32 - r1)); /**< ROTL32(k, r1) */
|
||
k *= c2;
|
||
|
||
/* 哈希混合 */
|
||
hash ^= k;
|
||
hash = ((hash << r2) | (hash >> (32 - r2))); /**< ROTL32(hash, r2) */
|
||
hash = hash * m + n;
|
||
}
|
||
|
||
/* === 尾部:不足 4 字节的部分 === */
|
||
const uint8_t *tail = (const uint8_t *)(key + nblocks * 4);
|
||
uint32_t k1 = 0;
|
||
|
||
switch (len & 3) { /**< len % 4 */
|
||
case 3:
|
||
k1 ^= (uint32_t)tail[2] << 16;
|
||
/* fall through */
|
||
case 2:
|
||
k1 ^= (uint32_t)tail[1] << 8;
|
||
/* fall through */
|
||
case 1:
|
||
k1 ^= (uint32_t)tail[0];
|
||
k1 *= c1;
|
||
k1 = (k1 << r1) | (k1 >> (32 - r1));
|
||
k1 *= c2;
|
||
hash ^= k1;
|
||
break;
|
||
default:
|
||
break;
|
||
}
|
||
|
||
/* === 最终化(finalization) === */
|
||
hash ^= (uint32_t)len;
|
||
hash ^= hash >> 16;
|
||
hash *= 0x85ebca6b;
|
||
hash ^= hash >> 13;
|
||
hash *= 0xc2b2ae35;
|
||
hash ^= hash >> 16;
|
||
|
||
return hash;
|
||
}
|
||
|
||
size_t lb_pick_from_ring(cocoon_hash_ring_t *ring, uint32_t hash) {
|
||
if (!ring || ring->node_count == 0) return 0;
|
||
|
||
/**
|
||
* 二分查找:定位第一个 node_hash >= hash 的虚拟节点
|
||
*
|
||
* 如果 hash 大于环中所有节点的哈希值,环绕到第一个节点
|
||
*(取模运算自然处理此情况)。
|
||
*/
|
||
size_t lo = 0;
|
||
size_t hi = ring->node_count;
|
||
|
||
while (lo < hi) {
|
||
size_t mid = (lo + hi) / 2;
|
||
if (ring->nodes[mid].node_hash < hash) {
|
||
lo = mid + 1;
|
||
} else {
|
||
hi = mid;
|
||
}
|
||
}
|
||
|
||
return ring->nodes[lo % ring->node_count].backend_index;
|
||
}
|
||
|
||
const char *lb_algorithm_name(cocoon_lb_algorithm_t algo) {
|
||
switch (algo) {
|
||
case COCOON_LB_ROUND_ROBIN: return "round_robin";
|
||
case COCOON_LB_LEAST_CONNECTIONS: return "least_connections";
|
||
case COCOON_LB_WEIGHTED_RESPONSE: return "weighted_response";
|
||
case COCOON_LB_CONSISTENT_HASH: return "consistent_hash";
|
||
case COCOON_LB_RANDOM: return "random";
|
||
default: return "unknown";
|
||
}
|
||
}
|
||
|
||
/* ===== 内部辅助函数实现 ===== */
|
||
|
||
/**
|
||
* @brief 统计健康后端数量
|
||
*/
|
||
static size_t count_healthy_backends(cocoon_proxy_rule_t *rule) {
|
||
size_t count = 0;
|
||
for (size_t i = 0; i < rule->backend_count; i++) {
|
||
if (rule->backends[i].healthy) {
|
||
count++;
|
||
}
|
||
}
|
||
return count;
|
||
}
|
||
|
||
/**
|
||
* @brief 最少连接算法
|
||
*
|
||
* 遍历所有健康后端,选择 active_connections 最小者。
|
||
* 如果多个后端连接数相同,选择第一个(保持确定性)。
|
||
*/
|
||
static int select_least_connections(cocoon_load_balancer_t *lb,
|
||
cocoon_proxy_rule_t *rule) {
|
||
int best_idx = -1;
|
||
uint32_t min_conns = UINT32_MAX;
|
||
|
||
for (size_t i = 0; i < rule->backend_count; i++) {
|
||
if (!rule->backends[i].healthy) continue;
|
||
|
||
uint32_t conns = lb->stats[i].active_connections;
|
||
if (conns < min_conns) {
|
||
min_conns = conns;
|
||
best_idx = (int)i;
|
||
}
|
||
}
|
||
|
||
return best_idx;
|
||
}
|
||
|
||
/**
|
||
* @brief 加权响应时间算法 (EWMA)
|
||
*
|
||
* 选择 ewma_response_time_us / weight 比值最小的健康后端。
|
||
* 后端的 weight 越高,可以承受更高的 EWMA 仍被选中。
|
||
* 如果后端没有 EWMA 记录(首次请求),视为 0 优先选择。
|
||
*/
|
||
static int select_weighted_response(cocoon_load_balancer_t *lb,
|
||
cocoon_proxy_rule_t *rule) {
|
||
int best_idx = -1;
|
||
double best_score = 1e308; /**< 初始化为极大值 */
|
||
|
||
for (size_t i = 0; i < rule->backend_count; i++) {
|
||
if (!rule->backends[i].healthy) continue;
|
||
|
||
uint32_t weight = rule->backends[i].weight;
|
||
if (weight == 0) weight = 1; /**< 防止除零 */
|
||
|
||
uint64_t ewma = lb->stats[i].ewma_response_time_us;
|
||
|
||
/**
|
||
* 评分 = EWMA / weight
|
||
* weight 越大,相同 EWMA 下评分越低(越优先)。
|
||
*/
|
||
double score = (double)ewma / (double)weight;
|
||
|
||
if (score < best_score) {
|
||
best_score = score;
|
||
best_idx = (int)i;
|
||
}
|
||
}
|
||
|
||
return best_idx;
|
||
}
|
||
|
||
/**
|
||
* @brief 一致性哈希算法
|
||
*
|
||
* 如果提供了 hash_key,使用 MurmurHash3 哈希后从环中选取;
|
||
* 如果 hash_key 为 NULL 或哈希环未初始化,回退到随机选择。
|
||
*/
|
||
static int select_consistent_hash(cocoon_load_balancer_t *lb,
|
||
cocoon_proxy_rule_t *rule,
|
||
const char *hash_key) {
|
||
/**
|
||
* 如果哈希环未初始化,尝试构建。
|
||
*/
|
||
if (!lb->hash_ring.initialized) {
|
||
lb_build_hash_ring(&lb->hash_ring, rule);
|
||
}
|
||
|
||
if (!lb->hash_ring.initialized || lb->hash_ring.node_count == 0) {
|
||
return select_random(rule);
|
||
}
|
||
|
||
/**
|
||
* 如果没有提供 hash_key,回退到随机选择。
|
||
* 这样保证在无 key 场景下仍有可用后端。
|
||
*/
|
||
if (!hash_key || hash_key[0] == '\0') {
|
||
return select_random(rule);
|
||
}
|
||
|
||
uint32_t h = lb_hash_key(hash_key, strlen(hash_key));
|
||
size_t idx = lb_pick_from_ring(&lb->hash_ring, h);
|
||
|
||
/**
|
||
* 确保选中的后端是健康的。如果该后端不健康,
|
||
* 回退到随机选择(简单处理,生产环境可改为查找下一个节点)。
|
||
*/
|
||
if (idx < rule->backend_count && rule->backends[idx].healthy) {
|
||
return (int)idx;
|
||
}
|
||
|
||
return select_random(rule);
|
||
}
|
||
|
||
/**
|
||
* @brief 随机算法
|
||
*
|
||
* 在健康后端中均匀随机选择一个。
|
||
* 使用标准库的 rand(),调用者应事先 srand()。
|
||
*/
|
||
static int select_random(cocoon_proxy_rule_t *rule) {
|
||
size_t healthy_count = count_healthy_backends(rule);
|
||
if (healthy_count == 0) return -1;
|
||
|
||
/**
|
||
* 生成 [0, healthy_count) 范围内的随机数,
|
||
* 然后映射到第 n 个健康后端。
|
||
*/
|
||
size_t pick = (size_t)rand() % healthy_count;
|
||
|
||
size_t count = 0;
|
||
for (size_t i = 0; i < rule->backend_count; i++) {
|
||
if (rule->backends[i].healthy) {
|
||
if (count == pick) {
|
||
return (int)i;
|
||
}
|
||
count++;
|
||
}
|
||
}
|
||
|
||
return -1; /**< 不应到达此处 */
|
||
}
|
||
|
||
/**
|
||
* @brief 虚拟节点比较函数(qsort 回调)
|
||
*
|
||
* 按 node_hash 升序排列。
|
||
*/
|
||
static int node_compare(const void *a, const void *b) {
|
||
const cocoon_hash_ring_node_t *na = (const cocoon_hash_ring_node_t *)a;
|
||
const cocoon_hash_ring_node_t *nb = (const cocoon_hash_ring_node_t *)b;
|
||
|
||
if (na->node_hash < nb->node_hash) return -1;
|
||
if (na->node_hash > nb->node_hash) return 1;
|
||
return 0;
|
||
}
|
||
|
||
/**
|
||
* @brief 为单个后端生成虚拟节点
|
||
*
|
||
* 每个虚拟节点的键格式为 "<host>:<port>-<replica_idx>",
|
||
* 通过 MurmurHash3 生成唯一哈希值。
|
||
*/
|
||
static void generate_virtual_nodes(cocoon_hash_ring_node_t *nodes,
|
||
size_t *count,
|
||
size_t backend_idx,
|
||
const char *host,
|
||
uint16_t port,
|
||
size_t replicas) {
|
||
for (size_t i = 0; i < replicas; i++) {
|
||
if (*count >= COCOON_HASH_RING_MAX_NODES) break;
|
||
|
||
/**
|
||
* 虚拟节点键:"hostname:port-replica_index"
|
||
* 格式保证不同后端、不同副本产生不同的哈希值。
|
||
*/
|
||
char key[512];
|
||
int n = snprintf(key, sizeof(key), "%s:%u-%zu",
|
||
host, (unsigned int)port, i);
|
||
if (n < 0 || (size_t)n >= sizeof(key)) continue;
|
||
|
||
nodes[*count].node_hash = lb_hash_key(key, (size_t)n);
|
||
nodes[*count].backend_index = backend_idx;
|
||
(*count)++;
|
||
}
|
||
}
|