Dubbo中的负载均衡算法之一致性哈希算法

Dubbo中的负载均衡算法之一致性哈希算法

哈希算法

假设这样一个场景,我们申请一台服务器来缓存100万的数据,这个时候是单机环境,所有的请求都命中到这台服务器。后来业务量上涨,我们的数据也从100万上升到了300万,原来的单机环境,无法容纳如此多的数据,所以我们决定扩容到三台服务器。但这个时候有一个问题,这三百万的数据如何平均分配到这三台服务器上呢?假设我们随机分配,那么当我们需要查询某一个缓存项时,则需要先去第一台机器进行查找,如果找到则返回给用户,如果没有,继续去第二台机器查找,如果还没有则继续去第三台机器查找,这样一来,查询缓存项的性能则会明显下降,有没有办法根据缓存项的键,直接定位到正确的机器进行查询呢?

一种行之有效的方法就是通过哈希算法去定位。对于相同的键,每次计算得出的哈希值应该是一致的。假设我们有三台机器,可以对键通过哈希算法得到哈希值,然后对3取余,结果只能是0、1或2,我们将这三个结果分别作为三台机器的编号。这样每次有查询缓存的请求过来,我们都可以按照以下步骤去做

  1. 对键进行哈希运算,得到一个哈希值,然后对3取余,得到一个机器编号
  2. 前往对应的机器进行查询。如果找到缓存项,直接返回结果;如果未找到,表示缓存项不存在。
    在这里插入图片描述

上述的方案看起来已经解决了问题,但是,如果在分布式场景中,却有着很大的局限性。因为在生产环境中,不可避免会发生以下几种情况:

  1. 服务器故障宕机,无法提供服务而下线
  2. 业务高峰期需要弹性扩容
  3. 业务低谷期需要弹性缩容

这几种场景都会导致后台服务器的数量 n 发生变化,那么我们原来的公式hash(key) % n因为n发生了变化,导致计算出的值也会发生变化,这样一来就会导致大量的缓存失效,造成缓存雪崩。如果是采用这种方案做负载均衡的话,也会导致大量的请求路由到错误的服务器,找不到对应的服务,导致服务异常。因此就有了一致性hash算法,用来解决这个问题,保证当后端移除或者添加一个服务器时,要尽可能小的改变存量请求与服务器之间的正确映射关系。

一致性哈希算法

基本介绍

一致性哈希算法最早是由David Karger等人在1997年发表的论文Consistent Hashing and Random Trees: Distributed Caching Protocols for Relieving Hot Spots on the World Wide Web 中提出的。该算法的设计目标是为了解决因特网中的热点问题,即如何将数据尽可能均匀地分布到不同的节点上,以避免某些节点承受过大的负载。

它的主要思路是将整个哈希值空间组织成一个虚拟的圆环,让每个服务器节点在环上占据一个位置。有新的请求的时候,通过hash算法将请求映射到环上的一个位置,然后顺时针找到最近的服务器去进行处理。这样当一个服务器节点加入或退出时,只会影响到其附近的一小部分数据,而不会对整个系统造成很大的影响。他的一般步骤为:

  1. 如下图所示,我们虚拟出一个有232-1个节点的hash环,然后将服务器地址(ip+port)计算hash值,,对232-1取模,算出其在hash环上的位置,假设我们有三个服务器节点:S1、S2 、S3,其映射关系如图所示。
  2. 当一个新的服务请求到来时,也对该请求的标识符进行哈希计算,得到其在环上的位置。
  3. 从环上顺时针找到距离该请求最近的一个节点,将请求分配给该节点处理。例如请求R1,R2被分配给S1这个服务器去处理

在这里插入图片描述

如何解决服务器节点增加、减少带来的问题的

如果在hash环上加一个节点,例如我们添加了S4这个节点,那么原本应该路由到S1的请求和路由到S2的请求不会受到影响,依然可以正确路由,不过原本路由到S3的部分请求则将会被路由到新节点S4。这样就做到了我们前面说的尽可能小的改变已存在的服务请求与处理请求服务器之间的映射关系。

在这里插入图片描述

减少一个节点也是同样的道理,例如下图中我们下线了节点S3,那么原本应该路由到S1的请求和路由到S2的请求不会受到影响,依然可以正确路由,受影响的只是原本应该路由到S3的请求因为S3下线,而会路由到S1。

在这里插入图片描述

什么是虚拟节点

前面的图例中,我们可以看到S1,S2,S3三个节点是均匀分布在hash环上的,但是实际上,我们通过hash算法计算的时候可能达不到理想的效果,有可能会得到这样的分布效果。这就会导致大量的请求被路由到S3这个节点。因此提出了虚拟节点的概念。
在这里插入图片描述

