分布式技术原理(八):分布式存储

目录

 

分布式存储

分布式系统设计原则

CAP准则

CAP策略选择

分布式存储系统三要素

三要素含义

主流分布式数据存储系统

数据分布及原则

数据分布设计原则

常见数据分布方法

分布式数据复制(副本)

同步复制

异步复制

半同步复制


分布式存储

分布式系统设计原则

分布式数据存储系统的设计需要首先遵循CAP 理论,CAP 理论指导分布式系统的设计以保证系统的可用性、数据一致性等特征,比如电商系统中,保证用户可查询商品数据、保证不同地区访问不同服务器查询的数据是一致的等。

CAP准则

C 代表 Consistency一致性,A 代表 Availability可用性,P 代表 Partition Tolerance,分区容错性。一致性、可用性和分区容错性,就是分布式系统的三个特征。假设某电商在北京、杭州、上海三个城市建立了仓库,同时建立了对应的服务器{A, B, C}用于存储商品信息。比如,某电吹风在北京仓库有 20 个,在杭州仓库有 10 个,在上海仓库有 30 个。那么,CAP 这三个字母在这个例子中分别代表什么呢?

C 代表 Consistency,一致性,是指所有节点在同一时刻的数据是相同的,即更新操作执行结束并响应用户完成后,所有节点存储的数据会保持相同,在电商系统中,A、B、C 中存储的该电吹风的数量应该是 20+10+30=60。假设,现在有一个北京用户买走一个电吹风,服务器 A 会更新数据为 60-1=59,与此同时要求 B 和 C 也更新为 59,以保证在同一时刻,无论访问 A、B、C 中的哪个服务器,得到的数据均是 59。

A 代表 Availability,可用性,是指系统提供的服务一直处于可用状态,对于用户的请求可即时响应。在电商系统中,用户在任一时刻向 A、B、C 中的任一服务器发出请求时,均可得到即时响应,比如查询商品信息等。

P 代表 Partition Tolerance,分区容错性,是指在分布式系统遇到网络分区的情况下,仍然可以响应用户的请求。网络分区是指因为网络故障导致网络不连通,不同节点分布在不同的子网络中,各个子网络内网络正常。在电商系统中,假设 C 与 A 和 B 的网络都不通了,A 和 B 是相通的。也就是说,形成了两个分区{A, B}和{C},在这种情况下,系统仍能响应用户请求。

CAP 理论指的就是,在分布式系统中 C、A、P 这三个特征不能同时满足,只能满足其中两个,如图所示:

分布式技术原理(八):分布式存储_第1张图片

假如网络中有两台服务器 Server1 和 Server2,分别部署了数据库 DB1 和 DB2,这两台机器组成一个服务集群,DB1 和 DB2 两个数据库中的数据要保持一致,共同为用户提供服务。用户 User1 可以向 Server1 发起查询数据的请求,用户 User2 可以向服务器 Server2 发起查询数据的请求,它们共同组成了一个分布式系统。

分布式技术原理(八):分布式存储_第2张图片

这个系统来说:

 

  • 在满足一致性 C 的情况下,Server1 和 Server2 中的数据库始终保持一致,即 DB1 和 DB2 内容要始终保持相同;
  • 在满足可用性 A 的情况下,用户无论访问 Server1 还是 Server2,都会得到即时响应;
  • 在满足分区容错性 P 的情况下,Server1 和 Server2 之间即使出现网络故障也不会影响 Server1 和 Server2 分别处理用户的请求。

当用户发起请求时,收到请求的服务器会及时响应,并将用户更新的数据同步到另一台服务器,保证数据一致性。具体的工作流程:

1.用户 User1 向服务器 Server1 发起请求,将数据库 DB1 中的数据 a 由 1 改为 2;

2.系统会进行数据同步,即图中的 S 操作,将 Server1 中 DB1 的修改同步到服务器 Server2 中,使得 DB2 中的数据 a 也被修改为 2;

3.当 User2 向 Server2 发起读取数据 a 的请求时,会得到 a 最新的数据值 2。

分布式技术原理(八):分布式存储_第3张图片

在实际场景中网络环境不可能百分之百不出故障,比如网络拥塞、网卡故障等,会导致网络故障或不通,从而导致节点之间无法通信,或者集群中节点被划分为多个分区,分区中的节点之间可通信,分区间不可通信,这种由网络故障导致的集群分区情况,通常被称为“网络分区”。在分布式系统中,网络分区不可避免,因此分区容错性 P 必须满足。接下来,我们就来讨论一下在满足分区容错性 P 的情况下,一致性 C 和可用性 A 是否可以同时满足。

假设,Server1 和 Server2 之间网络出现故障,User1 向 Server1 发送请求,将数据库 DB1 中的数据 a 由 1 修改为 2,而 Server2 由于与 Server1 无法连接导致数据无法同步,所以 DB2 中 a 依旧是 1。这时,User2 向 Server2 发送读取数据 a 的请求时,Server2 无法给用户返回最新数据,那么该如何处理呢?处理方式有如下两种。

