分布式

Posted by Liber Sun on September 28, 2018

我们这里首先需要区分分布式和集群的区别。

计算机集群简称集群是一种计算机系统,它通过一组松散集成的计算机软件和/或硬件连接起来高度紧密地协作完成计算工作。在某种意义上,他们可以被看作是一台计算机。集群系统中的单个计算机通常称为节点,通常通过局域网连接,但也有其它的可能连接方式。集群计算机通常用来改进单个计算机的计算速度和/或可靠性。一般情况下集群计算机比单个计算机,比如工作站或超级计算机性能价格比要高得多。

集群就是同一个代码在不同的计算机上运行。

分布式系统是一组计算机,通过网络相互连接传递消息与通信后并协调它们的行为而形成的系统。组件之间彼此进行交互以实现一个共同的目标。

分布式是将应用程序按模块功能进行划分,模块之间独立部署到不同的服务器,在使用时通过多个独立模块的相互调用从而构成一个应用。

分布式和服务化模式

什么是”分布式系统”?如果一下子就想到微服务、注册中心,就意味着你把服务化的模式(SOA,微服务)和分布式系统错误的划上了等号。

什么是服务化?服务化的本质分而治之,首先我们需要进行拆分,然后才能进行治理。在拆分的过程中,高内聚,低耦合的思想就起了一个非常重要的作用,我们要求 拆分之后的各个部分之间协作的复杂度应该在一个比较低的程度。而且往往拆分并不是一次性的拆分,往往需要在不断的实践中,循序渐进的拆分。 而我们采用了何种服务模式(SOA、微服务等),并不重要。

可以看到服务化体现了“分治”的效果,这也是分布式系统的核心思想,因此从“分治”这个本质上来看,服务化的确是分布式系统,但分布式系统不仅仅停留在那些服务化的模式上。

我们在解决一个问题的时候,需要做到拆分,通过梳理、归类,将不同的紧密相关的部分收敛到一个独立的逻辑体中。

分布式服务

分布式服务是一个挺有意思的东西,也很常用,简单来说,就是把服务组件部署在不同节点上,通过rpc的方式访问,为了实现这一功能,我们需要考虑通信协议,序列化方式,进一步来说,我们还要了解如何做服务注册和发现,以及如何做限流,做服务熔断和降级,等等等等。

常见的分布式服务框架有dubbo,以及Spring Cloud这类产品,学会使用他们,然后了解它们的底层实现原理,相信会是一个很有趣的过程。

分布式和中间件

又或许一提到”分布式系统”,我们就想到了MQ(消息队列)框架,RPC(Remote Procedure Call)框架或者DAL(Distributed Data Access Layer)框架,这样我们就把中间件和分布式系统划上了等号。

这里我们要认识到,中间件起了一个什么样的作用?标准化的作用!!! 中间件承载了标准化思想,在一定成都上起到了引导和约束的作用,以此减少了系统的复杂成都和协作成本。

  • MQ框架标准化了不同应用程序间非实时异步通信的方式。
  • RPC框架标准化了不同应用程序间实时通讯的方式。
  • DAL框架标准化了应用程序和数据库之间通讯的方式。

为什么我们需要标准化?这也是中间件存在的一个价值:避免我们将精力过多的花费在某个子功能下众多差异不大的选项中。 以DAL为例,标准化了访问数据库的过程;据库访问框架的作用是为了标准化操作不同数据库的差异,使得上层应用程序不用纠结于该怎么与mysql交互或者该怎么与SQL SERVER交互。

分布式系统必定会使用中间件,但分布式系统绝不仅仅是用了什么中间件。要了解每个中间件做了什么,以及它带来的优点和缺点,这样你才能从不同的,日新月异的框架中选出真正适合当前系统的技术框架。

RPC

RPC(Remote Procedure Call)—远程过程调用,它是一种通过网络从远程计算机程序上请求服务,而不需要了解底层网络技术的协议。 比如两个不同的服务A,B部署在两台不同的机器上,那么服务 A 如果想要调用服务 B 中的某个方法该怎么办呢?使用 HTTP请求 当然可以,但是可能会比较慢而且一些优化做的并不好。RPC 的出现就是为了解决这个问题。

