消息队列之-Kafka

目录

消息队列

消息队列(Message Queue)是一种跨进程的通信机制,用于在分布式系统中发送、存储和接收消息。它允许不同的应用程序或服务以异步的方式进行数据交换。以下是消息队列的一些基本概念:
消息 :消息是通信的基本单位,通常包含两部分:头部(metadata),如发送者信息、时间戳等;以及主体(payload),即实际要传输的数据内容。
生产者/发布者 :指的是发送消息的应用程序或服务。生产者创建消息并将其发送到消息队列中。
消费者/订阅者 :指的是接收消息的应用程序或服务。消费者从消息队列中读取消息,并根据消息的内容执行相应的操作。
队列 :是一个临时存储消息的地方,用于保存生产者发送的消息直到这些消息被消费者处理。队列遵循先进先出(FIFO, First In First Out)原则,除非特别配置。
Broker(代理) :在某些消息队列实现中,会有一个中间件称为broker,负责消息的传递、存储及路由。它作为生产者和消费者之间的桥梁,确保消息能够正确地从生产者发送到消费者。
持久化与非持久化 :消息队列中的消息可以配置为持久化的或非持久化的。持久化消息即使在系统崩溃后也能恢复,而非持久化消息则不会在系统故障时保留。
广播与点对点 :消息队列支持两种主要的消息分发模式。广播模式下,每条消息会被发送给所有订阅了该主题的消费者;而在点对点模式下,每条消息只会被一个消费者处理。
事务支持 :一些消息队列系统提供了事务支持,确保一组消息要么全部成功提交,要么全部不提交,以此来保证数据的一致性和完整性。

使用消息队列有助于解耦应用组件、提高系统的可扩展性、增强系统的容错能力,并且能够平衡负载,使得不同速度的服务之间能够高效协作。常见的消息队列产品包括RabbitMQ、Apache Kafka、ActiveMQ等。

消息队列的使用场景

解耦服务 :通过消息队列,生产者和消费者可以独立地部署、扩展和开发。例如,在电子商务系统中,订单服务生成订单后,可以通过消息队列通知库存服务更新库存,而无需直接调用库存服务的接口。
异步处理 :对于一些不需要立即响应的操作,可以采用异步的方式进行处理,从而提高系统的响应速度。比如用户注册成功后发送欢迎邮件,这个操作就可以通过消息队列异步执行,而不必等待邮件发送完成后再返回给用户注册成功的提示。
流量削峰:在高并发的情况下,如秒杀活动或节假日促销,短时间内大量请求可能会导致系统过载。使用消息队列可以在流量高峰期缓冲这些请求,平滑地将任务分配给后台处理,保护系统免受瞬间高峰流量的影响。

初识Kafka

Kafka 是一个分布式的基于发布/订阅模式的消息队列(Message Queue),主要应用与大数据实时处理领域。

Kafka 存储的消息来自任意多被称为 Producer 生产者的进程。数据从而可以被发布到不同的 Topic 主题下的不同 Partition 分区。

在一个分区内,这些消息被索引并连同时间戳存储在一起。其它被称为 Consumer 消费者的进程可以从分区订阅消息。

Kafka 运行在一个由一台或多台服务器组成的集群上,并且分区可以跨集群结点分布。

下面给出 Kafka 一些重要概念,让大家对 Kafka 有个整体的认识和感知:
Producer : 消息生产者,向 Kafka Broker 发消息的客户端。
Consumer :消息消费者,从 Kafka Broker 取消息的客户端。
Consumer Group :消费者组(CG),消费者组内每个消费者负责消费不同分区的数据,提高消费能力。一个分区只能由组内一个消费者消费,消费者组之间互不影响。所有的消费者都属于某个消费者组,即消费者组是逻辑上的一个订阅者。
Broker :一台 Kafka 机器就是一个 Broker。一个集群由多个 Broker 组成。一个 Broker 可以容纳多个 Topic。
Topic :可以理解为一个队列,Topic 将消息分类,生产者和消费者面向的是同一个 Topic。
Partition :为了实现扩展性,提高并发能力,一个非常大的 Topic 可以分布到多个 Broker (即服务器)上,一个 Topic 可以分为多个 Partition,每个 Partition 是一个 有序的队列。
Replica :副本,为实现备份的功能,保证集群中的某个节点发生故障时,该节点上的 Partition 数据不丢失,且 Kafka 仍然能够继续工作,Kafka 提供了副本机制,一个 Topic 的每个分区都有若干个副本,一个 Leader 和若干个 Follower。
Leader :每个分区多个副本的"主"副本,生产者发送数据的对象,以及消费者消费数据的对象,都是 Leader。
Follower :每个分区多个副本的"从"副本,实时从 Leader 中同步数据,保持和 Leader 数据的同步。Leader 发生故障时,某个 Follower 还会成为新的 Leader。
Offset :消费者采用pull的方式从broker上拉取消息进行消费,该值记录消费的位置信息,当消费者挂掉再重新恢复的时候,可以从消费位置继续消费。
Zookeeper:Kafka 集群能够正常工作,需要依赖于 Zookeeper,Zookeeper 帮助 Kafka 存储和管理集群信息。