1.保证一致性 C,牺牲可用性 A:Server2 选择让 User2 的请求阻塞,一直等到网络恢复正常,Server1 被修改的数据同步更新到 Server2 之后,即 DB2 中数据 a 修改成最新值 2 后,再给用户 User2 响应。

分布式技术原理(八):分布式存储_第4张图片

2.保证可用性 A,牺牲一致性 C:Server2 选择将旧的数据 a=1 返回给用户,等到网络恢复,再进行数据同步。

分布式技术原理(八):分布式存储_第5张图片

没有其他方案可以选择,可以看出在满足分区容错性 P 的前提下,一致性 C 和可用性 A 只能选择一个,无法同时满足。

CAP策略选择

分布式系统无法同时满足 CAP 这三个特性,那该如何进行取舍呢?其实,C、A 和 P,没有谁优谁劣,只是不同的分布式场景适合不同的策略。接下来,我就以一些具体场景为例,分别与你介绍保 CA 弃 P、保 CP 弃 A、保 AP 弃 C 这三种策略,以帮助你面对不同的分布式场景时,知道如何权衡这三个特征。

对于涉及钱的交易时,数据的一致性至关重要,因此保 CP 弃 A 应该是最佳选择。2015 年发生的支付宝光纤被挖断的事件,就导致支付宝就出现了不可用的情况。显然,支付宝当时的处理策略就是,保证了 CP 而牺牲了 A。而对于其他场景,大多数情况下的做法是选择 AP 而牺牲 C,因为很多情况下不需要太强的一致性(数据始终保持一致),只要满足最终一致性即可,最终一致性指的是,不要求集群中节点数据每时每刻保持一致,在可接受的时间内最终能达到一致就可以了

这个方案在应用节点之间引入了消息中间件,不同节点之间通过消息中间件进行交互,比如主应用节点要执行修改数据的事务,只需要将信息推送到消息中间件,即可执行本地的事务,而不需要备应用节点同意修改数据才能真正执行本地事务,备应用节点可以从消息中间件获取数据。

满足CA

在分布式系统中,现在的网络基础设施无法做到始终保持稳定,网络分区(网络不连通)难以避免。牺牲分区容错性 P,就相当于放弃使用分布式系统,既然分布式系统不能采用这种策略,那单点系统毫无疑问就需要满足 CA 特性了。比如关系型数据库 DBMS(比如 MySQL、Oracle)部署在单台机器上,因为不存在网络通信问题,所以保证 CA 就可以了。

满足CP

如果一个分布式场景需要很强的数据一致性,或者该场景可以容忍系统长时间无响应的情况下,保 CP弃 A 这个策略就比较适合,一个保证 CP 而舍弃 A 的分布式系统,一旦发生网络分区会导致数据无法同步情况,就要牺牲系统的可用性,降低用户体验,直到节点数据达到一致后再响应用户,这种策略通常用在涉及金钱交易的分布式场景,因为它任何时候都不允许出现数据不一致的情况,否则就会给用户造成损失,因此这种场景下必须保证CP。

保证 CP 的系统有很多,典型的有 Redis、HBase、ZooKeeper 等。接下来,我就以 ZooKeeper 为例,带你了解它是如何保证 CP 的,ZooKeeper 架构图:

分布式技术原理(八):分布式存储_第6张图片

ZooKeeper 集群包含多个节点(Server),这些节点会通过分布式选举算法选出一个 Leader 节点。在 ZooKeeper 中选举 Leader 节点采用的是 ZAB 算法,在 ZooKeeper 集群中,Leader 节点之外的节点被称为 Follower 节点,Leader 节点会专门负责处理用户的写请求:

1.当用户向节点发送写请求时,如果请求的节点刚好是 Leader,那就直接处理该请求;

2.如果请求的是 Follower 节点,那该节点会将请求转给 Leader,然后 Leader 会先向所有的 Follower 发出一个 Proposal,等超过一半的节点同意后,Leader 才会提交这次写操作,从而保证了数据的强一致性。

分布式技术原理(八):分布式存储_第7张图片

当出现网络分区时,如果其中一个分区的节点数大于集群总节点数的一半,那么这个分区可以再选出一个 Leader,仍然对用户提供服务,但在选出 Leader 之前,不能正常为用户提供服务;如果形成的分区中,没有一个分区的节点数大于集群总节点数的一半,那么系统不能正常为用户提供服务,必须待网络恢复后,才能正常提供服务。这种设计方式保证了分区容错性,但牺牲了一定的系统可用性。

满足AP

如果一个分布式场景需要很高的可用性,或者说在网络状况不太好的情况下,该场景允许数据暂时不一致,那这种情况下就可以牺牲一定的一致性了,网络分区出现后,各个节点之间数据无法马上同步,为了保证高可用,分布式系统需要即刻响应用户的请求。但,此时可能某些节点还没有拿到最新数据,只能将本地旧的数据返回给用户,从而导致数据不一致的情况。

适合保证 AP 放弃 C 的场景比如查询网站、电商系统中的商品查询等,用户体验非常重要,所以大多会保证系统的可用性,而牺牲一定的数据一致性。

