关于中间件:得物技术消息中间件应用的常见问题与方案

3次阅读

共计 8190 个字符,预计需要花费 21 分钟才能阅读完成。

1. 引言

音讯队列(MQ)中间件曾经遍及很多年了,在互联网利用中,通常稍大一些的利用,咱们都能够见到 MQ 的身影。以后市面上有很多中消息中间件,包含但不限于 RabbitMQ、RocketMQ、ActiveMQ、Kafka(流解决中间件) 等。很多开发人员曾经纯熟的把握了一个或者多个消息中间件的应用。然而依然有一些小伙伴们对消息中间件不是特地相熟,因为各种起因不能深刻的去学习理解个中原理和细节,导致应用的时候可能呈现这样那样的问题。在这里,咱们就针对音讯队列中间件应用中的典型问题作一番剖析(包含程序音讯、可靠性保障、音讯幂等、延时音讯等),并提供一些解决方案。

2. 消息中间件利用背景

2.1 消息中间件根本思维

咱们在单个零碎中,一些业务解决能够程序顺次的进行。而波及到跨零碎(有时候零碎外部亦然)的时候,会产生比较复杂数据交互(也能够了解为消息传递)的需要,这些数据的交互传递形式,能够是同步也能够是异步的。在异步传递数据的状况下,往往须要一个载体,来长期存储与散发音讯。在此基础上,专门针对音讯接管、存储、转发而设计与开发进去的业余应用程序,都能够了解为音讯队列中间件。

引申一下:如果咱们本人简略的应用一张数据库表,来记录数据,而后承受数据存储在数据表,通过定时工作再将数据表的数据散发进来,那么咱们曾经实现了一个最简略的音讯零碎(这就是本地音讯表)。

咱们能够认为消息中间件的根本思维就是 利用高效牢靠的消息传递机制进行异步的数据传输。在这个根本思维的领导下,不同的音讯两头,因为其偏重场景目标不同,在性能、性能、整体设计理念上又各有差异。

音讯队列(MQ)自身是实现了生产者到消费者的单向通信模型,RabbitMQ、RocketMQ、Kafka 这些罕用的 MQ 都是指实现了这个模型的消息中间件。目前最罕用的几个消息中间件次要有,RabbitMQ、RocketMQ、Kafka(分布式流解决平台)、Pulsar(分布式音讯流平台)。这里我将两个流解决平台纳入其中了, 更早的一些其余消息中间件曾经缓缓淡出视线。业务选型的时候咱们遵循两个次要的准则:最大相熟水平准则(便于运维、应用牢靠)、业务符合准则(中间件性能能够撑持业务体量、满足业务性能需要)。

这几个罕用的消息中间件选型比照,很容易找到,这里就不详细描述了。大略说一下:Pulsar 目前用的不如 RabbitMQ、RocketMQ、Kafka 多。RabbitMQ 次要并重是高可靠消息,RocketMQ 性能和性能并重,Kafka 次要是在大数据处理中利用比拟多(Pulsar 比拟相似)。

2.2 引入消息中间件的意义

咱们先简略举例介绍一下异步、解藕、削峰的意义与价值(参考上面这张流程图):

对于一个用户注册接口,假如有 2 个业务点,别离是注册、发放新人福利,各须要 50ms 去解决逻辑。如果咱们将这两个业务流程耦合在一个接口,那么总计须要 100ms 解决实现。然而该流程中,用户注册时候,能够不必关怀本人的福利是否立刻发放,只有尽快注册胜利返回数据即可,后续新人福利这一部分业务能够在主流程之外解决。咱们如果将其剥离进去,接口主流程中只解决登陆逻辑,并通过 MQ 推送一条音讯,通过异步形式解决后续的发放新人福利逻辑,这样即可保障注册接口 50ms 左右即能获取后果。而发放新人福利的业务,则通过异步工作缓缓解决。通过拆分业务点,咱们曾经做到解耦,注册的从属业务中减少或缩小性能点都不会影响主流程。另外如果一个业务主流程在某个点申请并发比拟高,正好通过异步形式,能够将压力扩散到更长的时间段中去,达到加重固定时间段解决压力的目标,这就是流量削峰。

** 另外,单线程模型的语言,通常对消息中间件的需要更强烈。多线程模型的语言,或者协程型语言,尽管能够通过本身的多线程 (或协程) 机制,来实现业务外部的异步解决,然而思考到长久化问题以及治理难度,还是成熟的中间件更适宜用来做异步数据通信,中间件还能实现分布式系统之间的数据异步通信。

