分布式技术与实战第五课 分布式-消息中间件选型

564 篇文章 136 订阅

第26讲:消息队列有哪些应用场景?

分布式系统不同模块之间的通信,除了远程服务调用以外,消息中间件是另外一个重要的手段,在各种互联网系统设计中,消息队列有着广泛的应用。从本课时开始,专栏进入分布式消息的模块,将讨论消息队列使用中的高频问题,先来看一下,消息队列的应用场景。

什么是消息队列

消息队列,顾名思义,就是传递消息的队列,学习操作系统中进程通信的时候我们知道,消息队列是进程之间的一种很重要的通信机制。随着分布式系统的发展,消息队列在系统设计中又有了更多的应用。

参与消息传递的双方称为生产者和消费者,生产者和消费者可以只有一个实例,也可以集群部署,典型架构如下图所示:

image.png

其中消息体是参与生产和消费两方传递的数据,消息格式既可以是简单的字符串,也可以是序列化后的复杂文档信息。队列是消息的载体,用于传输和保存消息,它和数据结构中的队列一样,可以支持先进先出、优先级队列等不同的特性。

消息队列有哪些应用

消息队列可以用于系统内部组件之间的通信,也可以用于系统跟其他服务之间的交互,消息队列的使用,增加了系统的可扩展性。下面把消息队列的应用归纳为以下几点。

系统解耦

设计模式中有一个开闭原则,指的是软件实体应该对扩展开放、对修改关闭,尽量保持系统之间的独立,这里面蕴含的是解耦思想。而消息队列的使用,可以认为是在系统中隐含地加入了一个对外的扩展接口,能够方便地对业务进行解耦,调用方只需要发送消息而不用关注下游逻辑如何执行。

image (1).png

那你可能会有疑问,系统之间的解耦,使用 RPC 服务调用也可以实现,使用消息队列有什么好处吗?使用远程服务调用,需要在其中一个调用方进行显式地编码业务逻辑;如果使用消息队列就不会有这个问题了,系统之间可以更好地实现依赖倒转,这也是设计模式中的一个重要原则。

异步处理

异步化是一个非常重要的机制,在处理高并发、高可用等系统设计时,如果不需要或者限制于系统承载能力,不能立即处理消息,此时就可以应用消息队列,将请求异步化。

异步处理的一个典型场景是流量削峰,我们用电商的秒杀场景来举例。秒杀抢购的流量峰值是很高的,很多时候服务并不能承载这么高的瞬间流量,于是可以引入消息队列,结合限流工具,对超过系统阈值的请求,在消息队列中暂存,等待流量高峰过去以后再进行处理。

请求缓冲

在典型的生产者和消费者模型中,就是通过一个队列来实现缓冲的。使用消息队列,可以作为一个缓冲层,平滑各个业务系统之间处理性能的不同等,在早期的企业应用系统中,有一个企业数据总线(ESB)的概念,实现的就是内部各个系统之间的集成。

数据分发

消息队列有不同的订阅模式,支持一对多的广播机制,可以用来实现数据的分发。典型的比如关系型数据库对 binlog 订阅的处理,由于主库的 binlog 只有一份,但是下游的消费方可能包括各种文件索引、离线数据库等,这时候就可以应用消息队列来实现数据的分发。

除了这些典型应用,消息队列还可以用来实现分布式事务,在第 06 课时“分布式事务有哪些解决方案”中我们提过,利用数据库+本地消息表的方式分布式一致性,是一个非常经典的分布式事务解决方案。

几种常见的消息队列

主流的消息中间件有以下几种,其中每种 MQ 又有其对应的应用场景。

Apache Kafka

大名鼎鼎的 Kafka 是高性能消息队列的代表,Kafka 是 LinkedIn 开源的一个分布式消息系统,主要使用 Scala 语言开发,已经加入 Apache 顶级项目。

Kafka 集群部署时依赖 ZooKeeper 环境,相比其他的消息队列,运维成本要高很多,ZooKeeper 的引入,使得 Kafka 可以非常方便地进行水平扩展,支持海量数据的传输。

Kafka 的另外一个特点是高吞吐率,在消息持久化写入磁盘的过程中,使用了多种技术来实现读写的高性能,包括磁盘的顺序读写、零拷贝技术等。

Apache RocketMQ

RocketMQ 是阿里巴巴开源的一款消息中间件,使用Java语言开发,在阿里内部应用非常广泛,很多高并发的业务场景下都有 RocketMQ 的应用。

RocketMQ 经过了双十一的检验,消息传递的稳定性和可靠性都比较有保障。以消息持久化为例,我们知道,Linux 文件在写入磁盘时,也就是常说的刷盘操作,因为存在缓存,可能会出现数据丢失的情况,RocketMQ 为了保证数据一致性,在写入磁盘时支持同步刷盘方式,即消息存储磁盘成功,才会返回消息发送成功的响应。

RocketMQ 在实现上有很多这种细节的设计,尽可能地保证了消息投递中的顺序一致性及可靠性,并且优化了响应时间,特别适合电商等相对复杂的业务中应用。

Apache RabbitMQ

RabbitMQ 是使用 Erlang 语言编写的一个开源消息队列,功能比较全面,支持多种消息传输的协议。

我们知道不同的消息队列有很多,为了约束其实现,也就有了一些对应的实现标准,AMQP 是一个异步消息传输的网络协议,RabbitMQ 是典型实现代表,除了 AMQP,RabbitMQ 同时支持 MQTT、STOMP 等协议,对于具体的协议内容,这里不展开,感兴趣的同学可以去找相关资料了解下。Kafka 和 RocketMQ 实现的是自定义协议,实现起来灵活度更高。

除了顺序传输,RabbitMQ 还可以支持优先级队列等特性,不过,它不适合处理大数据量的消息,一旦出现消息堆积,性能下降比较快,所以 RabbitMQ 比较适合企业级应用。

除了上面提到的三款主流消息队列,还有 ActiveMQ、ZeroMQ 等,也都有各自适合的应用场景。思考一下,如果在一个电商系统的构建中,这三款消息队列可以怎样组合使用呢?

Kafka 可以在各类数据埋点中使用,比如电商营销的转化率日志收集和计算,另外,Kafka 的高性能使得特别它适合应用在各类监控、大数据分析等场景。

RocketMQ 对一致性的良好保证,可以应用在电商各级业务调用的拆分中,比如在订单完成后通知用户,物流信息更新以后对订单状态的更新等。

RabbitMQ 则可以在数据迁移、系统内部的业务调用中应用,比如一些后台数据的同步、各种客服和 CRM 系统。

总结