以电商购物系统为例,如下图所示,某电吹风在北京仓库有 20 个,在杭州仓库有 10 个,在上海仓库有 30 个。初始时,北京、杭州、上海分别建立的服务器{A, B, C}存储该电吹风的数量均为 60 个。假如上海的网络出现了问题,与北京和杭州网络均不通,此时北京的用户通过北京服务器 A 下单购买了一个电吹风,电吹风数量减少到 59,并且同步给了杭州服务器 B。也就是说,现在用户的查询请求如果是提交到服务器 A 和 B,那么查询到的数量为 59。但通过上海服务器 C 进行查询的结果,却是 60。

分布式技术原理(八):分布式存储_第8张图片

当然,待网络恢复后,服务器 A 和 B 的数据会同步到 C,C 更新数据为 59,最终三台服务器数据保持一致,用户刷新一下查询界面或重新提交一下查询,就可以得到最新的数据。而对用户来说,他们并不会感知到前后数据的差异,到底是因为其他用户购买导致的,还是因为网络故障导致数据不同步而产生的。

这种场景适合优先保证 AP,因为如果等到数据一致之后再给用户返回的话,用户的响应太慢,可能会造成严重的用户流失。目前,采用保 AP 弃 C 的系统也有很多,比如 CoachDB、Eureka、Cassandra、DynamoDB 等。

分布式技术原理(八):分布式存储_第9张图片

分布式技术原理(八):分布式存储_第10张图片

 

分布式存储系统三要素

在分布式系统中,不能同时满足一致性、可用性和分区容错性,指导了分布式数据存储系统的设计,分布式存储系统的核心逻辑是将用户需要存储的数据根据某种规则存储到不同的机器上,当用户想要获取指定数据时,再按照规则到存储数据的机器里获取。

如下图所示,当用户(即应用程序)想要访问数据 D,分布式操作引擎通过一些映射方式,比如 Hash、一致性 Hash、数据范围分类等,将用户引导至数据 D 所属的存储节点获取数据。

分布式技术原理(八):分布式存储_第11张图片

获取数据的整个过程与你到商店购物的过程是不是有些类似,顾客到商店购物时,导购会根据顾客想要购买的商品引导顾客到相应的货架,然后顾客从这个货架上获取要购买的商品完成购物。这里的顾客就是图中的应用程序,导购就相当于分布式操作引擎,它会按照一定的规则找到相应的货架,货架就是存储数据的不同机器节点。

其实,这个过程就是分布式存储系统中获取数据的通用流程,顾客、导购和货架组成了分布式存储系统的三要素,分别对应着分布式领域中的数据生产者 / 消费者、数据索引和数据存储。

三要素含义

顾客就是数据的生产者和消费者,也就是说顾客代表两类角色,生产者会生产数据(比如,商店购物例子中的供货商就属于生产类顾客),将数据存储到分布式数据存储系统中,消费者是从分布式数据存储系统中获取数据进行消费(比如,商店购物例子中购买商品的用户就属于消费类顾客);导购就是数据索引,将访问数据的请求转发到数据所在的存储节点;货架就是存储设备,用于存储数据。

生产者和消费者:顾客相当于分布式存储系统中的应用程序,而数据是应用程序的原动力。根据数据的产生和使用,顾客分为生产者和消费者两种类型。生产者负责给存储系统添加数据,而消费者则可以使用系统中存储的数据。

就像是火车票存储系统,铁路局就相当于生产者类型的顾客,而乘客就相当于消费者类型的顾客,铁路局将各个线路的火车票信息发布到订票网站的后台数据库中,乘客通过订票网站访问数据库进行查询余票、订票、退票等操作。

分布式技术原理(八):分布式存储_第12张图片

生产者和消费者生产和消费的数据通常是多种多样的,不同应用场景中数据的类型、格式等都不一样。根据数据的特征,这些不同的数据通常被划分为三类:结构化数据、半结构化数据和非结构化数据。

结构化数据通常是指关系模型数据,其特征是数据关联较大、格式固定。火车票信息比如起点站、终点站、车次、票价等,就是一种结构化数据。结构化数据具有格式固定的特征,因此一般采用分布式关系数据库进行存储和查询。

半结构化数据通常是指非关系模型的,有基本固定结构模式的数据,其特征是数据之间关系比较简单。比如 HTML 文档,使用标签书写内容。半结构化数据大多可以采用键值对形式来表示,比如 HTML 文档可以将标签设置为 key,标签对应的内容可以设置为 value,因此一般采用分布式键值系统进行存储和使用。

非结构化数据是指没有固定模式的数据,其特征是数据之间关联不大。比如文本数据就是一种非结构化数据。这种数据可以存储到文档中,通过 ElasticSearch(一个分布式全文搜索引擎)等进行检索。

索引:索引是分布式存储系统必不可少的要素,就像没有导购,顾客就需要逐个货架去寻找自己想要的商品,如果你去订票网站订火车票,按照自己的需求点击查询车票后,系统会逐个扫描分布式存储系统中每台机器的数据,寻找你想要购买的火车票。如果系统中存储的数据不多,响应时间也不会太长,毕竟计算机的速度还是很快的;但如果数据分布在几千台甚至上万台机器中,系统逐个机器扫描后再给你响应,这种定位数据存储位置的方式会浪费时间严重影响体验。因此,在分布式存储系统中,必须有相应的数据导购,否则系统响应会很慢,效率很低。为解决这个问题,数据分片技术就走入了分布式存储系统的大家庭。