Kafka设计思想

Consumer Group :Kafka按照消费组来消费消息,一个消费组下面的机器组成一个Consumer Group,每条消息只能被该Consumer Group一个Consumer消费。不同的Consumer Group可以消费同一条消息。
消息状态 :在Kafka中,消息是否被消费的状态保存在Consumer中,Broker不会关心是否被消费或被谁消费,Consumer会记录一个offset值(指向partition中吓一跳将要被消费的消息位置)。
消息持久化 :Kafka会把消息持久化到本地文件,并且具有极高性能(零拷贝)。
批量发送 :Kafka支持以消息集合为单位进行批量发送,以提高效率。
Push-and-Pull : Producer向Broker push消息,Consumer从Broker pull消息。
分区机制(Partition)

Kafka 的 Topic 被划分为多个分区(Partition),每个分区是一个有序、不可变的消息队列。分区的主要作用包括:

提供并行处理能力:不同的分区可以分布在不同的 Broker 上,从而支持高吞吐量。

保证消息顺序:在一个分区内,消息是严格按顺序存储和消费的。

支持水平扩展:通过增加分区数,可以提升系统的并发处理能力。

提供数据备份:支持数据容灾能力,支持服务高可用

Kafka消息结构

在 Kafka 中,每条消息(也称为记录)都有一个明确的格式和组成部分。以下是 Kafka 消息的基本结构:
Offset :每个消息在它所属的分区中都有一个唯一的序列号,称为 Offset。这个数字用于标识消息在分区中的位置。
Message Size :表示消息体的大小,以字节为单位。这有助于消费者知道需要读取多少数据。
Key(可选) :消息键,可以为空。如果定义了 Key,它可以用来确定消息如何被路由到特定的分区。Kafka 使用 Key 的哈希值来决定消息应该放在哪个分区,从而保证具有相同 Key 的消息总是被发送到同一个分区。
Value :这是实际的消息内容,即要传输的数据。它可以是任何类型的序列化后的数据,如字符串、JSON、Avro 等。
Headers(可选) :从 Kafka 0.11.0 版本开始引入,允许用户为消息添加元数据信息。这些元数据是以键值对的形式存在,可以用来携带关于消息的额外信息而不影响消息的实际内容。

Timestamp:表示消息创建的时间或当消息到达 broker 时的时间戳。时间戳对于日志压缩、基于时间的查询等功能非常重要。

消息发送

分区选择策略

如果消息没有指定 Key(即 key == null)
轮询(Round-Robin)策略(默认选用) ,将消息均匀地分配到所有可用分区中。
随机策略 :从分区中随机选择一个

如果消息指定了 Key,则根据 Key 的哈希值计算目标分区:

java 复制代码
partition = hash(key) % numPartitions

自定义策略

Kafka 允许用户通过实现 org.apache.kafka.clients.producer.Partitioner 接口来自定义分区逻辑。例如,可以根据业务规则将某些类型的消息路由到特定的分区。

消息消费

Consumer Group保存了自己的位移信息,只需要一个简单的整数表示位置就可以了。

老版本的位移是提交到Zookeeper中,但是Zookeeper不适合记性大批量的读写操作,尤其是写操作。

从0.9版本开始kafka增加了__consumer_offsets这个Topic,将Offset这个信息写入Topic,这样就不需要以来Zookeeper。

