什么是一致性哈希(Consistent Hashing)
一致性哈希(Consistent Hashing)是一种特殊的哈希技术,广泛应用于分布式缓存和负载均衡场景。它的核心目标是:当后端服务器数量发生变化时,尽量少的键(请求)需要重新映射。
传统取模哈希 hash(key) % N 的致命缺陷是:一旦 N 变化,几乎所有键的映射都会改变,导致缓存大面积失效。
一致性哈希通过将哈希空间组织成一个哈希环(Hash Ring),将服务器节点和请求键都映射到这个环上,从而将变动影响范围从 O(N) 降低到 O(1/N)。
为什么Nginx需要一致性哈希
Nginx 原生提供了以下几种负载均衡算法:
- 轮询(Round Robin):默认方式,按顺序分配请求
- 加权轮询(weight):按权重比例分配
- 最少连接(least_conn):优先分配给活跃连接数最少的服务器
- IP哈希(ip_hash):根据客户端IP哈希结果分配
其中 ip_hash 可以实现会话保持,但有一个明显缺陷:它是基于客户端IP的前三个八位组(C类网络)进行哈希的,在以下场景中效果不理想:
- 客户端通过代理或NAT上网,多个用户共享同一个出口IP
- 使用了CDN,后端看到的是CDN节点IP
- IPv6环境下,ip_hash的行为不符合预期
一致性哈希的优势:
- 支持自定义哈希键(URI、请求参数、请求头、Cookie等)
- 服务器增减时只影响 1/N 的键重新映射
- 后端为缓存服务器(Redis、Memcached)时,显著提高缓存命中率
Nginx原生不支持consistent哈希的解决办法
Nginx 开源版(community version)的 hash 指令不支持 consistent 参数。
只有 Nginx Plus(商业版) 才原生支持:
upstream backend {
hash $request_uri consistent;
server 192.168.1.10:8080;
server 192.168.1.11:8080;
}
对于开源版用户,有两种主流解决方案:
- 方案一:编译安装
nginx-upsync-module(微博开源,支持consistent哈希 + 动态上游) - 方案二:使用 OpenResty +
lua-resty-consistent-hash库,通过Lua实现
方案一:nginx-upsync-module 实现consistent哈希(推荐)
nginx-upsync-module 是微博开源的Nginx模块,支持从 Consul/Etcd 动态同步上游服务器,同时提供 consistent_hash 指令实现一致性哈希负载均衡。
编译安装 nginx-upsync-module
步骤1:下载Nginx源码和模块
# 以 Nginx 1.24.0 为例
wget http://nginx.org/download/nginx-1.24.0.tar.gz
tar -xzvf nginx-1.24.0.tar.gz
# 克隆 nginx-upsync-module
git clone https://github.com/weibocom/nginx-upsync-module.git
步骤2:编译Nginx,添加upsync模块
cd nginx-1.24.0/
./configure \
--prefix=/usr/local/nginx \
--add-module=../nginx-upsync-module \
--with-http_ssl_module \
--with-http_v2_module
make -j$(nproc)
sudo make install
步骤3:验证模块是否加载成功
/usr/local/nginx/sbin/nginx -V 2>&1 | grep upsync
如果输出中包含 --add-module=../nginx-upsync-module,说明模块编译成功。
consistent_hash 指令配置详解
nginx-upsync-module 提供了 consistent_hash 指令:
syntax: consistent_hash $variable
context: upstream
基本配置示例:按URI一致性哈希
http {
upstream backend {
# 使用一致性哈希,基于请求URI
consistent_hash $request_uri;
server 192.168.1.10:8080 weight=3;
server 192.168.1.11:8080 weight=2;
server 192.168.1.12:8080 weight=1;
}
server {
listen 80;
server_name example.com;
location / {
proxy_pass http://backend;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
}
}
}
上述配置中,相同的URI会被路由到同一台后端服务器,实现了基于请求路径的会话保持。
基于自定义哈希键的配置
在实际应用中,我们可能需要根据用户ID、Session ID、请求参数等进行哈希。
示例1:基于请求参数 user_id 哈希
upstream backend {
# 从查询参数中取 user_id
set $user_id $arg_user_id;
consistent_hash $user_id;
server 192.168.1.10:8080;
server 192.168.1.11:8080;
}
示例2:基于Cookie中的session_id哈希
upstream backend {
# 从Cookie中取session_id,如果不存在则使用空字符串
set $session_id $cookie_session_id;
consistent_hash $session_id;
server 192.168.1.10:8080;
server 192.168.1.11:8080;
}
示例3:基于请求头 X-User-ID 哈希
upstream backend {
consistent_hash $http_x_user_id;
server 192.168.1.10:8080;
server 192.168.1.11:8080;
}
方案二:OpenResty + lua-resty-consistent-hash 实现
如果你已经在使用 OpenResty,可以通过Lua库实现一致性哈希,无需重新编译Nginx。
安装 lua-resty-consistent-hash
# 使用 OpenResty 的包管理器 opm
opm get openresty/lua-resty-consistent-hash
配置示例
http {
upstream backend {
server 192.168.1.10:8080;
server 192.168.1.11:8080;
server 192.168.1.12:8080;
}
server {
listen 80;
location / {
access_by_lua_block {
local consistent = require "resty.consistent"
local ch = consistent:new()
ch:add("192.168.1.10:8080", 100)
ch:add("192.168.1.11:8080", 100)
ch:add("192.168.1.12:8080", 100)
local key = ngx.var.request_uri
local target = ch:get(key)
ngx.var.target_upstream = target
}
proxy_pass http://$target_upstream;
}
}
}
注意:上述Lua示例为思路演示,实际生产环境需要结合 balancer_by_lua 阶段来实现动态 upstream 选择,完整实现较为复杂。对于大多数场景,推荐直接使用 方案一(nginx-upsync-module)。
一致性哈希的虚拟节点优化
一致性哈希存在一个经典问题:哈希环倾斜(Hash Ring Skew)。
如果物理节点较少,它们在哈希环上的分布可能不均匀,导致某些节点承载了远多于其他节点的请求。
解决方法:引入虚拟节点(Virtual Nodes)
每个物理节点对应多个虚拟节点(通常100-200个),均匀分布在哈希环上。
nginx-upsync-module 的 consistent_hash 内部已经实现了虚拟节点机制,无需手动配置。这也是推荐使用成熟模块的原因。
健康检查与一致性哈希配合使用
当使用一致性哈希时,如果某台服务器故障,原本路由到该服务器的请求需要重新分配。此时需要配合健康检查机制。
方案:nginx-upsync-module + nginx_upstream_check_module
http {
upstream backend {
consistent_hash $request_uri;
# 动态从consul同步上游(可选)
upsync 127.0.0.1:8500/v1/kv/upstreams/backend/ upsync_timeout=6m upsync_interval=500ms upsync_type=consul strong_dependency=off;
upsync_dump_path /usr/local/nginx/conf/servers/backend.conf;
include /usr/local/nginx/conf/servers/backend.conf;
# 健康检查(需要编译nginx_upstream_check_module)
# check interval=3000 rise=2 fall=3 timeout=1000 type=http;
# check_http_send "HEAD / HTTP/1.0\r\n\r\n";
# check_http_expect_alive http_2xx http_3xx;
}
server {
listen 80;
location / {
proxy_pass http://backend;
}
# 查看上游状态(需要nginx_upstream_check_module)
# location /upstream_status {
# check_status;
# access_log off;
# }
}
}
生产环境最佳实践
- 选择合适的哈希键:缓存场景用
$request_uri,用户会话场景用$cookie_session_id或$arg_user_id - 权重配置要合理:一致性哈希不保证绝对均匀,需要结合权重和监控数据持续调整
- 扩容时逐步切流量:新增服务器后,可以先将少量流量(如5%)导入新服务器,观察无误后再全量接入
- 配合健康检查:故障服务器被剔除后,其上的请求会自动转移到其他服务器(这正是一致性哈希的优势)
- 监控哈希分布均匀性:通过Nginx日志分析各上游服务器的请求量分布
常见问题排查清单
问题1:配置consistent_hash后报错 "unknown directive"
原因:Nginx未编译 nginx-upsync-module 模块。
解决:重新编译Nginx并添加模块,或者切换到Nginx Plus。
问题2:会话没有保持,同一URI路由到了不同服务器
排查步骤:
- 检查URI是否包含随机参数(如
?t=123456),如果有,需要使用$uri而不是$request_uri - 检查是否有其他负载均衡指令(如
ip_hash、least_conn)覆盖了consistent_hash - 检查上游服务器列表是否频繁变化(如果使用动态上游)
问题3:新增服务器后,所有请求都重新分配了
原因:可能没有正确使用一致性哈希,而是使用了普通哈希取模。
解决:确认配置文件中使用的是 consistent_hash 指令(nginx-upsync-module)或 hash ... consistent;(Nginx Plus),而不是普通的 hash 指令。
问题4:如何验证一致性哈希是否生效?
验证方法:
- 在Nginx配置中打开访问日志,记录
$upstream_addr变量 - 对同一URI发送多个请求,检查访问日志中
$upstream_addr是否始终指向同一服务器 - 查看后端服务器的访问日志,统计请求分布是否均匀
# nginx 访问日志格式中添加 upstream_addr
log_format main '$remote_addr - $upstream_addr - "$request" $status';
access_log /var/log/nginx/access.log main;
总结
Nginx开源版虽然不原生支持一致性哈希,但通过 nginx-upsync-module 模块可以完美实现,并且还额外获得了动态上游同步的能力。
一致性哈希的核心价值在于:最小化服务器变动对流量的影响,这对于缓存集群、会话保持场景尤为重要。
在生产环境中部署前,建议先在测试环境验证哈希分布的均匀性,并配置完善的健康检查机制。
如果你正在使用Nginx做负载均衡,并且遇到了会话保持或缓存命中率的问题,不妨尝试一下一致性哈希方案!
相关阅读:
版权声明
本文仅代表个人观点。
本文系AI辅助作者原创,未经许可,转载请保留原文链接。

发表评论