数据分片技术,是指分布式存储系统按照一定的规则将数据存储到相对应的存储节点中,或者到相对应的存储节点中获取想要的数据,这是一种很常用的导购技术。这种技术,一方面可以降低单个存储节点的存储和访问压力;另一方面,可以通过规定好的规则快速找到数据所在的存储节点,从而大大降低搜索延迟,提高用户体验。

也就是说当铁路局发布各个线路的火车票信息时,会按照一定规则存储到相应的机器中,比如北京到上海的火车票存储到机器 A 中,西安到重庆的火车票存储到机器 B 中。当乘客查询火车票时,系统就可以根据查询条件迅速定位到相对应的存储机器,然后将数据返回给用户,响应时间就大大缩短了。如图当查询北京 - 上海的火车票相关信息时,可以与机器 A 进行数据交互。

分布式技术原理(八):分布式存储_第13张图片

按照数据起点、终点的方式划分数据,将数据分为几部分存储到不同的机器节点中,就是数据分片技术的一种。当查询数据时,系统可以根据查询条件迅速找到对应的存储节点,从而实现快速响应。上述的例子中,按照数据特征进行了数据分片,当然,还有其他很多数据分片的方案。比如,按照数据范围,采用哈希映射、一致性哈希环等对数据划分。针对数据范围的数据分片方案是指,按照某种规则划分数据范围,然后将在这个范围内的数据归属到一个集合中。这就好比数学中通常讲的整数区间,比如 1~1000 的整数,[1,100] 的整数属于一个子集、[101,1000] 的整数属于另一个子集。

对于火车票的案例,按照数据范围分片的话可以将属于某条线的所有火车票数据划分到一个子集或分区进行存储,比如机器 A 存储京广线的火车票数据,机器 B 存储京沪线的火车票数据。也就是说,数据范围的方案是按照范围或区间进行存储或查询。如图所示,当用户查询北京 - 上海的火车票相关信息时,首先判断查询条件属于哪个范围,由于北京 - 上海的火车线路属于京沪线,因此系统按照规则将查询请求转到存取京沪线火车票数据的机器 B,然后由机器 B 进行处理并给用户返回响应结果。

分布式技术原理(八):分布式存储_第14张图片

为了提高分布式系统的可用性与可靠性,除了通过数据分片减少单个节点的压力外,数据复制也是一个非常重要的方法。数据复制就是将数据进行备份,以使得多个节点存储该数据。当某个存储节点出现故障时,如果只采用数据分片技术,那这个节点的数据就会丢失,从而给用户造成损失。因此,数据复制在分布式存储系统中是不可或缺的;

分布式技术原理(八):分布式存储_第15张图片

数据 A 被拆分为两部分存储在两个节点 Node1 和 Node2 上,属于数据分片;而对数据 B 来说,同一份完整的数据在两个节点中均有存储,就属于数据复制。在实际的分布式存储系统中,数据分片和数据复制通常是共存的:数据通过分片方式存储到不同的节点上,以减少单节点的性能瓶颈问题;

而数据的存储通常用主备方式保证可靠性,也就是对每个节点上存储的分片数据,采用主备方式存储,以保证数据的可靠性。其中,主备节点上数据的一致,是通过数据复制技术实现的。参考Kafka 集群消息存储架构图,这就是数据分片和数据复制共存的一个典型应用场景。

分布式技术原理(八):分布式存储_第16张图片

消息数据以 Partition(分区)进行存储,一个 Topic(主题)可以由多个 Partition 进行存储,Partition 可以分布到多个 Broker 中;同时,Kafka 还提供了 Partition 副本机制(对分区存储的信息进行备份,比如 Broker 1 中的 Topic-1 Partion-0 是对 Broker 0 上的 Topic-1 Partition-0 进行的备份),从而保证了消息存储的可靠性。

存储介质:存储介质是用来存储数据的,存储的数据类型与顾客产生和消费的数据类型是一致的,即包括结构化数据、半结构化数据和非结构化数据。针对这三种不同的数据类型,存储“货架”可以大致划分为以下三种:

分布式数据库:通过表格来存储结构化数据,方便查找。常用的分布式数据库有 MySQL Sharding、Microsoft SQL Azure、Google Spanner、Alibaba OceanBase 等。

分布式键值系统:通过键值对来存储半结构化数据。常用的分布式键值系统有 Redis、Memcache 等,可用作缓存系统;

分布式存储系统:通过文件、块、对象等来存储非结构化数据。常见的分布式存储系统有 Ceph、GFS、HDFS、Swift 等。

而对货架材料也就是存储介质的选择,本质就是选择将数据存储在磁盘还是内存(缓存)上:磁盘存储量大,但 IO 开销大,访问速度较低,常用于存储不经常使用的数据。比如,电商系统中,排名比较靠后或购买量比较少、甚至无人购买的商品信息,通常就存储在磁盘上。内存容量小,访问速度快,因此常用于存储需要经常访问的数据。比如,电商系统中,购买量比较多或排名比较靠前的商品信息,通常就存储在内存中。