2.3 消息中间件的利用场景

消息中间件的利用场景次要有:

    • 异步通信:能够用于业务零碎外部的异步通信,也能够用于分布式系统信息交互
    • 零碎解耦:将不同性质的业务进行隔离切分,晋升性能,主附流程分层,依照重要性进行隔离,缩小异样影响
    • 流量削峰:间歇性突刺流量扩散解决,缩小零碎压力,晋升零碎可用性
    • 分布式事务一致性:RocketMQ 提供的事务音讯性能能够解决分布式事务一致性(如电商订单场景)。当然,也能够应用分布式事务中间件。
    • 音讯程序收发:这是最根底的性能,先进先出,音讯队列必备
    • 延时音讯:提早触发的业务场景,如下单后提早勾销未领取订单等
    • 大数据处理:日志解决,kafka
    • 分布式缓存同步:生产 MySQLbinlog 日志进行缓存同步,或者业务变动间接推送到 MQ 生产

所以,如果你的业务中有以上列举的场景,或者相似的性能、性能需求,那么快快引入 消息中间件来晋升你的业务性能吧。

3. 引入消息中间件带来的一系列问题

尽管消息中间件引入有以上那么多益处,然而应用的时候仍然会存在很多问题。例如:

  • 引入消息中间件减少了零碎复杂度,怎么应用保护
  • 音讯发送失败怎么办(音讯失落)
  • 为了确保能发胜利,音讯反复发送了怎么办(音讯反复)
  • 音讯在中间件流转出现异常怎么解决
  • 音讯生产时候,如果生产流程失败了怎么解决,还能不能从新从中间件获取到这条音讯
  • 生产失败如果还能获取,那会不会呈现失败状况下,始终反复生产同一条音讯,从而流程卡死
  • 生产失败如果不能再获取,那么咱们该怎么确保这条音讯能再次被解决
  • 反复生产到雷同的音讯流程怎么解决,会不会导致业务异样
  • 那么咱们该怎么确保生产流程只胜利执行一次
  • 对于那些有程序的音讯咱们应该怎么保障发送和生产的程序统一
  • 音讯太多了,怎么保障生产脚本生产速度,以便更得上业务的解决需要,防止音讯有限积压
  • 我想要发送的音讯,等上几秒钟的工夫再生产到,该怎么做

当然咱们对于以上的这些问题,针对业务开发者来说,能够进行提炼,失去以下几个重点问题:

  • 音讯程序性保障
  • 防止音讯失落
  • 音讯的反复问题
  • 音讯积压解决
  • 提早音讯解决

4. 问题的解决方案

4.1 音讯程序性保障

惯例的消息中间件和流解决中间件,自身设计个别都能反对程序音讯,然而依据中间件自身不同的设计指标,有不同的原理架构,导致咱们业务中应用中间件的时候,要针对性做不同的解决。

以下几个罕用音讯或流中间件的程序音讯设计以及应用中乱序问题剖析:

RabbitMQ:

RabbitMQ 的单个队列(queue)本身,能够保障音讯的先进先出,在设计上,RabbitMQ 所提供的单个队列数据是存储在单个 broker 节点上的,在开启镜像队列的状况下,镜像的队列也只是作为音讯正本而存在,服务仍然由主队列提供。这种状况下在单个队列上进行生产,人造就是程序性的。不过因为单个队列反对多消费者同时生产,咱们在开启多个消费者生产对立队列上的数据时候,音讯扩散到多个消费者上,在并发高的时候,多个消费者无奈保障解决音讯的程序性。

解决办法就是对于须要强制程序的音讯,应用同一个 MQ 队列,并且针对单个队列只开启一个消费者生产(保障并发解决时候的程序性,多线程同理)。由此引发的单个队列吞吐降落的问题,能够采取 kafka 的设计思维,针对繁多工作开启一组多个队列,将须要程序的音讯依照其固定标识(例如:ID)进行路由,扩散到这一组队列中,雷同标识的音讯进入到雷同的队列,单个队列应用单个消费者生产,这样即能够保障音讯的程序与吞吐。

如图所示:

Kafka:

Kafka 是流解决中间件,在其设计中,没有队列的概念,音讯的收发依赖于 Topic,单个 topic 能够有多个 partition(分区),这些 partition 能够扩散到多台 broker 节点上,并且 partition 还能够设置正本备份以保障其高可用。