这一课时分享了消息队列的知识点,包括消息队列的结构、消息队列的应用场景,以及几种常见的消息队列的应用。

通过本课时的学习,你已经了解了消息队列的基本应用,你可以结合自己的工作,思考一下都在哪些地方应用了消息队列,以及发挥了什么作用。继续扩展一下,如果让你来设计一个消息队列,应该怎么设计呢?比如消息体是否需要持久化?如何存储消息,如何保证消息的顺序投递,如果出现重复消费该如何解决,欢迎留言分享你的想法,关于这些问题的讨论,也会在后面的课时中展开讲解。


第27讲:集群消费和广播消费有什么区别?

为了规范消息队列中生产者和消费者的行为,消息中间件的构建中会实现不同的消费模型。这一课时讨论的话题来自 RocketMQ 中具体的两种消费模式,是消息队列中两种典型消费模型的实现。接下来我们就一起来看一下消息队列都有哪些消费模型,以及对应的具体实现。

消息队列的消费模型

先来看一下消息队列的两种基础模型,也就是点对点发布订阅方式。

这两种模型来源于消息队列的 JMS 实现标准,消息队列有不同的实现标准,比如 AMQP 和 JMS,其中 JMS(Java Message Service)是 Java 语言平台的一个消息队列规范,上一课时中讲过的 ActiveMQ 就是其典型实现。

AMQP 和 JMS 的区别是,AMQP 额外引入了 Exchange 的 Binding 的角色,生产者首先将消息发送给 Exchange,经过 Binding 分发给不同的队列。

Drawing 1.png

和 JMS 一样,AMQP 也定义了几种不同的消息模型,包括 direct exchange、topic change、headers exchange、system exchange 等。其中 direct exchange 可以类比点对点,其他的模型可以类比发布订阅,这里不做展开介绍了,具体可参考 AMPQ 的其他资料查阅。

点到点模型

在点对点模型下,生产者向一个特定的队列发布消息,消费者从该队列中读取消息,每条消息只会被一个消费者处理。

Drawing 3.png

发布/订阅模型

大部分人在浏览资讯网站时会订阅喜欢的频道,比如人文社科,或者娱乐新闻,消息队列的发布订阅也是这种机制。在发布订阅模型中,消费者通过一个 Topic 来订阅消息,生产者将消息发布到指定的队列中。如果存在多个消费者,那么一条消息就会被多个消费者都消费一次。

Drawing 5.png

点对点模型和发布订阅模型,主要区别是消息能否被多次消费,发布订阅模型实现的是广播机制。如果只有一个消费者,则可以认为是点对点模型的一个特例。

现代消息队列基本都支持上面的两种消费模型,但由于消息队列自身的一些特性,以及不同的应用场景,具体实现上还有许多的区别。下面看一下几种代表性的消息队列。

Kafka 的消费模式

先来看一下 Kafka,在分析 Kafka 消费模式之前,先来了解一下 Kafka 的应用设计。

Kafka 系统中的角色可以分为以下几种:

Drawing 7.png

  • Producer:消息生产者,负责发布消息到 broker。

  • Consumer:消息消费者,从 broker 中读取消息。

  • Broker:Broker 在 Kafka 中是消息处理的节点,可以对比服务器,一个节点就是一个 broker,Kafka 集群由一个或多个 broker 组成。

  • Topic:Topic 的语义和发布订阅模型中的主题是一致的,Kafka 通过 Topic 对消息进行归类,每一条消息都需要指定一个 Topic。

  • ConsumerGroup:消费组是对消费端的进一步拆分,每个消费者都属于一个特定的消费组,如果没有指定,则属于默认的消费组。

上面是一个 Kafka 集群的示意图,图中的 ZooKeeper 在 Kafka 中主要用于维护 Offset 偏移量,以及集群下的 Leader 选举,节点管理等。ZooKeeper 在 Kafka 中的作用,也是消息队列面试中的一个高频问题,感兴趣的同学可以去扩展一下。

从上面的分析中可以看到,Kafka 的消费是基于 Topic 的,属于发布订阅机制,它会持久化消息,消息消费完后不会立即删除,会保留历史消息,可以比较好地支持多消费者订阅。

RocketMQ 的消费模式

RocketMQ 实现的也是典型的发布订阅模型,在细节上和 Kafka 又有一些区别。RocketMQ 的系统设计主要由 NameServer、Broker、Producer 及 Consumer 几部分构成。

Drawing 8.png

NameServer 在 RocketMQ 集群中作为节点的路由中心,可以管理 Broker 集群,以及节点间的通信,在后面的消息队列高可用课时,我会进一步分析集群下的高可用实现。

具体的消费模式中,RocketMQ 和 Kafka 类似,除了 Producer 和 Consumer,主要分为 Message、Topic、Queue 及 ConsumerGroup 这几部分,同时,RocketMQ 额外支持 Tag 类型的划分。

  • Topic:在 RocketMQ 中,Topic 表示消息的第一级归属,每条消息都要有一个 Topic,一个 Group 可以订阅多个主题的消息。对于电商业务,根据业务不同,可以分为商品创建消息、订单消息、物流消息等。

  • Tag:RocetMQ 提供了二级消息分类,也就是 Tag,使用起来更加灵活。比如在电商业务中,一个订单消息可以分为订单完成消息、订单创建消息等,Tag 的添加,使得 RokcetMQ 中对消息的订阅更加方便。

  • ConsumerGroup:一个消费组可以订阅多个 Topic,这个是对订阅模式的扩展。

在 RocketMQ 中,一个 Topic 下可以有多个 Queue,正是因为 Queue 的引入,使得 RocketMQ 的集群具有了水平扩展能力。

在上一课时中提过, Kafka 使用 Scala 实现、RabbitMQ 使用 Erlang 实现,而 RokcetMQ 是使用 Java 语言实现的。从编程语言的角度,RocketMQ 的源码学习起来比较方便,也推荐你看一下 RokcetMQ 的源码,点击这里查看源码

RocketMQ 的消费模式分为集群消费广播消费两种,默认是集群消费。那么,在 RocketMQ 中这两种模式有什么区别呢?

集群消费实现了对点对点模型的扩展,任意一条消息只需要被集群内的任意一个消费者处理即可,同一个消费组下的各个消费端,会使用负载均衡的方式消费。对应 Topic 下的信息,集群消费模式的示意图如下。

Drawing 10.png

