RocketMQ知识总结

持续更新...

一、消息队列

“消息”是在两台计算机间传送的数据单位。消息可以是简单的字符串,也可以是复杂对象。
“消息队列”就是在消息的传输过程中保存消息的容器。消息队列的主要目的是提供路由并保证消息的传递;如果发送消息时接收者不可用,消息队列会保留消息,直到可以成功地传递它。

1.应用场景

异步

减少调用链路,缩短接口整体的响应时间。

解耦

易于系统扩展,如果在后续中增加其它业务处理,都可以通过独立的服务订阅mq来进行处理。

削峰

当流量激增,服务器、数据库等无法在短时间内处理大量的请求,此时可以把请求先放到mq中,然后根据服务器的处理能力进行消费,不至于直接让服务器挂掉。

2.主流消息队列

常用消息队列的特性对比:

3.RocketMQ优势和劣势

优势:吞吐量高;高可用;支持各种高级的功能,如延迟消息、事务消息、消息回溯、死信队列等;基于Java开发的,符合大多数公司的技术栈,很容易就可以阅读源码和定制开发。

劣势:RocketMQ的官方文档相对简单

二、RocketMQ集群架构

RocketMQ包括哪些部分?集群又是如何运转的?

1.技术架构

RocketMQ架构上主要分为四部分,如上图:

  • 消息生产者(Producer):消息发布的角色,通过MQ的负载均衡模块选择相应的Broker集群队列进行消息投递,投递的过程支持快速失败并且低延迟。

RocketMQ提供多种发送方式:

同步发送:同步发送指消息发送方发出数据后会在收到接收方发回响应之后才发下一个数据包。一般用于重要通知消息,例如短信通知。

异步发送:异步发送指发送方发出数据后,不等接收方发回响应,接着发送下个数据包,一般用于可能链路耗时较长而对响应时间敏感的业务场景,例如用户视频上传后通知启动转码服务。

单向发送:单向发送是指只负责发送消息而不等待服务器回应且没有回调函数触发,适用于耗时非常短但对可靠性要求并不高的场景,例如日志收集。

  • 消息消费者(Consumer):消息消费的角色,支持以push推,pull拉两种模式对消息进行消费。同时也支持集群方式和广播方式的消费。

集群模式:集群消费模式下,相同Consumer Group的每个Consumer实例平均分摊消息。

广播模式:广播消费模式下,相同Consumer Group的每个Consumer实例都接收全量的消息。

  • 名字服务(NameServer):NameServer是Topic路由注册中心,支持Broker的动态注册与发现。Broker管理,NameServer接受Broker集群的注册信息并且保存下来作为路由信息的基本数据。然后提供心跳检测机制,检查Broker是否还存活;路由信息管理,每个NameServer将保存关于Broker集群的整个路由信息和用于客户端查询的队列信息。Producer和Conumser通过NameServer就可以知道整个Broker集群的路由信息,从而进行消息的投递和消费。NameServer通常也是集群的方式部署,但相互独立,没有信息交换。Broker是向每一台NameServer注册自己的路由信息,所以每一个实例上面都保存一份完整的路由信息。当某NameServer因某种原因下线了,Broker仍然可以向其它NameServer同步其路由信息,Producer,Consumer仍然可以动态感知Broker的路由的信息。
  • 代理服务器(BrokerServer):负责存储消息、转发消息。代理服务器在RocketMQ系统中负责接收从生产者发送来的消息并存储、同时为消费者的拉取请求作准备。代理服务器也存储消息相关的元数据,包括消费者组、消费进度偏移和主题和队列消息等。

其它概念:

  • 主题(Topic):表示一类消息的集合,每个主题包含若干条消息,每条消息只能属于一个主题,是RocketMQ进行消息订阅的基本单位。
  • 消息(Message):消息系统所传输信息的物理载体,生产和消费数据的最小单位,每条消息必须属于一个主题。RocketMQ中每个消息拥有唯一的Message ID,且可以携带具有业务标识的Key。系统提供了通过Message ID和Key查询消息的功能。
  • 标签(Tag):为消息设置的标志,来自同一业务的消息可以在同一主题下设置不同标签。标签能够有效地保持代码的清晰度和连贯性,并优化RocketMQ提供的查询系统。消费者可以根据Tag实现对不同子主题的不同消费逻辑,实现更好的扩展性。
  • 消息队列(MessageQueue):对于每个Topic都可以设置一定数量的消息队列用来进行数据的读取

2.集群支持

