分布式应用解决方案之一致性Hash

news2024/12/23 17:06:02

什么是一致性Hash
一致性Hash就是将整个hash值空间按照顺时针方向形成一个虚拟的环,整个环状结构就称之为Hash环。那为什么叫做一致性Hash环?一致性是由于Hash环应用场景一般在分布式应用服务中,各个服务提供者分布在hash环中,当某一个服务出现问题的时候,我们还是能够保证绝大多数服务保持正常使用,对于绝大多数数据前后是一致的。

hash函数 VS 一致性hash
我们以分布式应用服务负载均衡来提供分析场景
1、hash函数路由到服务器,需要根据服务提供者数量取模,比如 hash(ip) % serverNum。如果服务数量变动,会导致绝大多数服务路由异常。
2、一致性hash,我们均匀的将服务提供者ip分布在环上,当我们获取服务ip时候会根据hash值找到第一个大于当前hash的节点即为路由ip。如果服务数量变动,也只会影响部分少量的数据,大部分数据还是正确的路由地址。
综上所述,一致性hash函数在分布式数据缓存的应用场景下完全碾压普通hash函数。

一致性hash原理
我们以分布式应用服务负载均衡来提供分析场景
1、由于int的存储位数为32位,那么可以保存2^32个数。我们的一致性hash通过固定的hash算法的结果用int保存,那么hash环的取值范围则为 0~2^32 -1。
2、一致性hash首先会将服务器ip通过hash运算后放置在虚拟环上
比如我们三个服务ip1~3 【10.10.10.202,10.10.10.203,10.10.10.204】
在这里插入图片描述

3、我们在实际业务中可以通过用户ID等其他信息计算出hash值,然后找到hash环上大于当前值的第一个节点,该节点则为目标路由节点。
比如:user1\user3的hash值小于ip1的hash,那么ip1节点就这两个用户的实际路由节点
在这里插入图片描述

4、当前的hash环上的ip节点都是实际节点,如果全部为实节点会有一个问题,就是如果某个服务掉线会将掉线服务请求全部打在后续节点,就会造成路由不均衡,严重情况会导致后续服务雪崩。那么,如果解决这个问题呢?我们可以采用虚节点,就是在各个实际节点处分散为多个虚拟节点,各个hash路由也都映射在虚节点上,这样就能够做大限度保证数据的均衡性。
比如:ip2服务宕机,本来该路由到ip2的请求全部会打在ip3
在这里插入图片描述

我们象征性添加9个虚拟节点,每个ip增加三个虚节点,每个虚节点都均匀的分散在环上
在这里插入图片描述