使用RPC,能让我们像调用本地服务一样调用远程服务,而让调用者对网络通信这些细节透明,那么将大大提高生产力,比如服务消费方在执行orderService.buy(“HHKB键盘”)时,实质上调用的是远端的服务。

在RPC框架中,存在三个主要角色,分别是Provider、Consumer以及Registry。

实现RPC我们需要用到的技术涉及到:

  • 动态代理,生成client和server。
  • 序列化,传输java对象需要序列化
  • NIO,基于Netty的IO通信框架
  • 服务注册中心 ,Redis、Zookeeper、Consul、Etcd。

MQ

消息队列(英语:Message queue)是一种进程间通信或同一进程的不同线程间的通信方式,软件的贮列用来处理一系列的输入,通常是来自用户。 消息队列提供了异步的通信协议,每一个贮列中的纪录包含详细说明的数据,包含发生的时间,输入设备的种类,以及特定的输入参数,也就是说:消息的发送者和接收者不需要同时与消息队列互交。消息会保存在队列中,直到接收者取回它。

消息队列

消息队列是分布式系统中的重要组成组建,使用消息队列主要是为了异步处理请求提高系统性能消峰、降低系统耦合程度

特点:

  • 异步性,将耗时的同步操作,通过以发送消息的方式,进行了异步化处理。减少了同步等待的时间
  • 松耦合,消息队列减少了服务之间的耦合性,不同的服务可以通过消息队列进行通信,而不用关心彼此的实现细节,只要定义好消息的格式就行。
  • 分布式,通过对消费者的横向扩展,降低了消息队列阻塞的风险,以及单个消费者产生单点故障的可能性
  • 可靠性,消息队列一般会把接收到的消息存储到本地硬盘上,这样即使应用挂掉或者消息队列本身挂掉,消息也能够重新加载。

你不用知道具体的服务在哪,如何调用。你要做的只是将该发送的消息,向你们约定好的地址进行发送,你的任务就完成了。对应的服务自然能监听到你发送的消息,进行后续的操作。这就是消息队列最大的特点,将同步操作转为异步处理,将多服务共同操作转为职责单一的单服务操作,做到了服务间的解耦。

当然我们要意识到消息队列是一种FIFO的数据结构,因此我们消费的时候必须按照顺序消费,及生产者发送消息1,2,3,消费者必须按照1,2,3来消费。但是我们是不能保证消费的顺序一定的正确,以及我们不能保证消息不被重复消费,或者消息的传输的丢失乃至于数据不一致等等问题,因此引入MQ,务必会提高系统的复杂程度。

两种模式

1.点对点模式

生产者发送一条消息到queue,一个queue可以有很多消费者,但是一个消息只能被一个消费者接受,当没有消费者可用时,这个消息会被保存直到有 一个可用的消费者,所以Queue实现了一个可靠的负载均衡。

2.发布订阅模式

发布者发送到topic的消息,只有订阅了topic的订阅者才会收到消息。topic实现了发布和订阅,当你发布一个消息,所有订阅这个topic的服务都能得到这个消息,所以从1到N个订阅者都能得到这个消息的拷贝。

消息生产者向频道发布一个消息之后,多个消费者就可以从该频道订阅到这条消息,并进行消费。这里使用的是发布与订阅模式,和观察者模式有以下不同:

  • 观察者模式中,观察者和事件都知道对方的存在;而发布订阅模式,彼此不知道存在,他们通过频道进行通信。
  • 观察者模式是同步的,当事件触发时,观察者调用监听的方法,然后等待方法返回;而发布与订阅模式是异步的,生产者向频道发送一个消息之后,就不需要关心消费者何时去订阅这个消息,可以立即返回。

区别

消息队列的好处

1.通过异步处理提高系统性能

每个接口模块的吞吐能力是有限的,这个上限能力如果决堤,会导致整个系统的崩溃。

使用消息队列

在不使用消息队列服务器的时候,用户的请求数据直接写入数据库,在高并发的情况下数据库压力剧增,使得响应速度变慢。但是在使用消息队列之后,用户的请求数据发送给消息队列之后立即 返回,再由消息队列的消费者进程从消息队列中获取数据,异步写入数据库。由于消息队列服务器处理速度快于数据库(消息队列也比数据库有更好的伸缩性),因此响应速度得到大幅改善。

