MQ黄金三剑客:RabbitMQ、RocketMQ和Kafka深入解密常见问题及功能对比指南?

2023年 7月 14日 58.7k 0

1、消息丢失问题

RabbitMQ解决消息丢失的问题:

  • RabbitMQ通过消息持久化和消息确认机制来确保消息的可靠传递。生产者可以选择将消息标记为持久化,使得即使在消息队列服务器故障后,消息也能被保存并传递给消费者。
  • RabbitMQ还提供了多种消息确认机制,如发布确认(Publish Confirm)和事务机制(Transaction),生产者可以通过这些机制获取消息是否成功被RabbitMQ接收和处理的确认。

RocketMQ解决消息丢失的问题:

  • RocketMQ通过持久化存储和副本机制来保证消息的可靠传递。消息在发送前会被持久化存储到磁盘上,即使在消息服务器故障时也能够恢复消息。
  • RocketMQ支持多副本机制,将消息复制到多个Broker节点上,即使其中一个Broker节点发生故障,仍然可以从其他副本节点读取和传递消息。

Kafka解决消息丢失的问题:

  • Kafka通过持久化存储和副本机制来保证消息的可靠传递。消息在发送前被持久化存储到磁盘上,即使在服务器重启后也不会丢失。
  • Kafka采用多副本机制,将消息复制到多个Broker节点上,即使其中一个Broker节点故障,仍然可以从其他副本节点读取和传递消息。

2、消息积压问题

RabbitMQ解决消息积压的问题:

  • RabbitMQ通过调整消费者的消费速率来控制消息积压。可以使用QoS(Quality of Service)机制设置每个消费者的预取计数,限制每次从队列中获取的消息数量,以控制消费者的处理速度。
  • RabbitMQ还支持消费者端的流量控制,通过设置basic.qos或basic.consume命令的参数来控制消费者的处理速度,避免消息过多导致积压。

RocketMQ解决消息积压的问题:

  • RocketMQ通过动态调整消费者的消费速率来控制消息积压。可以根据系统的负载情况和消息队列的堆积情况,动态调整消费者的并发消费线程数,以适应消息的处理需求。
  • RocketMQ还提供了消息拉取和推拉模式,消费者可以根据自身的处理能力主动拉取消息,避免消息积压过多。

Kafka解决消息积压的问题:

  • Kafka通过分区和副本机制来实现消息的并行处理和负载均衡。可以根据消息的负载情况和消费者的处理能力,通过增加分区数量、调整副本分配策略等方式来提高系统的处理能力。
  • Kafka还提供了消息清理(compaction)和数据保留策略,可以根据时间或者数据大小来自动删除过期的消息,避免消息积压过多。

3、消息重复消费问题