广播消费实现的是发布订阅模式,发送到消费组中的消息,会被多个消费者分别处理一次。在集群消费中,为了将消息分发给消费组中的多个实例,需要实现消息的路由,也就是我们常说的负载均衡,在 RocketMQ 中,支持多种负载均衡的策略,主要包括以下几种:

  • 平均分配策略,默认的策略

  • 环形分配策略

  • 手动配置分配策略

  • 机房分配策略

  • 一致性哈希分配策略

以上的几种策略,可以在 RocketMQ 的源码中 AllocateMessageQueueStrategy 接口相关的实现中:

Drawing 11.png

总结

这一课时分析了消息队列中的两种消息模型,以及不同消息模型在 Kafka 和 RocketMQ 等消息队列中的具体实现。

消息模型的概念是分布式消息的基础知识,不同的消息模型会影响消息队列的设计,进而影响消息队列在消息一致性、时序性,以及传输可靠性上的实现方式。了解了这些,才能更好地展开关于消息队列各种特性的讨论。

在分布式系统中,为了保证高可用,引入了各种集群和副本技术,使得实际消息队列中的实现往往要比模型定义中复杂很多。上面提到的 Kafka 和 RocketMQ 实现的都是以发布订阅模式为主,但是在另外一个消息队列 RabbitMQ 中,实现的就是点对点的消息传输模式。RabbitMQ 是 AMQP 模型的典型实现,那么 RabbitMQ 是如何实现集群扩展的呢,以及集群模式有哪些区别?感兴趣的同学可以找相关的资料来了解一下,欢迎留言分享。


第28讲:业务上需要顺序消费,怎么保证时序性?

消息传输和消费的有序性,是消息队列应用中一个非常重要的问题,在分布式系统中,很多业务场景都需要考虑消息投递的时序。例如,电商中的订单状态流转、数据库的 binlog 分发,都会对业务的有序性有要求。今天我们一起来看下,消息队列顺序消费的相关内容。

消息顺序消费有哪些困难

我们知道,消息队列中的队列是一个有序的数据结构,消息传递是顺序的,但在实际开发中,特别是在分布式场景下,消息的有序性是很难保证的,那么为什么实现有序性这么困难呢?下面进行拆解。

分布式的时钟问题

有序性可以分为业务上的有序和时间上的有序,先看一下时钟上的有序。在分布式环境下,消息的生产者、消费者和队列存储,可能分布在不同的机器上,不同的机器使用各自的本地时钟,由于服务器存在时钟偏斜等问题,本地时间会出现不一致,所以不能用消息发送和到达的时间戳作为时序判断标准。另一方面,分布式系统下缺乏全局时钟,这就使得绝对的时间顺序实现起来更加困难。

消息发送端和消费端的集群

在目前大多数消息队列的应用中,生产者和消费者都是集群部署,通过 ProducerGroup 和 ConsumerGroup 的方式来运行。

生产者如果存在多个发送实例,那么各个发送方的时间戳无法同步,所以消息发送端发送时的时序不能用来作为消息发送的有序判断。

同样的,消费端可能存在多个实例,即使队列内部是有序的,由于存在消息的分发过程,不同消费实例的顺序难以全局统一,也无法实现绝对的有序消费。

消息重传等的影响

我们知道,消息队列在传输消息时,可能会出现网络抖动导致的消息发送失败等,对这种场景的兼容,一般是通过进行合理地重传。消息的重传发生在什么时候是不可预知的,这也会导致消息传输出现乱序。

网络及内部并发

消息生产者集群或者消费端集群的方式,无法保证消息的绝对时序,如果只有一个消费端或者只有一个生产端呢?可以考虑这样一个场景,如果单纯地依靠消息队列本身来保证,那么在跨实例的情况下,因为网络传输的不稳定会有先后顺序,以及内部消费的并发等,仍然无法实现绝对有序。

通过上面的分析可以看到,保证消息绝对的有序,实现起来非常困难,除非在服务器内部,并且一个生产者对应一个消费者。但是这种情况的消息队列肯定是无法在实际业务中应用的,那么解决消息队列的有序性有哪些手段呢?下面从消息队列本身,以及业务设计上进行分析。

不同消息队列对顺序消费的保证

消息传输的有序性和不同的消息队列,不同业务场景,以及技术方案的实现细节等都有关系,解决消息传输的有序性,需要依赖消息队列提供对应的方式。

从消息队列自身的角度,可以分为全局有序和局部有序。当前大部分消息队列的应用场景都是集群部署,在全局有序的情况下,无法使用多分区进行性能的优化。在实际开发中,一般是应用局部有序,把业务消息分发到一个固定的分区,也就是单个队列内传输的方式,实现业务上对有序的要求。

以 Kafka 和 RocketMQ 为例,都实现了特定场景下的有序消息。

Kafka 顺序消息

Kafka 保证消息在 Partition 内的顺序,对于需要确保顺序的消息,发送到同一个 Partition 中就可以。单分区的情况下可以天然满足消息有序性,如果是多分区,则可以通过制定的分发策略,将同一类消息分发到同一个 Partition 中。

例如,电商系统中的订单流转信息,我们在写入 Kafka 时通过订单 ID 进行分发,保证同一个订单 ID 的消息都会被发送到同一个 Partition 中,这样消费端在消费的时候,可以保证取出数据时是有序的。

一个比较特殊的情况是消息失败重发的场景,比如同一个订单下的消息 1 和 2,如果 1 发送失败了,重发的时候可能会出现在 2 的后边,这种情况可以通过设置“max.in.flight.requests.per.connection”参数来解决,该参数可以限制客户端能够发送的未响应请求的个数,还可以在一定程度上避免这种消息乱序。

RocketMQ 顺序消息

RocketMQ 对有序消息的保证和 Kafka 类似,RocketMQ 保证消息在同一个 Queue 中的顺序性,也就是可以满足队列的先进先出原则。

如果把对应一个业务主键的消息都路由到同一个 Queue 中就可以实现消息的有序传输,并且 RocketMQ 额外支持 Tag 的方式,可以对业务消息做进一步的拆分,在消费时相对更加灵活。

从业务角度保证顺序消费

在我之前的项目中,消息消费的有序性,归根到底是一个业务场景的设计问题,可以在业务中进行规避,或者通过合理的设计方案来解决。

消息传输的有序性是否有必要

山不过来,我就过去,解决一个问题,如果从正面没有很好的解决方案,那么我们就可以考虑是否绕过它。考虑在你的业务中,是否必须实现绝对的消息有序,或者是否必须要有消息队列这样的技术手段。

比如在一个订单状态消息流转的业务场景中,订单会有创建成功、待付款、已支付、已发货的状态,这几个状态之间是单调流动的,也就是说,订单状态的更新需要保证有序性。考虑一下,如果我们要实现的功能是根据发货的状态,进行物流通知用户的功能,实际上因为这个状态是单调不可逆向的,我们可以忽略订单状态的顺序,只关注最后是否已发货的状态。