消息队列具有很好的削峰作用的功能——即通过异步处理,将短时间高并发产生的事务消息存储在消息队列中,从而削平高峰期的并发事务。

但是我们注意,因为用户请求数据写入消息队列之后就立即返回给用户了,但是请求数据在后续的业务校验、写数据库等操作中可能失败。 因此使用消息队列进行异步处理之后,需要适当修改业务流程进行配合,比如用户在提交订单之后,订单数据写入消息队列,不能立即返回用户订单提交成功,需要在消息队列的订单消费者进程真正处理完该订单之后,甚至出库后,再通过电子邮件或短信通知用户订单成功,以免交易纠纷。这就类似我们平时手机订火车票和电影票。

2.降低系统耦合性

我们的系统中如果存在耦合严重的接口,那么每次增加下层接口,都需要对上层的分发接口进行改造。

我们知道如果模块之间不存在直接调用,那么新增模块或者修改模块就对其他模块影响较小,这样系统的可扩展性就会更好一些。

我们最常见的事件驱动架构类似生产者消费者模式,在大型网站中通常用利用消息队列实现事件驱动结构。如下图所示:

生产者消费者模式

上图中利用,消息队列,生产者和消费者并没有直接耦合。消息发送者将消息发送至分布式消息队列即结束对消息的处理,消息接受者从分布式消息队列获取该消息后进行后续处理,并不需要知道该消息从何而来。这就类似于你写信对新增业务,只要对该类消息感兴趣,即可订阅该消息,对原有系统和业务没有任何影响,从而实现网站业务的可扩展性设计。

3.性能问题

RPC接口基本上是同步调用的,整体的服务性能遵循“木桶理论”,及依赖于最慢的那个接口。

消息队列的缺点

  • 系统可用性降低:在加入MQ之后,你需要考虑消息丢失或者MQ宕机的情形。
  • 系统复杂性提高:加入MQ之后,你需要保证消息没有被重复消费、处理消息丢失的情况、保证消息传递的顺序性等等问题!
  • 一致性问题: 会导致数据不一致的问题。

常见问题

  1. 引入消息队列之后如何保证高可用性
  2. 如何保证消息不被重复消费呢?
  3. 如何保证消息的可靠性传输(如何处理消息丢失的问题)?
  4. 我该怎么保证从消息队列里拿到的数据按顺序执行?
  5. 如何解决消息队列的延时以及过期失效问题?消息队列满了以后该怎么处理?有几百万消息持续积压几小时,说说怎么解决?
  6. 如果让你来开发一个消息队列中间件,你会怎么设计架构?

RabbitMQ

RabbitMQ 是采用 Erlang 语言实现 AMQP(Advanced Message Queuing Protocol,高级消息队列协议)的消息中间件,它最初起源于金融系统,用于在分布式系统中存储转发消息。 我们也将以RabbitMQ为案列来讲解消息队列。

1.生产者和消费者

一个消息一般由两部分组成:消息头和消息体。消息头由一系列的属性组成,属性包括routing-key、priority、delivery-mode等。生产者把消息交给MQ之后,MQ会将消息发给感兴趣的消费者

2.exchange(交换器)

在 RabbitMQ 中,消息并不是直接被投递到 Queue(消息队列) 中的,中间还必须经过 Exchange(交换器) 这一层,Exchange(交换器) 会把我们的消息分配到对应的 Queue(消息队列) 中。

Exchange(交换器) 用来接收生产者发送的消息并将这些消息路由给服务器中的队列中(根据routing-key),如果路由不到,或许会返回给 Producer(生产者) ,或许会被直接丢弃掉 。这里可以将RabbitMQ中的交换器看作一个简单的实体。

3.消息队列

Queue(消息队列) 用来保存消息直到发送给消费者。它是消息的容器,也是消息的终点。一个消息可投入一个或多个队列。消息一直在队列里面,等待消费者连接到这个队列将其取走。 多个消费者可以订阅同一个队列,这时队列中的消息会被平均分摊(Round-Robin,即轮询)给多个消费者进行处理,而不是每个消费者都收到所有的消息并处理,这样避免的消息被重复消费。

RabbitMQ使用流程

DAL

分布式的数据访问层,定义了应用程序如何访问数据库

分布式理论

CAP