2.1 单Master模式

风险大,一旦Broker重启或者宕机时,会导致整个服务不可用,多用于本地测试。

2.2 多Master模式

一个集群无Slave,全是Master,例如2个Master或者3个Master:

  • 优点:配置简单,单个Master宕机或重启维护对应用无影响,在磁盘配置得当(RAID10),消息也不会丢(异步刷盘丢失少量消息,同步刷盘一条不丢),性能最高;
  • 缺点:单台机器宕机期间,这台机器上未被消费的消息在机器恢复之前不可订阅,消息实时性会受到影响。

2.3 多Master多Slave模式-异步复制

每个Master配置一个Slave,有多对Master-Slave,HA采用异步复制方式,主备有短暂消息延迟(ms)

  • 优点:即使磁盘损坏,消息丢失的非常少,且消息实时性不会受影响,同时Master宕机后,消费者仍然可以从Slave消费,性能同多Master模式几乎一样;
  • 缺点:Master宕机,磁盘损坏情况下会丢失少量消息。

2.4 多Master多Slave模式-同步双写

每个Master配置一个Slave,有多对Master-Slave,HA采用同步双写方式,即只有主备都写成功,才向应用返回成功:

  • 优点:数据与服务都无单点故障,Master宕机情况下,消息无延迟,服务可用性与数据可用性都非常高;
  • 缺点:性能比异步复制模式略低,发送单个消息的RT会略高,且目前版本在主节点宕机后,备机不能自动切换为主机。

2.5 RocketMQ-on-DLedger

4.5版本之后,基于DLedger的RocketMQ集群可以实现,基于Raft协议

实现两个功能,数据同步主节点的选举

DLedger负责写入数据到CommitLog磁盘文件里去,将消息同步到其它Follower节点,只有大多数都接收到消息,Leader节点的这条日志状态就会更新为committed状态,通知其它的Follower节点

当有主节点挂掉,DLedger来进行Leader Broker选举出来一个新的Leader Broker继续对外提供服务,而且会对没有完成的数据同步进行一些恢复性的操作,保证数据不会丢失。

动画:http://thesecretlivesofdata.com/raft/

3.集群流程

  • 1.启动NameServer,NameServer起来后监听端口,等待Broker、Producer、Consumer连上来,相当于一个路由控制中心。
  • 2.Broker启动,跟所有的NameServer保持长连接,每隔30s定时发送心跳包。心跳包中包含当前Broker信息(IP+端口等)以及存储所有Topic信息。注册成功后,NameServer集群中就有Topic跟Broker的映射关系。Nameserver每隔10s扫描路由表,如果检测到Broker服务宕机,则移除对应的路由信息。
  • 3.收发消息前,先创建Topic,创建Topic时需要指定该Topic要存储在哪些Broker上,也可以在发送消息时自动创建Topic。
  • 4.Producer发送消息,启动时先跟NameServer集群中的其中一台建立长连接,并从NameServer中获取当前发送的Topic存在哪些Broker上,轮询从队列列表中选择一个队列,然后与队列所在的Broker建立长连接从而向Broker发消息。
  • 5.Consumer跟Producer类似,跟其中一台NameServer建立长连接,获取当前订阅Topic存在哪些Broker上,然后直接跟Broker建立连接通道,开始消费消息。

Producer 只能将消息发送到 Broker master,但是 Consumer 则不一样,它同时和提供 Topic 服务的 Master 和 Slave建立长连接,既可以从 Broker Master 订阅消息,也可以从 Broker Slave 订阅消息

三、RocketMQ特性

消费方式

  • 推送消费:该模式下Broker收到数据后会主动推送给消费端,该消费模式一般实时性较高。缺点可能超出客户端的消费能力。
  • 拉取消费: 应用通常主动调用Consumer的拉消息方法从Broker服务器拉消息、主动权由应用控制。一旦获取了批量消息,应用就会启动消费过程。缺点消息延迟较高,可能没有消息导致消息资源的浪费.

RocketMQ实现方式:通过长轮询的方式进行拉取消息。长轮询的机制是由客户端发起pull请求,服务端接收到客户端的请求后,如果发现队列中没有消息,并不立即返回,而是持有该请求一段时间,在此期间,服务端不断轮询队列中是否有新消息,如果有消息则用现有连接将消息返回给客户端,如果一段时间内还是没有新消息,则返回空。

