This commit is contained in:
louzefeng
2024-07-09 18:38:56 +00:00
parent 8bafaef34d
commit bf99793fd0
6071 changed files with 1017944 additions and 0 deletions

View File

@@ -0,0 +1,192 @@
<audio id="audio" title="23 | CAP理论这顶帽子我不想要" controls="" preload="none"><source id="mp3" src="https://static001.geekbang.org/resource/audio/fb/81/fbde89deaf0ca3ec2bc755c4680bf181.mp3"></audio>
你好,我是聂鹏程。今天,我来继续带你打卡分布式核心技术。
在开篇词中我将分布式计算划分为了四横四纵。而在前面的文章中我们已经一起学习了四横中的分布式计算、分布式通信和分布式资源池化三横的相关知识。比如在分布式计算中我们学习了分布计算模式包括MapReduce、Stream、Actor和流计算的原理和实际应用在分布式通信中我们学习了远程调用、订阅发布和消息队列模式的原理和应用在分布式资源池化中我们学习了分布式系统架构和分布式调度架构。
相信通过对这些内容的学习,你已经对分布式技术有比较深刻的了解了。分布式系统处理的关键对象是数据,前面这些文章也都是为数据处理服务的。那么,数据本身相关的分布式技术有哪些呢?这就是接下来的几讲,我要带你学习的四横中的最后一横“分布式数据存储与管理”的相关技术。
在正式介绍分布式数据存储技术之前我需要先带你了解一个基本理论也就是CAP理论。前面提到分布式系统处理的关键对象是数据而数据其实是与用户息息相关的。CAP理论指导分布式系统的设计以保证系统的可用性、数据一致性等特征。比如电商系统中保证用户可查询商品数据、保证不同地区访问不同服务器查询的数据是一致的等。
话不多说接下来我们就一起打卡CAP理论吧。
## 什么是CAP
如果你之前没有听说过CAP理论的话看到这三个字母第一反应或许是“帽子”吧。那么在分布式领域中CAP这顶“帽子”到底是什么呢我们先来看看**这三个字母分别指的是什么**吧。
接下来我结合电商的例子带你理解CAP的含义。
假设某电商,在北京、杭州、上海三个城市建立了仓库,同时建立了对应的服务器{A, B, C}用于存储商品信息。比如某电吹风在北京仓库有20个在杭州仓库有10个在上海仓库有30个。那么CAP这三个字母在这个例子中分别代表什么呢
首先我们来看一下C。**C代表Consistency**,一致性,是指所有节点在同一时刻的数据是相同的,即更新操作执行结束并响应用户完成后,所有节点存储的数据会保持相同。
在电商系统中A、B、C中存储的该电吹风的数量应该是20+10+30=60。假设现在有一个北京用户买走一个电吹风服务器A会更新数据为60-1=59与此同时要求B和C也更新为59以保证在同一时刻无论访问A、B、C中的哪个服务器得到的数据均是59。
然后看一下A。**A代表Availability**,可用性,是指系统提供的服务一直处于可用状态,对于用户的请求可即时响应。
在电商系统中用户在任一时刻向A、B、C中的任一服务器发出请求时均可得到即时响应比如查询商品信息等。
最后我们看一下P。**P代表Partition Tolerance**,分区容错性,是指在分布式系统遇到网络分区的情况下,仍然可以响应用户的请求。网络分区是指因为网络故障导致网络不连通,不同节点分布在不同的子网络中,各个子网络内网络正常。
在电商系统中假设C与A和B的网络都不通了A和B是相通的。也就是说形成了两个分区{A, B}和{C},在这种情况下,系统仍能响应用户请求。
一致性、可用性和分区容错性,就是分布式系统的三个特征。那么,我们平时说的**CAP理论又是什么呢**
**CAP理论**指的就是在分布式系统中C、A、P这三个特征不能同时满足只能满足其中两个如下图所示。这是不是有点像分布式系统在说这顶“帽子”我不想要呢
<img src="https://static001.geekbang.org/resource/image/85/b0/8544ff09258cea4d6d0c6e01cc2aa2b0.png" alt="">
接下来,我就通过一个例子和你进一步解释下,**什么是CAP以及CAP为什么不能同时满足**吧。
如下图所示网络中有两台服务器Server1和Server2分别部署了数据库DB1和DB2这两台机器组成一个服务集群DB1和DB2两个数据库中的数据要保持一致共同为用户提供服务。用户User1可以向Server1发起查询数据的请求用户User2可以向服务器Server2发起查询数据的请求它们共同组成了一个分布式系统。
<img src="https://static001.geekbang.org/resource/image/b3/a8/b335fa77228eb37d6f3596f9419a05a8.png" alt="">
对这个系统来说分别满足C、A和P指的是
- 在满足一致性C的情况下Server1和Server2中的数据库始终保持一致即DB1和DB2内容要始终保持相同
- 在满足可用性A的情况下用户无论访问Server1还是Server2都会得到即时响应
- 在满足分区容错性P的情况下Server1和Server2之间即使出现网络故障也不会影响Server1和Server2分别处理用户的请求。
当用户发起请求时,收到请求的服务器会及时响应,并将用户更新的数据同步到另一台服务器,保证数据一致性。具体的工作流程,如下所示:
1. 用户User1向服务器Server1发起请求将数据库DB1中的数据a由1改为2
1. 系统会进行数据同步即图中的S操作将Server1中DB1的修改同步到服务器Server2中使得DB2中的数据a也被修改为2
1. 当User2向Server2发起读取数据a的请求时会得到a最新的数据值2。
<img src="https://static001.geekbang.org/resource/image/c7/c5/c700058c70793b7dbe721e7f15d574c5.png" alt="">
这其实是在网络环境稳定、系统无故障的情况下的工作流程。但在实际场景中,网络环境不可能百分之百不出故障,比如网络拥塞、网卡故障等,会导致网络故障或不通,从而导致节点之间无法通信,或者集群中节点被划分为多个分区,分区中的节点之间可通信,分区间不可通信。
这种由网络故障导致的集群分区情况,通常被称为“网络分区”。
在分布式系统中网络分区不可避免因此分区容错性P必须满足。接下来我们就来讨论一下**在满足分区容错性P的情况下一致性C和可用性A是否可以同时满足。**
假设Server1和Server2之间网络出现故障User1向Server1发送请求将数据库DB1中的数据a由1修改为2而Server2由于与Server1无法连接导致数据无法同步所以DB2中a依旧是1。这时**User2向Server2发送读取数据a的请求时Server2无法给用户返回最新数据那么该如何处理呢**
我们能想到的处理方式有如下两种。
**第一种处理方式是,**保证一致性C牺牲可用性AServer2选择让User2的请求阻塞一直等到网络恢复正常Server1被修改的数据同步更新到Server2之后即DB2中数据a修改成最新值2后再给用户User2响应。
<img src="https://static001.geekbang.org/resource/image/6e/4f/6ec73c1d7061afe932f0a850547a854f.png" alt="">
**第二种处理方式是,**保证可用性A牺牲一致性CServer2选择将旧的数据a=1返回给用户等到网络恢复再进行数据同步。
<img src="https://static001.geekbang.org/resource/image/51/11/518102b750f1dc43d58256eac9776611.png" alt="">
除了以上这两种方案没有其他方案可以选择。可以看出在满足分区容错性P的前提下一致性C和可用性A只能选择一个无法同时满足。
## CAP选择策略及应用
通过上面的分析你已经知道了分布式系统无法同时满足CAP这三个特性那该如何进行取舍呢
其实C、A和P没有谁优谁劣只是不同的分布式场景适合不同的策略。接下来**我就以一些具体场景为例分别与你介绍保CA弃P、保CP弃A、保AP弃C这三种策略以帮助你面对不同的分布式场景时知道如何权衡这三个特征。**
比如对于涉及钱的交易时数据的一致性至关重要因此保CP弃A应该是最佳选择。2015年发生的支付宝光纤被挖断的事件就导致支付宝就出现了不可用的情况。显然支付宝当时的处理策略就是保证了CP而牺牲了A。
而对于其他场景大多数情况下的做法是选择AP而牺牲C因为很多情况下不需要太强的一致性数据始终保持一致只要满足最终一致性即可。
最终一致性指的是,不要求集群中节点数据每时每刻保持一致,在可接受的时间内最终能达到一致就可以了。不知道你是否还记得,在[第6篇文章](https://time.geekbang.org/column/article/144970)分布式事务中介绍的基于分布式消息的最终一致性方案没错这个方案对事务的处理就是选择AP而牺牲C的例子。
这个方案中,在应用节点之间引入了消息中间件,不同节点之间通过消息中间件进行交互,比如主应用节点要执行修改数据的事务,只需要将信息推送到消息中间件,即可执行本地的事务,而不需要备应用节点同意修改数据才能真正执行本地事务,备应用节点可以从消息中间件获取数据。
### 保CA弃P
首先我们看一下保CA弃P的策略。
在分布式系统中现在的网络基础设施无法做到始终保持稳定网络分区网络不连通难以避免。牺牲分区容错性P就相当于放弃使用分布式系统。因此在分布式系统中这种策略不需要过多讨论。
既然分布式系统不能采用这种策略那单点系统毫无疑问就需要满足CA特性了。比如关系型数据库 DBMS比如MySQL、Oracle部署在单台机器上因为不存在网络通信问题所以保证CA就可以了。
### 保CP弃A
如果一个分布式场景需要很强的数据一致性或者该场景可以容忍系统长时间无响应的情况下保CP弃A这个策略就比较适合。
一个保证CP而舍弃A的分布式系统一旦发生网络分区会导致数据无法同步情况就要牺牲系统的可用性降低用户体验直到节点数据达到一致后再响应用户。
我刚刚也提到了这种策略通常用在涉及金钱交易的分布式场景下因为它任何时候都不允许出现数据不一致的情况否则就会给用户造成损失。因此这种场景下必须保证CP。
保证CP的系统有很多典型的有Redis、HBase、ZooKeeper等。接下来我就**以ZooKeeper为例带你了解它是如何保证CP的。**
首先我们看一下ZooKeeper架构图。
<img src="https://static001.geekbang.org/resource/image/92/2b/9284b9f7bb413bdb80032866b106d42b.png" alt="">
备注:此图引自[ZooKeeper官网](https://zookeeper.apache.org/doc/current/zookeeperOver.html)。
ZooKeeper集群包含多个节点Server这些节点会通过分布式选举算法选出一个Leader节点。在ZooKeeper中选举Leader节点采用的是ZAB算法你可以再回顾下[第4篇文章](https://time.geekbang.org/column/article/143329)中的相关内容。
在ZooKeeper集群中Leader节点之外的节点被称为Follower节点**Leader节点会专门负责处理用户的写请求**
- 当用户向节点发送写请求时如果请求的节点刚好是Leader那就直接处理该请求
- 如果请求的是Follower节点那该节点会将请求转给Leader然后Leader会先向所有的Follower发出一个Proposal等超过一半的节点同意后Leader才会提交这次写操作从而保证了数据的强一致性。
具体示意图如下所示:
<img src="https://static001.geekbang.org/resource/image/ef/fb/ef48459152db9be2db80dc3e9e6b92fb.png" alt="">
当出现网络分区时如果其中一个分区的节点数大于集群总节点数的一半那么这个分区可以再选出一个Leader仍然对用户提供服务但在选出Leader之前不能正常为用户提供服务如果形成的分区中没有一个分区的节点数大于集群总节点数的一半那么系统不能正常为用户提供服务必须待网络恢复后才能正常提供服务。
这种设计方式保证了分区容错性,但牺牲了一定的系统可用性。
### 保AP弃C
如果一个分布式场景需要很高的可用性,或者说在网络状况不太好的情况下,该场景允许数据暂时不一致,那这种情况下就可以牺牲一定的一致性了。
网络分区出现后,各个节点之间数据无法马上同步,为了保证高可用,分布式系统需要即刻响应用户的请求。但,此时可能某些节点还没有拿到最新数据,只能将本地旧的数据返回给用户,从而导致数据不一致的情况。
**适合保证AP放弃C的场景有很多。**比如,很多查询网站、电商系统中的商品查询等,用户体验非常重要,所以大多会保证系统的可用性,而牺牲一定的数据一致性。
以电商购物系统为例如下图所示某电吹风在北京仓库有20个在杭州仓库有10个在上海仓库有30个。初始时北京、杭州、上海分别建立的服务器{A, B, C}存储该电吹风的数量均为60个。
假如上海的网络出现了问题与北京和杭州网络均不通此时北京的用户通过北京服务器A下单购买了一个电吹风电吹风数量减少到59并且同步给了杭州服务器B。也就是说现在用户的查询请求如果是提交到服务器A和B那么查询到的数量为59。但通过上海服务器C进行查询的结果却是60。
当然待网络恢复后服务器A和B的数据会同步到CC更新数据为59最终三台服务器数据保持一致用户刷新一下查询界面或重新提交一下查询就可以得到最新的数据。而对用户来说他们并不会感知到前后数据的差异到底是因为其他用户购买导致的还是因为网络故障导致数据不同步而产生的。
<img src="https://static001.geekbang.org/resource/image/45/90/45a06d5dae3b859011bfc77b27bde290.png" alt="">
当然,你可能会说,为什么上海服务器不能等网络恢复后,再响应用户请求呢?可以想象一下,如果用户提交一个查询请求,需要等上几分钟、几小时才能得到反馈,那么用户早已离去了。
也就是说这种场景适合优先保证AP因为如果等到数据一致之后再给用户返回的话用户的响应太慢可能会造成严重的用户流失。
目前采用保AP弃C的系统也有很多比如CoachDB、Eureka、Cassandra、DynamoDB等。
### 对比分析
保CA弃P、保CP弃A和保AP弃C这三种策略以方便你记忆和理解。
<img src="https://static001.geekbang.org/resource/image/1b/45/1b961be4510d86f52eb1c4d60c40d945.jpg" alt="">
## 知识扩展CAP和ACID的“C”“A”是一样的吗
首先我们看一下CAP中的C和ACID中的C是否一致。
- CAP中的C强调的是数据的一致性也就是集群中节点之间通过复制技术保证每个节点上的数据在同一时刻是相同的。
- ACID中的C强调的是事务执行前后数据的完整性保持一致或满足完整性约束。也就是不管在什么时候不管并发事务有多少事务在分布式系统中的状态始终保持一致。具体原理可参考第6篇文章“[分布式事务All or Nothing](https://time.geekbang.org/column/article/144970)”。
其次我们看一下CAP中的A和ACID中的A。
- CAP中的A指的是可用性Availability也就是系统提供的服务一直处于可用状态即对于用户的请求可即时响应。
- ACID中的A指的是原子性Atomicity强调的是事务要么执行成功要么执行失败。
因此CAP和ACID中的“C”和“A”是不一样的不能混为一谈。
## 总结
今天我主要与你分享的是CAP理论。
首先我通过电商的例子带你了解了CAP这三个字母在分布式系统中的含义以及CAP理论并与你证明了C、A和P在分布式系统中最多只能满足两个。
然后我为你介绍了分布式系统设计时如何选择CAP策略包括保CA弃P、保CP弃A、保AP弃C以及这三种策略适用的场景。
最后,我再通过一张思维导图来归纳一下今天的核心知识点吧。
<img src="https://static001.geekbang.org/resource/image/79/66/7986206234a8f0477cfe0cd7b639db66.png" alt="">
相信通过今天的学习你不仅对CAP理论有了更深刻的认识并且可以针对不同场景采用哪种策略给出自己的建议。加油行动起来为你的业务场景选择一种合适的策略来指导分布式系统的设计吧。相信你一定可以的
## 思考题
CAP理论和BASE理论的区别是什么
我是聂鹏程,感谢你的收听,欢迎你在评论区给我留言分享你的观点,也欢迎你把这篇文章分享给更多的朋友一起阅读。我们下期再会!

View File

@@ -0,0 +1,151 @@
<audio id="audio" title="24 | 分布式数据存储系统之三要素:顾客、导购与货架" controls="" preload="none"><source id="mp3" src="https://static001.geekbang.org/resource/audio/d1/c7/d14fe9ada04b5d45b6a76c8bc8ee0dc7.mp3"></audio>
你好,我是聂鹏程。今天,我来继续带你打卡分布式核心技术。
在上一篇文章中我们一起学习了CAP理论。该理论指出在分布式系统中不能同时满足一致性、可用性和分区容错性指导了分布式数据存储系统的设计。
随着数据量和访问量的增加,单机性能已经不能满足用户需求,分布式集群存储成为一种常用方式。把数据分布在多台存储节点上,可以为大规模应用提供大容量、高性能、高可用、高扩展的存储服务。而,分布式存储系统就是其具体实现。
在今天这篇文章,我将带你学习分布式存储系统的关键三要素,让你对分布式数据存储系统有一个直观的理解。在后面几篇文章中,我会针对这三要素中的关键技术进一步展开,以帮助你更深入地理解分布式数据存储系统。
接下来,我们就一起打卡分布式存储系统的三要素:顾客、导购与货架。
## 什么是分布式数据存储系统?
分布式存储系统的核心逻辑,就是将用户需要存储的数据根据某种规则存储到不同的机器上,当用户想要获取指定数据时,再按照规则到存储数据的机器里获取。
如下图所示当用户即应用程序想要访问数据D分布式操作引擎通过一些映射方式比如Hash、一致性Hash、数据范围分类等将用户引导至数据D所属的存储节点获取数据。
<img src="https://static001.geekbang.org/resource/image/60/43/60c0d9fad687be9c32fe0414e25e4c43.png" alt="">
静下心来想一下,获取数据的整个过程与你到商店购物的过程是不是有些类似呢?
顾客到商店购物时,导购会根据顾客想要购买的商品引导顾客到相应的货架,然后顾客从这个货架上获取要购买的商品,完成购物。这里的顾客就是图中的应用程序,导购就相当于分布式操作引擎,它会按照一定的规则找到相应的货架,货架就是存储数据的不同机器节点。
其实,这个过程就是分布式存储系统中获取数据的通用流程,**顾客、导购和货架**组成了分布式存储系统的三要素,分别对应着分布式领域中的**数据生产者/消费者、数据索引和数据存储**。
接下来,我们就详细看看这三个要素吧。
## 分布式数据存储系统三要素
顾客就是数据的生产者和消费者,也就是说顾客代表两类角色,生产者会生产数据(比如,商店购物例子中的供货商就属于生产类顾客),将数据存储到分布式数据存储系统中,消费者是从分布式数据存储系统中获取数据进行消费(比如,商店购物例子中购买商品的用户就属于消费类顾客);导购就是数据索引,将访问数据的请求转发到数据所在的存储节点;货架就是存储设备,用于存储数据。
### 顾客:生产和消费数据
顾客相当于分布式存储系统中的应用程序,而数据是应用程序的原动力。根据数据的产生和使用,顾客分为生产者和消费者两种类型。生产者负责给存储系统添加数据,而消费者则可以使用系统中存储的数据。
就像是火车票存储系统,如图所示,铁路局就相当于生产者类型的顾客,而乘客就相当于消费者类型的顾客。铁路局将各个线路的火车票信息发布到订票网站的后台数据库中,乘客通过订票网站访问数据库,来进行查询余票、订票、退票等操作。
<img src="https://static001.geekbang.org/resource/image/ac/36/acfc1621a27895b9520686062b91bd36.png" alt="">
生产者和消费者生产和消费的数据通常是多种多样的,不同应用场景中数据的类型、格式等都不一样。**根据数据的特征,这些不同的数据通常被划分为三类:结构化数据、半结构化数据和非结构化数据。**
- 结构化数据通常是指关系模型数据,其特征是数据关联较大、格式固定。火车票信息比如起点站、终点站、车次、票价等,就是一种结构化数据。结构化数据具有格式固定的特征,因此一般采用分布式关系数据库进行存储和查询。
- 半结构化数据通常是指非关系模型的有基本固定结构模式的数据其特征是数据之间关系比较简单。比如HTML文档使用标签书写内容。半结构化数据大多可以采用键值对形式来表示比如HTML文档可以将标签设置为key标签对应的内容可以设置为value因此一般采用分布式键值系统进行存储和使用。
- 非结构化数据是指没有固定模式的数据其特征是数据之间关联不大。比如文本数据就是一种非结构化数据。这种数据可以存储到文档中通过ElasticSearch一个分布式全文搜索引擎等进行检索。
### 导购:确定数据位置
导购是分布式存储系统必不可少的要素,如果没有导购, 顾客就需要逐个货架去寻找自己想要的商品。
想象一下,如果你去订票网站订火车票,按照自己的需求点击查询车票后,系统会逐个扫描分布式存储系统中每台机器的数据,寻找你想要购买的火车票。如果系统中存储的数据不多,响应时间也不会太长,毕竟计算机的速度还是很快的;但如果数据分布在几千台甚至上万台机器中,系统逐个机器扫描后再给你响应,我相信你会对这个订票网站很失望。
这种定位数据存储位置的方式会浪费你很多时间,严重影响购票体验。因此,在分布式存储系统中,必须有相应的数据导购,否则系统响应会很慢,效率很低。为解决这个问题,**数据分片技术**就走入了分布式存储系统的大家庭。
数据分片技术,是指分布式存储系统按照一定的规则将数据存储到相对应的存储节点中,或者到相对应的存储节点中获取想要的数据,这是一种很常用的导购技术。这种技术,一方面可以降低单个存储节点的存储和访问压力;另一方面,可以通过规定好的规则快速找到数据所在的存储节点,从而大大降低搜索延迟,提高用户体验。
也就是说当铁路局发布各个线路的火车票信息时会按照一定规则存储到相应的机器中比如北京到上海的火车票存储到机器A中西安到重庆的火车票存储到机器B中。当乘客查询火车票时系统就可以根据查询条件迅速定位到相对应的存储机器然后将数据返回给用户响应时间就大大缩短了。如图所示当查询北京-上海的火车票相关信息时可以与机器A进行数据交互。
<img src="https://static001.geekbang.org/resource/image/ed/f8/ed50b802e7ab87e4f175bae5277432f8.png" alt="">
这个例子中按照数据起点、终点的方式划分数据,将数据分为几部分存储到不同的机器节点中,就是数据分片技术的一种。当查询数据时,系统可以根据查询条件迅速找到对应的存储节点,从而实现快速响应。
上述的例子中,按照数据特征进行了数据分片,当然,还有其他很多数据分片的方案。比如,按照数据范围,采用哈希映射、一致性哈希环等对数据划分。我会在下一篇文章中,与你详细讲述哈希和一致性哈希的内容。
接下来,我就**针对数据范围这种数据分片方案做一个具体介绍**吧。
针对数据范围的数据分片方案是指按照某种规则划分数据范围然后将在这个范围内的数据归属到一个集合中。这就好比数学中通常讲的整数区间比如11000的整数[1,100]的整数属于一个子集、[101,1000]的整数属于另一个子集。
对于前面讲的火车票的案例按照数据范围分片的话可以将属于某条线的所有火车票数据划分到一个子集或分区进行存储比如机器A存储京广线的火车票数据机器B存储京沪线的火车票数据。也就是说数据范围的方案是按照范围或区间进行存储或查询。
如图所示,当用户查询北京-上海的火车票相关信息时,首先判断查询条件属于哪个范围,由于北京-上海的火车线路属于京沪线因此系统按照规则将查询请求转到存取京沪线火车票数据的机器B然后由机器B进行处理并给用户返回响应结果。
<img src="https://static001.geekbang.org/resource/image/0a/75/0aa5bd141f322e81efc9e9822cec0c75.png" alt="">
为了提高分布式系统的可用性与可靠性,**除了通过数据分片减少单个节点的压力外,数据复制也是一个非常重要的方法。**数据复制就是将数据进行备份,以使得多个节点存储该数据。
想象一下当某个存储节点出现故障时如果只采用数据分片技术那这个节点的数据就会丢失从而给用户造成损失。因此数据复制在分布式存储系统中是不可或缺的。关于数据复制技术我会在第26篇文章中与你详细讲解。
接下来,我与你说说数据复制和数据分片技术的区别吧。关于它们之间的区别,你可以先看看下面这张图片:
<img src="https://static001.geekbang.org/resource/image/03/56/03e3ac38ce5e4d24154127e76c3fa356.png" alt="">
数据A被拆分为两部分存储在两个节点Node1和Node2上属于数据分片而对数据B来说同一份完整的数据在两个节点中均有存储就属于数据复制。
**在实际的分布式存储系统中,数据分片和数据复制通常是共存的**
- 数据通过分片方式存储到不同的节点上,以减少单节点的性能瓶颈问题;
- 而数据的存储通常用主备方式保证可靠性,也就是对每个节点上存储的分片数据,采用主备方式存储,以保证数据的可靠性。其中,主备节点上数据的一致,是通过数据复制技术实现的。
讲到这里,我们再回忆下[第20篇文章](https://time.geekbang.org/column/article/161911)中涉及的Kafka集群的总体架构图吧。我从中抽取出Kafka集群消息存储架构图如下所示。
消息数据以Partition分区进行存储一个Topic主题可以由多个Partition进行存储Partition可以分布到多个Broker中同时Kafka还提供了Partition副本机制对分区存储的信息进行备份比如Broker 1中的Topic-1 Partion-0是对Broker 0上的Topic-1 Partition-0进行的备份从而保证了消息存储的可靠性。
<img src="https://static001.geekbang.org/resource/image/ef/9b/effd31ce90ac8ded8c3e72d6b18b1a9b.png" alt="">
这就是数据分片和数据复制共存的一个典型应用场景。
### 货架:存储数据
货架是用来存储数据的,因为数据是由顾客产生和消费的,因此货架存储的数据类型与顾客产生和消费的数据类型是一致的,即包括结构化数据、半结构化数据和非结构化数据。
**针对这三种不同的数据类型,存储“货架”可以大致划分为以下三种**
- 分布式数据库通过表格来存储结构化数据方便查找。常用的分布式数据库有MySQL Sharding、Microsoft SQL Azure、Google Spanner、Alibaba OceanBase等。
- 分布式键值系统通过键值对来存储半结构化数据。常用的分布式键值系统有Redis、Memcache等可用作缓存系统。具体的缓存技术我将在第27篇文章“分布式数据之缓存技术身手钥钱随身带”中与你详细介绍。
- 分布式存储系统通过文件、块、对象等来存储非结构化数据。常见的分布式存储系统有Ceph、GFS、HDFS、Swift等。
而**对货架材料也就是存储介质的选择,本质就是选择将数据存储在磁盘还是内存(缓存)上:**
- 磁盘存储量大但IO开销大访问速度较低常用于存储不经常使用的数据。比如电商系统中排名比较靠后或购买量比较少、甚至无人购买的商品信息通常就存储在磁盘上。
- 内存容量小,访问速度快,因此常用于存储需要经常访问的数据。比如,电商系统中,购买量比较多或排名比较靠前的商品信息,通常就存储在内存中。
## 知识扩展:业界主流的分布式数据存储系统有哪些?
在前面介绍货架的时候,我有提到针对结构化数据、半结构化数据和非结构化数据,分别对应不同的“货架”,即分布式数据库、分布式键值系统和分布式文件系统进行存储。
对于分布式键值系统我会在第27篇文章中进行讲解并与你介绍和分析主流存储系统。
所以在这里,我就重点与你对比分析分布式数据库和分布式文件系统的几款主流的系统,以便于你理解和选型。
首先我们看一下主流的分布式数据库主要包括MySQL Sharding、SQL Azure、Spanner、OceanBase等具体对比分析如下表所示。
<img src="https://static001.geekbang.org/resource/image/91/4e/9194bcd87dc55dd73e7fb2bf80cfc54e.jpg" alt="">
然后我们看一下主流的分布式存储系统主要包括Ceph、GFS、HDFS和Swift等具体对比分析如下所示。
<img src="https://static001.geekbang.org/resource/image/30/a3/30340066a300147f658dea9c03d8afa3.jpg" alt="">
## 总结
今天,我主要与你分享的是分布式数据存储系统的三要素,即顾客、导购和货架,对应到分布式领域的术语就是数据生产者/消费者、数据索引和数据存储。
其中,顾客包括产生数据的顾客和消费数据的顾客两类;导购,就是数据索引引擎,包括数据存储时确定数据位置,以及获取数据时确定数据所在位置;货架,负责数据存储,包括磁盘、缓存等存储介质等。
不同应用场景中,顾客产生的数据类型、格式等通常都不一样。根据数据的特征,这些不同的数据可以被划分为三类:结构化数据、半结构化数据和非结构化数据。与之相对应的,货架也就是数据存储系统,也包括三类:分布式数据库、分布式键值系统和分布式文件系统。
针对分布式数据库和分布式文件系统的主流框架我在“知识扩展模块”进行了对比分析以方便你理解、记忆与应用。而对于分布式键值系统我将在第27篇文章中进行详细介绍。
最后,我再通过一张思维导图来归纳一下今天的核心知识点吧。
<img src="https://static001.geekbang.org/resource/image/a9/b0/a99a26da898364eb03b00351ccda32b0.png" alt="">
相信通过今天的学习,你对分布式数据存储有了更深入的理解,对其中的核心角色和关键技术也有个更清晰的认识。加油,和我一起学习后面的章节,一起揭开分布式数据存储系统的神秘面纱吧!
## 思考题
传统单机关系型数据库与分布式数据库的区别是什么?
我是聂鹏程,感谢你的收听,欢迎你在评论区给我留言分享你的观点,也欢迎你把这篇文章分享给更多的朋友一起阅读。我们下期再会!

View File

@@ -0,0 +1,191 @@
<audio id="audio" title="25 | 数据分布方式之哈希与一致性哈希:“掐指一算”与“掐指两算”的事" controls="" preload="none"><source id="mp3" src="https://static001.geekbang.org/resource/audio/43/51/43dff6fa7ad0d9eb5cd86c6853a8b651.mp3"></audio>
你好!我是聂鹏程。今天,我来继续带你打卡分布式核心技术。
在上一篇文章中,我带你了解了分布式存储系统的三个要素:顾客、导购和货架。其中,导购实现了分布式数据存储系统中数据索引的功能,包括存储数据时确定存储位置,以及获取数据时确定数据所在位置。
那么,在分布式系统中,具体是如何实现数据索引或数据分布的呢?目前最常用的方法就是哈希和一致性哈希。
接下来,我们就一起打卡数据分布式方式中的哈希与一致性哈希吧。
首先,我们来看一下数据分布设计的原则。数据分布设计原则是分布式存储系统设计的基本原则,指导了哈希和一致性哈希方法的选择和应用。
## 数据分布设计原则
其实这里的数据分布主要就是数据分片。相信你还记得我在第24篇文章中与你分享分布式存储系统的导购时已经和你提到数据分片技术它解决了确定数据位置的问题并着重与你讲述了按照数据特征进行划分的分片方法。今天我主要与你讲解按照数据范围采用哈希、一致性哈希等对数据划分的方法。
假设现在有上百G数据需要进行分布式存储也就是要存储到不同的节点上。提到这个问题你可能立刻就会想到很多种方法比如随机分布、范围分布、映射分布等。那么我们应该如何选择到底要使用哪种方法呢
**在分布式数据存储系统中,存储方案选型时,通常会考虑数据均匀、数据稳定和节点异构性这三个维度。**
从**数据均匀**的维度考虑,主要包括两个方面:
- 不同存储节点中存储的数据要尽量均衡避免让某一个或某几个节点存储压力过大而其他节点却几乎没什么数据。比如现在有100G数据4个同类型节点通常希望数据存储时尽可能均衡比如每个节点存储25G数据。
- 另外用户访问也要做到均衡避免出现某一个或某几个节点的访问量很大但其他节点却无人问津的情况。比如现在有1000个请求对于上述存储数据的4个节点处理用户访问请求尽量均衡比如每个节点处理250个请求当然这是非常理想的情况实际情况下每个节点之间相差不太大即可。
从**数据稳定**的维度考虑,当存储节点出现故障需要移除或者扩增时,数据按照分布规则得到的结果应该尽量保持稳定,不要出现大范围的数据迁移。
比如现有100G数据刚开始有4个同类型节点节点1~4每个节点存储25G数据现在节点2故障了也就是说每个节点需要存储100G/3数据。
数据稳定就是尽可能只迁移节点2上的数据到其他节点上而不需要对大范围或所有数据进行迁移存储。当然如果有扩展同类型节点也是尽可能小范围迁移数据到扩展的节点上。具体的迁移方法可以采用下文介绍的一致性哈希方法。
从**节点异构性**的维度考虑,不同存储节点的硬件配置可能差别很大。比如,有的节点硬件配置很高,可以存储大量数据,也可以承受更多的请求;但,有的节点硬件配置就不怎么样,存储的数据量不能过多,用户访问也不能过多。
如果这种差别很大的节点,分到的数据量、用户访问量都差不多,本质就是一种不均衡。所以,一个好的数据分布算法应该考虑节点异构性。
当然除了上面这3个维度外我们一般还会考虑隔离故障域、性能稳定性等因素。
**隔离故障域**,是为了保证数据的可用和可靠性。比如,我们通常通过备份来实现数据的可靠性。但如果每个数据及它的备份,被分布到了同一块硬盘或节点上,就有点违背备份的初衷了。所以,一个好的数据分布算法,应该为每个数据映射一组存储节点,这些节点应该尽量在不同的故障域,比如不同机房、不同机架等。
**性能稳定性**是指,数据存储和查询的效率要有保证,不能因为节点的添加或者移除,造成存储或访问性能的严重下降。
了解了数据分布的设计原则后,接下来我们再看看主流的数据分布式方法,哈希和一致性哈希吧。其中,哈希和一致性哈希是数据分布的基础方法,在不同场景下,数据分布设计的原则需要考虑的维度也不一样。随着维度的增加,一致性哈希又可进一步演进为带有限负载的一致性哈希和带虚拟节点的一致性哈希方法。
接下来我们就一起看看这4种方法的具体原理和应用场景吧。
## 数据分布方法
哈希是指,将数据按照提前规定好的函数(哈希函数)映射到相应的存储节点,即进行一个哈希计算,得到的结果就是数据应该存储的节点。
一致性哈希同样是采用哈希函数,进行两步哈希:
1. 对存储节点进行哈希计算,也就是对存储节点做哈希映射;
1. 当对数据进行存储或访问时,首先对数据进行映射得到一个结果,然后找到比该结果大的第一个存储节点,就是该数据应该存储的地方。我会在下面的内容中,与你详细介绍其中的原理。
总结来讲,哈希是一步计算直接得到相应的存储节点,而一致性哈希需要两步才可以找到相应的存储节点。这,是不是就是“掐指一算”与“掐指两算”的事呢?
接下来,我们先一起看看哈希的具体原理吧。
### 哈希
哈希是一种非常常用的数据分布方法,其核心思想是,首先确定一个哈希函数,然后通过计算得到对应的存储节点。我们通过一个具体的例子来看一下吧。
假设有三个存储节点分别为Node1、Node2和Node3现有以下数据ID的范围为[0,1000]D0:{ id:100, name:a0}、D1:{ id:200, name:a1} 、D2:{ id:300, name:a2}、D3:{ id:400, name:a3}、D4:{ id:500, name:a4}、D5:{ id:600, name:a5}和D6:{ id:700, name:a6}。
假设哈希函数为“id%节点个数”通过计算可以得到每个数据应该存入的节点。在这个例子中哈希函数是“id%3”结果为0的数据存入Node1、结果为1的数据存入Node2、结果为2的数据存入Node3。
如图所示Node1将存储数据D2300%3=0和D5600%3=0Node2将存储数据D0100%3=1、D3400%3=1和D6700%3=1Node3将存储数据D1200%3=2和D4500%3=2
<img src="https://static001.geekbang.org/resource/image/ff/00/ff2578a3e75e85ffb8a3f63f87bc8600.png" alt="">
可以看出,哈希算法的一个优点是,只要哈希函数设置得当,可以很好地保证数据均匀性,但有一个较为严重的缺点,就是稳定性较差。
比如,随着数据量的增加,三个节点的容量无法再满足存储需求了,需要再添加一个节点。这时,哈希函数变成了 id%4原先存储在那三个节点的数据需要重新计算然后存入相应节点即需要大规模的数据迁移显然会降低稳定性。
所以,**哈希方法适用于同类型节点且节点数量比较固定的场景。**目前Redis就使用了哈希方法你可以再回顾下第10篇文章“分布式体系结构之非集中式结构众生平等”中的相关内容。
接下来,我们再看看一致性哈希吧。
### 一致性哈希
一致性哈希是指将存储节点和数据都映射到一个首尾相连的哈希环上存储节点可以根据IP地址进行哈希数据通常通过顺时针方向寻找的方式来确定自己所属的存储节点即从数据映射在环上的位置开始顺时针方向找到的第一个存储节点。
我们看看如何用一致性哈希方法,来实现上述案例的数据存储吧。
如图所示假设数据D0D7按照ID进行等值映射即映射值与ID值相等比如数据D0映射到哈希环上的值为100数据D1映射到哈希环上的值为200······同时假设存储节点Node1、Node2和Node3映射到哈希环上的值分别为400、600、900。
按照规则D0D1D2和D3顺时针方向的下一个存储节点为Node1因此Node1将存储数据D0id = 100、D1id = 200、D2id = 300和D3id = 400同理Node2将存取数据D4id = 500和D5id = 600Node3将存取数据D6id = 700
<img src="https://static001.geekbang.org/resource/image/c8/6c/c8098ceb11f706b93786f4cf9097d06c.png" alt="">
可以看出,**一致性哈希是对哈希方法的改进**,在数据存储时采用哈希方式确定存储位置的基础上,又增加了一层哈希,也就是在数据存储前,对存储节点预先进行了哈希。
这种改进可以很好地解决哈希方法存在的稳定性问题。当节点加入或退出时仅影响该节点在哈希环上顺时针相邻的后继节点。比如当Node2发生故障需要移除时由于Node3是Node2顺时针方向的后继节点本应存储到Node2的数据就会存储到Node3中其他节点不会受到影响因此不会发生大规模的数据迁移。
所以,**一致性哈希方法比较适合同类型节点、节点规模会发生变化的场景**。目前Cassandra就使用了一致性哈希方法你可以再回顾下第10篇文章“分布式体系结构之非集中式结构众生平等”中的相关内容。
一致性哈希方法虽然提升了稳定性,但随之而来的均匀性问题也比较明显,即对后继节点的负载会变大。有节点退出后,该节点的后继节点需要承担该节点的所有负载,如果后继节点承受不住,便会出现节点故障,导致后继节点的后继节点也面临同样的问题。
那么,有没有更好的方法来解决这个问题呢?
Google在2017年提出了带有限负载的一致性哈希算法就对这个问题做了一些优化。
### 带有限负载的一致性哈希
带有限负载的一致性哈希方法的核心原理是,给每个存储节点设置了一个存储上限值来控制存储节点添加或移除造成的数据不均匀。当数据按照一致性哈希算法找到相应的存储节点时,要先判断该存储节点是否达到了存储上限;如果已经达到了上限,则需要继续寻找该存储节点顺时针方向之后的节点进行存储。
我们看看如何用带有限负载的一致性哈希方法,来实现上述案例的数据存储吧。
如图所示假设每个存储节点设置的上限值为3按照一致性哈希算法当存储数据D3id = 400会发现应该存储到Node1中但Node1已经存储了三个数据D0id = 100、D1id = 200和D2id = 300达到了存储上限因此会存储到该节点顺时针方向的下一个节点Node2中。当然在存储前也会先检查Node2是否达到了存储上限如果达到了会继续寻找其他节点。
<img src="https://static001.geekbang.org/resource/image/ab/09/ab32844e83f73799d7d1123bda6e8309.png" alt="">
如果你想要了解该算法的详细内容,可以阅读“[Consistent Hashing with Bounded Loads](https://epubs.siam.org/doi/pdf/10.1137/1.9781611975031.39)”这篇论文。
**带有限负载的一致性哈希方法比较适合同类型节点、节点规模会发生变化的场景**。目前在Google Cloud Pub/Sub、HAProxy中已经实现该方法应用于Google、Vimeo等公司的负载均衡项目中。
其实,**哈希、一致性哈希、带有限负载的一致性哈希,都没有考虑节点异构性的问题。**如果存储节点的性能好坏不一,数据分布方案还按照这些方法的话,其实还是没做到数据的均匀分布。
接下来,我们再看一种主要针对存储节点为异构节点场景的方法,即带虚拟节点的一致性哈希吧。
### 带虚拟节点的一致性哈希
带虚拟节点的一致性哈希方法,核心思想是根据每个节点的性能,为每个节点划分不同数量的虚拟节点,并将这些虚拟节点映射到哈希环中,然后再按照一致性哈希算法进行数据映射和存储。
假设Node1性能最差Node2性能一般Node3性能最好。以Node1的性能作为参考基准Node2是Node1的2倍Node3是Node1的3倍。
因此Node1对应一个虚拟节点Node1_1Node2对应2个虚拟节点Node2_1和Node2_2Node3对应3个虚拟节点Node3_1、Node3_2和Node3_3。
假设虚拟节点Node1_1、Node2_1、Node2_2、Node3_1、Node3_2、Node3_3的哈希值分别为100、200、300、400、500、600。
那么,按照带虚拟节点的哈希一致性方法, 数据D0和D6按顺时针方向的下一个虚拟存储节点为Node 1-1因此节点Node1将会存储数据D0id = 100和D6id = 700同理Node2将会存储数据D1id = 200和D2id = 300Node3将会存储数据D3id = 400、D4id = 500和D5id = 600
<img src="https://static001.geekbang.org/resource/image/40/27/408bf17f91a77d89fbb2d41b6b3ec727.png" alt="">
可以看出,**带虚拟节点的一致性哈希方法比较适合异构节点、节点规模会发生变化的场景**。目前Memcached缓存系统实现了该方法我会在第27篇文章中与你详细分析。
这种方法不仅解决了节点异构性问题,还提高了系统的稳定性。当节点变化时,会有多个节点共同分担系统的变化,因此稳定性更高。
比如,当某个节点被移除时,对应该节点的多个虚拟节点均会移除,而这些虚拟节点按顺时针方向的下一个虚拟节点,可能会对应不同的物理节点,即这些不同的物理节点共同分担了节点变化导致的压力。
当然,这种方法引入了虚拟节点,增加了节点规模,从而增加了节点的维护和管理的复杂度,比如新增一个节点或一个节点故障时,对应到虚拟节点构建的哈希环上需要新增和删除多个节点,数据的迁移等操作相应地也会很复杂。
### 四种数据分布方法对比
为方便理解与记忆,我再通过一个表格和你对比分析下这四种方法吧。请注意,以下方法之间的对比都是相对的比较,实际性能优劣与哈希函数的设定以及具体的数据场景密切相关。
<img src="https://static001.geekbang.org/resource/image/ab/33/ab7df1d5a9b2c63b8929b18a0879d133.jpg" alt="">
## 知识扩展:数据分片和数据分区,有何区别?
首先我们一起回忆一下第20篇文章中提到的数据分区。
数据分区是从**数据存储块**的维度进行划分不同的分区物理上归属于不同的节点。比如现在有2个节点Node1和Node22个数据分区Partition1和Partition2Partition1属于Node1、Partition2属于Node2。
对于数据分区可用于存储不同的数据也可以用来存储相同的数据实现数据备份。数据分区可以归结为是“货架”相关的关键技术也就是为数据存储提供合适的位置。具体实例参见第20篇文章中介绍的Kafka分区的内容。
接下来,我们再看一下数据分片。
数据分片是从**数据**的维度进行划分,是指将一个数据集合按照一定的方式划分为多个数据子集,不同的数据子集存在不同的存储块上,而这些存储块可以在不同的节点上,也可以在同一节点上。
具体的数据分片策略可以采用我今天和你分享的哈希、一致性哈希等方法。数据分片是实现“导购”的关键技术,目的是构建索引,为数据确定位置,包括存储数据和查询数据时确定数据位置。具体例子,可参考上文的相关内容。
由此可见,**数据分片和数据分区是两个不同的概念,且属于分布式存储系统中不同角色的技术,前者是实现“导购”的关键技术,后者是“货架”相关的技术,不可直接等同。**
但正因为一个是导购相关的关键技术一个是货架相关的技术一个提供确定数据索引的位置一个提供合适的数据存储位置因此正如我在第24篇文章中所说这两个技术是可以共存的比如下面这个例子。
有3个节点{Node1, Node2, Node3}有3个分区{Partition1, Partition2, Partition3}用于存储用户信息每个节点上1个分区。现在有1000个用户信息需要存储用户id编号为[1,1000]为防止将所有信息存储到一个节点上所有用户发起请求时该节点成为瓶颈为此需要将这1000个用户信息存储到3个节点上。
假设我们采用最简单的哈希方法用户id%节点总数(3)进行哈希映射id%3 = 0的所有用户信息存储到节点1的Partition1 id%3=1的所有用户信息存储到节点2的Partition2id%3=2的所有用户信息存储到节点3的Partition3。
## 总结
今天,我主要带你学习了数据分布式方法中的哈希与一致性哈希。
首先,我带你了解了分布式数据存储系统中,设计数据分布方法需要考虑的原则,主要包括数据均匀性、稳定性和节点异构性。
其次,基于数据分布设计原则,我为你介绍了哈希、一致性哈希、带有限负载的一致性哈希和带虚拟节点的一致性哈希方法,并以例子进行辅助讲解,以便于你理解和学习。
最后,我再通过一张思维导图来归纳一下今天的核心知识点吧。
<img src="https://static001.geekbang.org/resource/image/a1/f8/a1fec7d2265432423bb9dfc0d7e4abf8.png" alt="">
加油,相信你通过对本讲的学习,对分布式数据存储系统中“导购”的角色有了更深入的理解,对分布式数据分布方法的原理,以及如何针对不同场景进行选型有了一定的认知。加油,赶紧行动起来,为你的应用场景选择一种合适的数据分布方法,并实践起来吧!
## 思考题
今天,我主要与你分享的是哈希方法相关的数据分布方法,你还知道哪些常用的数据分布方法吗?
我是聂鹏程,感谢你的收听,欢迎你在评论区给我留言分享你的观点,也欢迎你把这篇文章分享给更多的朋友一起阅读。我们下期再会!

View File

@@ -0,0 +1,142 @@
<audio id="audio" title="26 | 分布式数据复制技术:分身有术" controls="" preload="none"><source id="mp3" src="https://static001.geekbang.org/resource/audio/41/0d/412b84039fb0f8393f75e46f2a8d5b0d.mp3"></audio>
你好,我是聂鹏程。今天,我来继续带你打卡分布式核心技术。
在上一篇文章中,我为你讲解了数据分布(也称数据分片)技术,主要用于构建数据索引,是实现“导购”功能的关键技术。数据分布的本质是,将原数据集划分为多个数据子集,以存储到不同的地方,在一定程度上体现了数据的可用性和可靠性(一个存储节点故障,只影响该存储节点的数据)。
我在第24讲中介绍“导购”时提到数据分片和数据复制技术均是实现“导购”的关键技术。其中数据分片是确定数据位置数据复制是实现数据可靠性的关键方法。
**在实际情况下,仅考虑数据分片,其实是无法真正应用到生产环境的**。因为,故障导致数据丢失和不可用是很常见的情况。因此,在进行分布式数据存储设计时,通常会考虑对数据进行备份,以提高数据的可用性和可靠性,而实现数据备份的关键技术就是“数据复制技术”。
接下来,我们就一起打卡分布式数据复制技术吧。
## 什么是数据复制技术?
概括来讲数据复制是一种实现数据备份的技术。比如现在有节点1和节点2节点1上存储了10M用户数据直观地说数据复制技术就是将节点1上的这10M数据拷贝到节点2上以使得节点1和节点2上存储了相同的数据也就是节点2对节点1的数据进行了备份。当节点1出现故障后可以通过获取节点2上的数据实现分布式存储系统的自动容错。
也就是说,数据复制技术,可以保证存储在不同节点上的同一份数据是一致的。这样当一个节点故障后,可以从其他存储该数据的节点获取数据,避免数据丢失,进而提高了系统的可靠性。
这是不是就像数据有了自己的“分身”呢?那么,分布式系统是如何实现数据“分身有术”的呢?
接下来,我们通过一个例子来具体看下吧。
在分布式数据库系统中,通常会设置主备数据库,当主数据库出现故障时,备数据库可以替代主数据库进行后续的工作,从而保证业务的正常运行。这里,备数据库继续提供服务就是提高了分布式存储系统的可用性及可靠性。
那么,在这个过程中,又是如何实现备数据库替代主数据库的呢?这,就涉及到数据一致性的问题了,即只有主备数据库中的数据保持一致时,才可实现主备的替换。因此,在这个例子中,**数据复制技术实际就是指,如何让主备数据库保持数据一致的技术。**
理解了数据复制技术的基本含义,我们再一起看看数据复制技术的具体原理和应用吧。
## 数据复制技术原理及应用
不知你是否还记得,我在[第23篇文章](https://time.geekbang.org/column/article/166582)与你分享的CAP理论的C、A和P三个特性呢我曾提到在分布式存储系统中分区容错性是肯定要满足的为此需要在一致性和可用性之间做出权衡。
所以,对于数据的一致性,通常是指不同节点上数据要保持一致。要实现不同节点上的数据一致,数据复制技术必不可少。为此,对于分布式存储系统中的数据复制技术来讲,也需要在一致性和可用性之间做出一些权衡。因此,这就导致出现了多种数据复制技术方法,大体上有三类:
- 第一类方法,比较注重一致性,比如同步复制技术;
- 第二类方法,则更注重可用性,比如异步复制技术;
- 第三类方法,是介于前两者之间的,比如半同步复制技术。
接下来,我就针对同步数据复制技术、异步数据复制技术,以及半同步数据复制技术分别进行详细讲解。
### 同步复制技术原理及应用
同步复制技术是指,当用户请求更新数据时,主数据库必须要同步到备数据库之后才可给用户返回,即如果主数据库没有同步到备数据库,用户的更新操作会一直阻塞。这种方式保证了数据的强一致性,但牺牲了系统的可用性。
接下来,我们看一个具体的案例吧。
在一个分布式数据库系统中有两个节点分别作为主节点和备节点。通常情况下两个节点均可接收用户读请求然后将本节点的数据及时返回给用户也就是说读请求响应比较快。而如果用户发送的是写请求写操作必须由主节点进行即使用户将写请求发送到备节点备节点也会将该请求转发给主节点因此写请求通常比读请求响应慢。MySQL集群的读写分离就是一个典型实例。
如此设计的原因是,读请求不需要改变数据,只需要在更改数据时保证数据一致,就可以随时读;而写请求,因为要修改数据,如果每个节点均修改同一数据,则可能导致数据不一致。因此只有主节点可以进行写操作,但又要保证主节点和备节点的数据一致,这就是数据复制技术要发挥的作用了。
对于上述场景如果采用同步复制技术的话对于写请求主数据库会执行写操作并将数据同步到所有备数据库之后才可以响应用户。如图所示客户端向主数据库发起更新操作V将X设置为2主数据库会将写请求同步到备数据库备数据库操作完后会通知主数据库同步成功然后主数据库才会告诉客户端更新操作成功。MySQL集群支持的全复制模式就采用了同步复制技术。
<img src="https://static001.geekbang.org/resource/image/2e/73/2eba12801b2a5c17235b6aac569ab473.png" alt="">
在同步复制技术中,主数据库需要等待所有备数据库均操作成功才可以响应用户,性能不是很好,会影响用户体验,因此,**同步复制技术经常用于分布式数据库主备场景**(对于一主多备场景,由于多个备节点均要更新成功后,主节点才响应用于,所需时延比较长)**或对数据一致性有严格要求的场合**,比如金融、交易之类的场景。
### 异步复制技术原理及应用
异步复制技术是指,当用户请求更新数据时,主数据库处理完请求后可直接给用户响应,而不必等待备数据库完成同步,即备数据库会异步进行数据的同步,用户的更新操作不会因为备数据库未完成数据同步而导致阻塞。显然,这种方式保证了系统的可用性,但牺牲了数据的一致性。
如图所示,客户端 1向主数据库发起更新操作V主数据库执行该操作将X=1修改为X=2执行后直接返回给客户端 1更新操作成功而未将数据同步到备数据库。因此当客户端 2请求主数据库的数据X时可以得到X=2但客户端 3请求备数据库中的数据X时却只能得到X=1从而导致请求结果不一致。
<img src="https://static001.geekbang.org/resource/image/03/ff/03951adcbf196584fafe178d681091ff.png" alt="">
当然分布式数据库主备模式场景下若对数据一致性要求不高也可以采用异步复制方法。MySQL集群默认的数据复制模式采用的是异步复制技术我就以MySQL集群默认的复制模式为例与你简单描述下主备数据库同步的流程吧。
1. 主数据库完成写操作后可直接给用户回复执行成功将写操作写入binary log中binary log中记录着主数据库执行的所有更新操作以便备数据库获取更新信息。
1. 备数据库启动一个IO线程专门读取binary log中的内容然后写入relay log中。
1. 备数据库启动一个SQL线程会定时检查relay log里的内容如发现有新内容则会立即在备数据库中执行从而实现数据的一致。
<img src="https://static001.geekbang.org/resource/image/b6/ad/b61fea8af396e21ba2e4d67527ab95ad.png" alt="">
**异步复制技术大多应用在对用户请求响应时延要求很高的场景**比如很多网站或App等需要面向实际用户这时后台的数据库或缓存如果采用同步复制技术可能会流失用户因此这种场景采用异步复制技术就比较合适。
除了MySQL集群在缓存数据库Redis集群中采用的也是异步复制技术因此性能较高。但在Redis中还会有其他机制来保证数据的一致性我会在第27篇文章中与你详细介绍。
### 半同步复制技术原理及应用
同步复制技术会满足数据的强一致性,但会牺牲一定的可用性;异步复制技术会满足高可用,但一定程度上牺牲了数据的一致性。介于两者中间的是,半同步复制技术。
半同步复制技术的核心是,用户发出写请求后,主数据库会执行写操作,并给备数据库发送同步请求,但主数据库不用等待所有备数据库回复数据同步成功便可响应用户,也就是说主数据库可以等待一部分备数据库同步完成后响应用户写操作执行成功。
**半同步复制技术通常有两种方式:**
- 一种是,当主数据库收到多个备数据库中的某一个回复数据同步成功后,便可给用户响应写操作完成;
- 另一种是,主数据库等超过一半节点(包括主数据库)回复数据更新成功后,再给用户响应写操作成功。
显然,第二种半同步复制方案要求的一致性比第一种要高一些,但相对可用性会低一些。
前面所讲的MySQL集群在一主多备场景下也支持半同步复制模式一般采用的是第一种半同步复制技术这种技术既不会影响过多的性能还可以更好地实现对数据的保护。
还记得我在[第23篇文章](https://time.geekbang.org/column/article/166582)中提到的具有CP特性的ZooKeeper集群吗它采用的数据复制技术就是第二种半同步复制方案。在ZooKeeper集群中写请求必须由Leader节点进行处理每次写请求Leader会征求其他Follower的同意只有当多数节点同意后写操作才可成功因此保证了较高的一致性。
除此之外还有很多系统采用了第二种半同步复制方案比如微软云关系型数据库Microsoft SQL Azure的后端存储系统Cloud SQL Server、Kubenetes中保存集群所有网络配置和对象状态信息的Etcd组件该组件采用的是Raft一致性协议你可以再回顾下[第4篇文章](https://time.geekbang.org/column/article/143329)中的相关内容)等。
实际上,**多数的分布式存储系统可以通过配置来选择不同的数据复制技术**。比如上面讲过的MySQL数据库集群就支持全同步复制、异步复制和半同步复制三种模式再比如Oracle数据库也提供了三种模式
- 最大保护模式,对于写请求,要求主数据库必须完成至少一个备数据库的数据同步才可成功返回给客户端,采用的是半同步复制技术中的第一种方式。
- 最大性能模式,对于写请求,只要主数据库执行成功即可返回给客户端,采用的是异步复制技术。这种方式极大地提高了系统的可用性,但一致性难以保证。
- 最大可用性模式,介于最大保护模式和最大性能模式两者之间。这种模式是指,系统在通常情况下采用最大保护模式,但当主备之间出现网络故障时,切换为最大性能模式,等到网络恢复后,备数据库再进行数据同步。这种方式在系统的一致性和可用性之间做了一个权衡。
### 三种数据复制技术对比
以上,就是同步复制技术、异步复制技术和半同步复制技术的核心知识点了。接下来,我通过一张表格对比下这三种方法,以便于你记忆和理解。
<img src="https://static001.geekbang.org/resource/image/11/e6/1113ebfa8b40d4cd766a8708c7eb2ce6.jpg" alt="">
## 知识扩展:在半同步复制技术中,对于未回复数据更新结果的节点,如何解决数据不一致或冲突呢?
对于半同步复制技术,因为只有部分备节点更新数据后,主节点即可返回响应用户。那么,对于未回复数据更新结果的节点,如何解决可能存在的数据不一致或冲突呢?
对于这个问题不同的场景有不同的处理方式需要根据用户的需求进行选择比如以最新数据为准、以最大数据为准等没有统一的评判规则和用户的需求紧密相关。由于在分布式系统中很多系统采用了Raft算法你可以再回顾下[第4篇文章](https://time.geekbang.org/column/article/143329)中的相关内容因此这里我以Raft算法的处理策略为例与你展开介绍以便你理解大部分常用的分布式系统的处理策略。
我刚刚提到Raft算法采用的是第二种半同步复制技术也就是主数据库等超过一半节点包括主数据库回复数据更新成功后再给用户响应写操作成功。当有Follower节点的数据与Leader节点数据不一致时采用强制复制策略来解决不一致情况。
由于所有的数据更新操作最先在Leader节点执行因此当产生冲突时以Leader节点为准。Leader节点上会对比与自己数据不一致的Follower节点所存储的信息找到两者最后达成一致的地方然后强制将这个地方之后的数据复制到该Follower节点上。
**具体方法是,**Leader节点将每一次数据操作看作一条记录并对这条记录标记一个index用于索引。Leader节点会为每个Follower节点维护一个记录状态变量nextIndex即下一个记录的索引位置nextIndex的值为Leader节点当前存储数据记录的下一个Index值。Leader节点会将nextIndex发送给Follower节点若Follower节点发现与本节点的nextIndex不一致则告知Leader节点不一致Leader节点将nextIndex减1重复上述过程直到与Follower节点的nextIndex相等位置即找到了两者最后达成一致的地方。
比如对于变量XLeader节点记录的操作是{(Index 1, X = 1, Version:0), (Index 2, X=2, Version:1), (Index3 , X=3, Version:2)}其中Follower节点2记录的操作为{(Index 2, X=1, Version:0), (Index 6, X=4, Version:2)}。
那么Leader节点发现两者最后一致的状态是{(Index 1, X=1, Version:0)},为此将后续的{(Index 2, X=2, Version:1), (Index 3, X=3, Version:2)}复制到节点2上则节点2更新为(Index 1, X = 1, Version: 0), (Index 2, X=2, Version:1), (Index3 , X=3, Version:2)}。从而节点2与Leader节点的数据保持一致。
## 总结
今天,我主要和你分析的是分布式数据复制技术。
首先,我通过分布式数据库主备节点数据一致的例子,为你比较直观地讲解了什么是数据复制技术。
然后我为你介绍了数据复制技术的原理及应用以及同步复制技术、异步复制技术和半同步复制技术这三种方法。其中对于用户更新数据的请求同步复制技术要求主备节点均更新完成才返回结果告知用户更新成功异步复制技术只需要主节点更新成功就可返回结果半同步复制技术要求主节点更新成功且备节点中至少有1个或过半节点更新成功才可返回结果。
最后,我再通过一张思维导图来归纳一下今天的核心知识点吧。
<img src="https://static001.geekbang.org/resource/image/19/b6/1912e538e3469aa5188eb3be871c97b6.png" alt="">
相信通过今天的学习你对分布式数据复制技术已经有了深刻的理解并且对于不同技术适用的场景也有了自己的看法。加油赶紧行动起来相信你可以为你的业务场景选择一个合适的复制策略也能够比较容易看懂ZooKeeper等开源软件采用的复制技术的原理了。
## 思考题
本讲主要是从应用或分布式数据系统的角度介绍了三种比较基本的数据复制技术,你还知道一些其他的基于本讲介绍的复制技术的演进或变种的复制方法吗?
我是聂鹏程,感谢你的收听,欢迎你在评论区给我留言分享你的观点,也欢迎你把这篇文章分享给更多的朋友一起阅读。我们下期再会!

View File

@@ -0,0 +1,169 @@
<audio id="audio" title="27 | 分布式数据之缓存技术:“身手钥钱”随身带" controls="" preload="none"><source id="mp3" src="https://static001.geekbang.org/resource/audio/69/02/69563f0e61c8f42188ca9df43be05e02.mp3"></audio>
你好,我是聂鹏程。今天,我来继续带你打卡分布式核心技术。
不知不觉分布式数据存储这一站已经到了最后一讲。在前面几讲我与你分享了CAP理论、分布式存储系统的三要素顾客、导购和货架、数据分布式分片方法和数据复制技术其中数据分片方法和数据复制技术均是导购中的关键技术。
在这一讲,我将为你讲解分布式存储中“货架”的关键技术——缓存技术。
在计算机领域的各个方面,缓存都非常重要,是提升访问性能的一个重要技术。为什么这么说呢?
从单个计算机的体系结构来看,内存和处理器速度差异很大,如果不采用缓存技术,处理器的性能会受到很大的限制。
再看计算机应用,如果不采用缓存技术,对于每个请求,应用都要与后台数据库做一次交互,而数据库中的数据存储在磁盘上,因此每次请求都要和磁盘做交互,而磁盘访问的性能很低,造成访问延迟。
除此之外,还有网络访问,如果没有缓存机制,每次访问主机都要与远程机器做交互,速度又可想而知。
接下来,我们就一起打卡分布式缓存技术吧。
## 什么是分布式缓存?
打比方来说,缓存技术其实就像一个水缸,平时它会存储一定的水,而这些水就来自深井。如果每次都去深井打水,一方面井口比较小,导致一次能接收的用水请求有限;另一方面,井比较深,打水的工序比较复杂,导致所需时间比较长。
而有了这个水缸,我们就不需要去深井里打水,当水缸里没水时,水泵会将深井里的水抽到水缸中暂时存储起来。也就是说,“缓存技术”存储了满足人们一定时间内常用的“水量”,以提高用水效率。
在计算机领域,**缓存技术**一般是指,用一个更快的存储设备存储一些经常用到的数据,供用户快速访问。用户不需要每次都与慢设备去做交互,因此可以提高访问效率。
**分布式缓存**就是指在分布式环境或系统下,把一些热门数据存储到离用户近、离应用近的位置,并尽量存储到更快的设备,以减少远程数据传输的延迟,让用户和应用可以很快访问到想要的数据。这,是不是可以形象地理解为“身手钥钱”随身带呢?
其实,**我们通常说的分布式数据缓存,属于计算机应用中的缓存的一种**。而计算机应用中的缓存,一般指内存,即内存存储了用户经常访问的数据,用户或应用不再需要到磁盘中去获取相应的数据,大幅提高访问速度。
如下图所示数据A是应用经常访问的数据而数据B很少被应用访问因此当应用访问数据A时不需要到磁盘而直接访问内存即可得到对应的值速度较快相反访问数据B时由于内存中没有缓存数据B所以应用需要到磁盘中获取对应的值速度较慢。
<img src="https://static001.geekbang.org/resource/image/67/bf/67315610b5f0cb2b14588885a05335bf.png" alt="">
那么今天,我要与你分享的分布式数据存储相关的缓存技术,就是以内存做为磁盘的缓存。
## 分布式缓存原理
接下来我以主流的分布式缓存系统Redis和Memcached为例与你讲述分布式缓存技术以加深你的理解吧。
### Redis分布式缓存原理
Redis的全称是Remote Dictionary Server远程字典服务器。可以直观地看出它是以字典结构将数据存储在内存中应用可直接到内存读写Redis存储的数据。
Redis集群是一个典型的去中心化结构每个节点都负责一部分数据的存储同时每个节点还会进行主备设计来提高Redis的可靠性具体原理你可以再回顾下[第10篇文章](https://time.geekbang.org/column/article/149653)中的相关内容。
接下来我与你分享下Redis中与缓存关系最紧密的三个特性**:支持多数据结构、支持持久化和主备同步。**
**第一Redis支持多数据结构**
Redis是一个基于内存的key-value数据库为了方便支持多应用的缓存比如缓存文本类型、数据库的查询结果字段与字段对应的值等等支持的数据结构不仅有简单的kv类型还可以支持List、Set、Hash等复杂类型的存储。
以Hash这种复杂类型的存储为例Redis将Hash视作一个整体当作数据库的value可以是一个对象比如结构体对象进行存储。如果把Hash结构的整体看作对象的话Hash结构里的key-value相当于该对象的属性名和属性值。
比如插入Hash数据类型的命令HMSET test field1 “Hello” field2 “World”中如下图所示test为key值 field1 “Hello” field2 “World” 为value值如果把整个Hash结构看做对象的话则field1、field2类似于对象中的属性名“Hello”“World”类似于对象中的属性值。
<img src="https://static001.geekbang.org/resource/image/9f/1a/9f025bfd4d3731d378d01c4c19ede21a.png" alt="">
**第二Redis支持持久化。**
持久化是指将数据从内存这种易失性存储设备中写入磁盘从而让数据永久保存。Redis中存储的数据虽然是基于内存的但它也提供了持久化的机制主要有两种方式RDB和AOF。
**RDB**Redis DataBase也称快照方式简单来说就是Redis会定时将内存中的数据备份到磁盘中形成一个快照比如每天保存一下过去一周的数据。这样当节点出现故障时可以根据快照恢复到不同版本的数据。这种方式有一个明显的缺点是会造成数据丢失即当节点出现故障时新数据可能还未备份到磁盘中。
**AOF**Append Only File的出现主要弥补了RDB数据不一致的问题其思想与上一讲提到的数据库复制技术中binary log类似即记录下Redis中所有的更新操作。
在Redis中提供了三种实现AOF的策略
- AOF_FSYNC_NO (不同步),即不会自动触发写操作的同步;
- AOF_FSYNC_EVERYSEC (每秒同步),即每隔一秒都会将写操作同步到磁盘;
- AOF_FSYNC_ALWAYS (每次写都同步),即每次发生写操作会立即同步到磁盘。
Redis中默认采用AOF_FSYNC_EVERYSEC每秒同步的策略因为这种策略的性能很不错而且一旦出现故障最多只会丢失一秒的数据。
**第三Redis支持主备同步。**
说到主备同步我相信你应该想到了上一讲提到的数据复制技术。在Redis中采用的是异步复制技术但Redis可以通过配置min-replicas-to-write和min-replicas-max-lag这两个参数来有效地保证数据一致性。
比如设置min-replicas-to-write=3、min-replicas-max-lag=10表示当至少有3个备数据库连接主数据库的延迟时间小于10s时主数据库才可以执行写操作。延迟时间是从最后一次收到备数据库的心跳开始计算通常每秒发送一次心跳。
除了上面对写操作的同步在Redis中还有两种情况是需要进行数据同步的
- 一种情况是初次同步,即备数据库刚启动时的数据同步;
- 另一种情况是,因网络故障导致主备数据库断开连接,待网络恢复后的备数据库的数据同步。
针对这两种情况Redis提供了两种同步模式即完整重同步和部分重同步。
**完整重同步**的流程如下所示:
1. 当备服务器启动时会向主服务器发送SYNC命令
1. 主服务器收到命令后会生成RDB快照文件并记录从现在起新执行的写操作
1. RDB生成后会发送给备服务器备服务器通过RDB文件进行数据更新
1. 更新完成后,主服务器再将新记录的写操作发送给备服务器,备服务器执行完这些新记录的写操作,便与主服务器的数据保持一致了。
<img src="https://static001.geekbang.org/resource/image/4c/fa/4cc71fd16815139a2af93b9721a586fa.png" alt="">
简单地说,**部分重同步**就是,当网络恢复后,主数据库将主备数据库断开连接之后的一系列写操作发送给备服务器,备数据库执行这些写操作,从而保证数据保持一致。
在这种方式的实现中,主备数据库会共同维护一个复制偏移量,这样主数据库就知道应该将哪些写操作发给备数据库,备数据库同步时也知道应该从哪里继续执行操作。
如图所示主数据库的复制偏移量为5000时向备数据库发送了100个字节的数据发送结束后复制偏移量为5100。
此时主备数据库连接断开备数据库没有接收到这100个字节的数据等到备数据库重新与主数据库连接上之后会给主数据库发送PSYNC命令并带上自己的复制偏移量5000主数据库接收到信息后根据接收到的复制偏移量将5000之后的数据发给备数据库从而完成数据的部分重同步。
<img src="https://static001.geekbang.org/resource/image/3d/93/3da4db478862a8643d278333fdff5e93.png" alt="">
以上就是分布式缓存系统Redis中涉及的关键技术包括支持的数据结构、数据持久化方法和数据同步方法相信通过上面的介绍你对分布式缓存技术已经有了一定的了解。
接下来我再带你学习另一个缓存数据库Memcached。
### Memcached分布式缓存原理
与Redis类似Memcached也是一个基于内存的高性能key-value缓存数据库。Memcached比Redis问世更早也有很多公司在使用比如Facebook、Vox、LiveJournal等。
其实Memecached的缓存原理和Redis类似。所以接下来的内容我会着重于你讲述这两个数据库在支持的数据结构、持久化和主备同步上的不同。这样你可以对比着学习这两个数据库也会理解得更全面、深入些。
首先我要先带你了解一下Memcached的集群结构。
Redis的集群结构是每个节点负责一部分哈希槽且每个节点可以设计主备。与Redis不同Memcached集群采用一致性哈希的思路使用的是Ketama算法。该算法的主要思想就是**带虚拟节点的一致性哈希算法**。
在实际应用中每个物理节点对应100~200个虚拟节点才能到达一个较好的存储均衡。这里为了方便理解我对Memcached的集群结构做了简化如下图所示物理节点1对应两个虚拟节点1-1、1-2物理节点2对应三个虚拟节点2-1、2-2和2-3。
<img src="https://static001.geekbang.org/resource/image/af/68/af2a8805451df9b15d18a8672bd98c68.png" alt="">
采用带虚拟节点的一致性哈希方法,有一个优点是,当添加或移除节点时,不会出现大规模的数据迁移。你可以再回顾下[第25篇文章](https://time.geekbang.org/column/article/168940)中的相关内容。
对于数据结构的支持Memcached仅支持简单的kv数据类型如果想要存储复杂的数据类型比如List、Set和Hash等需要客户端自己处理将其转化为字符串然后进行存储。这样就导致了一个缺点操作不灵活。比如Memcached存储的数组中有一个元素需要修改则需要将整个数组的数据取出来修改后再整体写入到数据库中 。
而对于持久化Memcached是不支持的。这意味着断电时Memcached中存储的数据将会全部丢失。因为内存是一种易失性存储设备断电后将不会存储数据。
在Memcached中服务器和服务器之间没有任何通信即自身不支持主备。如果想要实现高可用需要通过第三方实现。比如Repcached实现了Memcached的复制功能支持一主一备从而使Memcached满足高可用。
### 对比分析
上面我以Redis和Memcached这两个主流的分布式缓存系统为例带你学习了分布式缓存技术。接下来我以一个表格对它们进行分析对比以便于你理解和查阅。
<img src="https://static001.geekbang.org/resource/image/4b/91/4b3069a37873b26db686a439a1921691.jpg" alt="">
## 知识扩展:除了分布式存储中的缓存,还有计算机体系结构和网络中的缓存,它们又分别是什么呢?
**计算机体系结构中的缓存**通常是指专用的缓存设备。由于内存和CPU访问速度相差很大为了提高CPU的性能计算机内部在CPU与内存之间设置了相应的缓存。
现在大多数机器分为三级缓存L1高级缓存、L2高级缓存和L3高级缓存。就访问速度来讲L1高级缓存 &gt; L2高级缓存 &gt; L3高级缓存&gt;内存。其中L1高级缓存的访问速度几乎和CPU中寄存器的访问速度一样快。
有了这三级缓存,很多数据不需要到内存中读取,而直接读取这三级缓存中的数据即可,缩短了数据访问的时间,使得计算机运行速度变得更快。
**网络访问中的缓存**,通常是指本地的“磁盘”。通过网络访问数据时,需要与远程服务器交互来进行传输,而网络间数据传输以及远程服务器对请求的响应,会耗费很多时间。如果本机器的磁盘可以对你经常访问的远程内容进行存储,这样就不用每次都与远程服务器交互,从而减少网络数据传输与服务器响应的延迟,极大地提高性能。
可以看出,**缓存的概念是相对的,基于不同的背景或应用场景,缓存所映射的存储设备是不一样的。**
## 总结
今天,我主要与你分享了分布式数据的缓存技术。
首先,我以水缸的例子带你直观了解了什么是缓存,并引出了什么是分布式数据缓存。分布式数据缓存是以内存作为磁盘的缓存,存储一些用户经常需要用的数据,以提高访问速度。
其次我以主流的Redis和Memcached为例与你介绍了分布式缓存技术中的关键技术包括支持的数据存储结构比如k/v、Set、List等、持久化技术包括快照方式等和数据同步技术具体技术原理可参见[第26篇文章](https://time.geekbang.org/column/article/168963))。
最后,我再通过一张思维导图来归纳一下今天的核心知识点吧。
<img src="https://static001.geekbang.org/resource/image/1e/45/1e625c22f729a87ea551f6bbc343f045.png" alt="">
相信通过本讲的学习你已不再觉得分布式缓存有多么神秘了不管是使用Redis还是看Redis等系统的源码一定会更容易理解和上手。加油行动起来吧
## 思考题
本讲我主要介绍了Redis和Memcached分布式数据缓存系统你还知道哪些主流的分布式数据缓存系统呢它们的缓存核心技术是什么呢
我是聂鹏程,感谢你的收听,欢迎你在评论区给我留言分享你的观点,也欢迎你把这篇文章分享给更多的朋友一起阅读。我们下期再会!