ip2服务不可用,直接将ip2的虚节点路由均衡的打在了ip1、ip3上面
![在这里插入图片描述](https://img-blog.csdnimg.cn/b00fa8c63abf4cf6a0d709e9c47eb15b.png

小试牛刀
我们直接实战一致性hash环虚拟节点这中方式,而且在实际业务场景中我们也是优先选用该种方式。
我们定义三个服务节点ip,分别为:ip1~3【10.10.10.202,10.10.10.203,10.10.10.204】。
为了演示方便我们每个实节点增加三个虚拟节点分别为 ip13-VN13。

1、一致性hash带虚拟节点工具类,提供初始化hash环、获取hash值、获取目标路由等方法

/**
 * 一致性hash函数--虚拟节点
 * @author senfel
 * @version 1.0
 * @date 2023/1/17 10:41
 */
@Slf4j
public class ConsistentHashVirtualNode {

    /**
     * 实际节点虚节点数量
     */
    private static int virtualNodeNum = 3;

    /**
     * 实际节点集合
     * 本次测试我们用hash(ip)作为环上实际节点
     */
    private static List<String> realNodeList = new ArrayList<>();

    /**
     * 虚拟节点集合
     * map key-虚拟节点hash
     *     value-虚拟节点
     */
    private static SortedMap<Integer,String> virtualNodeMap = new TreeMap<Integer,String>();


    /**
     * 计算hash值
     * 计算方法很多,普通hashcode相近数据分布不明显,我们采用FNV32进行测试
     * 1. 加法Hash;
     * 2. 位运算Hash;
     * 3. 乘法Hash;
     * 4. 除法Hash;
     * 5. 查表Hash;
     * 6. 混合Hash
     * 目前比较流行的是乘法hash FNV32
     * @param str
     * @author senfel
     * @date 2023/1/17 10:54
     * @return int
     */
    private static int getHash(String str){
        //return Math.abs(str.hashCode()); hash值分布不明显弃用
        final int p = 16777619;
        int hash = (int)2166136261L;
        for (int i = 0; i < str.length(); i++)
            hash = (hash ^ str.charAt(i)) * p;
        hash += hash << 13;
        hash ^= hash >> 7;
        hash += hash << 3;
        hash ^= hash >> 17;
        hash += hash << 5;

        // 如果算出来的值为负数则取其绝对值
        if (hash < 0)
            hash = Math.abs(hash);
        return hash;
    }


    /**
     * 初始化hash环
     * @param ipArray
     * @author senfel
     * @date 2023/1/17 10:48
     * @return void
     */
    public static void initHashNode(String[] ipArray){
        if(null == ipArray || ipArray.length == 0){
            return;
        }
        //将ip写入实际节点
        for(int i=0;i<ipArray.length;i++){
            realNodeList.add(ipArray[i]);
        }
        //获取各个节点的虚拟节点,将虚拟节点加入hash环
        realNodeList.forEach(ip ->{
            for(int j=1;j<=virtualNodeNum;j++){
                String virtualNode = ip+"&VN"+j;
                //计算虚拟节点hash
                int virtualHash = getHash(virtualNode);
                //将节点放置在环上
                virtualNodeMap.put(virtualHash,virtualNode);
                log.error("一致性hash环初始化-虚拟节点:{}加入hash环,当前节点hash值为:{}",virtualNode,virtualHash);
            }
        });
    }


    /**
     * 我们实战场景是根据用户ID进行hash,然后找到路由ip
     * @param userId
     * @author senfel
     * @date 2023/1/17 10:57
     * @return java.lang.String
     */
    public static String getServerIp(int userId){
        //获取用户hash
        int hash = getHash(userId + "");
        //根据用户hash获取hash环中大于当前值的后续节点
        SortedMap<Integer,String> sortedMap = virtualNodeMap.tailMap(hash);
        int firstKey = 0;
        String nodeIp = null;
        if(sortedMap == null){
            //没有大于当前hash数据,应该是等于最小节点hash,直接取第一个节点即可
            firstKey = virtualNodeMap.firstKey();
            //获取到当前节点的ip
            nodeIp = virtualNodeMap.get(firstKey);
        }else{
            //直接获取节点
            firstKey = sortedMap.firstKey();
            nodeIp = sortedMap.get(firstKey);
        }
        return nodeIp;
    }

}

2、测试方法,模拟服务注册初始化hash环,模拟用户访问路由

 public static void main(String[] args) {
        //测试ip 10.10.10.202,10.10.10.203,10.10.10.204
        String[] ipArray = {"10.10.10.202","10.10.10.203","10.10.10.204"};
        initHashNode(ipArray);
        //测试用户模拟9个用户
        List<Integer> userList = new ArrayList<>();
        int userId = 2023011700;
        for(int i=0;i<9;i++){
            userId += i * 100;
            userList.add(userId);
        }
        //模拟用户访问
        for (Integer id : userList) {
            String serverIp = getServerIp(id);
            String realIp = null != serverIp ? serverIp.substring(0, serverIp.indexOf("&")) : null;
            log.error("用户{}访问系统,路由到虚拟节点:{},实际路由ip为:{}",id,serverIp,realIp);
        }
    }

3、查看测试结果,展示初始化hash环各个虚拟节点加入、用户访问路由真实ip

hash环初始化

  • 一致性hash环初始化-虚拟节点:10.10.10.202&VN1加入hash环,当前节点hash值为:1417355037
  • 一致性hash环初始化-虚拟节点:10.10.10.202&VN2加入hash环,当前节点hash值为:1001023951
  • 一致性hash环初始化-虚拟节点:10.10.10.202&VN3加入hash环,当前节点hash值为:744395883
  • 一致性hash环初始化-虚拟节点:10.10.10.203&VN1加入hash环,当前节点hash值为:573863409
  • 一致性hash环初始化-虚拟节点:10.10.10.203&VN2加入hash环,当前节点hash值为:197370571
  • 一致性hash环初始化-虚拟节点:10.10.10.203&VN3加入hash环,当前节点hash值为:1087274216
  • 一致性hash环初始化-虚拟节点:10.10.10.204&VN1加入hash环,当前节点hash值为:944575377
  • 一致性hash环初始化-虚拟节点:10.10.10.204&VN2加入hash环,当前节点hash值为:2022065171
  • 一致性hash环初始化-虚拟节点:10.10.10.204&VN3加入hash环,当前节点hash值为:1365766942

用户访问ip路由

  • 用户2023011700访问系统,路由到虚拟节点:10.10.10.204&VN3,实际路由ip为:10.10.10.204
  • 用户2023011800访问系统,路由到虚拟节点:10.10.10.204&VN1,实际路由ip为:10.10.10.204
  • 用户2023012000访问系统,路由到虚拟节点:10.10.10.204&VN2,实际路由ip为:10.10.10.204
  • 用户2023012300访问系统,路由到虚拟节点:10.10.10.203&VN1,实际路由ip为:10.10.10.203
  • 用户2023012700访问系统,路由到虚拟节点:10.10.10.204&VN1,实际路由ip为:10.10.10.204
  • 用户2023013200访问系统,路由到虚拟节点:10.10.10.204&VN3,实际路由ip为:10.10.10.204
  • 用户2023013800访问系统,路由到虚拟节点:10.10.10.202&VN3,实际路由ip为:10.10.10.202
  • 用户2023014500访问系统,路由到虚拟节点:10.10.10.203&VN1,实际路由ip为:10.10.10.203
  • 用户2023015300访问系统,路由到虚拟节点:10.10.10.204&VN2,实际路由ip为:10.10.10.204

总结:
一致性hash能够满足分布式服务路由场景,服务的波动对用户影响不大。为了达到各个路由真正的负载均衡,建议采用带有虚拟节点的一致性hash函数。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/169081.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【Qt】一文总结新工程的创建

文章目录一、导读二、浅谈开发方式&#xff08;2-1&#xff09;C开发方式&#xff08;2-2&#xff09;QtQuick qml开发方式&#xff08;2-3&#xff09;python开发方式三、新工程创建向导下的Library四、其他项目五、其他工程项目六、Import Project选项七、总结一、导读 在使…

Linux-Find命令

目录 Find 命令格式&#xff1a; 常用查找条件 案例展示&#xff1a; Find find 命令根据预设的条件递归查找文件或目录所在位置 命令格式&#xff1a; 命令格式&#xff1a;find 查找路径 查找条件1 查找条件2 .. [-exec 处理命令 {} \; ] –exec 可接额外的命令来处理查…

【Kubernetes 企业项目实战】03、基于 Alertmanager 发送报警到多个接收方(下)

目录 一、promethues 采集 tomcat 监控数据 1.1 制作 tomcat 镜像 1.2 基于上面的镜像创建一个 tomcat 实例 1.3 采集数据 二、promethues 采集 redis 监控数据 2.1 配置一个 Redis 的 exporter 2.2 查看 Prometheus 2.3 grafana 导入模板 三、Prometheus 监控 mysql …

【微服务】Nacos 前端设计

目录 一、背景 二、选型 React 1、Vue vs React vs Angular 1.1、npm trends 2、GitHub Stats 3、根据自身情况选型 4、现状 5、小结 6、React/Vue ⽣态 三、方案 &#x1f496;微服务实战 &#x1f496; Spring家族及微服务系列文章 一、背景 我们需要提供⼀个简单…

Xilinx关于Aurora IP核仿真和使用

平台&#xff1a;vivado2017.4芯片&#xff1a;xc7k325tfbg676-2 (active)关于Aurora的开发学习。使用xilinx官方提供的IP核。官方资料&#xff0c;pg046-aurora-8b10b.pdf和pg074-aurora-64b66b-en-us-12.0.pdf。IP核的生成步骤首先在IP Catalog中搜索Aurora IP核关于此IP有两…

SpringBoot指标监控

目录 一、SpringBoot Actuator 1、简介 2、1.x与2.x的不同 3、如何使用 二、Actuator Endpoint 1、最常使用的端点 2、Health Endpoint 3、Metrics Endpoint 4、管理Endpoints 1、开启与禁用Endpoints 2、暴露Endpoints 三、定制 Endpoint 1、定制 Health 信息 2…

RepPoints原理与代码解析

paper&#xff1a;RepPoints: Point Set Representation for Object Detectioncode&#xff1a;https://github.com/microsoft/RepPoints背景在目标检测中&#xff0c;包含图像矩形区域的边界框bounding box作为处理的基本元素&#xff0c;贯穿整个检测流程&#xff0c;从ancho…

DevOps利器之一Docker

一、背景本篇文章主要阐述Docker在DevOps中的应用与价值&#xff0c;Docker部署与安装&#xff1b;因为搭建DevOps流程中所应用的工具及框架都部署到Docker&#xff0c;所以首先介绍Docker为后续做准备。Docker的主要目标是Build&#xff0c;Ship and Run Any App,Anywhere&…

Jitpack使用指南:maven-publish如虎,jitpack如翼 【安卓Java组件化模块化】【更多gradle技巧】

上文总结了三种多模块开发的方法。 第一种&#xff1a;在setting.gradle中定义子模块然后 api Project(:...)&#xff0c;直接引用 。第二种&#xff0c;使用 maven-publish 部署至本地仓库第三种&#xff0c;使用 jitpack.io 等部署至远程服务器 我的第一个开源项目就依次用…

Mysql之增强查询

增强查询主要是对之前一些指令的补充 查询增强 主要针对单表查询的增强操作&#xff0c;也是上面一些细节的补充 -- 使用where语句 -- 查找1991.1.1后入职的员工 -- 主要是介绍在mysql中日期类型可以直接比较&#xff0c;需要注意格式 SELECT * FROM empWHERE hiredate &g…

【异常】记一次因修复漏洞扫描导致SpringSecurity出现的循环依赖问题

一、循环依赖问题 APPLICATION FAILED TO START Description: The dependencies of some of the beans in the application context form a cycle: ┌─────┐ | springSecurityConfig (field private XXXX.config.MyauthenticationProvider XXXX.config.SpringSecurityC…

十五天学会Autodesk Inventor,看完这一系列就够了(十),凸雕、贴图

众所周知&#xff0c;Autocad是一款用于二维绘图、详细绘制、设计文档和基本三维设计&#xff0c;现已经成为国际上广为流行的绘图工具。Autodesk Inventor软件也是美国AutoDesk公司推出的三维可视化实体模拟软件。因为很多人都熟悉Autocad&#xff0c;所以再学习Inventor&…

springcloud alibaba -- seata原理和使用

文章目录一、认识Seata1.1 Seata 是什么?1.2 了解AT、TCC、SAGA事务模式?AT 模式前提整体机制如何实现写隔离如何实现读隔离TCC 模式Saga 模式Saga 模式适用场景Saga 模式优势Saga 模式缺点二、Seata安装2.1 下载2.2 创建所需数据表2.2.1 创建 分支表、全局表、锁表2.2.2 创建…

内存一致性模型概念

phrase-20230117184107 内存一致性模型(Memory Consistency Models)提供内存一致性保证&#xff0c;一致性结果体现在程序内存操作是可预测的。例如在多核或多处理器硬件上&#xff0c;在编写并行的程序时&#xff0c;如果理解当前系统所使用的一致性模型&#xff0c;有助于使…

OpenStack GPU直通服务器

layout: post title: OpenStack GPU直通服务器 catalog: true tag: [OpenStack, GPU] 1. 概述2. 直通GPU特性3. 功能说明 3.1. 操作系统支持3.2. 设备支持 4. 实现方案5. 部署方案 5.1. 示例环境说明5.2. 上线步骤 5.2.1. 硬件安装5.2.2. GPU计算节点主机配置 5.2.2.1. IOMMU设…

【数据结构与算法学习8】二叉查找树的基本介绍与添加数据的过程

程序员语录&#xff1a; 把时髦的技术挂在嘴边&#xff0c;还不如把过时的技术记在心里。 1 二叉查找树是什么&#xff1f; 二叉查找树是一种数据结构&#xff0c;又叫作二叉搜索树或二叉排序树,采用了图的树形结构&#xff0c;数据存储于二叉查找树的各个结点中&#xff0c;每…

GEE 9:Earth Engine Reducers 的基本操作

目录1.Image 、ImageCollection and Regions Reducers&#xff08;图层和区域的相关操作&#xff09;1.1 Image Reductions&#xff08;处理单个图层&#xff09;1.2 ImageCollection Reductions&#xff08;处理图层集&#xff09;1.3 Greenest pixel (maximum NDVI) composit…

01背包——二维动态规划【c++】代码实现

今天学了01背包&#xff0c;就想来讲一讲&#xff0c;正好回顾一下&#xff08;BZOJ上的题目&#xff09;。 01背包 所谓01背包&#xff0c;也就是背包的一种&#xff0c;01背包和完全背包的区别就在于&#xff0c;01背包一件物品只能选择一次&#xff0c;而完全背包可以重复…

架构运维篇(七):Centos7/Linux中安装Zookeeper

版本说明 JDK &#xff1a;1.8&#xff08;已安装&#xff09;ZK : 3.8.0 安装部署Zookeeper 第一步&#xff1a;下载最新版本 官网地址&#xff1a;Apache DownloadsHome page of The Apache Software Foundationhttps://www.apache.org/dyn/closer.lua/zookeeper/zookeep…

libvirt零知识学习1 —— libvirt简介

本文内容部分取自《KVM实战 —— 原理、进阶与性能调优》&#xff0c;非常好的一本书。 1. 概述 提到KVM的管理工具&#xff0c;首先必须要介绍的无疑是大名鼎鼎的libvirt。libvirt是目前使用最为广泛的对KVM虚拟机进行管理的工具和应用程序接口。并且&#xff0c;一些常用的虚…