主流分布式数据存储系统

在前面介绍货架的时候,我有提到针对结构化数据、半结构化数据和非结构化数据,分别对应不同的“货架”,即分布式数据库、分布式键值系统和分布式文件系统进行存储。对比分析分布式数据库和分布式文件系统的几款主流的系统,主流的分布式数据库包括 MySQL Sharding、SQL Azure、Spanner、OceanBase 等;

分布式技术原理(八):分布式存储_第17张图片

主流的分布式存储系统包括 Ceph、GFS、HDFS 和 Swift 等;

分布式技术原理(八):分布式存储_第18张图片

不同应用场景中,顾客产生的数据类型、格式等通常都不一样。根据数据的特征,这些不同的数据可以被划分为三类:结构化数据、半结构化数据和非结构化数据。与之相对应的,货架也就是数据存储系统,也包括三类:分布式数据库、分布式键值系统和分布式文件系统。

分布式技术原理(八):分布式存储_第19张图片

 

数据分布及原则

数据分布设计原则

数据分布主要就是数据分片,它解决了确定数据位置的问题,并着重与你讲述了按照数据特征进行划分的分片方法,假设现在有上百 G 数据需要进行分布式存储,也就是要存储到不同的节点上,你可能立刻就会想到很多种方法,比如随机分布、范围分布、映射分布等。

在分布式数据存储系统中,存储方案选型时,通常会考虑数据均匀、数据稳定节点异构性这三个维度。数据均匀的维度考虑,主要包括两个方面:

1.不同存储节点中存储的数据要尽量均衡,避免让某一个或某几个节点存储压力过大,而其他节点却几乎没什么数据。比如,现在有 100G 数据,4 个同类型节点,通常希望数据存储时尽可能均衡,比如每个节点存储 25G 数据。

2.用户访问也要做到均衡,避免出现某一个或某几个节点的访问量很大,但其他节点却无人问津的情况。比如,现在有 1000 个请求,对于上述存储数据的 4 个节点,处理用户访问请求尽量均衡,比如每个节点处理 250 个请求,当然这是非常理想的情况,实际情况下,每个节点之间相差不太大即可。

数据稳定的维度考虑:当存储节点出现故障需要移除或者扩增时,数据按照分布规则得到的结果应该尽量保持稳定,不要出现大范围的数据迁移。比如,现有 100G 数据,刚开始有 4 个同类型节点(节点 1~4),每个节点存储 25G 数据,现在节点 2 故障了,也就是说每个节点需要存储 100G/3 数据。

数据稳定就是尽可能只迁移节点 2 上的数据到其他节点上,而不需要对大范围或所有数据进行迁移存储。当然,如果有扩展同类型节点,也是尽可能小范围迁移数据到扩展的节点上;

节点异构性的维度考虑,不同存储节点的硬件配置可能差别很大。比如,有的节点硬件配置很高,可以存储大量数据,也可以承受更多的请求;但,有的节点硬件配置就不怎么样,存储的数据量不能过多,用户访问也不能过多。如果这种差别很大的节点,分到的数据量、用户访问量都差不多,本质就是一种不均衡。所以,一个好的数据分布算法应该考虑节点异构性。

除了上面 3 个维度外,还会考虑隔离故障域、性能稳定性等因素。

隔离故障域:为了保证数据的可用和可靠性,比如我们通常通过备份来实现数据的可靠性。但如果每个数据及它的备份,被分布到了同一块硬盘或节点上,就有点违背备份的初衷了。所以,一个好的数据分布算法,应该为每个数据映射一组存储节点,这些节点应该尽量在不同的故障域,比如不同机房、不同机架等。

性能稳定性:数据存储和查询的效率要有保证,不能因为节点的添加或者移除,造成存储或访问性能的严重下降。

主流的数据分布式方法包括哈希和一致性哈希。其中哈希和一致性哈希是数据分布的基础方法,在不同场景下,数据分布设计的原则需要考虑的维度也不一样。随着维度的增加,一致性哈希又可进一步演进为带有限负载的一致性哈希和带虚拟节点的一致性哈希方法。

常见数据分布方法

哈希是指将数据按照提前规定好的函数(哈希函数)映射到相应的存储节点,即进行一个哈希计算,得到的结果就是数据应该存储的节点。一致性哈希同样是采用哈希函数,进行两步哈希:

1.对存储节点进行哈希计算,也就是对存储节点做哈希映射;

2.当对数据进行存储或访问时,首先对数据进行映射得到一个结果,然后找到比该结果大的第一个存储节点,就是该数据应该存储的地方。

总结来讲,哈希是一步计算直接得到相应的存储节点,而一致性哈希需要两步才可以找到相应的存储节点。

哈希

哈希是一种非常常用的数据分布方法,其核心思想是,首先确定一个哈希函数,然后通过计算得到对应的存储节点。我们通过一个具体的例子来看一下吧。