Consumer Group采用pull的方式来消费消息,那么每个Consumer该消费哪个Partition的消息需要一套严格的机制来保证,防止各Consumer间重复消费消息。而且,Partition是可以水平无限扩展的,随着Partition的扩展,Consumer消费的Partition也会重新分配,在Kafka内部有两种默认的消费分区分配策略:Range和RoundRobin。

当发生以下事件时,Kafka会重新进行分配:

同一个Consumer Group内新增消费者

消费者离开当前所属的Group,比如机器Shutdown或者Crash

订阅的主题新增Partition

Range策略
工作原理:

RangeAssignor 是 Kafka 的默认分区分配策略。

它按照主题的分区范围(Range)将分区分配给消费者。
具体步骤如下:

将每个主题的所有分区按顺序编号(例如 P0, P1, P2, ..., Pn)。

按照消费者的字典顺序排序(例如 C0, C1, C2)。

根据消费者数量计算每个消费者应分配的分区范围。

分区总数 ÷ 消费者总数 = 每个消费者分到的分区数(整数部分)。

剩余的分区会依次分配给前面的消费者。

最终,每个消费者得到一个连续的分区范围。
示例

假设有一个 Topic 包含 8 个分区(P0-P7),消费者组中有 3 个消费者(C0, C1, C2)。使用 RangeAssignor 策略时,分区分配如下:

8 / 3 = 2 ,每个消费者分到2个分区

按顺序分配分区:

C0: P0, P1(基本分区)+ P2(剩余分区的第一个)

C1: P3, P4(基本分区)+ P5 (剩余分区的第二个)

C2: P6, P7(基本分区)

最终分配结果:

C0: P0, P1, P2

C1: P3, P4, P5

C2: P6, P7

RoundRobin策略
工作原理:

RoundRobinAssignor 使用轮询的方式将所有主题的分区均匀地分配给消费者。
具体步骤如下:

将所有主题的所有分区按顺序排列。

按照消费者的字典顺序排序。

使用轮询的方式依次为每个消费者分配分区。

直到所有分区都被分配完毕。
示例:

假设有一个 Topic 包含 8 个分区(P0-P7),消费者组中有 3 个消费者(C0, C1, C2)。使用 RoundRobinAssignor 策略时,分区分配如下:

将所有分区按顺序排列:P0, P1, P2, P3, P4, P5, P6, P7。

按照消费者的字典顺序排序:C0, C1, C2。

使用轮询方式分配分区:

第一轮:C0 -> P0, C1 -> P1, C2 -> P2。

第二轮:C0 -> P3, C1 -> P4, C2 -> P5。

第三轮:C0 -> P6, C1 -> P7。

最终分配结果:

C0: P0, P3, P6

C1: P1, P4, P7

C2: P2, P5
总结:

策略 优点 缺点 适用场景
RangeAssignor 实现简单,适合分区数量与消费者数量接近的场景 可能导致负载不均 分区数量与消费者数量接近
RoundRobinAssignor 分区分配更加均匀 实现稍复杂 分区数量较多或涉及多主题的场景

Kafka高可用

对分布式系统来说,当集群规模上升到一定程度后,一台或多台机器宕机的风险会增加,Kafka采用多机备份和消息ACK应答机制,解决数据丢失问题,并通过一套失败恢复机制解决服务不可用问题

消息备份机制

1. 基本原理

Kafka 每个主题(Topic)可以被划分为多个分区(Partition),而每个分区又可以有多个副本(Replica)。这些副本分布在不同的 Broker 上,其中一个副本作为 Leader 副本,其余的作为 Follower 副本。生产者总是向 Leader 副本写入数据,Follower 副本则从 Leader 异步拉取数据进行同步。

2. ISR(In-Sync Replicas)

  • 定义:ISR 是一组与 Leader 副本保持同步的副本集合。只有当一个副本能够跟上 Leader 的更新速度时,它才会被认为是 ISR 成员。
  • 作用:ISR 确保了即使发生故障,也总有一个最新的副本可以快速接管成为新的 Leader,从而减少数据丢失的风险。
  • 动态调整:如果某个 Follower 落后太多(例如因为网络延迟或负载过高),它将被从 ISR 中移除;被移除的Follower会向Leader发送FetchRequest请求,试图再次跟上Leader,重新进入ISR。