也就是说,在这个场景下,订单状态流转虽然是要考虑顺序,但是在具体的这个功能下,实际上不需要关注订单状态消息消费的时序。

业务中如何实现有序消费

除了消息队列自身的顺序消费机制,我们可以合理地对消息进行改造,从业务上实现有序的目的。具体的方式有以下几种。

  • 根据不同的业务场景,以发送端或者消费端时间戳为准

比如在电商大促的秒杀场景中,如果要对秒杀的请求进行排队,就可以使用秒杀提交时服务端的时间戳,虽然服务端不一定保证时钟一致,但是在这个场景下,我们不需要保证绝对的有序。

  • 每次消息发送时生成唯一递增的 ID

在每次写入消息时,可以考虑添加一个单调递增的序列 ID,在消费端进行消费时,缓存最大的序列 ID,只消费超过当前最大的序列 ID 的消息。这个方案和分布式算法中的 Paxos 很像,虽然无法实现绝对的有序,但是可以保证每次只处理最新的数据,避免一些业务上的不一致问题。

  • 通过缓存时间戳的方式

这种方式的机制和递增 ID 是一致的,即当生产者在发送消息时,添加一个时间戳,消费端在处理消息时,通过缓存时间戳的方式,判断消息产生的时间是否最新,如果不是则丢弃,否则执行下一步。

总结

这一课时讨论了消息队列有序性的话题,消息的有序性可以分为时间上的有序和业务上的有序。

通过上面的分析可以看到,绝对的时间有序实现起来是非常困难的,即使实现了这样的消息队列,但在实际应用中的意义并不大。消息队列只是一个消息传输的解决方案,不是软件开发中的银弹,一般来说,我们可以通过业务中不同的场景,进行合理的设计,实现业务上的有序性。

现在你可以思考一下,在你的项目中,哪些场景要求消息传输和消费的有序性,具体是如何解决的?欢迎留言进行分享。


第29讲:消息幂等:如何保证消息不被重复消费?

应用的幂等是在分布式系统设计时必须要考虑的一个方面,如果对幂等没有额外的考虑,那么在消息失败重新投递,或者远程服务重试时,可能会出现许多诡异的问题。这一课时一起来看一下,在消息队列应用中,如何处理因为重复投递等原因导致的幂等问题。

对业务幂等的理解

首先明确一下,幂等并不是问题,而是业务的一个特性。幂等问题体现在对于不满足幂等性的业务,在消息重复消费,或者远程服务调用失败重试时,出现的数据不一致,业务数据错乱等现象。

幂等最早是一个数学上的概念,幂等函数指的是对一个函数或者方法,使用相同的参数执行多次,数据结果是一致的。

以 HTTP 协议为例,我们知道 HTTP 协议中定义了交互的不同方法,比如 GET 和 POST,以及 PUT、DELETE 等,其中 GET、DELETE 等方法都是幂等的,而 POST 方法不是。

这个很好理解,GET 方法用于获取资源,不管调用多少次接口,结果都不会改变,所以是幂等的,DELETE 等可以类比。

这里有一点需要注意,业务上的幂等指的是操作不影响资源本身,并不是每次读取的结果都保证一致。比如通过 GET 接口查询一条订单记录,在多次查询的时间段内,订单状态可能会有新的更新而发生变化,查询到的数据可能不同,但是读接口本身仍然是一个幂等的操作。

在业务开发中对数据的操作主要是 CRUD,即在做数据处理时的 Create、Read、Update、Delete 这几种操作。很明显,这里的 Create 操作不是幂等的,Update 操作可能幂等也可能不幂等。例如,现在有一个订单表,下面的操作就是幂等的:

UPDATE order SET status=1 WHERE id=100

下面的这个操作,就不符合幂等性的要求:

UPDATE order SET price=price+1 WHERE id=100

对应的,Read 和 Delete 操作则是幂等的。

各类中间件对幂等性的处理

幂等处理不好,可能会出现很多问题,比如使用 binlog 分发进行数据同步,如果数据库更新消息被多次消费,可能会导致数据的不一致。

  • 远程服务调用的幂等问题

因为存在网络抖动等,远程服务调用出现失败,一般是通过配置重试,保证请求调用成功率,提高整体服务的可用性。

以 Apache Dubbo 为例,我一直觉得 Dubbo 对容错的支持特别全面,它支持多种集群容错的方式,并且可以针对业务特性,配置不同的失败重试机制,包括 Failover 失败自动切换、Failsafe 失败安全、Failfast 快速失败等。比如在 Failover 下,失败会重试两次;在 Failfast 下,失败则不会重试,直接抛出异常。

Dubbo 的容错机制考虑了多种业务场景的需求,根据不同的业务场景,可以选择不同的容错机制,进而有不同的重试策略,保证业务正确性。

Dubbo RPC 的重试和容错机制不是本课时的重点,如果想对 Dubbo 集群容错方式有进一步的了解,可以点击查看 Dubbo 官方文档

  • 消息消费中的重试问题

从本质上来讲,消息队列的消息发送重试,和微服务中的失败调用重试是一样的,都是通过重试的方式,解决网络抖动、传输不稳定等导致的偶发调用失败。这两者其实是一个问题,两个问题的解决方式也可以互相借鉴。

在分布式系统中,要解决这个问题,需从中间件和业务的不同层面,来保证服务调用的幂等性。下面从消息队列投递语义,以及业务中如何处理幂等,两个方面进行拆解。

消息投递的几种语义

为了进一步规范消息的调用,业界有许多消息队列的应用协议,其中也对消息投递标准做了一些约束。

  • At most once

消息在传递时,最多会被送达一次,在这种场景下,消息可能会丢,但绝不会重复传输,一般用于对消息可靠性没有太高要求的场景,比如一些允许数据丢失的日志报表、监控信息等。

  • At least once

消息在传递时,至少会被送达一次,在这种情况下,消息绝不会丢,但可能会出现重复传输。

绝大多数应用中,都是使用至少投递一次这种方式,同时,大部分消息队列都支持到这个级别,应用最广泛。

  • Exactly once

每条消息肯定会被传输一次且仅传输一次,并且保证送达,因为涉及发送端和生产端的各种协同机制,绝对的 Exactly once 级别是很难实现的,通用的 Exactly once 方案几乎不可能存在,可以参考分布式系统的「FLP 不可能定理」。