任何基于网络的数据共享系统最多只能满足数据一致性(Consistency)、可用性(Availability)和网络分区容忍(Partition Tolerance)三个特性中的两个,三个特性的定义如下:

  • 数据一致性:等同于所有节点拥有数据的最新版本
  • 可用性:数据具备高可用性
  • 分区容忍:容忍网络出现分区,分区之间网络不可达

在大规模的分布式网络下,分区容忍是必须接受的现实。因此我们只能在一致性和可用性中做出选择。CAP似乎对于分布式系统给出了一个悲观的结局。但是我们要认识到

CAP理论是对分布式系统中一个数据无法同时达到可用性和一致性的断言,而一个系统中往往存在很多类型的数据,部分数据(譬如银行账户中的余额)是需要强一致性的,而另外一部分数据(譬如银行的总客户数)并不要求强一致性,所以拿CAP理论来划分整个系统是不严谨的, CAP理论带来的价值是指引我们在设计分布式系统时需要区分各种数据的特点,并仔细考虑在小概率的网络分区发生时究竟为该数据选择可用性还是一致性。


对CAP理论的另外一种误读是系统设计时选择其一而完全不去优化另外一项,可用性和一致性的取值范围并不是只有0和1,可用性的值域可以定义成0到100%的连续区间,而一致性也可分为强一致性、弱一致性、读写一致性、最终一致性等多个不同的强弱等级,细想下去CAP理论定义的其实是在容忍网络分区的条件下,“强一致性”和“极致可用性”无法同时达到。

当你使用分布式架构的时候,你必然会碰到CAP的问题。我们来看看CAP意味着什么?

C:数据一致性(consistency) 所有节点拥有数据的最新版本 A:可用性(availability) 数据具备高可用性 P:分区容错性(partition-tolerance) 容忍网络出现分区,分区之间网络不可达。

这三点中我们要意识到,分区容错性是必须的,是客观世界一定存在的问题。

但是我们也需要认识到,不是说为了P,我们就只能在 C 和 A 中选择一个。 分区虽然是一定存在的,但是出现的情况是比较少的,这样 CAP 就在大多数的时间上能够满足 C 和 A。

BASE

BASE理论是由eBay架构师提出的,是基本可用(Basically Available)、软状态(Soft State)和最终一致性(Eventually Consistent)三个短语的缩写。

BASE是对CAP中一致性和可用性权衡的结果,其来源于对大规模互联网分布式系统实践的总结,是基于CAP定律逐步演化而来。

其核心思想是即使无法做到强一致性,但每个应用都可以根据自身业务特点,才用适当的方式来使系统打到最终一致性。

BASE理论满足三点内容:

  • 基本可用 什么是基本可用?假设系统出现了问题,但是相较于正常的系统而言,还是可以使用的。
  • 软状态 相对于原子性而言,要求多个节点的数据副本都是一致的,这是一种“硬状态”。允许系统中的数据存在中间状态,并认为该状态不影响系统的整体可用性,即允许系统在多个不同节点的数据副本存在数据延时。
  • 最终一致性 上面说软状态,然后不可能一直是软状态,必须有个时间期限。在期限过后,应当保证所有副本保持数据一致性,从而达到数据的最终一致性。

总体来说BASE理论面向的是大型高可用、可扩展的分布式系统。BASE提出通过牺牲强一致性来获得可用性,并允许数据段时间内的不一致,但是最终达到一致状态。

由于BASE理论需要在一致性和可用性方面做出权衡,因此涌现了很多关于一致性的算法和协议。其中比较著名的有二阶提交协议(2 Phase Commitment Protocol),三阶提交协议(3 Phase Commitment Protocol)和Paxos算法。

2PC

二阶段提交的算法思路可以概括为:每个参与者将操作成败通知协调者,再由协调者根据所有参与者的反馈情报,决定各参与者是否要提交操作还是中止操作。

协调者与参与者

所谓的两个阶段分别是:

第一阶段:准备阶段(投票阶段) 第二阶段:提交阶段(执行阶段)

准备阶段

准备阶段分为三个步骤:

准备阶段

a.事务询问。

协调者向所有的参与者询问,是否准备好了执行事务,并开始等待各参与者的响应。

b.执行事务

各参与者节点执行事务操作。如果本地事务成功,将Undo和Redo信息记入事务日志中,但不提交;否则,直接返回失败,退出执行。

