消息的发送方式有哪些?存储消息的可靠性面临哪些挑战?消费消息的确认机制是什么?本文从消息发送、消息存储和消息消费三个方面来分析消息流转的整个过程。本阶段介绍RocketMQ如何保证消息的可靠性。分布式系统的一个重要前提是所有的网络传输都是不可靠的。在网络传输不可靠的情况下,除了重试投递外,没有其他方法可以保证消息的可靠传输。大多数常用的消息队列RocketMQ、RabbitMQ等在消息传输中只能保证至少一次成功传输,即(Atleastonce),而不能保证只传输一次成功(Exactlyonce)。由于分布式系统网络的不可靠性,可能会出现消息丢失的情况,那么RocketMQ是如何最大程度保证消息不丢失的呢?然后需要分析从消息的产生到最终消费的全过程,消息是完整的。Links可以分为以下三个阶段:Production阶段:消息在producer发送端创建,通过网络传输发送到Broker存储端。存储阶段:消息存储在Broker端。如果是主从或者多副本,这个阶段会把消息复制到其他节点或者副本。消费阶段:consumer消费者从broker存储端拉取消息,通过网络传输发送给consumer消费者,通过重试最大程度保证消息的消费。1.发送端的消息可靠性发送端的生产者向Broker发送消息的核心逻辑如下图所示:消息一般有以下几种发送方式:同步、异步、单向。根据情况做出判断。下面详细介绍不同发送方式实现的消息可靠性保证。1同步发送同步发送是指发送消息时,发送方阻塞线程,等待服务器返回发送结果。如果发送端需要保证消息的可靠性,防止消息发送失败,可以使用同步阻塞发送,然后同步检查Brocker返回的状态,判断消息是否持久化成功。如果发送超时或失败,默认重试2次。RocketMQ选择至少有一次发送成功的消息模型,但由于网络传输不可靠,可能会出现重复发送的情况。具体重试策略请参考第四节。2异步发送异步发送是指发送方在发送消息时,传入回调接口的实现类。调用发送接口后,不会阻塞,发送方法会立即返回,回调任务会在另一个线程中执行,消息发送结果会传回对应的回调函数。具体业务实现可以根据发送的结果信息判断是否需要重试,以保证消息的可靠性。3单向发送单向发送是指发送端发送完成后,调用发送接口后立即返回,不返回发送结果。业务方无法根据发送状态判断消息是否发送成功。该方法是一种不可靠的消息发送方法,为保证消息发送的可靠性,不推荐使用该方法发送消息。4发送重试策略在RocketMQ架构模型中,会有多个Boker为某个topic提供服务,一个topic下的消息分散存储在多个Broker存储端,它们是多对多的关系。Broker会将其提供存储服务的topic的元数据信息上报给NameServer,由对等NameServer节点组成的高可用服务维护topic与Broker的映射关系。多对多的映射关系为消息可以重试发送到MultipleBrokers提供了前提和基础。当发送端需要发送消息时,如果该主题的路由信息??缓存在发送端并且包含消息队列,则直接返回路由信息;如果没有缓存或消息队列,则从NameServer中查询该主题的路由信息??。查询到路由消息后,使用指定的队列选择策略,选择对应的队列发送消息。默认是使用轮询策略。如果发送成功,它会返回。如果接收到异常,它会根据相应的策略进行重试。它可以基于发件人的感知。Broker的时延、上次发送失败的Broker信息、发送方配置的不同Broker参数是否重试、发送方设置的最大超时时间等,灵活实现不同级别的消息发送可靠性保证。重试策略可以有效保证消息发送成功的概率,最终提高消息发送的可靠性。2、存储端的消息可靠性RocketMQ的消息存储结构如下图所示:消息队列存储的最小单位是消息Message。同一主题下的消息被映射到多个逻辑队列中。不同topic的消息按照到达broker的先后顺序,以Append的形式添加到CommitLog中,顺序写入,随机读取。目前RocketMQ存储模型采用本地磁盘存储,数据写入方式为producer->directmemory->pagecache->disk。读取数据时,如果pagecache有数据,则直接从pagecache中读取,否则需要先从磁盘加载到pagecache中。Broker存储节点的文件存储方式如下图所示:Broker端的CommitLog采用顺序写入,可以大大提高写入效率。同时采用不同的刷盘方式,提供不同的数据可靠性保证。此外,ConsumeQueue中间结构用于存储部分数据。移动信息,实现消息的分发。由于ConsumeQueue结构固定,大小有限,在实际情况下,ConsumeQueue的大部分可以读入内存,可以达到内存读取的速度。另外,为了保证CommitLog和ConsumeQueue的一致性,CommitLog保存了ConsumeQueues、MessageKey、Tag等所有信息,即使ConsumeQueue丢失,也可以通过commitLog完全恢复,所以只要可靠性高CommitLog的数据是有保证的,Consume的Queue的可靠性是可以保证的。RocketMQ存储端使用本地磁盘存储CommitLog消息数据,这必然对存储可靠性带来挑战。如何保证消息不丢失,RocketMQ消息服务一直在不断提升数据可靠性。1存储可靠性挑战RocketMQ存储端,即Broker端,在存储消息时会面临以下存储可靠性挑战:Broker正常关闭Broker异常CrashOSCrash机器断电,但可以立即恢复供电机器无法开机(可能是cpu、主板、内存等关键设备损坏)磁盘设备损坏1正常关机,Broker可以正常启动并恢复所有数据。2、3、4同步刷入可以保证不丢失数据,异步刷入可能会造成少量数据丢失。5、6是单点故障,无法恢复。可以增加Slave节点来解决单点故障。主从异步复制还是会造成少量数据丢失,同步复制可以完全避免单点问题。一般来说,需要在性能和可靠性之间做出权衡。对于RocketMQ来说,Broker的可靠性主要通过两个方面来保证:消息强制刷新,主从复制,性能无疑会有所降低;如果不设置,会有一定的丢失消息的可能。RocketMQ一般是先将消息写入PageCache,然后持久化到磁盘。pagecache中的数据刷新到磁盘有两种方式,同步方式和异步方式。整体的消息写入和读取如下图所示:对于broker端单机存储的可靠性,主要取决于单机的刷盘策略。master和slave之间的copy复制,请参考下一章的master-slave模式。2同步刷入消息写入内存的PageCache后,立即通知刷入线程刷盘,然后等待刷入完成。刷新线程执行完毕后,唤醒等待线程,返回消息写入成功状态。这种方式可以保证绝对的数据安全,但是吞吐量不大。3异步刷新(默认)消息写入内存中的PageCache,写入成功立即返回给客户端。当PageCache中的消息累积到一定数量时,会触发写操作,或者采用定时等策略将数据保存在PageCache中。消息被写入磁盘。这种方式吞吐量高,性能高,但是PageCache中的数据可能会丢失,无法保证绝对的数据安全。在实际应用中,需要结合业务场景,合理设置刷机方式,尤其是同步刷机方式,由于频繁触发磁盘写动作,会显着降低性能。4删除过期文件由于RocketMQ基于文件内存映射机制来操作CommitLog和ConsumeQueue文件,启动时加载所有文件,为了避免内存和磁盘的浪费,可以回收磁盘,避免磁盘不足。消息不能写入等,引入文件过期删除机制。最终将磁盘水位保持在一定水平,最终保证了新写入消息的可靠存储。3、消费端的消息可靠性RockerMQ默认提供至少一次消费的消费语义,保证消息的可靠消费。通常,消费消息的确认机制一般分为两种思路:先提交,再消费,消费成功后再提交。思路一可以解决重复消费但是会丢消息的问题。因此,RocketMQ默认实现了思路2。业务端保证幂等性,解决重复消费问题。消费端消费者消息消费的核心逻辑如下图所示:1消费重试消费者从RocketMQ拉取消息后,需要返回消费成功,表示业务端正常消费完成。所以只有返回CONSUME_SUCCESS才算消费完成。如果返回CONSUME_LATER,则会根据不同的messageDelayLevel时间再次消费。时间从秒级到小时级,最长时间为2小时后重试消费。如果消费超过16次,仍然消费失败,则不会重试,消息会被送入死信队列,保证消息存储的可靠性。2对于死信队列消费失败的消息,消息队列不会立即丢弃该消息,而是将消息发送给死信队列,死信队列的名称是在原队列名前加上%DLQ%。如果消息最终进入死信队列队列,可以通过RocketMQ提供的相关接口从死信队列中获取对应的消息,保证了消息消费的可靠性。3消息回溯回溯消费是指Consumer消费成功的消息,或者上次消费的业务逻辑有问题,现在需要重新消费。为了支持该功能,Broker存储端向Consumer消费端下发成功消息后,仍需要保留该消息。二次消费一般是基于时间维度的。例如,由于消费系统故障,恢复后需要重新消费1小时前的数据。RocketMQBroker提供了一种可以根据时间维度回滚消费进度的机制,从而保证只要消息发送成功,只要消息还没有过期,就可以一直消费消息。4.小结本文从消息传递的全过程来分析RocketMQ是如何保证消息的可靠性的。消息发送通过不同的重试策略保证消息的可靠发送。消息存储通过不同的flush机制和多副本来保证消息的可靠性。存储和消息消费通过至少一次成功消费和消费重试机制来保证消息的可靠消费。RocketMQ在保证消息的可靠性上做到了全链路闭环,最大程度保证消息不丢失。