长轮询机制的好处在于,其本质还是pull,所以消息处理的主动权还是在客户端手中,客户端就可以根据自己的能力去做消息处理。而服务端持有请求一段时间的机制又很大程序的避免了空拉取,减少了资源的浪费

顺序消息

消息有序指的是一类消息消费时,能按照发送的顺序来消费。例如:一个订单产生了三条消息分别是订单创建、订单付款、订单完成,消费时要按照这个顺序消费才能有意义。顺序消息分为全局顺序消息与局部顺序消息。

  • 全局顺序

对于指定的一个 Topic,所有消息按照严格的FIFO 的顺序进行发布和消费。此时topic的队列需要设置为1,假设读写队列有多个,消息就会存储在多个队列中,消费者负载时可能会分配到多个消费队列同时进行消费,多队列并发消费时,无法保证消息消费顺序性

适用场景:性能要求不高,所有的消息严格按照 FIFO 原则进行消息发布和消费的场景

  • 局部顺序

对于指定的一个 Topic,同一个队列内的消息按照严格的 FIFO 顺序进行发布和消费。

适用场景:性能要求高,在同一个队列中严格的按照 FIFO 原则进行消息发布和消费的场景。

局部顺序需要保证

1.消息顺序发送,业务方在发送时,针对同一个业务编号(如同一订单)的消息需要保证在一个线程内顺序同步发送。

2.消息顺序存储,MQ的topic下会存在多个queue,要保证消息的顺序存储,可以使用Hash取模法发送,将消息发送到一个queue中。

3.消息顺序消费,要保证消息顺序消费,同一时刻一个消费队列只能被一个消费线程消费

