分布式系统与消息的投递¶

消息是一个很是有趣的概念,它是由来源发出一个离散的通讯单元,被发送给一个或者一群接受者,不管是单体服务仍是分布式系统中都有消息的概念,只是这两种系统中传输消息的通道方法或者通道不一样;单体服务中的消息每每能够经过 IO、进程间通讯、方法调用的方式进行通讯,而分布式系统中的远程调用就须要经过网络,使用 UDP 或者 TCP 等协议进行传输。git

然而网络在计算机的世界中是最不可控的,若是咱们经过网络请求调用其余服务的接口,可能就会因为种种缘由没有将消息送达至目标的服务,对于当前服务咱们并不能控制网络的传输,在不少时候也很难控制网络通讯的质量,这也就是为何『网络是稳定、可信赖的』分布式系统中常见的谬论之一。安全

  • 通讯渠道的不可靠是形成构建大规模分布式系统很是复杂而且困难的重要缘由。

网络请求

做为分布式系统之间各个节点的通讯渠道,网络实际上是很是不可靠通讯方式,若是咱们想要保证节点状态的一致性,这种通讯方式的复杂性使得咱们在进行跨服务调用时须要处理很是多的边界条件,在以前的文章 分布式系统 · 分布式事务的实现原理 中简单介绍过,网络通讯可能会包含,成功、失败以及超时三种状况。网络

每一次网络请求其实都是一次信息的投递,因为当前的节点没法得知其余节点信息,只能经过网络请求的响应来得知此次信息投递的结果。架构

成功与失败

虽然网络的状况比较不稳定,可是咱们在大多数时候经过网络传输一些信息时,不管是返回的结果是成功仍是失败,其实都能获得肯定的结果:分布式

每一次肯定的响应都须要此次请求在一个往返以及被调用节点中正确处理,流量既不能被中间代理丢包,也不能因为目标节点的错误致使没法发出响应,只有在同时知足了这两个条件的状况下,咱们才能获得肯定的响应结果。对于节点来讲,此次请求返回成功仍是失败都比较好处理,由于只要有肯定的结果,网络请求这种通讯方式与进程间通讯或者方法调用这些更可靠的途径在处理上都没有太多的区别,可是在通讯的过程当中出现其余的问题时就比较棘手了。性能

超时

在分布式系统中,不是任何的网络请求都可以获得肯定的响应,若是网络请求在往返以及被调用节点处理的过程当中出现了丢包或者节点错误,发出请求的节点就可能永远也没法获得此次请求的响应。ui

每个节点在发出请求以后,都对此次请求如何路由以及被处理一无所知,因此节点须要设置一个合适的超时时间,若是请求没有在规定的时间内返回,就会认为当前请求已经超时,也就是网络请求失败了。spa

超时的网络请求是致使分布式系统难以处理的根本缘由之一,在这种问题发生时节点并不知道目标节点是否收到了当前请求,对于幂等的网络请求还好,一旦请求可能会改变目标节点的状态就很是棘手了,由于咱们并不能肯定上一次网络请求是在哪一步失败的,若是是响应返回的过程当中发生了故障,那么若是重试一些请求就会出现问题,可能会触发银行的两次转帐,这是咱们不管如何也没法接受的;总而言之,网络通讯的不稳定迫使咱们处理因为超时而出现的复杂问题,这也是在开发分布式系统时不得不考虑的。线程

消息投递语义

在分布式系统中使用网络进行通讯确实是一种不可靠的方式,消息的发送者只能知道掌控当前节点,因此没有办法保证传输渠道的可靠性,网络超时这种常见的通讯错误极大地增长了分布式系统通讯的复杂度,咱们能够对网络提供的基本传输能力进行封装,保证数据通讯的可靠性。3d

网络请求因为超时的问题,消息的发送者只能经过重试的方式对消息进行重发,可是这就可能会致使消息的重复发送与处理,然而若是超时后不从新发送消息也可能致使消息的丢失,因此如何在不可靠的通讯方式中,保证消息不重不漏是很是关键的。

咱们通常都会认为,消息的投递语义有三种,分别是最多一次(At-Most Once)、最少一次(At-Least Once)以及正好一次(Exactly Once),咱们分别会介绍这三种消息投递语义到底是如何工做的。

最多一次

最多一次其实很是容易保证的,UDP 这种传输层的协议其实保证的就是最多一次消息投递,消息的发送者只会尝试发送该消息一次,并不会关心该消息是否获得了远程节点的响应。

不管该请求是否发送给了接受者,发送者都不会从新发送这条消息;这其实就是最最基本的消息投递语义,然而消息可能因为网络或者节点的故障出现丢失。

最少一次

为了解决最多一次时的消息丢失问题,消息的发送者须要在网络出现超时从新发送相同的消息,也就是引入超时重试的机制,在发送者发出消息会监听消息的响应,若是超过了必定时间也没有获得响应就会从新发送该消息,直到获得肯定的响应结果。

对于最少一次的投递语义,咱们不只须要引入超时重试机制,还须要关心每一次请求的响应,只有这样才能确保消息不会丢失,可是却可能会形成消息的重复,这就是最少一次在解决消息丢失后引入的新问题。

正好一次

虽然最少一次解决了最多一次的消息丢失问题,可是因为重试却带来了另外一个问题 - 消息重复,也就是接受者可能会屡次收到同一条消息;从理论上来讲,在分布式系统中想要解决消息重复的问题是不可能的,不少消息服务提供了正好一次的 QoS 实际上是在接收端进行了去重。