所谓虚拟节点就是说并非真实的物理节点,而是对物理节点拷贝的一个副本,他们和其对应的真实物理节点有一样的IP和端口号,因此称为虚拟节点。如下图所示,我们有三个物理节点,S1、S2、 S3。然后对每个物理节点虚拟出了两个虚拟节点,将其分布在hash环上。这样假设有一个请求访问过来,通过hash计算落到了S3-2到S2-2的区间,那么他将会路由到S2-2这个服务器节点,而S2-2其实是S2的一个虚拟节点,所以实际处理这个请求的是S2这个物理节点。通过图我们可以直观的看到,引入虚拟节点后,请求的分布会进一步变得平衡,而引入的虚拟节点数量越多,则平衡性越好

在这里插入图片描述

Dubbo一致性哈希负载均衡的实现

dubbo一致性哈希负载均衡策略的实现在ConsistentHashLoadBalance这个类里,它继承了AbstractLoadBalance这个抽象类,实现了方法doSelect()方法,这个方法的作用是用来从多个服务提供者里面选择一个来调用。这里有两个重要的参数InvokerInvocationInvoker 我们可以理解为对服务提供者的一个封装,所有的服务提供者在dubbo里都会被转化为Invoker。所以List<Invoker<T>> invokers其实就是我们本次请求的所有服务提供者的列表。Invocation我们理解为对本次请求会话的一个封装,里面包括了请求的参数、方法以及变量

InvokerInvocation 的概念可参考:代码架构 | Apache Dubbo、实现细节 | Apache Dubbo

/*** 线程安全的一个map,缓存了所有的Selector(可以理解为一个选择器,用来从众多的服务提供者里面选择一个,实现负载均衡的效果),* key:服务提供者的签名* value:具体某一个服务对应的Selector*/
private final ConcurrentMap<String, ConsistentHashSelector<?>> selectors = new ConcurrentHashMap<String, ConsistentHashSelector<?>>();/*** 从invokers里选一个调用*/
protected <T> Invoker<T> doSelect(List<Invoker<T>> invokers, URL url, Invocation invocation) {String methodName = RpcUtils.getMethodName(invocation);//  1. 根据服务提供者的签名从前面提到的map里找对应的选择器String key = invokers.get(0).getUrl().getServiceKey() + "." + methodName;int invokersHashCode = invokers.hashCode();ConsistentHashSelector<T> selector = (ConsistentHashSelector<T>) selectors.get(key);// 1.1  没有找到?那么初始化,这是一个懒加载的过程if (selector == null || selector.identityHashCode != invokersHashCode) {selectors.put(key, new ConsistentHashSelector<T>(invokers, methodName, invokersHashCode));selector = (ConsistentHashSelector<T>) selectors.get(key);}// 1.2 通过selector选一个具体的服务提供者,处理请求return selector.select(invocation);
}

前面提到如果没有找打对应的Selector,那么我们要去做初始化,这里是如何初始化一个一致性哈希负载均衡策略的逻辑。

private static final class ConsistentHashSelector<T> {// 用TreeMap模拟一个hash环private final TreeMap<Long, Invoker<T>> virtualInvokers;private final int replicaNumber;private final int identityHashCode;private final int[] argumentIndex;// 初始化Selector的逻辑ConsistentHashSelector(List<Invoker<T>> invokers, String methodName, int identityHashCode) {this.virtualInvokers = new TreeMap<Long, Invoker<T>>();this.identityHashCode = identityHashCode;URL url = invokers.get(0).getUrl();// 物理节点的副本数,默认160this.replicaNumber = url.getMethodParameter(methodName, HASH_NODES, 160);String[] index = COMMA_SPLIT_PATTERN.split(url.getMethodParameter(methodName, HASH_ARGUMENTS, "0"));argumentIndex = new int[index.length];for (int i = 0; i < index.length; i++) {argumentIndex[i] = Integer.parseInt(index[i]);}// 初始化hash环,将所有的invokers均匀的分布到环上for (Invoker<T> invoker : invokers) {String address = invoker.getUrl().getAddress();// 创建虚拟节点,每个物理机节点应该有replicaNumber个虚拟节点,但是这里用两次for循环,应该是为了加强hash的随机性for (int i = 0; i < replicaNumber / 4; i++) {byte[] digest = Bytes.getMD5(address + i);for (int h = 0; h < 4; h++) {long m = hash(digest, h);virtualInvokers.put(m, invoker);}}}}// 具体的选择逻辑,对请求计算md5值,算一个hashpublic Invoker<T> select(Invocation invocation) {byte[] digest = Bytes.getMD5(RpcUtils.getMethodName(invocation));return selectForKey(hash(digest, 0));}// 用ceilingEntry方法模拟从哈希环上选择顺时针最近的一个服务节点private Invoker<T> selectForKey(long hash) {Map.Entry<Long, Invoker<T>> entry = virtualInvokers.ceilingEntry(hash);if (entry == null) {entry = virtualInvokers.firstEntry();}return entry.getValue();}private long hash(byte[] digest, int number) {return (((long) (digest[3 + number * 4] & 0xFF) << 24)| ((long) (digest[2 + number * 4] & 0xFF) << 16)| ((long) (digest[1 + number * 4] & 0xFF) << 8)| (digest[number * 4] & 0xFF))& 0xFFFFFFFFL;}
}

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

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

