负载均衡算法实践笔记
这次做高可用架构,调研了各种负载均衡算法,从简单的轮询到复杂的一致性哈希,各有各的适用场景。
适用于长连接场景,如 WebSoc
最简单的负载均衡算法,按顺序分发请求。
轮询算法
最简单的负载均衡算法,按顺序分发请求。
简单轮询
servers = ['server1', 'server2', 'server3']
current_index = 0
def get_server():
global current_index
server = servers[current_index]
current_index = (current_index + 1) % len(servers)
return server
加权轮询
如果服务器配置不同,可以分配不同权重:
servers = [
{'name': 'server1', 'weight': 3},
{'name': 'server2', 'weight': 2},
{'name': 'server3', 'weight': 1}
]
def get_weighted_server():
total_weight = sum(s['weight'] for s in servers)
current_weight = 0
for server in servers:
current_weight += server['weight']
if random.random() * total_weight <= current_weight:
return server['name']
Nginx 的轮询配置:
upstream backend {
server 192.168.1.1 weight=3;
server 192.168.1.2 weight=2;
server 192.168.1.3 weight=1;
}
最少连接算法
把请求分配给当前连接数最少的服务器。
import heapq
servers = {
'server1': {'connections': 0},
'server2': {'connections': 0},
'server3': {'connections': 0}
}
def get_least_connections():
# 使用堆找到连接数最少的服务器
min_conn = min(servers.items(), key=lambda x: x[1]['connections'])
return min_conn[0]
适用于长连接场景,如 WebSocket。
源地址哈希
根据客户端 IP 地址决定分发到哪个服务器。
import hashlib
servers = ['server1', 'server2', 'server3']
def get_server_by_ip(client_ip):
hash_value = int(hashlib.md5(client_ip.encode()).hexdigest(), 16)
index = hash_value % len(servers)
return servers[index]
Nginx 配置:
upstream backend {
ip_hash;
server 192.168.1.1;
server 192.168.1.2;
server 192.168.1.3;
}
优势:同一客户端的请求会路由到同一服务器,利于会话保持
劣势:如果某台服务器宕机,会话会丢失
一致性哈希
解决服务器增删时,数据分布剧烈变化的问题。
基本原理
将服务器和请求映射到哈希环上,请求顺时针路由到下一个服务器。
graph TB
subgraph 哈希环
A[哈希环0<br/>360度]
B[服务器1<br/>位置30度]
C[服务器2<br/>位置120度]
D[服务器3<br/>位置240度]
E[请求1<br/>位置40度] --> B
F[请求2<br/>位置200度] --> D
G[请求3<br/>位置100度] --> C
A --> B
A --> C
A --> D
end
style A fill:#87CEEB
style B fill:#90EE90
style C fill:#FFD700
style D fill:#FFB6C1
虚拟节点
为了数据分布更均匀,每个服务器对应多个虚拟节点。
import hashlib
servers = ['server1', 'server2', 'server3']
virtual_nodes = 100
ring = {}
# 为每个服务器创建虚拟节点
for server in servers:
for i in range(virtual_nodes):
virtual_key = f"{server}#{i}"
hash_value = int(hashlib.md5(virtual_key.encode()).hexdigest(), 16)
ring[hash_value] = server
def get_consistent_server(key):
hash_value = int(hashlib.md5(key.encode()).hexdigest(), 16)
sorted_ring = sorted(ring.keys())
# 找到第一个大于等于 hash_value 的节点
for node in sorted_ring:
if node >= hash_value:
return ring[node]
# 如果没找到,返回第一个节点(环形)
return ring[sorted_ring[0]]
优势
- 服务器增删时,只会影响部分请求
- 数据分布更均匀
- 适合缓存、分布式存储场景
实践中的选择
场景一:无状态服务
对于无状态服务,如 REST API,轮询或最少连接就够了。
upstream backend {
least_conn;
server 192.168.1.1;
server 192.168.1.2;
server 192.168.1.3;
}
场景二:有状态服务
对于有状态服务,如 WebSocket,最少连接更合适。
场景三:需要会话保持
对于需要会话保持的服务,源地址哈希或 Cookie 哈希。
upstream backend {
hash $cookie_session_id consistent;
server 192.168.1.1;
server 192.168.1.2;
server 192.168.1.3;
}
场景四:缓存或分布式存储
一致性哈希是最佳选择,避免数据重新分布带来的缓存失效。
写在最后
负载均衡算法这东西,没有最好的,只有最适合的。
选型之前先问自己几个问题:
- 服务是无状态还是有状态?
- 需要会话保持吗?
- 服务器配置是否相同?
- 是否需要快速增删服务器?
回答清楚这些问题,选择就简单了。
这次调研下来,最大的收获是:不要追求"最优"算法,要追求"够用"的算法。轮询最简单,但它解决了很多问题。
版权声明: 本文首发于 指尖魔法屋-负载均衡算法实践笔记(https://blog.thinkmoon.cn/post/51-load-balancing-algorithms-roundrobin-consistent-hashing/) 转载或引用必须申明原指尖魔法屋来源及源地址!
评论
使用 GitHub 账号登录后即可留言,支持 Markdown。