我觉得消息投递的语义,和数据库的隔离级别很像,不同语义的实现,付出的成本也不一样。上面定义的消息投递语义,主要在消息发送端,在消费端也可以定义类似的消费语义,比如消费端保证最多被消费一次,至少被消费一次等,这两种语义是相对应的,可以认为是同一个级别的两种描述。

不同消息队列支持的投递方式

以 RocketMQ 为例,我们来看下对应的投递支持。

RocketMQ 支持 At least once 的投递语义,也就是保证每个消息至少被投递一次。在 RocketMQ 中,是通过消费端消费的 ACK 机制来实现的:

在消息消费过程中,消费端在消息消费完成后,才返回 ACK,如果消息已经 pull 到本地,但还没有消费,则不会返回 ack 响应。

在业务上应用 RcoketMQ 时,也可以根据不同的业务场景实现其他级别的投递语义,比如最多送达一次等,由于篇幅限制这里不展开详细讲解了,感兴趣的同学可以查阅 RocketMQ 相关的源码和文档学习。

业务上如何处理幂等

消息消费的幂等和我们在上一课时中提到的时序性一样,本质上也是一个系统设计的问题。

消息队列是我们为了实现系统目标而引入的手段之一,并且分布式消息队列天然存在消费时序、消息失败重发等问题。所以要保证消息队列的消费幂等,还是要回到业务中,结合具体的设计方案解决。

天然幂等不需要额外设计

参考上面对 HTTP 协议方法的幂等性分析,有部分业务是天然幂等的,这部分业务,允许重复调用,即允许重试,在配置消息队列时,还可以通过合理的重试,来提高请求的成功率。

利用数据库进行去重

业务上的幂等操作可以添加一个过滤的数据库,比如设置一个去重表,也可以在数据库中通过唯一索引来去重。

举一个例子,现在要根据订单流转的消息在数据库中写一张订单 Log 表,我们可以把订单 ID 和修改时间戳做一个唯一索引进行约束。

当消费端消费消息出现重复投递时,会多次去订单 Log 表中进行写入,由于我们添加了唯一索引,除了第一条之外,后面的都会失败,这就从业务上保证了幂等,即使消费多次,也不会影响最终的数据结果。

设置全局唯一消息 ID 或者任务 ID

还记得我们在第 15 课时「分布式调用链跟踪」中,提到的调用链 ID 吗?调用链 ID 也可以应用在这里。我们在消息投递时,给每条业务消息附加一个唯一的消息 ID,然后就可以在消费端利用类似分布式锁的机制,实现唯一性的消费。

还是用上面记录订单状态流转消息的例子,我们在每条消息中添加一个唯一 ID,消息被消费后,在缓存中设置一个 Key 为对应的唯一 ID,代表数据已经被消费,当其他的消费端去消费时,就可以根据这条记录,来判断是否已经处理过。

总结

这一课时分享了消息幂等的知识点,包括对幂等的理解,以及消息队列投递时的不同语义,另外简单介绍了业务上处理幂等的两种方式。

西方有一句谚语:当你有了一个锤子,你看什么都像钉子。在我刚开始学习分布式系统时,学习了各种中间件,每个中间件都希望能用上,这其实脱离了系统设计的初衷。

课程内容到这里,已经展开了许多分布式系统的常用组件,提到这个谚语,主要是希望你在做技术方案,特别是做分布式系统设计方案时,不是为了设计而设计。方案设计的目的是实现业务目标,并不是在系统中加入各种高大上的中间件,这个方案就是正确的。

我之前读过一本《系统之美》的图书,从复杂系统的角度来看,系统中的元素越多,为了维持系统的平衡,需要付出的势能必然也越大。

对应到系统设计中,系统拆解的粒度越大,对应各个组件之间的耦合就越小,但是需要解决的组件协同问题也越多,实现数据的一致性也越困难。我们在系统设计时,要避免过度设计,把握技术方案的核心目的,在这个基础上进行针对性设计。

对于这一课时的内容,你可以思考下当前的项目中是如何处理重复消息的,有没有考虑消息处理的幂等性?欢迎留言分享。


第30讲:高可用:如何实现消息队列的 HA?

管理学上有一个木桶理论,一只水桶能装多少水取决于它最短的那块木板,这个理论推广到分布式系统的可用性上,就是系统整体的可用性取决于系统中最容易出现故障,或者性能最低的组件。系统中的各个组件都要进行高可用设计,防止单点故障,消息队列也不例外,这一课时一起来看一下消息中间件的高可用设计。

消息队列高可用手段

一般来说,分布式系统的高可用依赖副本技术,副本的引入,使得分布式系统可以更好地进行扩展,当出现某个节点宕机时,由于副本的存在,也能够快速地进行替换,提升系统整体可靠性,防止数据丢失。

消息队列如何实现高可用的问题,如果出现在面试中,一般是作为一个相对开放的话题,你可以根据自己对分布式系统的了解,围绕副本、集群、一致性等和面试官展开讨论。消息队列在系统中承担了数据存储和数据传输的两种功能,所以消息队列的高可用设计,也比数据库、文件索引等持久性存储要复杂。

下面的内容,我以 Apache Kafka 为例,简单介绍一下消息队列的高可用设计。

Kafka 的副本机制

Kafka 的高可用实现主要依赖副本机制,我把 Kakfa 的高可用,拆分成几个小问题来讲解,一来是为了更好地理解,二来很多细节问题也可能出现在面试中,方便你更好地掌握。

Broker 和 Partition 的关系

在分析副本机制之前,先来看一下 Broker 和 Partition 之间的关系。Broker 在英文中是代理、经纪人的意思,对应到 Kafka 集群中,是一个 Kafka 服务器节点,Kafka 集群由多个 Broker 组成,也就是对应多个 Kafka 节点。

image (14).png

Kafka 是典型的发布订阅模式,存在 Topic 的概念,一个 Broker 可以容纳多个 Topic,也就是一台服务器可以传输多个 Topic 数据。

不过 Topic 是一个逻辑概念,和物理上如何存储无关,Kafka 为了实现可扩展性,将一个 Topic 分散到多个 Partition 中,这里的 Partition 就是一个物理概念,对应的是具体某个 Broker 上的磁盘文件。

从 Partition 的角度,Kafka 保证消息在 Partition 内部有序,所以 Partition 是一段连续的存储,不能跨多个 Broker 存在,如果是在同一个 Broker 上,也不能挂载到多个磁盘。从 Broker 的角度,一个 Broker 可以有多个 Topic,对应多个 Partition。

除此之外,Partition 还可以细分为一个或者多个 Segment,也就是数据块,每个 Segment 都对应一个 index 索引文件,以及一个 log 数据文件。对 Partition 的进一步拆分,使得 Kafka 对 分区的管理更加灵活。