c.各参与者向协调者反馈事务询问的响应

如果参与者成功执行了事务操作,那么就反馈给协调者 Yes响应,表示事务可以执行提交;如果参与者没有成功执行事务,就返回No给协调者,表示事务不可以执行提交。

提交阶段

在提交阶段中,会根据准备阶段的投票结果执行2种操作:执行事务提交,中断事务。

提交事务过程:

提交事务过程:

a. 发送提交请求

协调者向所有参与者发出commit请求。

b. 事务提交

参与者收到commit请求后,会正式执行事务提交操作,并在完成提交之后,释放整个事务执行期间占用的事务资源。

c. 反馈事务提交结果

参与者在完成事务提交之后,向协调者发送Ack信息。

d. 事务提交确认

协调者接收到所有参与者反馈的Ack信息后,完成事务。

中断事务过程

中断事务过程

a. 发送回滚请求

协调者向所有参与者发出Rollback请求。

b. 事务回滚

参与者接收到Rollback请求后,会利用其在提交阶段种记录的Undo信息,来执行事务回滚操作。在完成回滚之后,释放在整个事务执行期间占用的资源。

c. 反馈事务回滚结果

参与者在完成事务回滚之后,想协调者发送Ack信息。

d. 事务中断确认

协调者接收到所有参与者反馈的Ack信息后,完成事务中断。

优缺点

优点:原理简单,实现方便。 缺点:同步阻塞,单点问题,数据不一致,容错性不好。

1.同步阻塞 在二阶段提交的过程中,所有的节点都在等待其他节点的响应,无法进行其他操作。这种同步阻塞极大的限制了分布式系统的性能。 2.单点问题 协调者在整个二阶段提交过程中很重要,如果协调者在提交阶段出现问题,那么整个流程将无法运转。更重要的是,其他参与者将会处于一直锁定事务资源的状态中,而无法继续完成事务操作。 3.数据不一致 假设当协调者向所有的参与者发送commit请求之后,发生了局部网络异常,或者是协调者在尚未发送完所有 commit请求之前自身发生了崩溃,导致最终只有部分参与者收到了commit请求。这将导致严重的数据不一致问题。 4.容错性不好 如果在二阶段提交的提交询问阶段中,参与者出现故障,导致协调者始终无法获取到所有参与者的确认信息,这时协调者只能依靠其自身的超时机制,判断是否需要中断事务。显然,这种策略过于保守。换句话说,二阶段提交协议没有设计较为完善的容错机制,任意一个节点是失败都会导致整个事务的失败。

3PC

3Pc

与两阶段提交不同的是,三阶段提交有两个改动点。

  1. 引入超时机制 - 同时在协调者和参与者中都引入超时机制。
  2. 在第一阶段和第二阶段中插入一个准备阶段,保证了在最后提交阶段之前各参与节点的状态是一致的。

相对于二阶段提交,三阶段提交主要解决的单点故障问题,并减少了阻塞的时间。 因为一旦参与者无法及时收到来自协调者的信息之后,他会默认执行 commit。而不会一直持有事务资源并处于阻塞状态。

三阶段提交也会导致数据一致性问题。由于网络原因,协调者发送的 abort 响应没有及时被参与者接收到,那么参与者在等待超时之后执行了 commit 操作。 这样就和其他接到 abort 命令并执行回滚的参与者之间存在数据不一致的情况。

Paxos

无法达到同时达到强一致性和极致可用性,但我们可以根据数据类型在二者中选择其一后去优化另外一个,Paxos协议就是一种在保证强一致性前提下把可用性优化到极限的算法。

Paxos 算法是 基于消息传递 且具有 高效容错特性 的一致性算法,目前公认的解决 分布式一致性问题 最有效的算法之一.

只要系统中2f+1个节点中的f+1个节点可用,那么系统整体就可用并且能保证数据的强一致性,它对于可用性的提升是极大的。