3. ACK(Acknowledgements)

  • 定义 :ACK 指的是生产者发送消息后等待 Broker 返回确认的过程。Kafka 提供了三种 ACK 策略:
    • acks=0:生产者不等待任何确认,消息可能丢失。
    • acks=1:生产者只需 Leader 副本确认收到消息即可。
    • acks=allacks=-1:生产者需要所有同步副本(ISR)确认收到消息。
  • 影响 :选择不同的 ACK 策略是在性能和可靠性之间做出权衡。acks=all 提供了最高的数据安全性,但也会增加延迟。

4. LEO(Log End Offset)

  • 定义:LEO 表示每个副本当前已写入的最大偏移量。换句话说,它是该副本日志文件的末端位置。
  • 作用:LEO 反映了副本上的最新消息位置。对于 Leader 和 Follower 副本来说,它们各自的 LEO 可能不同,特别是在 Follower 正在追赶 Leader 的情况下。

5. HW(High Watermark)

  • 定义:HW 是消费者可以看到的最大偏移量。它代表了所有 ISR 中最小的 LEO。换句话说,HW 标识了所有副本都已确认的消息边界。
  • 作用:HW 确保了即使某些副本落后,消费者也只能读取到已经被所有 ISR 成员确认的消息。这提供了一种保证,即消费者不会看到未完全同步的消息,从而避免了数据不一致的问题。

实际场景示例

假设有一个 Topic 包含三个副本(复制因子为 3),并且设置了 min.insync.replicas=2acks=all。在这种情况下:

  1. 生产者发送消息:生产者将消息发送给 Leader 副本,并等待所有 ISR 成员(至少两个)确认接收。
  2. Leader 更新 LEO:Leader 将消息写入其日志并更新自身的 LEO。
  3. Follower 同步数据:Follower 副本从 Leader 获取新消息并更新自己的 LEO。
  4. 确定 HW:一旦至少有两个副本(包括 Leader)确认接收到消息,Leader 会更新 HW 到最新的安全偏移量。
  5. 消费者读取消息 :消费者只能读取到 HW 之前的消息,确保了数据的一致性和可靠性。
    这种设计使得 Kafka 在保证高性能的同时,也能提供强大的容错能力和数据一致性保障。
    ISR 确保了在 Leader 故障时能迅速选出新的 Leader 而不影响服务;
    ACK 策略让用户可以根据需求选择合适的数据安全级别;
    LEO 和 HW 的机制则进一步保障了数据的一致性和可靠性。

故障恢复

Broker 故障处理

当一个或多个 Broker 发生故障时,Kafka 依靠其内置的副本(Replication)和选举机制来恢复服务。

1. 检测 Broker 故障
  • Kafka 使用 ZooKeeper 来监控集群中的所有 Broker。每个 Broker 在启动时都会在 ZooKeeper 中注册一个临时节点。
  • 如果某个 Broker 宕机或与 ZooKeeper 断开连接超过一定时间(由 session.timeout.ms 控制),ZooKeeper 将删除该 Broker 的临时节点,从而触发故障检测。
2. Leader 副本故障
  • 对于受影响的每个分区,如果 Leader 副本所在的 Broker 宕机,Kafka 控制器将从 ISR(In-Sync Replicas)中选择一个新的 Leader。
  • 新的 Leader 被选中后,控制器会更新元数据并将新的 Leader 信息传播给其他 Broker 和客户端。
  • 这个过程通常非常快,因为 ISR 中的副本已经与原 Leader 同步了最新的数据。
3. Follower 副本故障
  • 如果某个 Follower 副本所在 Broker 宕机,它将暂时无法同步数据,并被从 ISR 中移除。
  • 当该 Broker 恢复正常后,它会尝试重新加入 ISR。首先,它需要从当前的 Leader 同步丢失的数据,直到赶上最新状态。
  • 一旦同步完成,该副本可以重新加入 ISR 并恢复正常操作。
4. 配置参数影响
  • min.insync.replicas:决定了必须有多少个副本确认接收到消息才能被视为已提交。这影响了系统在部分副本失败时的容错能力。
  • unclean.leader.election.enable:控制是否允许非 ISR 成员成为 Leader。默认情况下禁用此选项以避免潜在的数据丢失风险。

Controller 故障处理

Kafka 集群中的控制器(Controller)负责管理主题、分区、副本等资源的状态变化,如 Leader 选举、分区分配等任务。控制器本身也是一个 Broker,但它承担了额外的责任。

