- 分布式和集群
分布式和集群是不⼀样的, 分布式⼀定是集群,但是集群不⼀定是分布式(因为集群就是多个实例⼀起⼯作,分布式将⼀个系统拆分之后那就是多个实例;
集群并不⼀定是分布式,因为复制型的集群不是拆分⽽是复制)
一、Hash算法应用场景
Hash算法在分布式集群架构中的应用场景,如 分布式集群架构Redis、Hadoop、Elasticsearch, Mysql分库分表, Nginx负载均衡
主要的应用场景归纳起来两个
- 请求的负载均衡(比如 nginx的ip_hash策略)
nginx的ip_hash 策略可以在客户端ip不变的情况下,将其发出的请求始终路由到同一个目标服务器上,实现会话粘滞,避免处理session共享问题
使用Hash算法,我们可以对ip地址或sessionId 计算hash值 hash值 % 服务器数量 = 当前请求路由到的服务器编号,如此 , 同一个客户ip地请求可以路由到同台机器上,实现会话粘滞
/**
* @description 一般Hash算法
*/
public class GeneralHash {
public static void main(String[] args) {
// 定义 客户端IP
String[] clients = {"10.78.12.3","113.25.63.1","126.12.3.8"};
//定义服务器数量
int serverCountn = 5; // 0 , 1 , 2
for (String client: clients){
int hash = Math.abs(client.hashCode()) % serverCountn;
System.out.println(client+" -> "+ hash);
}
}
}
nginx 的 ip_hash代码在
- 分布式存储 (如Elasitcsearch路由,经过Hash计算存储到哪台机器上)
<key1, value1> 数据存储到哪个服务器当中呢,针对key进行hash处理, hash(key1) % 3 = index
redis1, redis2, redis3 三台Redis服务器
二、普通 Hash算法存在的问题
普通Hash算法存在的问题示意图
5 这台机器会 请求不到 原来的session, 重新进行Hash计算 代价很大
三、一致性Hash算法
将1~2^32-1 围成一个图, 4台服务器 分布在 圆圈上的 4个节点上(图中的大红点)。 同时 客户的请求根据ip也是相同的处理(图中的绿色小人)
当用户请求来临时,按照用户请求计算的值 ,按顺时针去 请求对应的服务器
- 当服务器缩容(有服务器宕机后)
服务器3下线后,原来路由到3的客户端重新路由到服务器4,对于其他客户端没有影响
只是这⼀⼩部分受影响(请求的迁移达到了最⼩,这样的算法对分布式集群来说⾮常合适的,避免了⼤量请求迁移 )
- 当服务器扩容(增加服务器后)
增加服务器5之后,原来路由到3的部分客户端路由到新增服务器5上,对于其他客户端没有影响
只是这⼀⼩部分受影响(请求的迁移达到了最⼩,这样的算法对分布式集群来说⾮常合适的,避免了⼤量请求迁移 )
/**
* @description 一致性Hash算法(无虚拟节点)
*/
public class ConsistentHashNoVirtualHost {
public static void main(String[] args) {
// 初始化,把服务器节点ip映射 到hash环
// 定义服务器ip
SortedMap<Integer,String> hashServerMap = new TreeMap<>();
String[] tomcatServers = new String[]{"123.111.0.0","123.101.3.1","111.20.35.2","123.98.26.3"};
for (String serverIp: tomcatServers){
//求ip的hash值 ,对应到Hash环上,
int serverHash = Math.abs(serverIp.hashCode());
// 存储ip和hash值 的对应关系
hashServerMap.put(serverHash,serverIp);
}
String[] clients = {"10.78.12.3","113.25.63.1","126.12.3.8"};
// 求客户端的 ip
for (String client: clients){
int clientHash = Math.abs(client.hashCode());
// 针对客户端,找到能够 处理当前 客户的服务器 (顺时针找)
// tailMap(key) 根据传进来的key, 来获取大于key值组成的集合,返回新map
SortedMap<Integer, String> integerStringSortedMap = hashServerMap.tailMap(clientHash);
Integer serverIp = Integer.MIN_VALUE;
if (integerStringSortedMap.isEmpty()){
serverIp = hashServerMap.firstKey();
} else {
serverIp = integerStringSortedMap.firstKey();
}
System.out.println("------------------ client :" + client+" 被路由到 : " + hashServerMap.get(serverIp));
}
}
}
- ⼀致性hash算法+虚拟节点⽅案示意
当服务器集群节点分布不均匀时,会造成节点1的负载较大,造成数据倾斜的问题,如下图所示。
此时我们可以增加虚拟节点,(比如对于节点1, 再进行计算 节点1的ip#1 节点1的ip#2 节点1的ip#3 这些值的hash值 , 落在虚拟节点上的请求,还是由节点1去处理请求), 节点二同理。
这样可以减少 数据倾斜问题
/**
* @description 一致性Hash算法(含有虚拟节点)
*/
public class ConsistentHashWithVirtualHost {
public static void main(String[] args) {
// 初始化,把服务器节点ip映射 到hash环
// 定义服务器ip
SortedMap<Integer,String> hashServerMap = new TreeMap<>();
String[] tomcatServers = new String[]{"123.111.0.0","123.101.3.1","111.20.35.2","123.98.26.3"};
// 定义虚拟服务器ip
int virtualCount = 3;
for (String serverIp: tomcatServers){
//求ip的hash值 ,对应到Hash环上,
int serverHash = Math.abs(serverIp.hashCode());
// 存储ip和hash值 的对应关系
hashServerMap.put(serverHash,serverIp);
// 处理虚拟节点
for (int i = 0 ; i < virtualCount; i++){
int virtualHash = Math.abs((serverIp + "#" + i).hashCode());
hashServerMap.put(virtualHash,"---> 由虚拟节点 #"+i+" 转发过来的: "+serverIp);
}
}
String[] clients = {"10.78.12.3","113.25.63.1","126.12.3.8"};
// 求客户端的 ip
for (String client: clients){
int clientHash = Math.abs(client.hashCode());
// 针对客户端,找到能够 处理当前 客户的服务器 (顺时针找)
// tailMap(key) 根据传进来的key, 来获取大于key值组成的集合,返回新map
SortedMap<Integer, String> integerStringSortedMap = hashServerMap.tailMap(clientHash);
Integer serverIp = Integer.MIN_VALUE;
if (integerStringSortedMap.isEmpty()){
serverIp = hashServerMap.firstKey();
} else {
serverIp = integerStringSortedMap.firstKey();
}
System.out.println("------------------ client :" + client+" 被路由到 : " + hashServerMap.get(serverIp));
}
}
}
四、Nginx配置一致性负载均衡策略
https://github.com/replay/ngx_http_consistent_hash 下载 ngx_http_consistent_hash-master.zip, 上传到服务器上,并解压
编译安装
make
make install
在nginx/conf/nginx.conf中进行配置
配置说明 :
ngx_http_upstream_consistent_hash 模块是⼀个负载均衡器,使⽤⼀个内部⼀致性hash算法来选择合适的后端节点。
该模块可以根据配置参数采取不同的⽅式将请求均匀映射到后端机器,
- consistent_hash $remote_addr:可以根据客户端ip映射
- consistent_hash $request_uri:根据客户端请求的uri映射
- consistent_hash $args:根据客户端携带的参数进⾏映射