假设有三个存储节点分别为 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 将存储数据 D2(300%3=0)和 D5(600%3=0),Node2 将存储数据 D0(100%3=1)、D3(400%3=1)和 D6(700%3=1),Node3 将存储数据 D1(200%3=2)和 D4(500%3=2)。

分布式技术原理(八):分布式存储_第20张图片

可以看出,哈希算法的一个优点是,只要哈希函数设置得当,可以很好地保证数据均匀性,但有一个较为严重的缺点,就是稳定性较差。比如随着数据量的增加,三个节点的容量无法再满足存储需求了,需要再添加一个节点。这时,哈希函数变成了 id%4,原先存储在那三个节点的数据需要重新计算,然后存入相应节点,即需要大规模的数据迁移,显然会降低稳定性。哈希方法适用于同类型节点且节点数量比较固定的场景。目前,Redis 就使用了哈希方法;

一致性哈希

一致性哈希是指将存储节点和数据都映射到一个首尾相连的哈希环上,存储节点可以根据 IP 地址进行哈希,数据通常通过顺时针方向寻找的方式,来确定自己所属的存储节点,即从数据映射在环上的位置开始,顺时针方向找到的第一个存储节点。

如图所示,假设数据 D0~D7 按照 ID 进行等值映射,即映射值与 ID 值相等,比如数据 D0 映射到哈希环上的值为 100,数据 D1 映射到哈希环上的值为 200…;同时,假设存储节点 Node1、Node2 和 Node3 映射到哈希环上的值分别为 400、600、900。

按照规则,D0,D1,D2 和 D3 顺时针方向的下一个存储节点为 Node1,因此 Node1 将存储数据 D0(id = 100)、D1(id = 200)、D2(id = 300)和 D3(id = 400);同理,Node2 将存取数据 D4(id = 500)和 D5(id = 600),Node3 将存取数据 D6(id = 700)。

分布式技术原理(八):分布式存储_第21张图片

可以看出一致性哈希是对哈希方法的改进,在数据存储时采用哈希方式确定存储位置的基础上,又增加了一层哈希,也就是在数据存储前,对存储节点预先进行了哈希,这种改进可以很好地解决哈希方法存在的稳定性问题,当节点加入或退出时,仅影响该节点在哈希环上顺时针相邻的后继节点。比如,当 Node2 发生故障需要移除时,由于 Node3 是 Node2 顺时针方向的后继节点,本应存储到 Node2 的数据就会存储到 Node3 中,其他节点不会受到影响,因此不会发生大规模的数据迁移。

一致性哈希方法比较适合同类型节点、节点规模会发生变化的场景。目前,Cassandra 就使用了一致性哈希方法,一致性哈希方法虽然提升了稳定性,但随之而来的均匀性问题也比较明显,即对后继节点的负载会变大。有节点退出后,该节点的后继节点需要承担该节点的所有负载,如果后继节点承受不住,便会出现节点故障,导致后继节点的后继节点也面临同样的问题,Google 在 2017 年提出了带有限负载的一致性哈希算法,就对这个问题做了一些优化。

带有限负载的一致性哈希

带有限负载的一致性哈希方法的核心原理是,给每个存储节点设置了一个存储上限值来控制存储节点添加或移除造成的数据不均匀。当数据按照一致性哈希算法找到相应的存储节点时,要先判断该存储节点是否达到了存储上限;如果已经达到了上限,则需要继续寻找该存储节点顺时针方向之后的节点进行存储。

如图所示,假设每个存储节点设置的上限值为 3,按照一致性哈希算法,当存储数据 D3(id = 400)时,会发现应该存储到 Node1 中,但 Node1 已经存储了三个数据 D0(id = 100)、D1(id = 200)和 D2(id = 300),达到了存储上限,因此会存储到该节点顺时针方向的下一个节点 Node2 中。当然,在存储前,也会先检查 Node2 是否达到了存储上限,如果达到了,会继续寻找其他节点。

分布式技术原理(八):分布式存储_第22张图片

如果想要了解该算法的详细内容,可以阅读“Consistent Hashing with Bounded Loads”这篇论文。带有限负载的一致性哈希方法比较适合同类型节点、节点规模会发生变化的场景。目前,在 Google Cloud Pub/Sub、HAProxy 中已经实现该方法,应用于 Google、Vimeo 等公司的负载均衡项目中。

哈希、一致性哈希、带有限负载的一致性哈希,都没有考虑节点异构性的问题。如果存储节点的性能好坏不一,数据分布方案还按照这些方法的话,其实还是没做到数据的均匀分布。针对存储节点为异构节点场景的方法,出现了 带虚拟节点的一致性哈希。

带虚拟节点的一致性哈希

带虚拟节点的一致性哈希方法,核心思想是根据每个节点的性能,为每个节点划分不同数量的虚拟节点,并将这些虚拟节点映射到哈希环中,然后再按照一致性哈希算法进行数据映射和存储。