Kafka 同一个 topic 能够有多个消费者,甚至生产组。Kafka 中音讯生产个别应用生产组(生产组能够互不干涉的生产同一个 topic 下的音讯)来进行生产,生产组中能够有多个消费者。同一个生产组生产单个 topic 下的多个 partition 时,将由 kafka 来调节生产组中消费者与 partiton 的生产进度与平衡。然而有一点是能够保障的:那就是单个 partition 在同一个生产组中只能被一个消费者生产。

以上的设计理念下,Kafka 外部保障在同一个 partition 中的音讯是程序的,不保障 topic 下的音讯的程序性。Kafka 的音讯生产者发送音讯的时候,是能够抉择将音讯发送到哪个 partition 中的,咱们只有将须要程序解决的音讯,发送到 topic 下雷同的 partition,即可保障音讯生产的程序性。(多线程语言应用单个消费者,多线程解决数据时,须要本人去保障解决的程序,这里略过)。

RocketMQ:

RocketMQ 的一些基本概念和原理,能够通过阿里云的官网做一些理解:什么是音讯队列 RocketMQ 版?– 音讯队列 RocketMQ 版 – 阿里云。

RocketMQ 的音讯收发也是基于 Topic 的,Topic 下有多个 Queue,散布在一个或多个 Broker 上,用来保障音讯的高性能收发(与 Kafka 的 Topic-Partition 机制 有些相似,但外部实现原理并不相同)。

RocketMQ 反对部分程序音讯生产,也就是保障同一个音讯队列上的音讯程序生产。不反对音讯全局程序生产,如果要实现某一个主题的全局程序音讯生产,能够将该主题的队列数量设置为 1,就义高可用性。具体图解能够参考阿里云文档: 程序音讯 2.0 – 音讯队列 RocketMQ 版 – 阿里云

4.2 防止音讯失落

音讯失落须要分为三局部来看:音讯生产者发送音讯到消息中间件的过程不产生音讯失落,音讯在消息中间件中从承受存储到被生产的过程中音讯不失落,音讯生产的过程中保障能生产到中间件发送的音讯而不会失落。

生产者发送音讯不失落:

消息中间件个别都有音讯发送确认机制(ACK), 对于客户端来说,只有配置好消息发送须要 ACK 确认,就能够依据返回的后果来判断音讯是否胜利发送到中间件中。这一步通常与中间件的音讯承受存储流程设计有关系。依据中间件的设计,咱们通常采取的措施如下:

  • 开启 MQ 的 ACK(或 confirm)机制,间接获知音讯发送后果
  • 开启音讯队列的长久化机制(落盘,如果须要非凡设置的话)
  • 中间件自身做好高可用部署
  • 音讯发送失败弥补设计(重试等)

在具体的业务设计中,如果音讯发送失败,咱们能够依据业务重要水平,做相应的弥补,例如:

  1. 音讯失败重试机制(发送失败,持续重发,能够设置重试下限)
  2. 如果仍然失败,依据音讯重要性,抉择降级计划:间接抛弃或者降级到其余中间件或载体(同时须要相应的降级弥补推送或生产设计)

消息中间件音讯不失落:

数消息中间件的音讯接管存储机制各不相同,然而会依据其个性设计,最大限度保障音讯不会失落:

RabbitMQ 音讯接管与保留:

  • RabbitMQ 音讯发送能够开启发送者 confirm 模式,所有音讯是否发送胜利都会告诉发送者
  • 须要开启队列音讯长久化保障音讯落盘
  • RabbitMQ 通过镜像队列来保障音讯队列的高可用,然而镜像队列只有 Master 提供服务,其余 slave 只提供备份服务。
  • master 宕机会从 slave 中抉择一个成为新的 master 提供服务
  • master 的生产与生产的最新状态都会播送到 slave

RocketMQ 音讯承受与保留:

  • RocketMQ 一般音讯发送有三种形式:同步(Sync)发送、异步(Async)发送和单向(Oneway)发送,其区别与准确性保障能够参看 发送一般音讯(三种形式)– 音讯队列 RocketMQ 版 – 阿里云。
  • 具体的 RocketMQ 外部设计的 HA 机制是主从同步机制,音讯发送到 Topic 下并具体音讯队列的 Master Broker 中后,会将音讯同步到 Slave。
  • 只有 Master Broker 才能够接管生产者发送的音讯。而消费者,能够从 Master 也能够从 Slave 拉取并生产音讯。

