面试笔记:Kafka 和 RocketMQ 的区别(一)

90 阅读6分钟

面试笔记:Kafka 和 RocketMQ 的区别(一)

概述

  • 高吞吐量:Kafka 支持高吞吐量,RocketMQ 也支持高吞吐量。
  • 低延迟:Kafka 强调低延迟,而 RocketMQ 更注重可靠性和可用性。
  • 消息顺序:RocketMQ 支持全局和分区内的严格消息顺序,而 Kafka 只支持分区内的消息顺序。
  • 事务消息:RocketMQ 原生支持事务消息,而 Kafka 对事务的支持有限。
  • 可扩展性:Kafka 和 RocketMQ 都可以通过增加更多的 broker 和分区来水平扩展。
  • 消费者组:Kafka 支持消费者组进行并行处理,RocketMQ 也支持类似的消费者组功能。
  • 标签:RocketMQ 允许对消息进行标签以实现细粒度的过滤,而 Kafka 没有内置的消息标签支持。
  • 复制:Kafka 和 RocketMQ 都支持数据复制以实现高可用性和容错。
  • 使用场景:Kafka 适用于实时分析和日志处理,而 RocketMQ 适用于金融交易和需要保证消息传递和顺序的应用。

两者在 低延迟 消息顺序 事务消息 标签 方面是存在差异的

本章我们主要学习两者根本区别和消息顺序

系统性质对比

Kafka低延迟

Kafka 的设计目标之一是确保消息传递的低延迟。这意味着消息从生产者发送到消费者接收的时间非常短。Kafka 通过高效的磁盘 I/O 操作和分布式架构来实现这一点,使其非常适合实时数据处理和分析场景。

高效的磁盘 I/O 操作

Kafka 通过顺序写入磁盘来优化磁盘 I/O 性能。顺序写入比随机写入更快,因为它减少了磁盘寻道时间。 此外,Kafka 使用页缓存(page cache)来进一步提高读写性能,尽量减少磁盘访问次数。

分布式架构

Kafka 的分布式架构允许将数据分布在多个节点(broker)上。每个主题(topic)可以分为多个分区(partition),每个分区可以分布在不同的 broker 上。这样可以实现数据的并行处理,提高系统的吞吐量和容错能力。分布式架构还支持水平扩展,通过增加更多的 broker 来处理更多的数据和负载。

sequenceDiagram
    participant Producer
    participant KafkaCluster
    participant Broker1
    participant Broker2
    participant Disk
    participant PageCache

    Producer->>KafkaCluster: Send Message
    KafkaCluster->>Broker1: Route to Broker1
    Broker1->>Disk: Sequential Write
    Disk->>PageCache: Use Page Cache
    PageCache-->>Broker1: Cached Data
    Broker1-->>KafkaCluster: Acknowledge Write
    KafkaCluster-->>Producer: Acknowledge Receipt

    Producer->>KafkaCluster: Send Another Message
    KafkaCluster->>Broker2: Route to Broker2
    Broker2->>Disk: Sequential Write
    Disk->>PageCache: Use Page Cache
    PageCache-->>Broker2: Cached Data
    Broker2-->>KafkaCluster: Acknowledge Write
    KafkaCluster-->>Producer: Acknowledge Receipt

RocketMQ 注重可靠性和可用性

RocketMQ 的设计目标是确保消息传递的高可靠性和高可用性

可靠性指的是消息在传递过程中不会丢失,RocketMQ 通过消息持久化复制确认机制来实现这一点。

  graph TD
    A[高可靠] --> B[消息持久化]
    A --> C[消息复制]
    A --> D[确认机制]

    B --> E[消息写入磁盘]
    B --> F[日志文件存储]

    C --> G[主从复制]
    C --> H[多副本存储]

    D --> I[生产者确认]
    D --> J[消费者确认]
    D --> K[消息状态更新]

可用性指的是系统在出现故障时仍能继续运行,RocketMQ 通过主从复制和故障转移机制来保证系统的高可用性。

graph TD
    A[高可用] --> B[主从复制]
    A --> C[故障转移机制]

    B --> D[主节点]
    B --> E[从节点]
    D --> F[处理读写请求]
    E --> G[备份数据]

    C --> H[故障检测]
    C --> I[主节点故障]
    C --> J[从节点提升为主节点]
    C --> K[客户端重连]

    H --> L[心跳检测]
    H --> M[日志监控]

    I --> N[主节点不可用]
    I --> O[通知从节点]

    J --> P[选举新主节点]
    J --> Q[更新元数据]

    K --> R[客户端重新连接到新主节点]
    K --> S[恢复消息传递]

这使得 RocketMQ 非常适合需要严格消息顺序和事务支持的关键业务场景,如金融交易系统。

消息顺序

RocketMQ

全局消息顺序

全局消息顺序是指所有消息按照发送的顺序被消费。RocketMQ 通过以下方式实现全局消息顺序:

  1. 单一队列:将所有消息发送到同一个队列中,消费者按照消息的存储顺序进行消费。
  2. 顺序消费:消费者在消费消息时,确保按照消息的存储顺序进行处理。

分区内消息顺序