具体步骤如下:

  1. Proposer选择一个提案编号n,然后向半数以上的Acceptors发送编号为n的prepare请求。
  2. 如果一个Acceptor收到一个编号为n 的prepare请求,且 n 大于它已经响应的所有prepare请求的编号,那么它就会保证不会再通过(accept)任何编号小于 n 的提案,同时将它已经通过的最大编号的提案(如果存在的话)作为响应。

  3. 如果Proposer收到来自半数以上的Acceptor对于它的prepare请求(编号为n )的响应,那么它就会发送一个针对编号为 n ,value值为 v 的提案的accept请求给Acceptors,在这里 v 是收到的响应中编号最大的提案的值,如果响应中不包含提案,那么它就是任意值。
  4. 如果Acceptor收到一个针对编号n 的提案的accept请求,只要它还未对编号大于 n 的prepare请求作出响应,它就可以通过这个提案。

ZAB

Paxos协议虽然是完备的,但要把它应用到实际的分布式系统中还有些问题要解决:

  • 在多个Proposer的场景下,Paxos不保证先提交的提案先被接受,实际应用中要保证多提案被接受的先后顺序怎么办?
  • Paxos允许多个Proposer提交提案,那有可能出现活锁问题,出现场景是这样的:提案n在第二阶段还没有完成时,新的提案n+1的第一阶段prepare请求到达Acceptor,按协议规定Acceptor将响应新提案的prepare请求并保证不会接受小于n+1的任何请求,这可能导致提案n将不会被通过,同样在n+1提案未完成第二阶段时,假如提案n的提交者又提交了n+2提案,这可能导致n+1提案也无法通过。
  • Paxos协议规定提案的值v只要被大多数Acceptor接受过,后续的所有提案不能修改值v,那现实情况下我还要修改v值怎么办?

ZooKeeper的核心算法ZAB通过一个简单的约束解决了前2个问题:所有提案都转发到唯一的Leader(通过Leader选举算法从Acceptor中选出来的)来提交,由Leader来保证多个提案之间的先后顺序,同时也避免了多Proposer引发的活锁问题。

ZAB引入Leader后也会带来一个新问题: Leader宕机了怎么办?其解决方案是选举出一个新的Leader。

从保证一致性的算法核心角度看ZAB确实是借鉴了Paxos的多数派思想,但它提供的全局时序保证以及ZooKeeper提供给用户可修改的znode才让Paxos在开源界大放异彩,所以ZAB的价值不仅仅是提供了Paxos算法的优化实现,也难怪ZAB的作者一直强调ZAB和Paxos是不一样的算法。

Raft

Raft 也是一个 一致性算法,和 Paxos 目标相同。但它还有另一个名字 - 易于理解的一致性算法。Paxos 和 Raft 都是为了实现 一致性 产生的。这个过程如同选举一样,参选者 需要说服 大多数选民 (服务器) 投票给他,一旦选定后就跟随其操作。Paxos 和 Raft 的区别在于选举的 具体过程 不同。

CAP、Paxos、ZAB

CAP理论告诉我们在分布式环境下网络分区无法避免,需要去权衡选择数据的一致性和可用性,Paxos协议提出了一种极其简单的算法在保障数据一致性时最大限度的优化了可用性,ZooKeeper的ZAB协议把Paxos更加简化,并提供全局时序保证,使得Paxos能够广泛应用到工业场景。

分布式***

分布式事务

指事务的操作位于不同的节点上,需要保证事务的 ACID 特性。

例如在下单场景下,库存和订单如果不在同一个节点上,就涉及分布式事务。

分布式事务是一个复杂的概念,主要指分布式系统中需要强一致场景时所用到的事务。理解和实现它都不是简单的事情。

如果我们退而求其次,不要求强一致性,而选择最终一致性,则可以用更加灵活的方案,比如事务消息。

分布式Session

分布式session一般有多种实现方式,可以存数据库或者缓存,也可以单独部署成一个服务,总之最重要的一点就是,性能要好,并且要高可用。

分布式锁

在单机场景下,可以使用语言的内置锁来实现进程同步。但是在分布式场景下,需要同步的进程可能位于不同的节点上,那么就需要使用分布式锁。

分布式锁则用于一些需要一致性的场景中,比如订单生成这种全局唯一的功能,分布式锁通常可以用缓存或者数据库来实现,但为了保证高性能,并且避免死锁,我们一般采用Redis或者zookeeper来实现。

分布式主键

传统数据库软件开发中,主键自动生成技术是基本需求。而各个数据库对于该需求也提供了相应的支持,比如MySQL的自增键,Oracle的自增序列等。