Kafka 在音讯承受到保留所做的设计有:

  • 分区正本形式的设计保障音讯的高可用,在创立 topic 的时候都能够设置分区正本的数量
  • 生产者能够抉择接管不同类型的确认(ACK),比方在音讯被齐全提交时候(写入所有同步正本)的确认,或者在音讯被写入领袖正本时的确认,或者在音讯被发送到网络时确认
  • Kafka 的音讯,写入分区的时候仅仅是保留在某几个分区正本文件系统内存中,并不是间接刷到磁盘了,因而宕机时候,单个正本依然可能失落数据。Kafka 不能保障单个分区正本的数据肯定不失落,而是靠分区正本机制来确保音讯的欠缺性(散布到不同的 broker 上)

积压音讯保留时效问题

  • Kafka 对于 topic 下的数据,有容量下限、工夫下限两种音讯存储下限规定,触发其中任何一个规定,都会删除淘汰之前的音讯。这个尤其须要留神。
  • RocketMQ,音讯在服务器存储工夫也有下限,达到下限的音讯将会被删除。也须要做相应的考量。
  • 受长久化磁盘容量的影响,存储积压的数据不能超过磁盘的下限
  • 如果业务生产有异样,须要给足短缺的冗余量,防止因为生产不及时而失落数据。

消费者生产音讯不失落:

  • 音讯生产时候,也要开启相应的 ACK 机制,音讯生产胜利即 ACK(对于 Kafka 就是更新生产的 offset)
  • 对于 RocketMQ 这种有音讯从新生产设计的,须要设置最大生产次数,尝试失败的音讯反复生产

音讯 ACK 带来两个问题

  • 音讯生产失败如果不能 ACK 可能会导致音讯生产有限阻塞在某条音讯处
  • 音讯失败从新生产导致音讯生产反复

有限阻塞的问题,能够参考 RocketMQ 生产失败的重试机制,对音讯重试做肯定的设计:

  1. 在音讯体上设计重试次数的属性,生产失败的音讯减少重试次数后从新发送到中间件,期待下一次生产,本次生产胜利发回音讯间接 ACK
  2. 音讯重试次数达到下限之后,如果仍不能胜利,则启用降级计划,将音讯存储到异样信息长久化载体如 DB 中
  3. 手动或者定时工作弥补解决失败的音讯

音讯反复生产问题参考下一个大节。

4.3 音讯的反复问题(生产幂等)

在剖析罕用中间件的时候,咱们往往会发现,中间件设计者将这个问题的解决,下放给中间件使用者,也就是业务开发者了。诚然,业务生产解决的逻辑比音讯生产者简单的多。生产者只须要保障将音讯胜利发送到中间件即可,而消费者须要在生产脚本中解决各种简单的业务逻辑。

解决音讯反复生产的问题,外围是应用惟一标识,来标记某条音讯是否曾经解决过。具体计划可选的则有很多,比方:

  • 应用数据库自增主键,或者惟一键来保证数据不会反复变动
  • 应用中间状态,以及状态变动有序性来判断业务是否以曾经被解决
  • 利用一张日志表来记录曾经解决胜利的音讯的 ID,如果新到的音讯 ID 曾经在日志表中,那么就不再解决这条音讯
  • 或者音讯惟一标识,在 Redis 等 NoSQL 中保护一个解决缓存,判断是否曾经解决过
  • 如果消费者业务流程比拟长,则须要开发者本人保障整个业务生产逻辑中数据处理的事务性

4.4 音讯积压解决

通常咱们在引入消息中间件的时候,曾经会评估与测试音讯生产的生产与生产速率,尽量使其达到均衡。但业务也有一些不可预知的突发状况,可能会造成音讯的大量积压。在这个时候,咱们能够采取如下的形式,来做解决:

长期紧急扩容

  1. 通过减少生产脚本的形式,晋升生产速率,如果上游没有限度的话,能够很快的缩小音讯积压
  2. 如果消费者上游数据处理能力无限,咱们能够思考建设长期队列,通过长期脚本,将音讯疾速转移到长期队列,优先保障线上业务能顺利贯通,而后开启更多的生产脚本解决积压的数据。(程序音讯须要额定解决,并保障最终解决的程序)
  3. 优化生产脚本的处理速度,冲破上游限度,如果有可能,能够思考批量解决,上游扩容等形式。

音讯积压预防

  • 做好业务设计与降级,防止产生有效音讯占用资源
  • 依据音讯积压水平,动静增减消费者数量,缩小音讯积压
  • 做好音讯积压解决紧急预案,异常情况依据预案设计,迅速针对解决

4.5 提早音讯解决

提早音讯这一项性能,在局部 MQ 中间件中有实现。延时音讯和定时音讯其实能够相互转换。

