版本不一致_一致哈希:Beyond the basics
经典的一致哈希算法解决了模块化哈希算法的问题,其中哈希函数(键K的位置)与存储单元的数量相关,需要在按比例放大和缩小时重新分配所有键。
# modular hashinghash = key % N of nodes
另一方面,利用一致哈希,哈希功能与存储节点的数量无关。这使我们能够在添加或删除节点时动态地对数据进行分区,从而逐步扩展。
哈希空间保持巨大且恒定。它通常被称为 “环”。每个存储节点都在此散列空间ring中分配了一个随机位置。
通过哈希数据项的键以获取其在环上的位置,然后顺时针旋转环以找到位置大于该项位置的第一个节点,将每个数据项分配给一个节点。
因此,每个节点都负责它与其前任节点之间的环中区域。因此,添加或删除节点仅需要重新分配属于其区域的密钥,而不需要像模块化哈希那样重新分配所有密钥。
模块化与一致性哈希
一致性哈希提出了一些挑战。最明显的是围绕环上节点位置的随机分配,从而导致数据和负载分布不均匀。此外,当添加或删除节点时,必须通过从单个节点检索一组键来复制一些数据,这通常效率低下且速度慢。
受AWS DynamoDB的启发,本文讨论了经典的一致性哈希所带来的挑战,涉及了不同的扩展方面,例如可用性,一致性,性能和持久性。此外,它还讨论了数据版本控制和协调,节点成员资格以及故障检测和处理。它只是参考AWS DynamoDB已发表论文的一般想法和个人注释的摘要。DynamoDB是Amazon的高可用性键值存储。
将节点映射到T令牌
每个节点T个令牌
为了使用经典的一致性哈希解决非均匀数据和负载分配问题,我们可以将每个节点映射到环中的T个位置(称为“令牌”)。添加新节点时,会在环上为其分配随机散布的T位置(令牌)。这具有以下好处:
添加节点后,与现有可用节点相比,其负载量大致相等。
当某个节点被删除或由于故障而变得不可用时,该节点处理的负载将以相反的过程分配回来,从而有效地将负载平均分配到其余可用节点上。
可用性
— 复制
跨多个节点复制数据对于实现高可用性至关重要。每个数据项都在N个节点上复制。节点协调写请求,负责复制环中N-1个顺时针后继位置(令牌)中属于其范围内的数据项。第一后继位置可能是由另一个哈希函数确定的随机性,也可能是跨不同的数据中心确定的以提高可用性。
一些有用的定义:N是每个数据项的副本数,公共值为3;R是要确认/回复读请求的副本数;W则是要确认/保留写请求的副本数;S是系统中的节点数;T是环上物理节点的令牌(位置)数;密钥范围是指环上与令牌相关联的一组密钥(令牌由节点拥有)。
— 始终可写
我们都知道,在分布式系统世界中,网络故障和数据冲突并不少见,因此无法同时实现高一致性和数据可用性。传统算法会在故障情况下权衡数据的可用性,因此在绝对确定数据正确之前,使数据不可用。
相反,我们可以通过解决答案的正确性的不确定性来提高可用性,允许更改传播到后台的副本中,并且允许并发的,断开的工作。
这种方法的挑战在于,它可能导致冲突的变化,必须加以检测和解决。解决冲突的过程引入了两个问题:何时解决问题以及谁来解决。
一致性
— 解决冲突:何时
在读取或写入期间是否应解决冲突。许多传统的数据存储解决了写入期间的冲突,并使读取的复杂性保持简单。如果数据存储无法到达W节点,则会以拒绝写入为代价,从而有效地降低了系统可用性。将冲突解决方案推送至读取可确保永不拒绝写入。检测到数据版本冲突时,不会拒绝读取;现在的问题是谁以及如何解决?
— 解决冲突:谁
这可以通过数据存储本身或应用程序客户端来完成。
应用程序客户端知道数据模式和业务逻辑,因此可以决定冲突的解决方案。以购物车示例为例,当客户要将商品添加到购物车(或从购物车中删除)而最新版本不可用时,则将商品添加到旧版本(或从旧版本中删除),并且将不同版本以后和解。这种变化仍然有意义,应该保留。读取时,当检测到冲突版本时,应用程序客户端可以选择“合并”冲突版本并返回单个统一购物车。
另一方面,如果数据存储区正在处理冲突,则其选择相当有限,并且只能使用简单的策略,例如基于时间戳的“最后写入获胜”对帐逻辑(即,选择具有最新时间戳值的项目)作为正确的版本)。维护客户会话信息的服务就是该用例的一个很好的例子。
— 解决冲突:如何
数据版本控制使我们能够检测,解决冲突的版本,从而确保数据的一致性。节点执行的每次更新都被视为数据的新的和不变的版本。一个版本由(节点,计数器)对组成,即[(N,c),…],其中N是协调写请求的节点。
在大多数情况下,新版本包含以前的版本,并且数据存储本身可以确定权威版本。
但是,在出现故障并发更新的情况下,可能会发生版本(并行)分支,从而导致项目的版本冲突。在这种情况下,数据的多个分支折叠为一个。前面解释的一个典型示例是应用程序客户端“合并”客户购物车的不同版本。
为了说明读写工作流程:
读
协调读取请求的节点将从所有N个节点中获取给定项的键来请求该项的现有版本。
然后它将等待N个节点中的R个节点进行回复。
返回结果。如果检测到版本冲突,特别是数据存储无法协调的并行分支,它将把冲突的项目及其版本上下文信息返回给客户端。客户端通过将分支折叠成一个版本来协调不同版本后,执行更新。
写
协调写请求的节点将在本地存储它,生成一个新版本,并在N-1个位置(令牌)之间复制它。客户端必须指定要更新的版本。通过传递从较早的读取操作获得的上下文来完成此操作。
一旦N个节点中的W个节点作出响应,就认为写入请求成功。
性能
为读和写操作提供一致的高性能非常困难,因为性能受到R或W副本中最慢的限制。
一些应用需要高水平的性能,并且可以权衡耐用性与性能(即R = 1,W = 1)。为此,每个存储节点在其主内存中维护一个对象缓冲区。每个写操作都存储在缓冲区中,并定期写入持久性存储中。每个读取操作首先检查缓冲区中是否存在请求的密钥。如果是这样,则从缓冲区而不是存储引擎中读取对象。
这将导致延迟降低很多,但要兼顾耐久性。服务器崩溃可能导致丢失在缓冲区中排队的写操作。
持久性
为了降低持久性风险,处理写请求的协调器节点从N-1个副本中选择一个,将数据写到其持久性存储中。并且由于协调器节点仅等待W响应,因此写操作的性能不会受到影响。
通常,增加成功写入操作时需要确认的节点数量可以提高持久性,但同时也要牺牲可用性。如果没有足够多的活动节点可以答复,则写入请求可能会被拒绝。
跨数据中心复制数据项以承受因断电,网络故障和自然灾害导致的故障很重要。
节点成员资格
节点之间基于gossip的协议传播成员身份更改(节点加入或离开环)并维护成员身份的最终一致性。
每个节点每秒都与随机选择的一个对等方联系,并且两个节点有效地协调了其持久的成员资格更改历史记录。
因此,每个存储节点都知道其对等节点处理的令牌。这允许每个节点直接将密钥的读/写操作转发到正确的节点集。
这消除了维护故障状态的集中全局一致视图的需要。
故障检测与处理
故障检测和处理可以避免失败的读取或写入尝试,即使在最简单的故障条件下,这样做也会降低可用性和耐久性。
—临时故障:提示切换
如果节点B不响应节点A的消息,则节点A可能认为节点B暂时处于关闭状态。然后,节点A使用备用节点执行请求;节点A定期重试B以检查后者的恢复情况。
在没有客户端请求来驱动两个节点之间的流量的情况下,两个节点都不真正需要知道另一个节点是否可访问且是否响应。
为了处理此问题,通常将原本存在于B上的数据项现在将发送到节点X(B的临时副本)。发送到副本X的数据项将在其元数据中具有提示,提示哪个节点是副本的预期接收者(在本例中为B)。
接收提示数据项的节点会将它们保存在单独的本地数据库中,该数据库会定期进行扫描。在检测到B已恢复后,X将尝试将数据传递给B。一旦传输成功,X可能会从其本地存储中删除该数据项。
使用提示切换,我们确保写操作不会由于临时节点或网络故障而失败。
—持久故障:副本同步
在某些情况下(例如节点中断),节点B可能会长时间不可用,并且提示的数据项甚至可能无法返回。为解决此问题,使用了使用Merkle树的副本同步协议来使副本保持同步并检测不一致之处。
Merkle树遍历
Merkle树是一种哈希树,其中的叶子是各个键的值的哈希值。树中较高的父节点是其各自子节点的哈希。Merkle树的主要优点是,树的每个分支都可以独立且并行地进行检查。此外,Merkle树有助于减少需要传输的数据量并减少执行的磁盘读取次数。两个节点可以遍历其Merkle树,以共同拥有的关键范围来确定它们是否存在差异。
每个节点为其承载的每个密钥范围(与令牌关联的密钥集)维护一个单独的Merkle树。这允许节点比较密钥范围内的密钥是否为最新。
回顾
因此,总结一下添加和删除节点以及读取和写入的工作流程:
添加节点
当节点X加入时,它会在环上选择其随机令牌集。
对于分配给节点X的每个密钥范围,当前可能有许多节点负责处理属于其令牌范围内的密钥。由于将密钥范围分配给X,因此现有节点不再需要存储这些密钥,这些节点会将这些密钥转移到 X。
通过上面讨论的基于八卦的协议,协调成员资格更改历史并维护成员资格的最终一致性视图。
删除节点
当节点被删除或由于故障而变得不可用时,这表示永久离开,因此,成员资格更改会传播,以通知其他节点有关节点删除的信息。
对于由已删除节点处理的密钥范围,它们被随机分配到其余节点,因此,将负载平均分配到其余可用节点上。
读
客户端请求由负载均衡器统一分配给环中的节点。任何节点都可以充当读取请求的协调器。
协调读取请求的节点会将请求发送到密钥K的所有N个节点,并等待N个节点中的R个。
收集数据,确定是否需要对帐(如前所述)。此过程称为“读取修复”,因为它会修复错过了最近更新的副本,并使副本同步协议不必执行此操作。
写
与读取不同,写请求由带有密钥K的数据项的节点副本之一协调。如果接收到请求的节点不是该数据项的密钥的节点副本,它将把请求转发到N个副本之一此限制是由于这些首选节点还具有创建新版本标记的责任。如果版本控制方案基于物理时间戳,则任何节点都可以协调写请求。
等待N个节点之外的W个节点作出响应。
应用程序客户端库可以是可感知分区的客户端库,可将请求直接路由到适当的协调器节点。在这种情况下,我们可以实现较低的延迟,因为如果负载均衡器将请求分配给随机节点,它会跳过额外的网络跃点。
挑战性
由于令牌是在环上随机选择的,因此范围的大小会有所不同。随着节点的加入和离开系统,令牌集也会更改,因此范围也会更改。因此,“将节点映射到T令牌”策略提出了以下挑战:
当新节点加入系统时,在关键范围内处理该数据的节点到新节点必须扫描其本地持久性存储以检索适当的数据项集。扫描操作占用大量资源,因此需要在后台执行,而又不影响客户性能。此外,以最低优先级运行引导任务会极大地减慢引导过程,并在繁忙时段变得很麻烦。
当一个节点加入或离开系统时,其他一些节点处理的关键范围将发生变化,并且需要重新计算新范围的Merkle树,这对于在生产系统上执行来说是一项不平凡的操作。
最后,由于密钥范围的随机性,没有简单的方法可以对整个密钥空间进行快照,这使归档过程变得复杂。在这种情况下,归档整个密钥空间需要分别从每个节点检索密钥,这是非常低效的。
这种策略的根本问题是数据分区(按令牌划分)和数据放置(存储节点)是相互依赖的。在不影响数据分区的情况下添加或删除节点是不可能的。
将节点映射到T令牌和相等大小的Q分区
每个节点T个令牌和相等大小的Q分区
在这种策略中,将哈希空间划分为Q个大小相等的固定分区(或键范围),并且为每个节点分配T个随机令牌。这里的令牌不决定分区,因此两个连续的令牌不定义键范围或分区(它们仅定义环上的节点位置)。
通过这种策略,我们可以获得以下好处:
分区和分区放置的分离。数据项在给定键K的情况下映射到Q个分区中的一个,而负责存储该数据项的节点是通过从包含键K的分区的末尾顺时针旋转环来选择的,从而找到第一个令牌的方式,然后找到它的存储节点。
启用在运行时更改放置方案的可能性。添加或删除节点不会更改任何数据项的分区(键范围)。
通常将Q设置为Q > S * T,其中S是系统中的节点数。
但是,分配的T令牌的随机性以及密钥范围仍然是一个难题。传递数据的节点会扫描其本地持久性存储,从而导致引导速度变慢;重新计算Merkle树;并没有简单的快照方法。
将节点映射到Q / S令牌和相等大小的Q分区
每个节点的Q / S令牌和相等大小的Q分区
与策略2相似,此策略将哈希空间划分为Q个大小相等的分区,并且数据放置与数据分区分离。
此外,为每个节点分配Q / S令牌(即T = Q / S)。令牌数量随着我们添加或删除节点而改变。
当节点离开系统时,其令牌将随机分配到其余节点,以便保留这些属性。同样,当节点加入系统时,它会以保留这些属性的方式从系统中的节点窃取令牌。
此策略具有以下优点:
更快的引导和恢复。由于分区键范围是固定的,因此可以将它们存储在单独的文件中,因此,只需简单地传输文件,就可以将分区作为一个单元重新放置,从而避免了定位特定项目所需的随机访问。
易于归档:分区文件可以单独归档。相比之下,与以前的策略相比,令牌是随机选择的,并且对存储的数据进行归档需要分别从各个节点检索密钥,并且通常效率低下且速度慢。
这种策略的缺点是添加或删除节点需要我们保留其属性(即T = Q / S)。
参考文献:
Dynamo:亚马逊的高可用键值存储
链接:
https://medium.com/omarelgabrys-blog/consistent-hashing-beyond-the-basics-525304a12ba
翻译:奶酪二哈-BKing
The article is only for learning, if infringement, please contact BKing8@88.com to delete.
版本不一致_一致哈希:Beyond the basics相关推荐
- java version 和javac版本不一致_解决linux下javac -version和java -version版本显示不一致...
[javascript] view plaincopy [root@localhost usr]# $JAVA_HOME/bin/java -version bash: /bin/java: 没有那个 ...
- cuda nvcc版本不一致_显卡,显卡驱动,nvcc, cuda driver,cudatoolkit,cudnn到底是什么?
在使用深度学习框架的过程中一定会经常碰到这些东西,虽然anaconda有时会帮助我们自动地解决这些设置,但是有些特殊的库却还是需要我们手动配置环境,但是我对标题上的这些名词其实并不十分清楚,所以老是被 ...
- cuda nvcc版本不一致_入坑第一步:Win10安装cuda+cuDNN+TensorFlow-GPU走过的那些路
这两天安装tensorflow-gpu被折腾够呛,幸亏最后成功了,给想要安装的大神们看下我走过的坑,避免掉入. 如果是新手,需要安装下面几个软件: 第一步 安装anaconda 首先就是Python编 ...
- mysql不同版本乱码_技术|解决MySQL中文乱码以及版本不一致问题
这几天基于Heritrix写了一个爬虫,用到MySQL,在导入导出数据时,遇到一些乱码问题,好不容易解决了,记录一下,以备查看. 一.导出数据 先说明一下自己的环境:Mac OS X 10.8.3, ...
- mysql不同版本乱码_解决MySQL中文乱码以及版本不一致问题_MySQL
一.导出数据 先说明一下自己的环境:Mac OS X 10.8.3, MySQL Community Server 5.6.10, MySQL Workbench 5.2.47. 我想把本机数据库内的 ...
- Hadoop虚拟机的jdk版本和本地eclipse的版本不一致怎么办
在本周学习Hadoop遇到了一个问题,困扰了半天,本人在安装Hadoop时是按照视频来的,结果发现Hadoop上的jdk版本和本地eclipse的版本不一致,导致本地的程序到处jar包传到虚拟机上运用 ...
- 查看mysql版本不一致_MySQL-版本不一致
版本不一致,导致如下. MySQL [(none)]> select version(); 阿里云 +------------+ | version() | +----------- ...
- 怎么查看jre版本_javac和java版本不一致问题
今天有个小伙伴提了个问题:他在本地调试代码,编译和运行时,出现了版本不一致的问题.明明java_home配置没有问题,为什么会出现不一样的版本? 我先简单重现下当时的问题:首先,写了一个简单的java ...
- 关于eclpse java项目与tomcat jdk版本不一致的解决方法
最近,在eclipse中tomcat(jdk1.7)添加项目的时候,项目添加不进去,报jdk(项目中jdk1.8)版本不一致的错误.下面是我的解决过程: 选中项目按ALT+回车 一.选择替换jdk如下 ...
最新文章
- 过主动防御自启动代码delphi
- 【学术相关】魔术乘法:张成奇教授40年磨一剑!
- sqlplus -prelim,sqplus区别
- 在gradle中构建java项目
- python代码覆盖率怎么统计的_Python代码覆盖率统计工具coverage.py用法详解
- 针对新手的Java EE7和Maven项目–第2部分–为我们的应用程序定义一场简单的战争...
- 这届毕业生薪资高,是真的
- JAVA Opencv在图片上添加中文
- flask从表单中的提交中获取数据(不使用第三方库)
- windows内核基础
- mysql 明文密码_后台能看到明文密码的处理
- python微信红包代码_哄女朋友必备之微信自动发红包脚本(python+adb+androidviewclient)...
- 【有奖调研】| 参与区块链调研,赢千元大奖!
- 五邑大学计算机学院院长,五邑大学计算机学院研究生导师介绍曹彩凤
- itx机箱尺寸_鞋盒大小的ITX机箱初体验 | FORMULA X1装机展示
- Android 手机拨号
- 深度理解PHP执行流程
- Figma#5:文字
- 全球各个国家名的英文
- 毕业设计 Stm32人体心率血氧无线监测系统 - 单片机 物联网
热门文章
- One order datatype 命名规范
- Java static initialization研究
- 一个Java多线程练习的调试
- Java注解研究之@Required
- SAP UI5和CRM WebUI的View和Controller是如何绑定的
- fiddler抓包工具简介
- linux ora 01092,ORA-01173的模拟与故障处理
- 热点效应是指_热电偶的热电效应是什么意思 浅谈热电偶下的热点效应
- idea打印sql的插件_[Mybatis]-[基础支持层]-插件-自定义简易SQL打印插件
- php万年历上个月下个月,php 万年历