假设Node1 性能最差,Node2 性能一般,Node3 性能最好。以 Node1 的性能作为参考基准,Node2 是 Node1 的 2 倍,Node3 是 Node1 的 3 倍。因此,Node1 对应一个虚拟节点 Node1_1,Node2 对应 2 个虚拟节点 Node2_1 和 Node2_2,Node3 对应 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 将会存储数据 D0(id = 100)和 D6(id = 700);同理,Node2 将会存储数据 D1(id = 200)和 D2(id = 300),Node3 将会存储数据 D3(id = 400)、D4(id = 500)和 D5(id = 600)。

https://static001.geekbang.org/resource/image/40/27/408bf17f91a77d89fbb2d41b6b3ec727.png

带虚拟节点的一致性哈希方法比较适合异构节点、节点规模会发生变化的场景。目前 Memcached 缓存系统实现了该方法,这种方法不仅解决了节点异构性问题,还提高了系统的稳定性。当节点变化时,会有多个节点共同分担系统的变化稳定性更高。

比如,当某个节点被移除时,对应该节点的多个虚拟节点均会移除,而这些虚拟节点按顺时针方向的下一个虚拟节点,可能会对应不同的物理节点,即这些不同的物理节点共同分担了节点变化导致的压力。当然,这种方法引入了虚拟节点,增加了节点规模,从而增加了节点的维护和管理的复杂度,比如新增一个节点或一个节点故障时,对应到虚拟节点构建的哈希环上需要新增和删除多个节点,数据的迁移等操作相应地也会很复杂。

分布式技术原理(八):分布式存储_第23张图片

分布式技术原理(八):分布式存储_第24张图片

 

分布式数据复制(副本)

在实际情况下仅考虑数据分片无法真正应用到生产环境,因为故障导致数据丢失和不可用是很常见的情况。因此在进行分布式数据存储设计时,通常会考虑对数据进行备份以提高数据的可用性和可靠性,而实现数据备份的关键技术就是“数据复制技术”。

数据复制是一种实现数据备份的技术。比如,现在有节点 1 和节点 2,节点 1 上存储了 10M 用户数据,直观地说,数据复制技术就是将节点 1 上的这 10M 数据拷贝到节点 2 上,以使得节点 1 和节点 2 上存储了相同的数据,也就是节点 2 对节点 1 的数据进行了备份。当节点 1 出现故障后,可以通过获取节点 2 上的数据,实现分布式存储系统的自动容错。

也就是说数据复制技术可以保证存储在不同节点上的同一份数据是一致的,这样当一个节点故障后,可以从其他存储该数据的节点获取数据,避免数据丢失,进而提高了系统的可靠性。

在分布式数据库系统中,通常会设置主备数据库,当主数据库出现故障时,备数据库可以替代主数据库进行后续的工作,从而保证业务的正常运行。这里,备数据库继续提供服务就是提高了分布式存储系统的可用性及可靠性,那么,在这个过程中,又是如何实现备数据库替代主数据库的呢?这就是数据一致性的问题了;

在分布式存储系统中,分区容错性是肯定要满足的,为此需要在一致性和可用性之间做出权衡。对于数据的一致性,通常是指不同节点上数据要保持一致。要实现不同节点上的数据一致,数据复制技术必不可少。为此,对于分布式存储系统中的数据复制技术来讲,也需要在一致性和可用性之间做出一些权衡。因此,这就导致出现了多种数据复制技术方法,大体上有三类:

第一类方法,比较注重一致性,比如同步复制技术;

第二类方法,则更注重可用性,比如异步复制技术;

第三类方法,是介于前两者之间的,比如半同步复制技术。

同步复制

同步复制技术是指,当用户请求更新数据时,主数据库必须要同步到备数据库之后才可给用户返回,即如果主数据库没有同步到备数据库,用户的更新操作会一直阻塞。这种方式保证了数据的强一致性,但牺牲了系统的可用性。

假如在一个分布式数据库系统中,有两个节点,分别作为主节点和备节点。通常情况下,两个节点均可接收用户读请求,然后将本节点的数据及时返回给用户,也就是说读请求响应比较快。而如果用户发送的是写请求,写操作必须由主节点进行,即使用户将写请求发送到备节点,备节点也会将该请求转发给主节点,因此写请求通常比读请求响应慢。MySQL 集群的读写分离就是一个典型实例。

如此设计的原因是读请求不需要改变数据,只需要在更改数据时保证数据一致,就可以随时读;而写请求,因为要修改数据,如果每个节点均修改同一数据,则可能导致数据不一致。因此只有主节点可以进行写操作,但又要保证主节点和备节点的数据一致,这就是数据复制技术要发挥的作用了。

对于上述场景,如果采用同步复制技术的话,对于写请求,主数据库会执行写操作,并将数据同步到所有备数据库之后才可以响应用户。如图所示,客户端向主数据库发起更新操作 V,将 X 设置为 2,主数据库会将写请求同步到备数据库,备数据库操作完后会通知主数据库同步成功,然后主数据库才会告诉客户端更新操作成功。MySQL 集群支持的全复制模式就采用了同步复制技术。

分布式技术原理(八):分布式存储_第25张图片

