什么是MeteQ?
MetaQ是一款分布式、队列模型的消息中间件。基于发布订阅模式,有Push和Pull两种消费方式,支持严格的消息顺序,亿级别的堆积能力,支持消息回溯和多个维度的消息查询.
和其他消息中间件的区别
MetaQ实际上是淘宝早期为了解决流量高峰期间消息丢失、用户响应延迟高的问题而设计的中间件。因为淘宝的业务属性更加注重消息的完整性,所以MetaQ相比于其他消息中间件更加看重的是消息堆积能力,目前来说,MetaQ是不怕消息堆积的。同样重要的是应用的可靠性,所以MetaQ的各部分(如消费者、broker和生产者)都是分布式集群化的。
四个基本假设以及MetaQ的应对思想
想要了解MetaQ的核心技术以及在最合适的时候使用MetaQ,首先得从四个基本假设入手。这四个基本假设可以说是MetaQ的设计目的。本文也将沿着这四个假设以及对应的应对思想,针对MetaQ的核心技术进行串联与展开。
假设1与分布式集群化
从MetaQ集群化示意图可以看到为了实现分布式集群化,随着MQ Server的集群化,那么很自然地带来消息有序性问题和消息重复性问题。
什么是消息有序性问题?
答:我们来复现一下这个问题。如图3所示,发布者依次投递了消息1到MQ Server 1,消息2到MQ Server 2。MQ Server 1和MQ Server 2都收到了消息。但是MQ Server 1可能因为网络抖动或者GC问题,停顿了一下。MQ Server 2在MQ Server 1之前将消息推送了出去。那么就可能造成订单服务还没处理好,物流服务已经处理好出去了的问题。
消息失序问题的解决方案:如果业务实在需要进行保证消息的有序性,可以利用一致性哈希将必须保证有序性的消息投递到同一个MQ Server。实际上,是将MetaQ退化为单机,必须要MQ Server 1恢复通信之后才能继续使用系统,不算真正的高可用,只是影响范围变小了。但是,这个方案依旧能够极大地提高吞吐量。使用时在编程难度和高可用之间做取舍。
什么是消息重复性问题?
答:为了满足容错性,如图3所示,当发布者投递了消息1之后,如果没有响应,不可能一直等待。会进行重发,此时故障链路恢复就可能导致消息重复的问题。问题的根本原因是分布式系统中有三种状态:成功、失败和无响应。如何看待无响应到底应该视为成功还是失败需要结合具体业务。
消息重复问题的解决方案:从全局来讲,消息重复问题无法避免,因此一定要保证订阅者对消息的消费是幂等的。
集群化带来的消息失序和重复的示例解决方案
- 利用链式地订阅服务解决消息失序问题。
- 利用状态机的方式规避掉消息重复带来的重复性操作。
假设2与消息不丢失保证
在保证消息不丢失方面,用的是比较直观的方法:消息持久化。具体怎么持久化,参考rocketMQ的方式。有一个日志文件commitLog(和mysql的binlog很像)专门记录消息。
在同步调用的情况下,用消息持久化的方式保证消息不丢失是稳定有效的。如图所示,MetaQ提供同步刷盘和异步刷盘两种机制。同步刷盘是发布者将消息发送给MQ Server之后,还继续等待MQ Server将消息刷盘后通知发布者,发布者确认后再继续执行接下来的任务。默认采用异步刷盘的方式。
- 在高可用方面,MetaQ一般采用主从架构。MetaQ提供了同步复制和异步复制两种机制。同步复制要求主从都写入成功才返回,异步复制仅要求master写入成功。
- 为了提高吞吐量,默认采用异步刷盘+异步复制的方式。
tips:如果订阅者消费完消息之后,没有回传确认消息,MQ Server会误以为消息丢失进行重发。
假设3与消息堆积和削峰填谷
相比于其他消息中间件,MetaQ的消息堆积能力是比较优秀的。原因是消息持久化保存到磁盘中,且消费队列本身不保存消息本地,保存消息磁盘索引,从而具备了大量消息堆积的能力。通过FileChannel的MMAP机制实现内存映射,提高读写效率。
假设4与毫秒级投递
主动投递能够实现将时延缩短到最小,但是也带来订阅者消费能力未知问题。
订阅者消费能力未知问题会带来什么影响?
答:投递能力和消费能力的不匹配可能直接导致订阅者宕机。
为了避免订阅者消费能力未知问题,采用被动投递的方式。市面上主要有定期拉和长轮询的方式。Kafka采用的是定期拉,能够极大地保证吞吐量,但对于时延方面的影响较大。MetaQ采用长轮询的方式,当订阅者拉取请求过来,先hold住请求一段时间,如果有新消息立即返回,一直没有新消息就超时再返回。既减小了网络开销和照顾到系统吞吐量,又能够匹配上消费能力和投递速率。
MQ带来的影响
半事务:在没有消息中间件的系统中,一般事务是要做全的,但是为了用户体验,我们加入了消息中间件,那么我们只保证发布者到消息中间件这一段的事务。不保证消息中间件到订阅者那一段的事务。
使用场景
- 应用解耦:一个服务如果调用了很多个服务,如果服务的业务发生改变,就需要修改服务代码。如果是消息的话,可能只需要再写一个服务去消费消息就可以了。
- 异步:如果一个服务调用了很多个服务,其中一个服务速度很慢,就会拖累整个流程的响应时间。
- 流量高峰控制:能够做到削峰填谷的作用。
- 最终一致性:如果没有消息队列,那么当调用挂掉的服务时,可能该服务就直接丢失了请求。消息队列可以在服务起来的时候投递消息,保证消息至少被消费一次,从而就实现最终一致性。
支线知识点
为什么MetaQ的topic再多都不会影响读写速度,而Kafka会受影响?
原因:MetaQ的存储模型如图6所示,因为MetaQ将所有消息都写在一个commitLog文件里(受限于MMAP机制,只能映射1.5G~2G,所以和binlog一样,每1G截断一次,产生新文件),顺序写,利用偏移量随机读。而Kafka不同topic不一定是在一个文件,需要磁盘寻址,会降低性能。
消费失败会发生什么事情?怎么做?
如果消费失败,MQ Server没有收到确认消息,那么MQ Server会重发,重发次数达到一定数额后不会直接丢弃,而是进入死信队列中,在死信队列中,几天后才会丢弃,在阿里会有告警(开发中),建议:最后一次重试时,将消息手动持久化到DB中。
消息丢失后怎么办?
为了保证吞吐量,采用异步刷盘策略,如图5所示,主线程wakeup刷盘线程后返回了ack,但恰好此刻,这台broker宕机了。那么就不可避免地会发生消息丢失。如果是特别重要的消息,还是选择同步刷盘的策略比较保险。为了防止磁盘坏了,还要开启同步双写。
发送太大的消息会有什么后果?
- MetaQ通信层没有对大的请求做优化,采用的是典型的RPC方式,不适合大的请求传递,可能会导致网络层的Buffer异常。
- MetaQ的服务器存储是一个典型的LRU CACHE系统,过大的消息会占用较多Cache,对于其他应用Cache命中率产生影响
- MetaQ的磁盘资源通常比较紧张以及MetaQ暂不解决大消息存储问题
超过4k的消息会被自动压缩和自动解压、建议不超过16K
使用时的注意事项
- 建议设置message key属性,方便查询和定位消息是否被接收。
- 顺序消息消费,耗时时间有限制,要保证每条消息在30s内消费完,超过30s会有潜在的乱序问题。(原因是分布式锁超时问题,但概率极低)
- 非顺序消息消费,默认消费超时时间为15分钟,超时后会重投处理,超时重投,不会中断原消息处理,即原消息处理依然进行。应用应该尽可能保证耗时短,这样才能达到高性能,另外消费消息Hang住,会导致消息所在队列的消费动作暂停,直到Hang住的消息消费完。对其他队列不受影响。
- 消费完一定要通知MQ Server。
参考资料
- 精进消息中间件原理系列(一):之消息堆积
- MetaQ / Notify 原理与实践
- MetaQ阿里中间件产品介绍
- MetaQ梳理
- metaQ入门教程
- RocketMQ常见问题总结