Replication 之间如何同步数据

基于 Kafka 的系统设计,你可以思考一下,如果没有副本,那么当某个 Kafka Broker 挂掉,或者某台服务器宕机(可能部署了多个 Broker),存储在其上的消息就不能被正常消费,导致系统可用性降低,或者出现数据丢失,这不符合分布式高可用的要求,出现单点故障,也不满足 Kafka 数据传输持久性和投递语义的设计目标。

Kafka 中有一个配置参数 replication-factor(副本因子),可以调整对应分区下副本的数量,注意副本因子数包含原来的 Partition,如果需要有 2 个副本,则要配置为 3。

假设现在有一个订单的 Topic,配置分区数为 3,如果配置 replication-factor 为 3,那么对应的有三个分区,每个分区都有 3 个副本,在有多个副本的情况下,不同副本之间如何分工呢?

每个分区下配置多个副本,多个副本之间为了协调,就必须有一定的同步机制。Kafka 中同一个分区下的不同副本,有不同的角色关系,分为 Leader Replication 和 Follower Replication。Leader 负责处理所有 Producer、Consumer 的请求,进行读写处理,Follower 作为数据备份,不处理来自客户端的请求。

Follower 不接受读写请求,那么数据来自哪里呢?它会通过 Fetch Request 方式,拉取 Leader 副本的数据进行同步。

image (15).png

Fetch 这个词一般用于批量拉取场景,比如使用 Git 进行版本管理的 fetch 命令,在 Kafka 中,会为数据同步开辟一个单独的线程,称为 ReplicaFetcherThread,该线程会主动从 Leader 批量拉取数据,这样可以高性能的实现数据同步。

Replication 分配有哪些约定

Kafka 中分区副本数的配置,既要考虑提高系统可用性,又要尽量减少机器资源浪费。

一方面,为了更好地做负载均衡,Kafka 会将所有的 Partition 均匀地分配到整个集群上;另一方面,为了提高 Kafka 的系统容错能力,一个 Partition 的副本,也要分散到不同的 Broker 上,否则就去了副本的意义。

一般来说,为了尽可能地提升服务的可用性和容错率,Kafka 的分区和副本分配遵循如下的原则:

一个 Topic 的 Partition 数量大于 Broker 的数量,使 Partition 尽量均匀分配到整个集群上;
同一个分区,所有的副本要尽量均匀分配到集群中的多台 Broker 上,尽可能保证同一个 分区下的主从副本,分配到不同的 Broker 上。

Leader Replication 如何选举

一旦牵扯到数据同步,就必然会有 Leader 节点宕机以后重新选择的问题。引入 Replication 机制之后,同一个 Partition 可能会有多个副本,如果Leader挂掉,需要在这些副本之间选出一个 新的Leader。

Kafka 数据同步中有一个 ISR(In-Sync Replicas,副本同步队列)的概念,Leader 节点在返回 ACK 响应时,会关注 ISR 中节点的同步状态,所以这个队列里的所有副本,都和 Leader 保持一致。

Kafka 的 ISR 依赖 ZooKeeper 进行管理,ISR 副本同步队列中的节点,拥有优先选举的权利,因为 ISR 里的节点和 Leader 保持一致,如果必须满足一致性,只有 ISR 里的成员才能被选为 Leader。

如果某个 Broker 挂掉,Kafka 会从 ISR 列表中选择一个分区作为新的 Leader 副本。如果 ISR 列表是空的,这时候有两个策略,一个是直接抛出 NoReplicaOnlineException 异常,保证一致性;另外一个是从其他副本中选择一个作为 Leader,则可能会丢失数据,具体需要根据业务场景进行配置。

所有的副本都挂了怎么办

现在考虑一个极端情况,如果一个分区下的所有副本都挂掉了,那如何处理呢?在这种情况下,Kafka 需要等待某个副本恢复服务,具体可以有两种方案:

  • 等待 ISR 中的某个副本恢复正常,作为新的 Leader;

  • 等待任一个 副本恢复正常,作为新的 Leader。

在第二种方案下,由于选择的 Leader 节点可能不是来自 ISR,所以可能会存在数据丢失,不能保证已经包含全部 Commit 的信息;如果选择第一种方案,会保证数据不丢失,但是如果全部的 ISR 节点都彻底宕机,系统就无法对外提供服务了,对应的分区会彻底不可用。

方案一优先保证数据一致性,方案二优先保证服务可用性,在实际配置中,可以根据不同的业务场景选择不同的方案。

总结

这一课时分享了消息队列高可用相关的知识,并且针对 Kafka 的高可用实现,进行了简单的分析。

实际上,Kafka 添加副本机制之后,需要解决的细节问题有很多。举个例子,我们在第 29 课时讲过消息投递的不同语义,比如 At Most Once、At Least Once 等,当添加了 Partition 之后,Kafka 需要保持投递语义的完整,那么在生产者进行投递时,因为要考虑不同副本的状态,Leader 节点如何进行 ACK 呢?很明显,如果 Leader 节点等待所有的 Follower 节点同步后才返回 ACK,系统整体的性能和吞吐量会大幅降低,这也是 Kafka 引入 ISR 副本分层管理的原因之一。

除了 Kafka 以外,RocketMQ、RabbitMQ 等消息队列又是怎么实现高可用的呢?感兴趣的同学可以了解一下,欢迎留言分享。


第31讲:消息队列选型:Kafka 如何实现高性能?

在分布式消息模块的最后 2 个课时中,我将对消息队列中应用最广泛的 Kafka 和 RocketMQ 进行梳理,以便于你在应用中可以更好地进行消息队列选型。另外,这两款消息队列也是面试的高频考点。

所以,这一课时我们就一起来看一下,Kafka 是如何实现高性能的。

Kafka 的高性能

不知道你有没有了解过自己电脑的配置?

我们一般会认为高性能是和高配置联系在一起的,比如大内存比小内存快,8 核的机器比 4 核的机器快。我身边也有一些朋友是攒机爱好者,对各种硬件配置如数家珍。

对于服务器来说,家用电脑的性能与配置的关系也同样适用——价格更昂贵的服务器会有更好的性能——这并不是一件需要大张旗鼓去讲述的事情。但 Kafka 所实现的高性能不需要太高配置的机器,它使用普通服务器就能实现 TB 级别的传输性能。这一点也是 Kafka 对外宣传的一个特性,也正是因为这一点,Kafka 被广泛运用于大数据处理、流式计算、各类日志监控等需要处理海量数据的场景。