同步复制技术中主数据库需要等待所有备数据库均操作成功才可以响应用户,性能不是很好,会影响用户体验,因此,同步复制技术经常用于分布式数据库主备场景(对于一主多备场景,由于多个备节点均要更新成功后,主节点才响应用于,所需时延比较长)或对数据一致性有严格要求的场合,比如金融、交易之类的场景。

异步复制

异步复制技术是指,当用户请求更新数据时,主数据库处理完请求后可直接给用户响应,而不必等待备数据库完成同步,即备数据库会异步进行数据的同步,用户的更新操作不会因为备数据库未完成数据同步而导致阻塞。显然,这种方式保证了系统的可用性,但牺牲了数据的一致性。

如图所示,客户端 1 向主数据库发起更新操作 V,主数据库执行该操作,将 X=1 修改为 X=2,执行后直接返回给客户端 1 更新操作成功,而未将数据同步到备数据库。因此,当客户端 2 请求主数据库的数据 X 时,可以得到 X=2,但客户端 3 请求备数据库中的数据 X 时,却只能得到 X=1,从而导致请求结果不一致。

分布式技术原理(八):分布式存储_第26张图片

当然,分布式数据库主备模式场景下,若对数据一致性要求不高,也可以采用异步复制方法。MySQL 集群默认的数据复制模式采用的是异步复制技术,我就以 MySQL 集群默认的复制模式为例,与你简单描述下主备数据库同步的流程吧。

1.主数据库完成写操作后,可直接给用户回复执行成功,将写操作写入 binary log 中,binary log 中记录着主数据库执行的所有更新操作,以便备数据库获取更新信息。

2.备数据库启动一个 IO 线程专门读取 binary log 中的内容然后写入 relay log 中。

3.备数据库启动一个 SQL 线程会定时检查 relay log 里的内容,如发现有新内容则会立即在备数据库中执行,从而实现数据的一致。

分布式技术原理(八):分布式存储_第27张图片

异步复制技术大多应用在对用户请求响应时延要求很高的场景,比如很多网站或 App 等需要面向实际用户,这时后台的数据库或缓存如果采用同步复制技术,可能会流失用户,因此这种场景采用异步复制技术就比较合适。除了 MySQL 集群,在缓存数据库 Redis 集群中,采用的也是异步复制技术,因此性能较高;

半同步复制

同步复制技术会满足数据的强一致性,但会牺牲一定的可用性;异步复制技术会满足高可用,但一定程度上牺牲了数据的一致性。介于两者中间的是,半同步复制技术。同步复制技术的核心是用户发出写请求后,主数据库会执行写操作,并给备数据库发送同步请求,但主数据库不用等待所有备数据库回复数据同步成功便可响应用户,也就是说主数据库可以等待一部分备数据库同步完成后响应用户写操作执行成功。半同步复制技术通常有两种方式:

1.当主数据库收到多个备数据库中的某一个回复数据同步成功后,便可给用户响应写操作完成;

2.主数据库等超过一半节点(包括主数据库)回复数据更新成功后,再给用户响应写操作成功。

第二种半同步复制方案要求的一致性比第一种要高一些,但相对可用性会低一些。MySQL 集群,在一主多备场景下,也支持半同步复制模式,一般采用的是第一种半同步复制技术,这种技术既不会影响过多的性能,还可以更好地实现对数据的保护。ZooKeeper 集群采用的数据复制技术就是第二种半同步复制方案。在 ZooKeeper 集群中,写请求必须由 Leader 节点进行处理,每次写请求 Leader 会征求其他 Follower 的同意,只有当多数节点同意后写操作才可成功,因此保证了较高的一致性。

除此之外,还有很多系统采用了第二种半同步复制方案,比如微软云关系型数据库 Microsoft SQL Azure 的后端存储系统 Cloud SQL Server、Kubenetes 中保存集群所有网络配置和对象状态信息的 Etcd 组件(该组件采用的是 Raft 一致性协议)等。实际上,多数的分布式存储系统可以通过配置来选择不同的数据复制技术。比如上面讲过的 MySQL 数据库集群,就支持全同步复制、异步复制和半同步复制三种模式,再比如 Oracle 数据库,也提供了三种模式:

1.最大保护模式,对于写请求,要求主数据库必须完成至少一个备数据库的数据同步才可成功返回给客户端,采用的是半同步复制技术中的第一种方式。

2.最大性能模式,对于写请求,只要主数据库执行成功即可返回给客户端,采用的是异步复制技术。这种方式极大地提高了系统的可用性,但一致性难以保证。

3.最大可用性模式,介于最大保护模式和最大性能模式两者之间。这种模式是指,系统在通常情况下采用最大保护模式,但当主备之间出现网络故障时,切换为最大性能模式,等到网络恢复后,备数据库再进行数据同步。这种方式在系统的一致性和可用性之间做了一个权衡。

分布式技术原理(八):分布式存储_第28张图片

对于用户更新数据的请求,同步复制技术要求主备节点均更新完成,才返回结果告知用户更新成功;异步复制技术只需要主节点更新成功,就可返回结果;半同步复制技术,要求主节点更新成功,且备节点中至少有 1 个或过半节点更新成功,才可返回结果。

你可能感兴趣的:(分布式技术原理)