消息去重须要生产者生产消息时加入去重的 key,消费者能够经过惟一的 key 来判断当前消息是不是重复消息,从消息发送者的角度来看,实现正好一次的投递是不可能的,可是从总体来看,咱们能够经过惟一 key 或者重入幂等的方式对消息进行『去重』。

消息的重复是不可能避免的,除非咱们容许消息的丢失,然而相比于丢失消息,重复发送消息实际上是一种更能让人接受的处理方式,由于一旦消息丢失就没法找回,可是消息重复却能够经过其余方法来避免反作用。

投递顺序

因为一些网络的问题,消息在投递时可能会出现顺序不一致性的状况,在网络条件很是不稳定时,咱们就可能会遇到接收方处理消息的顺序和生产者投递的不一致;想要知足绝对的顺序投递,其实在生产者和消费者的单线程运行时是相对比较好解决的,可是在市面上比较主流的消息队列中,都不会对消息的顺序进行保证,在这种大前提下,消费者就须要对顺序不一致的消息进行处理,常见的两种方式就是使用序列号或者状态机。

序列号

使用序列号保证投递顺序的方式其实与 TCP 协议中使用的 SEQ 很是类似,由于网络并不能保证全部数据包传输的顺序而且每一个栈帧的传输大小有限,因此 TCP 协议在发送数据包时加入 SEQ,接受方能够经过 SEQ 将多个数据包拼接起来并交由上层协议进行处理。

在投递消息时加入序列号其实与 TCP 中的序列号很是相似,咱们须要在数据以外增长消息的序列号,对于消费者就能够根据每一条消息附带的序列号选择如何处理顺序不一致的消息,对于不一样的业务来讲,常见的处理方式就是用阻塞的方式保证序列号的递增或者忽略部分『过时』的消息。

状态机

使用序列号确实可以保证消息状态的一致,可是却须要在消息投递时额外增长字段,这样消费者才能在投递出现问题时进行处理,除了这种方式以外,咱们也能够经过状态机的方式保证数据的一致性,每个资源都有相应的状态迁移事件,这些事件其实就是一个个消息(或操做),它们可以修改资源的状态:

在状态机中咱们能够规定,状态的迁移方向,全部资源的状态只能按照咱们规定好的线路进行改变,在这时只要对生产者投递的消息状态作必定的约束,例如:资源一旦 completed 就不会变成 failed,由于这两个状态都是业务逻辑中定义的最终状态,因此处于最终状态的资源都不会继续接受其余的消息。

假设咱们有以下的两条消息 active 和 complete,它们分别会改变当前资源的状态,若是一个处于 pending 状态的资源先收到了 active 再收到 complete,那么状态就会从 pending 迁移到 active 再到 completed;可是若是资源先收到 complete 后收到 active,那么当前资源的状态会直接从 pending 跳跃到 completed,对于另外一条消息就会直接忽略;从整体来看,虽然消息投递的顺序是乱序的,可是资源最终仍是经过状态机达到了咱们想要的正确状态,不会出现不一致的问题。

协议

消息投递其实有很是多相关的应用,最多见的组件就是消息队列了,做为一种在各个 Web 项目中经常使用的组件,它提供了不少能力,包括消息的持久存储、不一样的投递语义以及复杂的路由规则等等,可以显著地增长系统的可用性、起到比较比明显的削峰效果。

在这里将介绍几种比较常见的消息队列协议,咱们将简单说明各个协议的做用以及它们的实现原理和关键特性,也会简单说起一些遵循这些协议实现的消息队列中间件。

AMQP 协议

AMQP 协议的全称是 Advanced Message Queuing Protocol,它是一个用于面向消息中间件的开放标准,协议中定义了队列、路由、可用性以及安全性等方面的内容。

该协议目前可以为通用的消息队列架构提供一系列的标准,将发布订阅、队列、事务以及流数据等功能抽象成了用于解决消息投递以及相关问题的标准,StormMQ、RabbitMQ 都是 AMQP 协议的一个实现。

在全部实现 AMQP 协议的消息中间中,RabbitMQ 实际上是最出名的一个实现,在分布式系统中,它常常用于存储和转发消息,当生产者短期内建立了大量的消息,就会经过消息中间件对消息转储,消费者会按照当前的资源对消息进行消费。

RabbitMQ 在消息投递的过程当中保证存储在 RabbitMQ 中的所有消息不会丢失、推送者和订阅者须要经过信号的方式确认消息的投递,它支持最多一次和最少一次的投递语义,当咱们选择最少一次时,须要幂等或者重入机制保证消息重复不会出现问题。

MQTT 协议

另外一个用于处理发布订阅功能的常见协议就是 MQTT 了,它创建在 TCP/IP 协议之上,可以在硬件性能底下或者网络状态糟糕的状况下完成发布与订阅的功能;与 AMQP 不一样,MQTT 协议支持三种不一样的服务质量级别(QoS),也就是投递语义,最多一次、最少一次和正好一次。

从理论上来看,在分布式系统中实现正好一次的投递语义是不可能的,这里实现的正好一次实际上是协议层作了重试和去重机制,消费者在处理 MQTT 消息时就不须要关系消息是否重复这种问题了。

总结

在分布式系统中想要保证消息的送达确实是一件比较复杂的事情,通讯方式的不肯定使得咱们须要处理不少问题,咱们既须要在网络错误或者超时时进行重试,还须要对一些请求支持重入和幂等,保证不会出现一致性的错误;这其实都是由于在分布式系统中,正好一次的消息投递语义是不存在的,消息要么可能会丢失,要么就可能会重复。

相关文章
相关标签/搜索