RocketMq
RocketMQ的使用场景
应用解耦
流量削峰
数据分发
RocketMQ的角色介绍
Rocket如何保证消息不丢失
要想保证消息不丢失,需要从以下几个方面考虑:
普通对于顺序消息,异常默认不重试,可以用户自己重试,并发送到其他队列。
严格有序消息:发送严格有序消息,通过指定队列,保证严格有序,异常默认不重试。
Producer 发送消息
同步发送
public void send() throws Exception {
String message = "test producer";
Message sendMessage = new Message("topic1", "tag1", message.getBytes());
sendMessage.putUserProperty("name1","value1");
SendResult sendResult = null;
DefaultMQProducer producer = new DefaultMQProducer("testGroup");
producer.setNamesrvAddr("localhost:9876");
producer.setRetryTimesWhenSendFailed(3);
try {
sendResult = producer.send(sendMessage);
} catch (Exception e) {
e.printStackTrace();
}
if (sendResult != null) {
System.out.println(sendResult.getSendStatus());
}
}
异步发送
重写回调方法,代码如下
public void sendAsync() throws Exception {
String message = "test producer";
Message sendMessage = new Message("topic1", "tag1", message.getBytes()); s
endMessage.putUserProperty("name1","value1");
DefaultMQProducer producer = new DefaultMQProducer("testGroup");
producer.setNamesrvAddr("localhost:9876");
producer.setRetryTimesWhenSendFailed(3);
producer.send(sendMessage, new SendCallback() {
@Override
public void onSuccess(SendResult sendResult) {
}
@Override
public void onException(Throwable e) {
TODO 可以在这里加入重试逻辑
}
});
}
Broker 保存消息
同步刷盘
消息写入内存后,立刻请求刷盘线程进行刷盘,如果消息未在约定的时间内(默认 5 s)刷盘成功,就返回 FLUSH_DISK_TIMEOUT,Producer 收到这个响应后,可以进行重试。同步刷盘策略保证了消息的可靠性,同时降低了吞吐量,增加了延迟。要开启同步刷盘,需要增加下面配置:
flushDiskType=SYNC_FLUSH
异步刷盘
默认。消息写入 CommitLog 时,并不会直接写入磁盘,而是先写入 PageCache 缓存后返回成功,然后用后台线程异步把消息刷入磁盘。异步刷盘提高了消息吞吐量,但是可能会有消息丢失的情况,比如断点导致机器停机,PageCache 中没来得及刷盘的消息就会丢失。
Broker集群主从之间复制过程出现异常
在默认方式下,当消息成功写入主节点时,就会返回确认响应给生产者,并异步将消息复制到从节点。然而,如果主节点突然宕机且无法恢复,尚未复制到从节点的消息将会丢失。
为了进一步提高消息的可靠性,我们可以采用同步复制方式。主节点将会同步等待从节点完成复制,然后才返回确认响应。这样可以确保消息的可靠性。可以通过brokerRole=SYNC_MASTER
参数进行控制。
Consumer 消费消息
先想想什么情况下,消息存储会丢失呢?
因为各种原因消费失败,但是还是提交了消费位点,这条消息从业务角度来说就“丢失”了。
那怎么解决这个问题?
跟消息生产一样,其实思路是比较直接的,就是 「消息确认机制」和「失败重试机制」。
消费者从RocketMQ拉取消息后,需要返回"CONSUME_SUCCESS"来表示业务方已经正常完成消费。只有返回"CONSUME_SUCCESS"才算作消费完成。这就是消费时的「消息确认机制」。
如果返回"CONSUME_LATER",则会按照不同的消息延迟级别进行再次消费,延迟级别从秒到小时不等,最长延迟时间为2个小时后再次尝试消费。这就是消费时的「失败重试机制」。
重试消息会被存入名为 "%RETRY%+消费组名称" 的Topic
中,原始主题Topic
会存入属性中。然后会基于定时任务机制,在到期时将任务再次拉取出来。
另外,RocketMQ跟kafka不同的是,天然支持了 「死信队列机制」。
如果在尝试消费的过程中达到了最大重试次数(通常为16次),仍然无法成功消费,则消息将被发送到死信队列,以确保消息存储的可靠性。后续业务可以根据死信队列,来做相关补偿措施。
极端情况
如果对消息丢失零容忍,我们必须要考虑极端情况,比如整个 RocketMQ 集群挂了,这时 Producer 端发送消息一定会失败,可以考虑在 Producer 端做降级,把要发送的消息保存到本地数据库或磁盘,等 RocketMQ 恢复以后再把本地消息推送出去。
RocketMq如何保证消息的幂等性
1. 使用唯一标识符:
为每条消息生成一个唯一的标识符,例如 UUID 或者基于业务逻辑的唯一键。
在消费端,接收到消息后,首先根据这个唯一标识符检查是否已经处理过该消息。如果已经处理过,则直接忽略,否则进行处理并将标识符记录下来(可以存储在数据库、缓存等)。
例如,假设消息中包含一个订单号作为唯一标识符,消费者在处理消息前先查询数据库中是否存在该订单号的处理记录,如果存在则跳过处理。
2. 基于数据库的乐观锁:
假设消费消息的操作涉及对数据库的更新,在数据库表中添加一个版本号字段。
消费者在更新数据时,基于版本号进行判断。如果版本号与预期相符,则进行更新并增加版本号;如果版本号不一致,说明该消息已经被处理过,直接忽略。
比如,有一张订单表包含 order_id(订单号)、status(订单状态)和 version(版本号)字段。消费者获取到订单处理消息后,根据订单号获取订单数据,只有在当前版本号与获取到的版本号一致时,才更新订单状态并将版本号加 1 。
3. 利用分布式锁:
可以使用分布式锁服务,例如 Redis 分布式锁。
在消费消息前尝试获取对应消息的分布式锁,如果获取成功则进行处理,处理完成后释放锁;如果获取锁失败,说明该消息正在被处理或者已经处理过,直接忽略。
例如,以消息的唯一标识符作为分布式锁的键,消费者在处理消息前尝试获取该键对应的锁。
4. 记录消费日志:
在消费端记录每条消息的处理日志,包括消息的标识符和处理时间等信息。
每次处理新消息时,先查询消费日志,判断是否已经处理过。
比如,将消息的标识符和处理时间存储在一个专门的消费日志表中,处理新消息前先查询该表。
通过以上方法,可以在 RocketMQ 中较好地实现消息的幂等性,确保相同的消息在多次处理时不会产生不一致的结果。
RocketMq如何保证消息的顺序消费
public class Producer { public static void main(String[] args) throws UnsupportedEncodingException { try { DefaultMQProducer producer = new DefaultMQProducer("please_rename_unique_group_name"); producer.start(); String[] tags = new String[] {"TagA", "TagB", "TagC", "TagD", "TagE"}; for (int i = 0; i < 100; i++) { int orderId = i % 10; Message msg = new Message("TopicTest", tags[i % tags.length], "KEY" + i, ("Hello RocketMQ " + i).getBytes(RemotingHelper.DEFAULT_CHARSET)); 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(); } catch (MQClientException | RemotingException | MQBrokerException | InterruptedException e) { e.printStackTrace(); } } }
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】凌霞软件回馈社区,博客园 & 1Panel & Halo 联合会员上线
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】博客园社区专享云产品让利特惠,阿里云新客6.5折上折
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步