但是随着应用的扩大,我们会考虑数据库的分片的问题,这个时候每个数据结点生产一个全局唯一主键就成了一个非常棘手的问题。 同一个逻辑表内的不同实际表之间的自增键由于无法互相感知而产生重复主键。

那么分布式主键我们需要考虑那些要求呢:

  • 全局唯一性:不能出现重复的ID号,既然是唯一标识,这是最基本的要求。
  • 趋势递增:在MySQL InnoDB引擎中使用的是聚集索引,由于多数RDBMS使用B-tree的数据结构来存储索引数据,在主键的选择上面我们应该尽量使用有序的主键保证写入性能。
  • 单调递增:保证下一个ID一定大于上一个ID,例如事务版本号、IM增量消息、排序等特殊需求。
  • 信息安全:如果ID是连续的,恶意用户的扒取工作就非常容易做了,直接按照顺序下载指定URL即可;如果是订单号就更危险了,竞对可以直接知道我们一天的单量。所以在一些应用场景下,会需要ID无规则、不规则。

其中3和4需求还是互斥的,无法使用同一个方案满足。

同时除了对ID号码自身的要求,业务还对ID号生成系统的可用性要求极高,想象一下,如果ID生成系统瘫痪,整个美团点评支付、优惠券发券、骑手派单等关键动作都无法执行,这就会带来一场灾难。

UUID

UUID可以在本地生成一个全局的唯一的ID,但是UUID太长,难以作为主键,同时其无序性会导致数据索引B+树的数据位置频繁变动。

类snowflake方案

这种方案大致来说是一种以划分命名空间(UUID也算,由于比较常见,所以单独分析)来生成ID的一种算法,这种方案把64-bit分别划分成多段,分开来标示机器、时间等。

其中的高位存储时间,保证了整个ID都是趋势递增的。但是其严格依赖机器的时钟,如果机器上时钟回拨,会导致发号重复或者服务会处于不可用状态。

Mongdb objectID是一种典型应用,通过“时间+机器码+pid+inc”共12个字节,通过4+3+2+3的方式最终标识成一个24长度的十六进制字符。

数据库生成

通过约束不同数据库的自增主键初始值和步长的方式避免碰撞。当具有大量的数据库是,会引入大量额外的运维规则,同时ID没有了单调递增的特性,只是趋势递增。

一致性Hash

增删服务器时,hash不失效,节点变动尽可能少。

  • 1.一致性Hash算法将整个哈希值空间组织成一个虚拟的圆环

Hash圆环

  • 2.将存储服务器的ip或者主机名作为关键字进行hash

主机Hash定位

  • 3.将服务器的存储对象进行hash,确定数据在环上的位置,放到哈希环顺时针第一个大于等于该hash值的服务器的结点上。这样从数据的hash位置沿环顺着服务器行走,第一台遇到的服务器必定是其定位的服务器

存储对象

  • 4.发生扩容和缩容时

如果NodeC宕机,其中NodeA,NodeB,NodeD都不会受到影响,只有C对象被重定位到NodeD。

宕机 扩容同理。

一致性Hash的特点

  • 单调性(Monotonicity),单调性是指如果已经有一些请求通过哈希分派到了相应的服务器进行处理,又有新的服务器加入、删除的时候,应保证原有的请求可以被映射到原有的或者新的服务器中去,而不会被映射到原来的其它服务器上去。
  • 平衡性(Balance):平衡性也就是说负载均衡,是指客户端hash后的请求应该能够分散到不同的服务器上去。一致性hash可以做到每个服务器都进行处理请求,但是不能保证每个服务器处理的请求的数量大致相同,

虚拟结点

上面描述的一致性哈希存在数据分布不均匀的问题,节点存储的数据量有可能会存在很大的不同。

数据不均匀主要是因为节点在哈希环上分布的不均匀,这种情况在节点数量很少的情况下尤其明显。

解决方式是通过增加虚拟节点,然后将虚拟节点映射到真实节点上。虚拟节点的数量比真实节点来得多,那么虚拟节点在哈希环上分布的均匀性就会比原来的真实节点好,从而使得数据分布也更加均匀。

总结

在分布式系统中一致性hash起着不可忽略的地位,无论是分布式缓存,还是分布式Rpc框架的负载均衡策略都有所使用。