RabbitMQ:

  • 幂等性处理:在消费者端实现幂等性逻辑,即无论消息被消费多少次,最终的结果应该保持一致。这可以通过在消费端进行唯一标识的检查或者记录已经处理过的消息来实现。
  • 消息确认机制:消费者在处理完消息后,发送确认消息(ACK)给RabbitMQ,告知消息已经成功处理。RabbitMQ根据接收到的确认消息来判断是否需要重新投递消息给其他消费者。
  • RocketMQ:

  • 使用消息唯一标识符(Message ID):在消息发送时,为每条消息附加一个唯一标识符。消费者在处理消息时,可以通过判断消息唯一标识符来避免重复消费。可以将消息ID记录在数据库或缓存中,用于去重检查。
  • 消费者端去重处理:消费者在消费消息时,可以通过维护一个已消费消息的列表或缓存,来避免重复消费已经处理过的消息。
  • Kafka:

  • 幂等性处理:在消费者端实现幂等性逻辑,即多次消费同一条消息所产生的结果与单次消费的结果一致。这可以通过在业务逻辑中引入唯一标识符或记录已处理消息的状态来实现。
  • 消息确认机制:消费者在处理完消息后,提交已消费的偏移量(Offset)给Kafka,Kafka会记录已提交的偏移量,以便在消费者重新启动时从正确的位置继续消费。消费者可以定期提交偏移量,确保消息只被消费一次。
  • 4、消息顺序性

    rabbitmq 的消息顺序性主要依赖于以下几个方面:

    • 单个队列:rabbitmq 保证了同一个队列中的消息按照发布的顺序进入和出队。

    rokcetmq 的消息顺序性主要依赖于以下几个方面:

    • 有序分区:rokcetmq 保证了同一个队列(topic + queueId)中的消息按照发布的顺序存储和消费。

    kafka 的消息顺序性主要依赖于以下几个方面:

    • 有序分区:kafka 保证了同一个分区(topic + partition)中的消息按照发布的顺序存储和消费。

    5、事务消息

    RabbitMQ的事务消息:

    • RabbitMQ支持事务消息的发送和确认。在发送消息之前,可以通过调用"channel.txSelect()"来开启事务,然后将要发送的消息发布到交换机中。如果事务成功提交,消息将被发送到队列,否则事务会回滚,消息不会被发送。
    • 在消费端,可以通过"channel.txSelect()"开启事务,然后使用"basicAck"手动确认消息的处理结果。如果事务成功提交,消费端会发送ACK确认消息的处理;否则,事务回滚,消息将被重新投递。
    public class RabbitMQTransactionDemo {
        private static final String QUEUE_NAME = "transaction_queue";
    
        public static void main(String[] args) {
            try {
                // 创建连接工厂
                ConnectionFactory factory = new ConnectionFactory();
                factory.setHost("localhost");
    
                // 创建连接
                Connection connection = factory.newConnection();
    
                // 创建信道
                Channel channel = connection.createChannel();
    
                // 声明队列
                channel.queueDeclare(QUEUE_NAME, false, false, false, null);
    
                try {
                    // 开启事务
                    channel.txSelect();
    
                    // 发送消息
                    String message = "Hello, RabbitMQ!";
                    channel.basicPublish("", QUEUE_NAME, null, message.getBytes());
    
                    // 提交事务
                    channel.txCommit();
                } catch (Exception e) {
                    // 事务回滚
                    channel.txRollback();
                    e.printStackTrace();
                }
    
                // 关闭信道和连接
                channel.close();
                connection.close();
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }
    

    RocketMQ的事务消息:

    • RocketMQ提供了事务消息的机制,确保消息的可靠性和一致性。发送事务消息时,需要将消息发送到半消息队列,然后执行本地事务逻辑。事务执行成功后,通过调用"TransactionStatus.CommitTransaction"提交事务消息;若事务执行失败,则通过调用"TransactionStatus.RollbackTransaction"回滚事务消息。事务消息的最终状态由消息生产者根据事务执行结果进行确认。
    public class RocketMQTransactionDemo {
    
        public static void main(String[] args) throws Exception {
            // 创建事务消息生产者
            TransactionMQProducer producer = new TransactionMQProducer("group_name");
            producer.setNamesrvAddr("localhost:9876");
            
            // 设置事务监听器
            producer.setTransactionListener(new TransactionListener() {
                @Override
                public LocalTransactionState executeLocalTransaction(Message msg, Object arg) {
                    // 执行本地事务逻辑,根据业务逻辑结果返回相应的状态
                    // 返回 LocalTransactionState.COMMIT_MESSAGE 表示事务提交
                    // 返回 LocalTransactionState.ROLLBACK_MESSAGE 表示事务回滚
                    // 返回 LocalTransactionState.UNKNOW 表示事务状态未知
                }
    
                @Override
                public LocalTransactionState checkLocalTransaction(MessageExt msg) {
                    // 根据消息的状态,来判断本地事务的最终状态
                    // 返回 LocalTransactionState.COMMIT_MESSAGE 表示事务提交
                    // 返回 LocalTransactionState.ROLLBACK_MESSAGE 表示事务回滚
                    // 返回 LocalTransactionState.UNKNOW 表示事务状态未知
                }
            });
            
            // 启动事务消息生产者
            producer.start();
    
            // 构造消息
            Message msg = new Message("topic_name", "tag_name", "Hello, RocketMQ!".getBytes());
    
            // 发送事务消息
            TransactionSendResult sendResult = producer.sendMessageInTransaction(msg, null);
            System.out.println("Send Result: " + sendResult);
    
            // 关闭事务消息生产者
            producer.shutdown();
        }
    }
    

    Kafka的事务消息:

    • Kafka引入了事务功能来确保消息的原子性和一致性。事务消息的发送和确认在生产者端进行。生产者可以通过初始化事务,将一系列的消息写入事务,然后通过"commitTransaction()"提交事务,或者通过"abortTransaction()"中止事务。Kafka会保证在事务提交之前,写入的所有消息不会被消费者可见,以保持事务的一致性。
    public class KafkaTransactionDemo {
        public static void main(String[] args) {
            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());
            props.put(ProducerConfig.TRANSACTIONAL_ID_CONFIG, "transactional_id");
    
            Producer producer = new KafkaProducer(props);
    
            // 初始化事务
            producer.initTransactions();
    
            try {
                // 开启事务
                producer.beginTransaction();
    
                // 发送消息
                ProducerRecord record = new ProducerRecord("topic_name", "Hello, Kafka!");
                producer.send(record);
    
                // 提交事务
                producer.commitTransaction();
            } catch (ProducerFencedException e) {
                // 处理异常情况
                producer.close();
            } finally {
                producer.close();
            }
        }
    }
    

    6、ACK机制

    RabbitMQ的ACK机制:

    RabbitMQ使用ACK(消息确认)机制来确保消息的可靠传递。消费者收到消息后,需要向RabbitMQ发送ACK来确认消息的处理状态。只有在收到ACK后,RabbitMQ才会将消息标记为已成功传递,否则会将消息重新投递给其他消费者或者保留在队列中。

    以下是RabbitMQ ACK的Java示例:

    public class RabbitMQAckDemo {
    
        public static void main(String[] args) throws Exception {
            // 创建连接工厂
            ConnectionFactory factory = new ConnectionFactory();
            factory.setHost("localhost");
    
            // 创建连接
            Connection connection = factory.newConnection();
    
            // 创建信道
            Channel channel = connection.createChannel();
    
            // 声明队列
            String queueName = "queue_name";
            channel.queueDeclare(queueName, false, false, false, null);
    
            // 创建消费者
            String consumerTag = "consumer_tag";
            boolean autoAck = false; // 关闭自动ACK
    
            // 消费消息
            channel.basicConsume(queueName, autoAck, consumerTag, new DefaultConsumer(channel) {
                @Override
                public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
                    // 消费消息
                    String message = new String(body, "UTF-8");
                    System.out.println("Received message: " + message);
    
                    try {
                        // 模拟处理消息的业务逻辑
                        processMessage(message);
    
                        // 手动发送ACK确认消息
                        long deliveryTag = envelope.getDeliveryTag();
                        channel.basicAck(deliveryTag, false);
                    } catch (Exception e) {
                        // 处理消息异常,可以选择重试或者记录日志等操作
                        System.out.println("Failed to process message: " + message);
                        e.printStackTrace();
    
                        // 手动发送NACK拒绝消息,并可选是否重新投递
                        long deliveryTag = envelope.getDeliveryTag();
                        boolean requeue = true; // 重新投递消息
                        channel.basicNack(deliveryTag, false, requeue);
                    }
                }
            });
        }
    
        private static void processMessage(String message) {
            // 模拟处理消息的业务逻辑
        }
    }
    

    RocketMQ的ACK机制:

    RocketMQ的ACK机制由消费者控制,消费者从消息队列中消费消息后,可以手动发送ACK确认消息的处理状态。只有在收到ACK后,RocketMQ才会将消息标记为已成功消费,否则会将消息重新投递给其他消费者。

    以下是RocketMQ ACK的Java示例:

    public class RocketMQAckDemo {
    
        public static void main(String[] args) throws Exception {
            // 创建消费者
            DefaultMQPushConsumer consumer = new DefaultMQPushConsumer("group_name");
            consumer.setNamesrvAddr("localhost:9876");
    
            // 订阅消息
            consumer.subscribe("topic_name", "*");
    
            // 注册消息监听器
            consumer.registerMessageListener((MessageListenerConcurrently) (msgs, context) -> {
                for (MessageExt message : msgs) {
                    try {
                        // 消费消息
                        String msgBody = new String(message.getBody(), "UTF-8");
                        System.out.println("Received message: " + msgBody);
    
                        // 模拟处理消息的业务逻辑
                        processMessage(msgBody);
    
                        // 手动发送ACK确认消息
                        return ConsumeConcurrentlyStatus.CONSUME_SUCCESS;
                    } catch (Exception e) {
                        // 处理消息异常,可以选择重试或者记录日志等操作
                        System.out.println("Failed to process message: " + new String(message.getBody()));
                        e.printStackTrace();
                        return ConsumeConcurrentlyStatus.RECONSUME_LATER;
                    }
                }
                return ConsumeConcurrentlyStatus.CONSUME_SUCCESS;
            });
    
            // 启动消费者
            consumer.start();
        }
    
        private static void processMessage(String message) {
            // 模拟处理消息的业务逻辑
        }
    }
    

    Kafka的ACK机制:

    Kafka的ACK机制用于控制生产者在发送消息后,需要等待多少个副本确认才视为消息发送成功。这个机制可以通过设置acks参数来进行配置。

    在Kafka中,acks参数有三个可选值:

  • acks=0:生产者在发送消息后不需要等待任何确认,直接将消息发送给Kafka集群。这种方式具有最高的吞吐量,但是也存在数据丢失的风险,因为生产者不会知道消息是否成功发送给任何副本。
  • acks=1:生产者在发送消息后只需要等待首领副本(leader replica)确认。一旦首领副本成功接收到消息,生产者就会收到确认。这种方式提供了一定的可靠性,但是如果首领副本在接收消息后但在确认之前发生故障,仍然可能会导致数据丢失。
  • acks=all:生产者在发送消息后需要等待所有副本都确认。只有当所有副本都成功接收到消息后,生产者才会收到确认。这是最安全的确认机制,确保了消息不会丢失,但是需要更多的时间和资源。acks=-1与acks=all是等效的。
  • 下面是一个使用Java编写的Kafka生产者示例代码:

    public class KafkaProducerDemo {
        public static void main(String[] args) {
            // 配置Kafka生产者的参数
            Properties props = new Properties();
            props.put("bootstrap.servers", "localhost:9092"); // Kafka集群的地址和端口
            props.put("key.serializer", "org.apache.kafka.common.serialization.StringSerializer"); // 键的序列化器
            props.put("value.serializer", "org.apache.kafka.common.serialization.StringSerializer"); // 值的序列化器
            props.put("acks", "all"); // 设置ACK机制为所有副本都确认
    
            // 创建生产者实例
            KafkaProducer producer = new KafkaProducer(props);
    
            // 构造消息
            String topic = "my_topic";
            String key = "my_key";
            String value = "Hello, Kafka!";
    
            // 创建消息记录
            ProducerRecord record = new ProducerRecord(topic, key, value);
    
            // 发送消息
            producer.send(record, new Callback() {
                @Override
                public void onCompletion(RecordMetadata metadata, Exception exception) {
                    if (exception != null) {
                        System.err.println("发送消息出现异常:" + exception.getMessage());
                    } else {
                        System.out.println("消息发送成功!位于分区 " + metadata.partition() + ",偏移量 " + metadata.offset());
                    }
                }
            });
    
            // 关闭生产者
            producer.close();
        }
    }
    

    如果文章对你有帮助,欢迎关注+点赞。必回关!!!

    相关文章

    JavaScript2024新功能:Object.groupBy、正则表达式v标志
    PHP trim 函数对多字节字符的使用和限制
    新函数 json_validate() 、randomizer 类扩展…20 个PHP 8.3 新特性全面解析
    使用HTMX为WordPress增效:如何在不使用复杂框架的情况下增强平台功能
    为React 19做准备:WordPress 6.6用户指南
    如何删除WordPress中的所有评论

    发布评论