RocketMQ使用场景之可靠性优先Case

一、顺序消息的实现

1.全局顺序消息
要保证全局顺序消息,需要先把 Topic 的读写队列数设置为 1,
然后 Producer 和 Consumer 的并发设置也要是1。
在发送端,要做到 把同一业务 ID 的消息发送到同一个 Message Queue
(使用 MessageQueueSelector类)

2.部分顺序消息
在消费过程中,要做到从 同一个 Message Queue 读取的消息不被并发处理
(使用 MessageListenerOrderly类)
具体实现方式:

在 MessageListenerOrderly 的实现中,为每个 Consumer Queue 加个锁,
消费每个消息前,需要先获得这个消息对应的 Consumer Queue 所对应的锁,
这样保证了同一时间,同一个 Consumer Queue 的消息不被并发消 费,
但不同 Consumer Queue 的消息可以并发处理 。

具体参考代码如下:

package org.apache.rocketmq.example.ordermessage;

import java.util.List;
import java.util.concurrent.atomic.AtomicLong;
import org.apache.rocketmq.client.consumer.DefaultMQPushConsumer;
import org.apache.rocketmq.client.consumer.listener.ConsumeOrderlyContext;
import org.apache.rocketmq.client.consumer.listener.ConsumeOrderlyStatus;
import org.apache.rocketmq.client.consumer.listener.MessageListenerOrderly;
import org.apache.rocketmq.client.exception.MQClientException;
import org.apache.rocketmq.common.consumer.ConsumeFromWhere;
import org.apache.rocketmq.common.message.MessageExt;

public class Consumer {

    public static void main(String[] args) throws MQClientException {
        DefaultMQPushConsumer consumer = new DefaultMQPushConsumer("please_rename_unique_group_name_3");

        consumer.setConsumeFromWhere(ConsumeFromWhere.CONSUME_FROM_FIRST_OFFSET);

        consumer.subscribe("TopicTest", "TagA || TagC || TagD");

        consumer.registerMessageListener(new MessageListenerOrderly() {
            AtomicLong consumeTimes = new AtomicLong(0);

            @Override
            public ConsumeOrderlyStatus consumeMessage(List<MessageExt> msgs, ConsumeOrderlyContext context) {
                context.setAutoCommit(false);
                System.out.printf("%s Receive New Messages: %s %n", Thread.currentThread().getName(), msgs);
                this.consumeTimes.incrementAndGet();
                if ((this.consumeTimes.get() % 2) == 0) {
                    return ConsumeOrderlyStatus.SUCCESS;
                } else if ((this.consumeTimes.get() % 3) == 0) {
                    return ConsumeOrderlyStatus.ROLLBACK;
                } else if ((this.consumeTimes.get() % 4) == 0) {
                    return ConsumeOrderlyStatus.COMMIT;
                } else if ((this.consumeTimes.get() % 5) == 0) {
                    context.setSuspendCurrentQueueTimeMillis(3000);
                    return ConsumeOrderlyStatus.SUSPEND_CURRENT_QUEUE_A_MOMENT;
                }

                return ConsumeOrderlyStatus.SUCCESS;
            }
        });

        consumer.start();
        System.out.printf("Consumer Started.%n");
    }

}

二、消息重复问题

消息重复一般情况下不会发生,但是如果消息量大,网络有波动,消息重复就是个大概率事件。 主要造成的原因是:
Producer有个setRetryTimesWhenSendFailed函数, 设置在同步方式下自动重试的次数,默认值是 2,这样当第一次发送消息时, Broker端接收到了消息但是没有正确返回发送成功的状态,就造成了消息重复。

解决办法:

  1. 保证消费逻辑的幕等性(多次调用和一次调用效果相同)
  2. 维护一个巳消费消息的记录,消费前查询这个消息是否被消费过 (只能在入库时,手动写代码去重)

三、消息优先级

RocketMQ 是个先入先出的队列,不支持消息级别或者 Topic 级别的优先级 ,只能通过间接方式。

情景1:
同一Topic下某一消息需要被及时处理,可另外开辟topic
情景2:

一个订单处理系统,接收从 100家快递门店过来的请求,把这些请求通过 Producer 写人 RocketMQ ;
订单处理程序通过 Consumer 从队列里读取消 息并处理,每天最多处理 1 万单 。 
如果这 100 个快递门店中某几个门店订单量大增,
比如门店一接了个大客户,一个上午就发出 2万单消息请求,
这样其他的 99 家门店可能被迫等待门店一的 2 万单处理完,
也就是两天后订单才能被处理,显然很不公平 。

解决:

创建一个 Topic, 设置 Topic 的 MessageQueue 数 量 超过 100 个,
Producer根据订单的门店号,把每个门店的订单写人 一 个 MessageQueue。 
DefaultMQPushConsumer默认是采用循环的方式逐个读取一个 Topic 的 所有 
MessageQueue,这样如果某家门店订单 量 大增,这家门店对应的 
MessageQueue 消息数增多,等待时间增长,但不会造成其他家门店等待时间增长。

情景3:

一个应用程序同时处理 TypeA、 TypeB、 TypeC 三类消息 。 
TypeA 处于第 一优先级,要确保只要有 TypeA消息,必须优先处理; 
TypeB处于第二优先级; 
TypeC 处于第三 优先级 。 

解决:

对这种要求,或者逻辑更复杂的要求,需要自己编码实现优先级控制,
如果上述的三类消息在一个 Topic 里,可以使 用 PullConsumer,
自主控制 Messag巳Queue 的遍历,以及消息的读取;

四、各种故障对消息队列的影响

故障清单:

1) Broker正常关闭,启动;
2) Broker异常 Crash,然后启动;
3) OS Crash,重启;
4 )机器断电,但能马上恢复供电;
5 )磁盘损坏;
6) CPU、 主板、内存等关键设备损坏 。

分析:

1 )情况 属于可控的软件 问题,内存中的数据不会丢失 。 
若重启过程中有持续运行的 Consumer, Master机器出故障后, 
Consumer会自动重连到对应的 Slave 机器,不会有消息丢失和偏差 。 
当 Master 角色的机器 重启 以 后, Consumer又会重新连接到 Master机器
若有持续运行的 Producer,一台Master 出故障后,
Producer只能向 Topic下其他的 Master机器发送消息,
如果Producer采用同步发送方式,不会有消息丢失 。

234)情况属于软件故障,内存的数据可能丢失,所以刷盘策略不同,造成的影响也不同,
如果 Master、 Slave都配置成 SYNC_FLUSH,可以达到和第 1 种情况相同的效果 。

56 )情况属于硬件故障 ,发生第 56 种情况的故障,原有机器的磁盘数据可能会丢失。 如果Master和Slave机器间配置成同步复制方式,某一台机器发生 56 的故障,也可以达到消息不丢失的效果 。 机器间是异步复制,两次 Sync间的消息会丢失。

常规思路:

总的来说,当设置成:
1 )多 Master,每个 Master 带有 Slave; 
2 )主从之间设置成 SYNC_MASTER; 
3 ) Producer 用同步方式写;
4 )刷盘策略设置成 SYNC FLUSH。
就可以消除单点依赖,即使某台机器出现极端故障也不会丢消息 。
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

喜鹊先生Richard

随缘~

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值