分区内消息顺序是指在同一个分区内,消息按照发送的顺序被消费。RocketMQ 通过以下方式实现分区内消息顺序:

  1. 分区机制:将消息按照某种规则(如消息的 key)分配到不同的分区中。
  2. 顺序消费:消费者在消费某个分区的消息时,确保按照消息的存储顺序进行处理。

为什么支持全局和分区内的严格消息顺序

  1. 单一队列的限制:全局消息顺序需要将所有消息发送到同一个队列中,这在高并发场景下会成为瓶颈,影响系统的吞吐量。
  2. 分区机制的优势:通过分区机制,可以将消息分散到多个分区中,每个分区内保证消息的顺序消费,从而提高系统的并发处理能力和吞吐量。
  3. 业务需求:有些业务场景需要严格的全局消息顺序,而有些业务场景只需要分区内的消息顺序,RocketMQ 提供了灵活的配置,满足不同的业务需求。

以下是一个示例,展示了如何在 RocketMQ 中配置和使用顺序消息:

import org.apache.rocketmq.client.producer.DefaultMQProducer;
import org.apache.rocketmq.client.producer.SendResult;
import org.apache.rocketmq.client.producer.selector.MessageQueueSelector;
import org.apache.rocketmq.common.message.Message;
import org.apache.rocketmq.common.message.MessageQueue;

import java.util.List;

public class OrderedProducer {
    public static void main(String[] args) throws Exception {
        DefaultMQProducer producer = new DefaultMQProducer("ordered_producer_group");
        producer.setNamesrvAddr("localhost:9876");
        producer.start();

        String[] tags = new String[]{"TagA", "TagB", "TagC"};
        for (int i = 0; i < 10; i++) {
            int orderId = i % 10;
            Message msg = new Message("TopicTest", tags[i % tags.length], "KEY" + i,
                    ("Hello RocketMQ " + i).getBytes());

            SendResult sendResult = producer.send(msg, new MessageQueueSelector() {
                @Override
                public MessageQueue select(List<MessageQueue> mqs, Message msg, Object arg) {
                    Integer id = (Integer) arg;
                    int index = id % mqs.size();
                    return mqs.get(index);
                }
            }, orderId);

            System.out.printf("%s%n", sendResult);
        }

        producer.shutdown();
    }
}

这个示例展示了如何使用 MessageQueueSelector 来确保消息按照特定的顺序发送到相应的分区,从而实现分区内的严格消息顺序。

kafka

分区内消息顺序

分区内消息顺序是指在同一个分区内,消息按照发送的顺序被消费。Kafka 通过以下方式实现分区内消息顺序:

  1. 分区机制:将消息按照某种规则(如消息的 key)分配到不同的分区中。
  2. 顺序消费:消费者在消费某个分区的消息时,确保按照消息的存储顺序进行处理。

为什么 Kafka 只支持分区内的消息顺序

  1. 高吞吐量:Kafka 设计的初衷是为了高吞吐量和高可用性。通过将消息分配到多个分区,可以并行处理消息,从而提高系统的吞吐量。
  2. 分布式架构:Kafka 是一个分布式系统,消息被分布在多个分区和多个节点上。全局消息顺序会导致单点瓶颈,影响系统的扩展性和性能。
  3. 负载均衡:通过分区机制,可以将消息负载均衡到多个分区和消费者组,从而提高系统的并发处理能力。
  4. 灵活性:分区内消息顺序已经能够满足大多数业务场景的需求,特别是那些只需要在特定上下文中保持顺序的场景。

以下是一个示例,展示了如何在 Kafka 中配置和使用顺序消息:

import org.apache.kafka.clients.producer.KafkaProducer;
import org.apache.kafka.clients.producer.ProducerConfig;
import org.apache.kafka.clients.producer.ProducerRecord;
import org.apache.kafka.clients.producer.RecordMetadata;
import org.apache.kafka.common.serialization.StringSerializer;

import java.util.Properties;
import java.util.concurrent.ExecutionException;

public class OrderedProducer {
    public static void main(String[] args) throws ExecutionException, InterruptedException {
        Properties props = new Properties();
        props.put(ProducerConfig.BOOTSTRAP_SERVERS_CONFIG, "localhost:9092");
        props.put(ProducerConfig.KEY_SERIALIZER_CLASS_CONFIG, StringSerializer.class.getName());
        props.put(ProducerConfig.VALUE_SERIALIZER_CLASS_CONFIG, StringSerializer.class.getName());

        KafkaProducer<String, String> producer = new KafkaProducer<>(props);

        String topic = "my-topic";
        for (int i = 0; i < 10; i++) {
            String key = "key-" + (i % 2); // 使用 key 确保消息发送到相同的分区
            String value = "message-" + i;
            ProducerRecord<String, String> record = new ProducerRecord<>(topic, key, value);
            RecordMetadata metadata = producer.send(record).get();
            System.out.printf("Sent message: key=%s, value=%s, partition=%d%n", key, value, metadata.partition());
        }

        producer.close();
    }
}

这个示例展示了如何使用 KafkaProducer 来确保消息按照特定的顺序发送到相应的分区,从而实现分区内的严格消息顺序。