分布式主要内容
第⼀部分:⼀致性Hash算法
第⼆部分:集群时钟同步问题
第三部分:分布式ID解决⽅案
数据表A(ID),A的数据量很⼤的情况下,我们会进⾏分表操作,A(ID)表拆分成了A1表
(ID)+A2表(ID),需要⼀种在分布式集群架构中能够产⽣全局唯⼀ID的⽅案
第四部分:分布式调度问题(定时任务的分布式)
第五部分:Session共享(⼀致性)问题
浏览器—>Nginx—>Tomcat1(Session中记录⽤户信息)
—>Tomcat2
—>Tomcat3
分布式⼀定是集群,但是集群不⼀定是分布式
1、一致性HASH算法
1、hash介绍
查询速度快,如果设计OK的话,时间复杂度接近于O(1) ->(一次拿到,比如:“直接寻址法“,缺点,浪费空间)。
涉及扩展:做顺序查找法,二分查找,除留余数法(容易冲突),开放寻址法(了解)
开放寻址法:1放进去了,6再来的时候,向前或者向后找空闲位置存放,不好的地⽅,如果数组⻓度定
义好了⽐如10,⻓度不能扩展,来了11个数据,不管Hash冲突不冲突,肯定存不下这么多数据
**拉链法:**数据⻓度定义好了,怎么存储更多内容呢,算好Hash值,在数组元素存储位置放了⼀个链表
Hash表的查询效率⾼不⾼取决于Hash算法,
除留余数法 3%5
线性构造Hash算法
直接寻址法也是⼀种构造Hash的⽅式,只不过更简单,表达式:H(key)=key
⽐如H(key)=a*key + b(a,b是常量)
hashcode其实也是通过⼀个Hash算法得来的
2、场景
1、请求的负载均衡:比如 nginx的ip_hash
nginx的ip_hash可以在客户端IP不冲突的情况下,将请求始终路由到同一个目标服务,实现会话粘带,避免处理 session共享问题
如果没有ip_hash策略,维护会话粘带,则需要维护一张表,,存储客户端IP或者sessionid与具体⽬标服务器的 映射关系<ip,tomcat1>
缺点:1、表可能会很大,2、目标的上下限,导致映射表重新维护,维护成本大。
解决:使用hash算法,以对ip地址或者sessionid进⾏计算哈希值, 哈希值与服务器数量进⾏取模运算,得到 的值就是当前请求应该被路由到的服务器编号,
2、分布式存储
以分布式内存数据库Redis为例,集群中有redis1,redis2,redis3 三台Redis服务器。那么,在进⾏数据存储时,数 据存储到哪个服务器当中呢?针对key进⾏hash处理。hash(key1)%3=index, 使⽤余数index锁定存储的具体服 务器节点
3、普通HASH算法存在的问题
普通Hash算法存在⼀个问题,以ip_hash为例,假定下载⽤户ip固定没有发⽣改变,现在tomcat3出现
了问题,down机了,服务器数量由3个变为了2个,之前所有的求模都需要重新计算。缩容和扩容都会存在这样 的问题,
4、一致性算法
hash环。我们把服务器的ip或者主机名求hash值然后对应到hash环上
假如将服务器3下线,服务器3下线后,原来路由到3的客户端重新路由到服务器4,对于其他客户端没有影响。 只是这⼀⼩部分受影响(请求的迁移达到了最⼩,这样的算法对分布式集群来说⾮常合适的,避免了大量请求 迁移 )
但是,⼀致性哈希算法在服务节点太少时,容易因为节点分部不均匀⽽造成数据倾斜问题。例如系统中
只有两台服务器,其环分布如下,节点2只能负责⾮常⼩的⼀段,⼤量的客户端
请求落在了节点1上,这就是数据(请求)倾斜问题
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-tTk2Lstf-1602604472444)(C:\Users\wangqingli\Desktop\hash一致性虚拟节点.jpg)]
解决方案:
为了解决这种数据倾斜问题,⼀致性哈希算法引⼊了虚拟节点机制,即对每⼀个服务节点计算多个哈希,每个 计算结果位置都放置⼀个此服务节点,称为虚拟节点。
具体做法可以在服务器ip或主机名的后⾯增加编号来实现。⽐如,可以为每台服务器计算三个虚拟节
点,于是可以分别计算 “节点1的ip#1”、“节点1的ip#2”、“节点1的ip#3”、“节点2的ip#1”、“节点2的
ip#2”、“节点2的ip#3”的哈希值,于是形成六个虚拟节点,当客户端被路由到虚拟节点的时候其实是被
路由到该虚拟节点所对应的真实节点
nginx 去ip前三段 计算hash(计算网段的)
考虑权重 等等
普通hash
/**
* 普通Hash算法实现
*/
public class GeneralHash {
public static void main(String[] args) {
// 定义客户端IP
String[] clients = new String[]{
"10.78.12.3","113.25.63.1","126.12.3.8"};
// 定义服务器数量
int serverCount = 5;// (编号对应0,1,2)
// hash(ip)%node_counts=index
//根据index锁定应该路由到的tomcat服务器
for(String client: clients) {
int hash = Math.abs(client.hashCode());
int index = hash%serverCount;
System.out.println("客户端:" + client + " 被路由到服务器编号为:" + index);
}
}
}
一致性:不含虚拟节点
import java.util.SortedMap;
import java.util.TreeMap;
public class ConsistentHashNoVirtual {
public static void main(String[] args) {
//step1 初始化:把服务器节点IP的哈希值对应到哈希环上
// 定义服务器ip
String[] tomcatServers = new String[]
{
"123.111.0.0","123.101.3.1","111.20.35.2","123.98.26.3"};
SortedMap<Integer,String> hashServerMap = new TreeMap<>();
for(String tomcatServer: tomcatServers) {
// 求出每⼀个ip的hash值,对应到hash环上,存储hash值与ip的对应关系
int serverHash = Math.abs(tomcatServer.hashCode());
// 存储hash值与ip的对应关系
hashServerMap.put(serverHash,tomcatServer);
}
//step2 针对客户端IP求出hash值
// 定义客户端IP
String[] clients = new String[]
{
"10.78.12.3","113.25.63.1","126.12.3.8"};
for(String client : clients) {
int clientHash = Math.abs(client.hashCode());
//step3 针对客户端,找到能够处理当前客户端请求的服务器(哈希环上顺时针最近)
// 根据客户端ip的哈希值去找出哪⼀个服务器节点能够处理()
SortedMap<Integer, String> integerStringSortedMap =
hashServerMap.tailMap(clientHash);
if(integerStringSortedMap.isEmpty()) {
// 取哈希环上的顺时针第⼀台服务器
Integer firstKey = hashServerMap.firstKey();
System.out.println("==========>>>>客户端:" + client + " 被
//路由到服务器:" + hashServerMap.get(firstKey));
}else{
Integer firstKey = integerStringSortedMap.firstKey<