web1-综合解决方案

假如有一个数组,采用二分查找找某一个元素:

第一次:n个元素里面找;

第二次:n/2个元素里面找;

第三次:n/4;

…………

第k次:n/(2^k-1);

假如第k次已经找到了,刚好是最后一个元素才被找到,那么

n/(2^k-1) = 1    ->     k = log2(n+1)   ->即log2n

一: hash算法

hash算法是一种高效的寻址法,通过hash值定位到目标位置。 

普通hash算法:

就是取模,然后计算服务节点,如分库分表,nginx ip_hash

问题:扩容和缩容都需要重新计算,影响很大

一致性hash:

1到2的32次方-1,形成闭环(因为hash值是32位无符号数值),服务器的hash值落到环上,client端ip也是同样算法落到环上,顺时针最近的服务节点即是访问节点

这样,缩容或扩容,都只会影响小部分请求,比如:

  • 节点2宕机,只会影响1-2之间,本应该落到节点2上,2宕机后落到了节点3上。
  • 2-3之间增加节点5,只会影响2-5之间,本该落到3的,会落到5上。

代码实现:

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的哈希值去找出哪⼀个服务器节点能够处理()
            //tailMap方法返回hashServerMap中key值>=当前key的值
            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();
                System.out.println("==========>>>>客户端:" + client + " 被路由到服务器:" + hashServerMap.get(firstKey));
            }
        }
    }
}

如果节点太少,异常时会影响很多数据。另外,正常时候也可能出现数据的情况,比如只有节点1和节点2,,那么大部分请求会落到节点1。

这时可以为每个真实节点增加多个虚拟节点,当落在虚拟节点上的请求,让它请求真实的节点:

如下: 对于虚拟节点2#2和2#3的请求,让他请求真实节点2#1。 

 代码实现:

public class ConsistentHashWithVirtual {
    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<>();
        // 定义针对每个真实服务器虚拟出来⼏个节点
        int virtaulCount = 3;
        for(String tomcatServer: tomcatServers) {
            // 求出每⼀个ip的hash值,对应到hash环上,存储hash值与ip的对应关系
            int serverHash = Math.abs(tomcatServer.hashCode());
            // 存储hash值与ip的对应关系
            hashServerMap.put(serverHash,tomcatServer);
            // 处理虚拟节点
            for(int i = 0; i < virtaulCount; i++) {
                int virtualHash = Math.abs((tomcatServer + "#" + i).hashCode());
                hashServerMap.put(virtualHash,"----由虚拟节点"+ i + "映射过来的请求:"+ 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();
                //如果是虚拟节点,就会打印出对应的真实节点
                System.out.println("==========>>>>客户端:" + client + " 被路由到服务器:" + hashServerMap.get(firstKey));
            }
        }
    }
}

nginx可以使用ngx_http_upstream_consistent_hash模块来配置请求的一致性hash:

consistent_hash $remote_addr :可以根据客户端 ip 映射
consistent_hash $request_uri :根据客户端请求的 uri 映射
consistent_hash $args :根据客户端携带的参数进⾏映
ngx_http_upstream_consistent_hash 模块是⼀个第三⽅模块,需要我们下载安装后使⽤
github 下载 nginx ⼀致性 hash 负载均衡模块 https://github.com/replay/ngx_http_consistent_hash

将下载的压缩包上传到nginx服务器,并解压

提前已经编译安装过 nginx ,此时进⼊当时 nginx 的源码⽬录,执⾏如下命令
./confifigure —add-module=/root/ngx_http_consistent_hash-master
make
make install

二: 时钟同步问题

1、各节点都可以联网,ntpdate -u ntp.api.bz 命令使用网络时间

2、不能联网,将某一台节点作为时间服务器,其他节点来同步;ntpdate 时间服务器ip

三 :分布式id

  • java.util包下的UUID:随机无意义字符串,不能排序,数据量大时候查询慢
  • 独立数据库表的自增id:有单点故障的隐患,且并发量大的情况下,写入和查询有性能瓶颈;如果使用主从模式,主节点挂了也没法同步
  • redis incr自增命令:依赖于redis,如果redis宕机有可能存在数据丢失,造成重复
  • SnowFlake 雪花算法构成-64位
    • 1、首位0,代表正数
    • 2、41位时间戳:41位最大值/一年的ms值,得到69年9个月6天零15小时47分35秒;时间部分计算方式为:当前时间戳-初始时间戳(自己设定开始时间)再往前移22位(64-1-41=22),因为时间戳所在段位是在64位的第2-42位置上
    • 3、10位机器id,代表可以部署2^10=1024台机器,一般分为前5位数据中心id:dataCenterId。。后5位机器id:workerId;dataCenterId代表机房数,可以手动设置编号1,2,3等,保证不同就行。workerId可以用ip来区分(比如取ip的最后一段),同一机房内能保证不重复。
      dataCenterId需要往前移17位(64-1-41-5=17),workerId需要移动12位(64-1-41-5-5=12),剩下的12位就是序列号了,不用移动。可以根据实际情况设置dataCenterId和workerId。比如,我们的机房就一个,只占1位,那么剩下的9位就是workerId的了,这时候,dataCenterId需要左移21位(64-1-41-1=21)
    • 4、12位序列号:同一个机房同一台机器在一个ms时间内,可以生成2^12-1=4095个序列号
    • 5、注意:避免并发状况,一定要加锁

猜你喜欢

转载自blog.csdn.net/growing_duck/article/details/113807542