Hash算法的应用场景
- 请求的负载均衡
Nginx的ip_hash策略可以在客户端ip不发生变化的情况下,将其发出的请求始终路由到同一个目标服务器上,实现会话粘滞,避免处理session共享问题。
- 如果没有ip_hash策略,可以通过维护一张映射表的方式来实现会话粘滞:
-
映射表存储的是客户端ip或者session与具体目标服务器的映射关系
<ip,server>
- 缺点:
- 客户端很多的情况下,映射表非常大,浪费内存空间
- 客户端上下线、目标服务器上下线都会导致重新维护映射表,维护成本大
- 缺点:
-
如果使用Hash算法,可以对ip或者session计算hash值,hash值与服务器数量进行取模运算,得到的值就是当前请求应该被路由到的服务器编号。由此,同一个ip发送过来的请求就可以路由到同一个目标服务器,实现会话粘滞。
-
- 分布式存储
有 Redis1、Redis2、Redis3 三台Redis服务器,可以针对key进行hash处理Hash(key)%3 = index
使用余数锁定存储的具体服务器节点。
普通Hash算法存在的问题
以ip_hash为例,假定用户ip固定不变,当后端服务器有一个宕机,数量由3变为2,那么之前所有用户的求模都需要重新计算。
如果在生产环境中,后台服务器有很多台,客户端也有很多个,出现这种问题的影响是很大的。服务器的扩缩容都会出现这样的问题,大量的用户请求被路由到其他的目标服务器处理,用户在原来服务器的会话都将丢失。
普通Hash算法:
// 定义客户端IP
String[] clients = {
"192.168.31.121", "192.168.3.21", "192.168.1.11"};
// 定义服务器数量
int server = 5;
// hash(ip) % server数量 = index
for (String client : clients) {
int hashCode = Math.abs(client.hashCode());
int index = hashCode % server;
System.out.println("客户端:\t" + client + "\t 分配到了服务器 " + (index + 1) + " 上");
}
一致性Hash算法的思路
- 首先有一条直线,直线开头和结尾分别定为1和232-1,这相当于一个地址
- 对于这样一条直线,首尾相连构成一个闭环,这样的圆环称为Hash环
- 把服务器IP或者主机名求Hash值然后对应到Hash环上,针对客户端IP求Hash值,对应到环上的某个位置,然后按照顺时针的方向找最近的服务器节点
- 缩容
假设将节点3下线,原来路由到3的客户端请求重新路由到节点4,对于其他客户端没有影响,只是这一小部分受到影响
请求的迁移量达到了最小,这样的算法对于分布式集群来说非常合适,避免了大量请求转移。
- 扩容
新增节点5之后,原来路由