Kafka 实现高性能的手段,是面试中经常被问到的问题。下面我从 Kafka 的磁盘读写、批量优化、零拷贝等方面,对 Kafka 的高性能特性进行分析。

分析 Kafka 的高性能会涉及操作系统的一些知识,比如文件系统、PageCache等,作为大学计算机专业的必修课,这些概念就不展开了。如果你觉得这方面比较生疏,可以回顾下操作系统课程的相关知识,找一些经典教材来学习。

磁盘顺序读写

Kafka 消息是存储在磁盘上的,大家都知道,普通的机械磁盘读取是比较慢的,那 Kafka 文件在磁盘上,如何实现高性能的读写呢?

Kafka 对磁盘的应用,得益于消息队列的存储特性。与普通的关系型数据库、各类 NoSQL 数据库等不同,消息队列对外提供的主要方法是生产和消费,不涉及数据的 CRUD。所以在写入磁盘时,可以使用顺序追加的方式来避免低效的磁盘寻址。

我们知道,数据存储在硬盘上,而硬盘有机械硬盘和固态硬盘之分。机械硬盘成本低、容量大,但每次读写都会寻址,再写入数据(在机械硬盘上,寻址是一个物理动作,耗时最大);SSD 固态硬盘性能很高,有着非常低的寻道时间和存取时间,但成本也特别高。

为了提高在机械硬盘上读写的速度,Kafka 使用了顺序读写。在一个分区内,Kafka 采用 append 的方式进行顺序写入,这样即使是普通的机械磁盘,也可以有很高的性能。

除了顺序读写,在提到磁盘写入的时候,还有一个问题避免不了,那就是何时进行刷盘。

在 Linux 系统中,当我们把数据写入文件系统之后,其实数据是存放在操作系统的 page cache 里面,并没有刷到磁盘上,如果服务器宕机,数据就丢失了。

写到磁盘的过程叫作 Flush。刷盘一般有两种方式,一种是依靠操作系统进行管理,定时刷盘,另一种则是同步刷盘,比如调用 fsync 等系统函数。

同步刷盘保证了数据的可靠性,但是会降低整体性能。Kafka 可以配置异步刷盘,不开启同步刷盘,异步刷盘不需要等写入磁盘后返回消息投递的 ACK,所以它提高了消息发送的吞吐量,降低了请求的延时,这也是 Kafka 磁盘高性能的一个原因。

批量操作优化

批量是一个常见的优化思路,比如大家熟悉的 Redis,就实现了 pipeline 管道批量操作。Kafka 在很多地方也应用了批量操作进行性能优化。

Kafka 的批量包括批量写入、批量发布等。它在消息投递时会将消息缓存起来,然后批量发送;同样,消费端在消费消息时,也不是一条一条处理的,而是批量进行拉取,提高了消息的处理速度。

除了批量以外,Kafka 的数据传输还可以配置压缩协议,比如 Gzip 和 Snappy 压缩协议。虽然在进行数据压缩时会消耗少量的 CPU 资源,但可以减少网络传输的数据大小、优化网络 IO、提升传输速率。

Sendfile 零拷贝

零拷贝是什么?它是操作系统文件读写的一种技术。

零拷贝不是不需要拷贝,而是减少不必要的拷贝次数,这里会涉及 Linux 用户态和内核态的区别。

用户进程是运行在用户空间的,不能直接操作内核缓冲区的数据。所以在用户进程进行系统调用的时候,会由用户态切换到内核态,待内核处理完之后再返回用户态。

传统的 IO 流程,需要先把数据拷贝到内核缓冲区,再从内核缓冲拷贝到用户空间,应用程序处理完成以后,再拷贝回内核缓冲区。这个过程中发生了多次数据拷贝。

为了减少不必要的拷贝,Kafka 依赖 Linux 内核提供的 Sendfile 系统调用。 在 Sendfile 方法中,数据在内核缓冲区完成输入和输出,不需要拷贝到用户空间处理,这也就避免了重复的数据拷贝。在具体的操作中,Kafka 把所有的消息都存放在单独的文件里,在消息投递时直接通过 Sendfile 方法发送文件,减少了上下文切换,因此大大提高了性能。

MMAP 技术

Kafka 是使用 Scala 语言开发的。Scala 运行在 Java 虚拟机上,也就是说 Kafka 节点运行需要 JVM 的支持,但是 Kafka 并不直接依赖 JVM 堆内存。如果 Kafka 所有的数据操作都在堆内存中进行,则会对堆内存造成非常大的压力,影响垃圾回收处理,增加 JVM 的停顿时间和整体延迟。

因此,除了 Sendfile 之外,还有一种零拷贝的实现技术,即 Memory Mapped Files。

Kafka 使用 Memory Mapped Files 完成内存映射,Memory Mapped Files 对文件的操作不是 write/read,而是直接对内存地址的操作。如果是调用文件的 read 操作,则把数据先读取到内核空间中,然后再复制到用户空间。 但 MMAP 可以将文件直接映射到用户态的内存空间,省去了用户空间到内核空间复制的开销,所以说 MMAP 也是一种零拷贝技术。

那 MMAP 和上面的 Sendfile 有什么区别呢?

MMAP 和 Sendfile 并没有本质上的区别,它们都是零拷贝的实现。零拷贝是一种技术思想,除了我们说到的这两种,还有DMA,以及缓冲区共享等方式,感兴趣的同学可以去扩展了解一下。

总结

这一课时讲解了 Kafka 如何实现高性能,介绍了顺序读写、批量优化、零拷贝等技术,对于大部分业务开发的同学,这部分知识了解即可。

Kafka 的高性能实现原理,在很多地方都有应用,比如 Netty 中也有零拷贝技术。Linux 中,一切皆文件,Netty 关注的是网络 IO 的传输,Kafka 等存储关注的是文件 IO 的传输,但在操作系统中都是 IO 操作,在优化手段上非常类似。

另外,上面提到的 Sendfile 可以大幅提升文件传输性能,在 Apache、Nginx 等 Web 服务器当中,都有相关的应用。感兴趣的同学可以了解下 Netty 等网络组件的性能优化方式,欢迎留言进行分享。


第32讲:消息队列选型:RocketMQ 适用哪些场景?

关于消息队列的应用场景有很多,不同消息队列由于在实现上有着细微的差别,所以就有各自适合的应用场景。

如果你的工作以业务开发为主,建议了解一下消息队列背后的设计思想,以及其基本的特性,这样才能在业务开发中应用消息队列时,对消息队列进行合理的选型。这一课时我们一起来对 RocketMQ 做一个拆解。

RocketMQ 应用