【蓝桥每日一题]-二分精确(保姆级教程 篇4) #kotori的设备 #银行贷款 #一元三次方程求解

今天讲二分精确题型 目录 题目&#xff1a;kotori的设备 思路&#xff1a; 题目&#xff1a;银行贷款 思路&#xff1a; 题目&#xff1a;一元三次方程求解 思路&#xff1a; 题目&#xff1a;kotori的设备 思路&#xff1a; 求&#xff1a;设备最长使用时间 二分查找&#…

数据结构(超详细讲解!!)第十九节 块链串及串的应用

1.定义 由于串也是一种线性表&#xff0c;因此也可以采用链式存储。由于串的特殊性&#xff08;每个元素只有一个字符&#xff09;&#xff0c;在具体实现时&#xff0c;每个结点既可以存放一个字符&#xff0c;也可以存放多个字符。每个结点称为块&#xff0c;整个链表称为块链…

C# 发送邮件

1.安装 NuGet 包 2.代码如下 SendMailUtil using MimeKit; using Srm.CMER.Application.Contracts.CmerInfo; namespace Srm.Mail { public class SendMailUtil { public async static Task<string> SendEmail(SendEmialDto sendEmialDto,List<strin…

DC系列 DC:3

DC系列 DC:3 文章目录 DC系列 DC:3调试靶机信息收集IP端口信息收集 框架漏洞利用joomscan扫描工具利用msf工具利用(无法使用)kali漏洞库利用sqlmap利用 文件上传提权 调试靶机 点击虚拟机设置选择CD/DVD点击高级将IDE调成画面中这个选项 信息收集 IP端口信息收集 对自己网…

DI93a HESG440355R3 通过其Achilles级认证提供网络安全

DI93a HESG440355R3 通过其Achilles级认证提供网络安全 施耐德电气宣布推出Modicon M580以太网PAC (ePAC)自动化控制器&#xff0c;该控制器采用开放式以太网标准&#xff0c;通过其Achilles级认证提供网络安全。M580 ePAC使工厂操作员能够设计、实施和运行一个积极利用开放网…

量子计算与量子密码(入门级-少图版)

量子计算与量子密码 写在最前面一些可能带来的有趣的知识和潜在的收获 1、Introduction导言四个特性不确定性&#xff08;自由意志论&#xff09;Indeterminism不确定性Uncertainty叠加原理(线性)superposition (linearity)纠缠entanglement 虚数的常见基本运算欧拉公式&#x…

nodejs国内镜像及切换版本工具nvm

淘宝 NPM 镜像站&#xff08;http://npm.taobao.org&#xff09;已更换域名&#xff0c;新域名&#xff1a; Web 站点&#xff1a;https://npmmirror.com Registry Endpoint&#xff1a;https://registry.npmmirror.com 详见&#xff1a; 【望周知】淘宝 NPM 镜像换域名了&…

java基础--多线程学习

写在前面&#xff1a; 多线程在面试中问的很多&#xff0c;之前没有过系统的学习&#xff0c;现在来进行一个系统的总结学习 文章目录 基础java多线程实现无参无返回值线程快速创建start和run方法的探讨run方法线程状态 有返回值线程线程池执行小结关于抛出异常的扩展 线程方…

CLion 2023.2.2(C ++ IDE智能代码编辑器)

CLion 2023是一款跨平台C/C集成开发环境&#xff08;IDE&#xff09;。它为Mac用户提供了高效的编程体验&#xff0c;帮助程序员们在Mac平台上进行C/C开发。 CLion 2023支持多种编译器和调试器&#xff0c;并具有强大的代码分析和导航功能。它还为用户提供了许多便捷的工具和插…

Mac连接linux的办法(自带终端和iterm2)

1. 使用Mac自带终端Terminal 1.1 点击右上角的聚焦搜索&#xff0c;再输入终端 1.2 查找linux系统的ip地址 在虚拟机里输入如下命令&#xff0c;找到蓝色区域的就是ip地址 ip addr 如果没有显示ip地址&#xff0c;可以重新安装一下虚拟机&#xff0c;之后确保以太网的连接是打…

Apache ActiveMQ (版本 < 5.18.3) (CNVD-2023-69477)RCE修复方案/缓解方案

一、漏洞描述 Apache ActiveMQ 是美国阿帕奇&#xff08;Apache&#xff09;基金会的一套开源的消息中间件&#xff0c;它支持 Java 消息服务、集群、Spring Framework 等。 二、漏洞成因 ActiveMQ 默认开放了 61616 端口用于接收 OpenWire 协议消息&#xff0c;由于针对异常…

MySQL BinLog实战应用之二

一、前言 上篇 MySQL Binlog实战应用之一 主要讲了BinLog的开启以及用MySQLBinLog读取BigLog二进制文件&#xff0c;但MySQLBinLog很难直接对接Java&#xff0c;所以有了Canal这个Alibaba开发的用于MySQL增量日志解析&#xff0c;提供增量数据的订阅和消费组件。 二、Canal原…

什么是Webpack?它的主要功能是什么?

聚沙成塔每天进步一点点 ⭐ 专栏简介 前端入门之旅&#xff1a;探索Web开发的奇妙世界 欢迎来到前端入门之旅&#xff01;感兴趣的可以订阅本专栏哦&#xff01;这个专栏是为那些对Web开发感兴趣、刚刚踏入前端领域的朋友们量身打造的。无论你是完全的新手还是有一些基础的开发…

犀牛8 for Mac – 强大的三维建模软件,轻松实现创意设计

你是否正在寻找一款功能强大、易于使用的三维建模软件&#xff1f;犀牛8 for Mac是你的不二选择&#xff01;作为最新版本的犀牛系列软件&#xff0c;它汇集了众多令人惊叹的特性&#xff0c;让你能够轻松实现自己的创意设计。 犀牛8 for Mac拥有丰富而强大的三维建模工具&…

Python库Requests的爬虫程序爬取视频通用模版

目录 一、引言 二、Requests库介绍 三、通用视频爬虫模板设计 1、确定目标网站和视频页面结构 2、发送HTTP请求获取页面内容 3、解析HTML内容提取视频链接 4、下载视频文件 四、模板应用与实践 五、注意事项 总结与展望 一、引言 随着互联网的发展&#xff0c;视频内…

机器人仿真-gazebo学习笔记(3)URDF和机器人模型

1.URDF简介 URDF(统一机器人麦哦书格式)是ROS中的重要机器人模型描述格式&#xff0c;ROS提供了URDF文件的c解析器&#xff0c;可以解析URDF文件中使用XML格式的机器人模型。 urdf - ROS Wiki 自己查阅ros官方对URDF的介绍其实会强于大部分网上流传的文章。 1.URDF文件常用的…

uni-app 解决钉钉小程序日期组件uni-datetime-picker不兼容ios问题

最近在使用uni-app开发 钉钉小程序 &#xff0c;遇到一个ios的兼容性问题 uni-datetime-picker 组件在模拟器上可以使用&#xff0c;在真机上不生效问题 文章目录 1. 不兼容的写法&#xff0c;uni-datetime-picker 不兼容IOS2. 兼容的写法&#xff0c;使用 dd.datePicker 实现。…

记录 vue + vuetify + electron 安装过程

NodeJs 版本&#xff1a; 20 内容来自&#xff1a; Electron Vue.js Vuetify 构建跨平台应用_思月行云的博客-CSDN博客文章浏览阅读61次。Go coding!https://blog.csdn.net/kenkao/article/details/132600542 npm config set registry https://registry.npm.taobao.org np…

MongoDB——MongoDB删除系统自带的local数据库

一、MongoDB删除系统自带的local数据库 1.1、linux环境进入mongo客户端 输入 mongo 命令&#xff0c;进入命令行客户端 进入admin库&#xff0c;并登录&#xff0c;查看所有数据库 #进入admin库 use admin #并登录admin db.auth("username","password")…

【蓝桥杯】2023省赛H题

考察知识点&#xff1a;双向链表&#xff0c;小根堆 完整代码在文章末尾 题目 【问题描述】 给定一个长度为 N 的整数数列: A1,A2,...,AN。你要重复以下操作 K 次 :…