RocketMQ:\
RocketMQ 定时音讯不反对任意的工夫精度(出于性能考量)。只反对特定级别的提早音讯。音讯提早级别在 broker 端通过 messageDelayLevel 配置。其外部对每一个提早级别创立对应的音讯生产队列,而后创立对应提早级别的定时工作,从音讯生产队列中将音讯拉取并复原音讯的原主题和原音讯生产队列。

RabbitMQ:

RabbitMQ 实现提早音讯通常有两个计划:一是创立一个音讯提早死信队列,搭配一个死信转发队列来实现生产延时。然而该形式如果前一个音讯没达到 TTL 工夫,后一个音讯即使达到了,也不会被转发到转发队列中;另一个是应用延时 Exchange 插件(rabbitmq_delayed_message_exchange),音讯在达到 TTL 之后才会转发到对应的队列中并被生产。

Kafka 自身不反对延时音讯或定时音讯, 想要实现音讯的延时,须要应用其余的计划。

借助数据库与定时工作实现延时音讯:

罕用数据库的索引构造都反对数据的顺序索引。借助数据库能够很不便的实现任意工夫音讯的延时生产。应用一张表存储数据的生产工夫,开启定时工作,在满足条件之后将该音讯提取进去,后续转发到程序队列去解决或者间接解决都能够(已解决须要做标记,后续不再呈现),然而间接解决须要思考吞吐量和并发重复性等问题。不如单个脚本转发到一般队列去解决不便。数据库反对的定时工作音讯积压是可控的,然而吞吐量会有局限。

借助 Reids 的有序列表实现延时音讯:

Reids 的有序列表 zset 构造,能够实现延时音讯。将音讯的生产工夫作为分值,把音讯增加到 zset 中。应用 zrangebyscore 命令生产音讯

# 命令格局 zrangebysocre key min max withscores limit 0 1 生产最早的一条音讯 # min max 别离示意开始的分值与完结的分值区间,别离应用 0 和以后工夫戳,能够查出达到生产工夫的音讯 # withscores 示意查问的数据要带分值。limit 前面 就是查问的起始 offset 和数量 zrangebyscore key 0 {以后工夫戳} withscores limit 0 1

当然,这个计划也有局限性,首先,redis 必须配置长久化避免音讯失落(如果配置不合理不能 100% 保障,然而每个命令都长久化会造成性能降落,须要衡量);其次,如果延时音讯过多会造成音讯的积压造成大 key;再次,须要本人做反复生产和生产失败的均衡解决(当然有可能,还是倡议开启单个生产过程将延时音讯转移到一般队列去生产)。

基于工夫轮的任务调度:

在很多软件中,都有基于工夫轮实现定时工作的实现,应用工夫轮以及多级工夫轮能够实现延时任务调度。如果咱们心愿本人实现延时工作队列,能够思考应用此算法来实现工作的调度,然而须要本人依据具体的需要去设计反对工作的延时下限以及调度的工夫粒度(多层级)。工夫轮算法我这里就先不解说了,感兴趣的能够本人去搜寻理解。

5. 总结

通过以上几个大节的介绍,置信各位曾经能很天然的了解 音讯队列 异步解耦 的性能与核心思想,并且对如何应用 MQ 来架构本人的业务有了肯定的认知。大多数 MQ 应用中的问题,只是要求咱们多思考,将细节思虑周到,以保障业务的高可用。甚至,咱们还能够在这几个解决方案中提炼一些外围进去,以便在业务中参照相似的思维,优化咱们的业务。比方 音讯程序性保障 其外围是程序音讯生产者发送到惟一分区,再维持固定分区的单消费者程序生产;防止音讯失落的外围是每个步骤的确认与降级机制;生产幂等的外围是唯一性标识与步进状态;音讯积压解决的外围是疾速响应应急预案;提早音讯的外围是音讯排序,优化点是性能晋升。

迷信的办法有演绎和演绎,学习问题解决计划的过程中,提炼出相应的核心思想,并在应用中演绎,将这些演绎总结的知识点,再利用到业务中去,更加得心应手的解决相应的事务,构建出高可用的业务架构,这才是咱们最须要做到的。\

参考:

  • 音讯队列 RocketMQ 版 – 帮忙核心 – 阿里云
  • 丁威,周继锋 .《RocketMQ 技术底细——RocketMQ 架构设计与实现原理》. 机械工业出版社
  • Neha Narkhede,Gwen Shapira,Todd Palino .《Kafka 权威指南》. 人民邮电出版社

文 /LISUXING

关注得物技术,做最潮技术人!

正文完
 0