RocketMQ 在阿里巴巴被大规模应用,其前身是淘宝的 MetaQ,后来改名为 RocketMQ,并加入了 Apache 基金会。RocketMQ 基于高可用分布式集群技术,提供低延时、高可靠的消息发布与订阅服务。

RocketMQ 整体设计和其他的 MQ 类似,除了 Producer、Consumer,还有 NameServer 和 Broker。

image (5).png

NameServer 存储了 Topic 和 Broker 的信息,主要功能是管理 Broker,以及进行消费的路由信息管理。

在服务器启动时,节点会注册到 NameServer 上,通过心跳保持连接,并记录各个节点的存活状态;除此之外,NameServer 还记录了生产者和消费者的请求信息,结合消息队列的节点信息,实现消息投递的负载均衡等功能。

RocketMQ 的 Broker 和 Kafka 类似,Broker 是消息存储的承载,作为客户端请求的入口,可以管理生产者和消费者的消费情况。

Broker 集群还承担了消息队列高可用的责任,它可以扩展副本机制,通过主从节点间的数据同步保证高可用,这一点和 Kafka 的分区副本机制非常类似。

我们知道,消息队列的 Topic 是逻辑概念,实际会分散在多个队列中传输。在 RocketMQ 中,队列均匀分散在各个 Broker 上。在消息投递时,消息生产者通过不同的分发策略,对投递的消息进行分发,保证消息发布的均匀。

Broker 可以进行横向扩展——如果消息队列集群不能满足目前的业务场景,那么可以增加新的机器,扩展 Broker 集群。新的 Broker 节点启动以后,会注册到 NameServer 上,集群中的生产者和消费者通过 NameServer 感知到新的节点,接下来就可以进行消息的发布和消费。

和其他的消息队列不同,RocketMQ 还支持 Tag,Tag 是对 Topic 的进一步扩展,可以理解为一个子主题。有了 Tag,在进行消息队列的主题划分时,可以把一个业务模块的消息进一步拆分,使其更加灵活。

比如在电商业务场景中,通常我们会按照订单、商品、交易、物流等大的模块进行划分,但是在实际应用中,订单消息仍有订单创建、订单支付、订单配送等不同的消息,商品消息也会有商品价格更新、库存更新等不同的分类。使用一级主题,对消息的拆分也许不能满足业务的要求。但通过 Tag,我们可以把订单消息统一为 Order-Topic。下面继续创建 Order-Create-Message、Order-Pay-Message 等子主题,对各项信息进行细化,使其在应用中变得更加方便,在业务开发中会更加灵活。

RocketMQ 特性

在本课时开始提到一个问题,即 RocketMQ 适用哪些场景?可以从两个方面入手,第一个方面是消息队列的通用业务场景,第二个是从 RocketMQ 的特性入手。

RocketMQ 消息中间件的使用场景比较广泛,对于需要通过 MQ 进行异步解耦的分布式应用系统来说,都可以应用 RocketMQ 作为解决方案。下面梳理两个 RocketMQ 的典型应用。

实现 Binlog 分发

很多业务场景都对消息消费的顺序有很高的要求。以电商业务中的 Binlog 消息分发为例,我们知道,在大多数业务场景下,除了数据库作为持久层存储以外,还会有文件索引、各类缓存的存在。

比如电商中的订单信息,订单信息在用户端的展示是通过 ElasticSearch 等文件索引实现的。在订单状态修改后,需要实时同步修改,但一般业务中不会直接操作文件索引,那如何同步数据呢?

业务数据被分散在不同的存储中,就一定要考虑数据一致性,一个典型的解决方案是基于 Binlog 的数据同步。

使用 RocketMQ 实现 Binlog 数据同步,有一个成熟的方案,那就是 RocketMQ 结合阿里的 Canal。Canal 是阿里巴巴开源的数据库组件,可以基于 MySQL 数据库进行增量日志解析,实现增量数据订阅和消费,目前已经在很多大公司中应用。

image (7).png

Canal 的实现原理特别巧妙。不知道你有没有看过谍战题材的影片,比如 007 系列。Canal 在这里就好像一个伪装的特工,它模拟 MySQL Slave 的交互协议,把自己作为 MySQL 主从同步中的一个从节点,拉取 Binlog 日志信息,然后进行分发。

Canal 和 RokcetMQ 都是阿里巴巴开源的组件,并且都在阿里云上实现了商业化,二者的集成也是顺其自然的。在 Canal 中已经内置了对 RocketMQ 的支持,支持开箱即用的配置方式。

除此之外,Canal 的解决方案还包括一个可视化界面,该界面可以进行动态管理,配置 RocketMQ 集群。如果你在调研 Binlog 数据同步机制,并且自己所在的团队又没有大量的人力进行支持,那可以了解一下这个解决方案。

对 Canal 感兴趣的同学,可以点击查看 Canal 的代码仓库

实现分布式一致性

RocketMQ 支持事务消息,那什么是事务消息呢?在 RocketMQ 中,事务消息就是支持类似 XA 规范的分布式事务功能,通过 RocketMQ 达到分布式事务的最终一致。

以电商中的下单后扣款为例,用户在完成商品购买后,点击确认支付,这时候会调用交易模块的服务,更新账户金额,或者从第三方支付扣款。

这是一个典型的分布式事务问题。关于分布式事务,我们在第 06 课时讨论过,可以使用 TCC 进行改造,也可以使用基于消息队列的本地消息表。

RocketMQ 实现的事务消息,其实和本地消息表非常类似。RokcetMQ 在事务消息的实现中添加了一个 Half Message 的概念,我理解为“半事务消息”,或者“事务中消息”。Half Message 表示事务消息处于未完成状态,利用这个中间状态,可以实现一个类似于两阶段提交的流程,实现最终的一致性。

关于 RocketMQ 的事务消息原理,官方文档中有一篇文章做了比较深入地介绍,这里我就不再展开介绍了,感兴趣的同学可点击这里查阅

总结

这一课时的内容分享了 RocketMQ 的应用场景,列举了两个典型应用。

在专栏中我多次推荐大家去看一下 RocketMQ 的源码,为什么呢?

首先,源码之下无秘密,想要彻底学习并且搞懂一个组件,学习源码是最有效的手段之一;其次,专栏的读者中有相当一部分是做 Java 语言开发的,RocketMQ 的源码就是 Java 语言,比起 Kafka 或者 RabbitMQ 的源码,阅读起来要简单很多。

在你的项目中是否应用到了 RocketMQ,在应用时又利用了它的哪些特性呢?欢迎留言讨论。


  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

办公模板库 素材蛙

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值