发送消息:


    DefaultMQProducer producer = new DefaultMQProducer("ProducerGroup");
    producer.setNamesrvAddr("127.0.0.1:9876");
    producer.start();

    String[] tags = new String[] {"TagA", "TagB", "TagC", "TagD", "TagE"};

    for (int i = 0; i < 8; i++) {
        int orderId = i % 10;
        Message msg =
            new Message("OrderTopicTest", 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("orderId="+orderId+"".hashCode() + " %s%n", sendResult);
    }

    producer.shutdown();

定时消息

定时消息(延迟队列)是指消息发送到broker后,不会立即被消费,等待特定时间投递给真正的topic。

使用场景:下单之后如果30分钟没有付款就自动取消订单;或者一定时间后订单自动评价。解决了大量订单的定时扫描的问题。

使用限制:RocketMQ并不支持任意时间的延时(支持任意时间broker需要对消息进行排序,增加性能开销),需要设置固定的延时等级,如下:

阿里提供的RocketMQ参数可设置40天内的任何时刻

实现原理:

1.定时消息会暂存在名为SCHEDULE_TOPIC_XXXX的topic中,并根据delayTimeLevel存入特定的queue,queueId = delayTimeLevel – 1,即一个queue只存相同延迟的消息,保证具有相同发送延迟的消息能够顺序消费。

2.broker通过定时任务消费SCHEDULE_TOPIC_XXXX,判断是否到时间开始消费,然后清除了消息的延迟级别,并且恢复了真正的消息主题和队列Id,消费者就可以消费消息了。

CommitLog处理延时消息,延迟消息的主题都被暂时更改为SCHEDULE_TOPIC_XXXX,并且根据延迟级别延迟消息变更了新的队列Id。

// 延迟级别大于0就是延时消息
if (msg.getDelayTimeLevel() > 0) {
    if (msg.getDelayTimeLevel() > this.defaultMessageStore.getScheduleMessageService().getMaxDelayLevel()) {
        msg.setDelayTimeLevel(this.defaultMessageStore.getScheduleMessageService().getMaxDelayLevel());
    }

    //获取延迟消息对应的topic
    topic = TopicValidator.RMQ_SYS_SCHEDULE_TOPIC;
    //获取延迟消息队列id
    queueId = ScheduleMessageService.delayLevel2QueueId(msg.getDelayTimeLevel());

    //备份真正的主题和队列id
    MessageAccessor.putProperty(msg, MessageConst.PROPERTY_REAL_TOPIC, msg.getTopic());
    MessageAccessor.putProperty(msg, MessageConst.PROPERTY_REAL_QUEUE_ID, String.valueOf(msg.getQueueId()));
    msg.setPropertiesString(MessageDecoder.messageProperties2String(msg.getProperties()));
    //设置延迟消息的主题和队列id
    msg.setTopic(topic);
    msg.setQueueId(queueId);
}

 

ScheduleMessageService遍历所有延迟级别,根据延迟级别获得对应队列的偏移量,如果偏移量不存在,则设置为0。然后为每个延迟级别创建定时任务。然后,又创建了一个定时任务,用于持久化每个队列消费的偏移量。持久化的频率由flushDelayOffsetInterval属性进行配置,默认为10秒。

public void start() {
    if (started.compareAndSet(false, true)) {
        this.timer = new Timer("ScheduleMessageTimerThread", true);
        //遍历所有延迟级别
        for (Map.Entry<Integer, Long> entry : this.delayLevelTable.entrySet()) {
            Integer level = entry.getKey();
            Long timeDelay = entry.getValue();
            //根据延迟级别获取对应队列的偏移量
            Long offset = this.offsetTable.get(level);
            // 如果偏移量为null,则设置为0
            if (null == offset) {
                offset = 0L;
            }

            if (timeDelay != null) {
                //为每个延迟级别创建定时任务
                this.timer.schedule(new DeliverDelayedMessageTimerTask(level, offset), FIRST_DELAY_TIME);
            }
        }

        //每隔10s,持久化信息到磁盘
        this.timer.scheduleAtFixedRate(new TimerTask() {

            @Override
            public void run() {
                try {
                    if (started.get()) ScheduleMessageService.this.persist();
                } catch (Throwable e) {
                    log.error("scheduleAtFixedRate flush exception", e);
                }
            }
        }, 10000, this.defaultMessageStore.getMessageStoreConfig().getFlushDelayOffsetInterval());
    }
}


//重新构建消息,发送到原来的topic中
MessageExtBrokerInner msgInner = this.messageTimeup(msgExt);
if (TopicValidator.RMQ_SYS_TRANS_HALF_TOPIC.equals(msgInner.getTopic())) {
    log.error("[BUG] the real topic of schedule msg is {}, discard the msg. msg={}",
            msgInner.getTopic(), msgInner);
    continue;
}
PutMessageResult putMessageResult =
    ScheduleMessageService.this.writeMessageStore
        .putMessage(msgInner);

消息过滤

RocketMQ的消费者可以根据Tag进行消息过滤,也支持自定义属性过滤。消息过滤目前是在Broker端实现的,优点是减少了对于Consumer无用消息的网络传输,缺点是增加了Broker的负担,而且实现相对复杂。除此之外还支持SQL过滤

// tag过滤
consumer.subscribe("TagFilterTest", "TagA || TagC");

// sql过滤 需要在broker.conf配置enablePropertyFilter=true
consumer.subscribe("SqlFilterTest",
    MessageSelector.bySql("(TAGS is not null and TAGS in ('TagA', 'TagB'))" +
        "and (a is not null and a between 0 and 3)"));
 

回溯消费

回溯消费是指Consumer已经消费成功的消息,由于业务上需求需要重新消费。重新消费一般是按照时间维度,例如由于Consumer系统故障,恢复后需要重新消费1小时前的数据,那么Broker要提供一种机制,可以按照时间维度来回退消费进度。RocketMQ支持按照时间回溯消费,时间维度精确到毫秒。

//创建生产者 enableMsgTrace=true
DefaultMQProducer producer = new DefaultMQProducer("ProducerGroupName",true);

//创建消费者enableMsgTrace=true
DefaultMQPushConsumer consumer = new DefaultMQPushConsumer("CID_JODIE_1",true);
consumer.subscribe("TopicTest", "*");
consumer.setConsumeFromWhere(ConsumeFromWhere.CONSUME_FROM_FIRST_OFFSET);

// 设置回溯消息时间
consumer.setConsumeTimestamp("20181109221800");
consumer.registerMessageListener(new MessageListenerConcurrently() {

    @Override
    public ConsumeConcurrentlyStatus consumeMessage(List<MessageExt> msgs, ConsumeConcurrentlyContext context) {
        System.out.printf("%s Receive New Messages: %s %n", Thread.currentThread().getName(), msgs);
        return ConsumeConcurrentlyStatus.CONSUME_SUCCESS;
    }
});
consumer.start();
System.out.printf("Consumer Started.%n");
 

事务消息

Half Message(半消息)Producer已经把消息成功发送到了 Broker 端,但此消息消息对于consumer是不可见状态,对消息的二次确认后,Consumer才能去消费它。

  1. 生产者先发送Half Message到Broker端,会存储到RMQ_SYS_TRANS_HALF_TOPIC的消费队列中,此时对consumer是不可见状态
  2. 发送成功后,执行本地事务
  3. 执行本地事务,进行消息确认,如果本地事务成功,那么producer向Broker服务器发送Commit,这样B服务消费者就可以消费该message。如果失败,那么producer向Broker服务器发Rollback,那么就会直接删除上面这条半消息。
  4. 如果因为网络等原因没有返回失败还是成功,那么会执行RocketMQ的回调接口,来进行事务的回查。一旦发现事务处理成功,则把当前这条消息设置为可见

消息回查,由于网络等原因,导致 Producer 端一直没有对 Half Message进行二次确认。Broker服务器会定时扫描半消息,会主动询问 Producer端该消息的最终状态(Commit或者Rollback)即为消息回查。

实现监听器,重写方法,注册到Producer上。

public interface TransactionListener {
    /**
     * When send transactional prepare(half) message succeed, this method will be invoked to execute local transaction.
     *
     * @param msg Half(prepare) message
     * @param arg Custom business parameter
     * @return Transaction state
     */
    LocalTransactionState executeLocalTransaction(final Message msg, final Object arg);

    /**
     * When no response to prepare(half) message. broker will send check message to check the transaction status, and this
     * method will be invoked to get local transaction status.
     *
     * @param msg Check message
     * @return Transaction state
     */
    LocalTransactionState checkLocalTransaction(final MessageExt msg);
}
 

消息重试

生产者重试:

DefaultMQProducer可以设置消息发送失败的最大重试次数,并可以结合发送的超时时间来进行重试的处理

// 同步发送消息,如果5秒内没有发送成功,则重试5次
DefaultMQProducer producer = new DefaultMQProducer("DefaultProducer");
producer.setRetryTimesWhenSendFailed(5);
producer.send(msg,5000L);
 

消费者重试:

Consumer消费消息失败后,提供一种重试机制令消息再消费一次。RocketMQ会为每个消费组都设置一个Topic名称为“%RETRY%+consumerGroup”的重试队列,用于暂时保存因为各种异常而导致Consumer端无法消费的消息。考虑到异常恢复起来需要一些时间,会为重试队列设置多个重试级别,每个重试级别都有与之对应的重新投递延时,重试次数越多投递延时就越大。

而如果一直这样重复消费都持续失败到一定次数,就会投递到死信队列。应用可以监控死信队列来做人工干预。死信队列的topic以%DLQ%开头。

没有必要重试十几次浪费时间和资源,可以设置当尝试重复一定次数达到我们想要的结果时如果还是消费失败,那么我们可以将对应的消息进行记录,并且结束重复尝试

consumer.registerMessageListener((MessageListenerConcurrently) (list,
consumeOrderlyContext) -> {
for (MessageExt messageExt : list) {
//获取重试次数
if(messageExt.getReconsumeTimes()==3) {
//可以将对应的数据保存到数据库,以便人工干预
System.out.println(messageExt.getMsgId()+","+messageExt.getBody());
return ConsumeConcurrentlyStatus.CONSUME_SUCCESS;
}
} 
eturn ConsumeConcurrentlyStatus.CONSUME_SUCCESS;
});
 

消息存储

消息存储结构

RocketMQ采用的是混合型的存储结构,以文件系统的方式来存储消息,即为Broker单个实例下所有的队列共用一个日志数据文件(即为CommitLog)来存储。消息的存储是由ConsumeQueue和CommitLog配合完成的。CommitLog是消息真正的物理存储文件,消息存储在CommitLog中就不会丢失。ConsumeQueue是消息的逻辑队列,类似于数据库的索引文件,里面存储的是指向CommitLog文件中消息存储的地址。

CommitLog

CommitLog是用来存放消息的物理文件,每个broker上的commitLog本当前机器上的所有

consumerQueue共享,不做任何的区分。

CommitLog中的文件默认大小为1G; 当一个文件写满以后,会生成一个新的commitlog文件。所有的Topic数据是顺序写入在CommitLog文件中的。

ConsumeQueue

consumeQueue表示消息消费的逻辑队列,为提高消息消费的性能Consumer即可根ConsumeQueue来查找待消费的消息。内容包含MessageQueue在commitlog中的其实物理位置偏移量offset,消息实体内容的大小和Message Tag的hash值。文件大小约5.72M。

IndexFile

索引文件,如果一个消息包含Key值的话,会使用IndexFile存储消息索引。Index索引文件提供了对

CommitLog进行数据检索,提供了一种通过key或者时间区间来查找CommitLog中的消息的方法。在物理存储中,文件名是以创建的时间戳命名,固定的单个IndexFile大小大概为400M

页缓存与内存映射

Broker读写磁盘的时候,把mmap技术和pagecache技术结合起来使用的,通过mmap技术减少数据拷贝次数,利用pagecache技术实现尽可能优先读写内存,提升了文件的读写效率。

页缓存(PageCache)是OS对文件的缓存,用于加速对文件的读写。一般来说,程序对文件进行顺序读写的速度几乎接近于内存的读写速度,主要原因就是由于OS使用PageCache机制对读写访问操作进行了性能优化,将一部分的内存用作PageCache。对于数据的写入,OS会先写入至Cache内,随后通过异步的方式由pdflush内核线程将Cache内的数据刷盘至物理磁盘上。对于数据的读取,如果一次读取文件时出现未命中PageCache的情况,OS从物理磁盘上访问读取文件的同时,会顺序对其他相邻块的数据文件进行预读取。

在RocketMQ中,ConsumeQueue逻辑消费队列存储的数据较少,并且是顺序读取,在page cache机制的预读取作用下,Consume Queue文件的读性能几乎接近读内存,即使在有消息堆积情况下也不会影响性能。而对于CommitLog消息存储的日志数据文件来说,读取消息内容时候会产生较多的随机访问读取,严重影响性能。如果选择合适的系统IO调度算法,比如设置调度算法为“Deadline”(此时块存储采用SSD的话),随机读的性能也会有所提升。

另外,RocketMQ主要通过MappedByteBuffer对文件进行读写操作。其中,利用了NIO中的FileChannel模型将磁盘上的物理文件直接映射到用户态的内存地址中(这种mmap的方式减少了传统IO将磁盘文件数据在操作系统内核地址空间的缓冲区和用户应用程序地址空间的缓冲区之间来回进行拷贝的性能开销),将对文件的操作转化为直接对内存地址进行操作,从而极大地提高了文件的读写效率(正因为需要使用内存映射机制,故RocketMQ的文件存储都使用定长结构来存储,方便一次将整个文件映射至内存)mmap技术在进行文件映射的时候,一般有大小限制,在1.5GB~2GB之间

消息刷盘

(1) 同步刷盘:如图所示,只有在消息真正持久化至磁盘后RocketMQ的Broker端才会真正返回给Producer端一个成功的ACK响应。同步刷盘对MQ消息可靠性来说是一种不错的保障,但是性能上会有影响,一般适用于金融业务应用该模式较多。

(2) 异步刷盘:能够充分利用OS的PageCache的优势,只要消息写入PageCache即可将成功的ACK返回给Producer端。消息刷盘采用后台异步线程提交的方式进行,降低了读写延迟,提高了MQ的性能和吞吐量。

过期文件策略

为了避免内存与磁盘的浪费,不可能将消息永久存储在消息服务器上,所以需要一种机制来删除已过期的文件。broker后台每隔10s会执行一个任务,检测是否需要清除过期文件,默认超过48小时的文件(可通过修改fileReservedTime配置保存文件时间)。

清理文件的时候,如果磁盘没有满 ,那么每天就默认一次会删除磁盘文件,默认就是凌晨4点执行。

(修改配置deleteWhen

如果磁盘使用率超过85%了,那么此时可以允许继续写入数据,但是此时会立马触发删除文件的逻辑;如果磁盘使用率超过90%了,那么此时不允许在磁盘里写入新数据,立马删除文件。因为一旦磁盘满了,写入磁盘会失败MQ就彻底故障了。

消息零丢失方案

适合的场景:金融相关的核心系统中

发送消息到MQ的零丢失:

同步发送消息 + 反复多次重试;采用发送事务消息

MQ收到消息之后的零丢失:防止broker挂掉或者broker磁盘出现问题

开启同步刷盘策略 + 主从架构同步机制,只要让一个Broker收到消息之后同步写入磁盘,同时同步复制给其他Broker,然后再返回响应给生产者说写入成功,此时就可以保证MQ自己不会弄丢消息

消费消息的零丢失:采用RocketMQ的消费者天然就可以保证你处理完消息之后,才会提交消息的offset到broker去,不要采用多线程异步处理消息的方式即可。未提交情况下消费者挂掉会交给其它的消费者进行消费。

优势:消息不丢失

劣势:吞吐量下降明显;发送消息需要二次确认;写入broker需要写入磁盘比写入os cache中慢了很多;而且需要同步到其它的节点上。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值