1. 自动选举新控制器
  • Kafka 集群中只有一个活跃的控制器。当现有控制器发生故障时,Kafka 会在剩余的 Broker 中自动选举出一个新的控制器。
  • 所有 Broker 在启动时都会试图注册成为候选控制器,但最终只会有一个成功。这个过程通过 ZooKeeper 实现,利用了 ZooKeeper 的原子性特性确保只有一个 Broker 成为控制器。
2. 接管职责
  • 新当选的控制器立即接管原有控制器的所有职责,包括:
    • 管理分区的 Leader 选举。
    • 监控 Broker 的上线和下线情况。
    • 处理主题的创建、删除及配置变更等操作。
3. 状态恢复
  • 新控制器需要从 ZooKeeper 中读取集群的当前状态信息,以便正确地继续管理工作。这包括但不限于:
    • 当前的主题和分区信息。
    • 分区的 Leader 和 ISR 列表。
    • 其他重要的集群配置和状态数据。
4. 防止脑裂问题
  • "脑裂"是指在网络分区的情况下,两个不同的控制器同时认为自己是主控制器的情况。为了避免这种情况,Kafka 使用 ZooKeeper 来保证只有一个控制器处于活动状态。只有当现有的控制器失去与 ZooKeeper 的联系时,才会允许其他 Broker 竞争成为新的控制器。

实际场景示例

假设在一个 Kafka 集群中有三个 Broker(B1, B2, B3),其中 B1 是当前的控制器。现在考虑以下两种场景:

场景 1:Broker 故障
  • 如果 B1 宕机,而 B1 上托管了一个 Topic 的 Leader 副本。
  • 控制器将从 ISR 中选出一个新的 Leader(可能是 B2 或 B3)。
  • 更新后的 Leader 信息会被传播给集群中的其他 Broker 和客户端。
  • 当 B1 恢复后,它可以作为普通 Broker 重新加入集群,并开始从当前的 Leader 同步数据。
场景 2:Controller 故障
  • 如果 B1(控制器)宕机,ZooKeeper 会注意到这一点并触发新的控制器选举。
  • 假设 B2 成功当选为新的控制器。
  • B2 将接管所有的控制器职责,包括管理 Leader 选举、处理主题变更等。
  • 如果 B1 恢复,它将不会自动重新成为控制器,除非当前控制器再次失效且 B1 再次赢得选举。

Kafka高性能

1. 批量发送消息

Kafka 通过批量发送消息的方式显著提高了吞吐量,减少了网络传输的开销。

(1)批量机制的工作原理

  • Kafka 生产者不会逐条发送消息,而是将多条消息打包成一个批次(Batch),然后一次性发送到 Broker。
  • 批量发送的核心思想是利用缓冲区(Buffer)暂存消息,并在满足一定条件时触发发送。

(2)关键配置参数

  • batch.size:控制每个批次的最大字节数。当累积的消息达到这个大小时,生产者会立即发送该批次。
  • linger.ms :指定生产者等待更多消息加入当前批次的时间。即使批次未满,也会在 linger.ms 时间后发送消息。
    • 如果 linger.ms 设置为 0,则只要消息生成就会立即发送。
    • 如果设置为非零值(如 5ms),则允许生产者稍微延迟发送以积累更多的消息,从而提高吞吐量。

(3)性能优势

  • 减少网络开销:每次网络请求都会带来一定的开销(如 TCP 连接建立、头部信息等)。批量发送可以显著减少网络请求的次数,从而降低这些开销。
  • 压缩效率提升:Kafka 支持对消息进行压缩(如 GZIP、Snappy、LZ4 等)。由于压缩算法通常在更大的数据块上表现更好,因此批量发送可以进一步提高压缩效率。

2. 持久化消息

Kafka 使用顺序写入磁盘和操作系统缓存来实现高效的消息持久化,确保数据的安全性和高性能。

(1)顺序写入磁盘

  • Kafka 的日志文件是以追加的方式写入磁盘的。这种顺序写入操作比随机写入快得多,因为磁盘的寻址时间和旋转延迟在顺序写入中被最小化。
  • 现代硬盘(尤其是 SSD)对顺序写入有非常高的吞吐量支持,因此 Kafka 能够充分利用硬件性能。

(2)操作系统页缓存(Page Cache)

  • Kafka 并不直接将消息写入磁盘,而是依赖于操作系统的页缓存(Page Cache)。生产者发送的消息首先被写入内存中的页缓存,随后由操作系统异步地刷新到磁盘。
  • 优点
    • 写入速度极快,因为写入的是内存而非直接写入磁盘。
    • 消费者的读取操作可以直接从页缓存中获取数据,而无需访问磁盘,进一步提升了读取性能。

(3)刷盘策略

  • Kafka 提供了灵活的刷盘策略,用户可以通过以下参数控制数据的持久性:
    • acks 参数
      • acks=0:生产者不等待任何确认,消息可能会丢失。
      • acks=1:生产者只需 Leader 副本确认收到消息即可。
      • acks=allacks=-1:生产者需要所有同步副本(ISR)确认收到消息。
    • flush.messagesflush.ms
      • 控制 Kafka 将消息从页缓存刷到磁盘的频率。
      • 默认情况下,Kafka 不会频繁刷盘,而是依赖操作系统的定时同步机制。

(4)性能优势

  • 高吞吐量:通过顺序写入和页缓存,Kafka 实现了极高的写入吞吐量。
  • 低延迟:消费者的读取操作直接从页缓存中获取数据,避免了磁盘 I/O 开销。
  • 数据可靠性:通过灵活的刷盘策略,用户可以在性能和可靠性之间找到平衡。

3. 零拷贝(Zero-Copy)

零拷贝技术是 Kafka 实现高性能的关键之一,它通过减少数据在不同内存区域之间的复制次数来提高吞吐量。

(1)传统数据传输方式的问题

在传统的数据传输过程中,数据需要经过多次拷贝:

  1. 数据从磁盘读取到内核空间的缓冲区。
  2. 数据从内核空间拷贝到用户空间的缓冲区。
  3. 用户空间的应用程序处理数据后,再将其拷贝回内核空间的缓冲区。
  4. 最后,数据从内核空间发送到网络接口。
    这种多次拷贝的过程不仅增加了 CPU 的开销,还引入了额外的延迟。

(2)Kafka 的零拷贝实现

Kafka 使用了 Linux 的 sendfile 系统调用来实现零拷贝。以下是零拷贝的工作流程:

  1. 数据从磁盘直接加载到内核空间的页缓存。
  2. 使用 sendfile 系统调用,数据直接从内核空间的页缓存发送到网络接口,而无需经过用户空间。

(3)性能优势

  • 减少 CPU 开销:零拷贝避免了用户空间和内核空间之间的数据拷贝,降低了 CPU 的使用率。
  • 降低延迟:数据传输路径更短,减少了上下文切换和内存拷贝的时间。
  • 提高吞吐量:通过减少不必要的操作,Kafka 能够以更高的速度处理大规模数据。

总结

优化点 实现方式 性能优势
批量发送消息 将多条消息打包成一个批次发送,减少网络请求次数 减少网络开销,提高吞吐量,增强压缩效率
持久化消息 使用顺序写入磁盘和操作系统页缓存,结合灵活的刷盘策略 高吞吐量、低延迟、可靠的数据存储
零拷贝 利用 Linux 的 sendfile 系统调用,避免数据在用户空间和内核空间之间的拷贝 减少 CPU 开销,降低延迟,提高吞吐量
相关推荐
hzulwy1 小时前
Kafka基础理论
分布式·kafka
明达智控技术2 小时前
MR30分布式IO在全自动中药煎药机中的应用
分布式·物联网·自动化
jakeswang3 小时前
细说分布式ID
分布式
失散134 小时前
分布式专题——1.2 Redis7核心数据结构
java·数据结构·redis·分布式·架构
王中阳Go5 小时前
头一次见问这么多kafka的问题
分布式·kafka
鼠鼠我捏,要死了捏5 小时前
Kafka Exactly-Once 语义深度解析与性能优化实践指南
kafka·exactly-once·performance-optimization
boonya7 小时前
Kafka核心原理与常见面试问题解析
分布式·面试·kafka
KIDAKN8 小时前
RabbitMQ 重试机制 和 TTL
分布式·rabbitmq
lifallen8 小时前
Kafka 内存池MemoryPool 设计
数据结构·kafka·apache
JAVA学习通8 小时前
【RabbitMQ】----初识 RabbitMQ
分布式·rabbitmq