你知道的越多,你不知道的越多
点赞再看,养成习惯
前言
消息队列在互联 技术存储方面使用如此广泛,几乎所有的后端技术面试官都要在消息队列的使用和原理方面对小伙伴们进行360°的刁难。
作为一个在互联 公司面一次拿一次Offer的面霸,打败了无数竞争对手,每次都只能看到无数落寞的身影失望的离开,略感愧疚(请允许我使用一下夸张的修辞手法)。
于是在一个寂寞难耐的夜晚,我痛定思痛,决定开始写《吊打面试官》系列,希望能帮助各位读者以后面试势如破竹,对面试官进行360°的反击,吊打问你的面试官,让一同面试的同僚瞠目结舌,疯狂收割大厂Offer!
捞一下
消息队列系列前面两章分别讲了消息队列的基础知识,还有比较常见的问题和常见分布式事务解决方案,那么在实际开发过程中,我们使用频率比较高的消息队列中间件有哪些呢/p>
帅丙我工作以来接触的消息队列中间件有RocketMQ、Kafka、自研,是的因为我主要接触的都是电商公司,相对而言业务体量还有场景来说都是他们比较适合,再加上杭州阿里系公司偏多,身边同事或者公司老大基本都是阿里出来创业的,那在使用技术栈的时候阿里系的开源框架也就成了首选。
就算是自研的中间件多多少少也是借鉴RocketMQ、Kafka的优点自研的,那我后面两章就分别简单的介绍下两者,他们分别在业务场景和大数据领域各自发光发热。
那到底是道德的沦丧,还是人性的泯灭,让我们跟着敖丙走进RocketMQ的内心世界。
正文
RocketMQ简介
RocketMQ是一个纯Java、分布式、队列模型的开源消息中间件,前身是MetaQ,是阿里参考Kafka特点研发的一个队列模型的消息中间件,后开源给apache基金会成为了apache的顶级开源项目,具有高性能、高可靠、高实时、分布式特点。
回顾一下他的心路历程
2007年:淘宝实施了“五彩石”项目,“五彩石”用于将交易系统从单机变成分布式,也是在这个过程中产生了阿里巴巴第一代消息引擎——Notify。
2010年:阿里巴巴B2B部门基于ActiveMQ的5.1版本也开发了自己的一款消息引擎,称为Napoli,这款消息引擎在B2B里面广泛地被使用,不仅仅是在交易领域,在很多的后台异步解耦等方面也得到了广泛的应用。
2011年:业界出现了现在被很多大数据领域所推崇的Kafka消息引擎,阿里巴巴在研究了Kafka的整体机制和架构设计之后,基于Kafka的设计使用Java进行了完全重写并推出了MetaQ 1.0版本,主要是用于解决顺序消息和海量堆积的问题。
2012年:阿里巴巴开源其自研的第三代分布式消息中间件——RocketMQ。
经过几年的技术打磨,阿里称基于RocketMQ技术,目前双十一当天消息容量可达到万亿级。
2016年11月:阿里将RocketMQ捐献给Apache软件基金会,正式成为孵化项目。
阿里称会将其打造成顶级项目。这是阿里迈出的一大步,因为加入到开源软件基金会需要经过评审方的考核与观察。
坦率而言,业界还对国人的代码开源参与度仍保持着刻板印象;而Apache基金会中的342个项目中,暂时还只有Kylin、CarbonData、Eagle 、Dubbo和 RocketMQ 共计五个中国技术人主导的项目。
2017年2月20日:RocketMQ正式发布4.0版本,专家称新版本适用于电商领域,金融领域,大数据领域,兼有物联 领域的编程模型。
以上就是RocketMQ的整体发展历史,其实在阿里巴巴内部围绕着RocketMQ内核打造了三款产品,分别是MetaQ、Notify和Aliware MQ。
这三者分别采用了不同的模型,MetaQ主要使用了拉模型,解决了顺序消息和海量堆积问题;Notify主要使用了推模型,解决了事务消息;而云产品Aliware MQ则是提供了商业化的版本。
经历多次双11洗礼的英雄
在备战2016年双十一时,RocketMq团队重点做了两件事情,优化慢请求与统一存储引擎。
- 优化慢请求:这里主要是解决在海量高并发场景下降低慢请求对整个集群带来的抖动,毛刺问题。这是一个极具挑战的技术活,团队同学经过长达1个多月的跟进调优,从双十一的复盘情况来看,99.996%的延迟落在了10ms以内,而99.6%的延迟在1ms以内。优化主要集中在RocketMQ存储层算法优化、JVM与操作系统调优。更多的细节大家可以参考《万亿级数据洪峰下的分布式消息引擎》。
- 统一存储引擎:主要解决的消息引擎的高可用,成本问题。在多代消息引擎共存的前提下,我们对Notify的存储模块进行了全面移植与替换。
RocketMQ天生为金融互联 领域而生,追求高可靠、高可用、高并发、低延迟,是一个阿里巴巴由内而外成功孕育的典范,除了阿里集团上千个应用外,根据我们不完全统计,国内至少有上百家单位、科研教育机构在使用。
RocketMQ在阿里集团也被广泛应用在订单,交易,充值,流计算,消息推送,日志流式处理,binglog分发等场景。
他所拥有的功能
我们直接去GitHub上看Apache对他的描述可能会好点
他的核心模块:
- rocketmq-broker:接受生产者发来的消息并存储(通过调用rocketmq-store),消费者从这里取得消息
- rocketmq-client:提供发送、接受消息的客户端API。
- rocketmq-namesrv:NameServer,类似于Zookeeper,这里保存着消息的TopicName,队列等运行时的元信息。
- rocketmq-common:通用的一些类,方法,数据结构等。
- rocketmq-remoting:基于Netty4的client/server + fastjson序列化 + 自定义二进制协议。
- rocketmq-store:消息、索引存储等。
- rocketmq-filtersrv:消息过滤器Server,需要注意的是,要实现这种过滤,需要上传代码到MQ!(一般而言,我们利用Tag足以满足大部分的过滤需求,如果更灵活更复杂的过滤需求,可以考虑filtersrv组件)。
- rocketmq-tools:命令行工具。
他的架构组成,或者理解为为什么他这么快么强么厉害
他主要有四大核心组成部分:NameServer、Broker、Producer以及Consumer四部分。
Producer
消息生产者,负责产生消息,一般由业务系统负责产生消息。
-
Producer由用户进行分布式部署,消息由Producer通过多种负载均衡模式发送到Broker集群,发送低延时,支持快速失败。
-
RocketMQ 提供了三种方式发送消息:同步、异步和单向
-
同步发送:同步发送指消息发送方发出数据后会在收到接收方发回响应之后才发下一个数据包。一般用于重要通知消息,例如重要通知邮件、营销短信。
-
异步发送:异步发送指发送方发出数据后,不等接收方发回响应,接着发送下个数据包,一般用于可能链路耗时较长而对响应时间敏感的业务场景,例如用户视频上传后通知启动转码服务。
-
单向发送:单向发送是指只负责发送消息而不等待服务器回应且没有回调函数触发,适用于某些耗时非常短但对可靠性要求并不高的场景,例如日志收集。
Broker
消息中转角色,负责存储消息,转发消息。
- Broker是具体提供业务的服务器,单个Broker节点与所有的NameServer节点保持长连接及心跳,并会定时将Topic信息注册到NameServer,顺带一提底层的通信和连接都是基于Netty实现的。
- Broker负责消息存储,以Topic为纬度支持轻量级的队列,单机可以支撑上万队列规模,支持消息推拉模型。
- 官 上有数据显示:具有上亿级消息堆积能力,同时可严格保证消息的有序性。
Consumer
消息消费者,负责消费消息,一般是后台系统负责异步消费。
-
Consumer也由用户部署,支持PUSH和PULL两种消费模式,支持集群消费和广播消息,提供实时的消息订阅机制。
-
Pull:拉取型消费者(Pull Consumer)主动从消息服务器拉取信息,只要批量拉取到消息,用户应用就会启动消费过程,所以 Pull 称为主动消费型。
-
Push:推送型消费者(Push Consumer)封装了消息的拉取、消费进度和其他的内部维护工作,将消息到达时执行的回调接口留给用户应用程序来实现。所以 Push 称为被动消费类型,但从实现上看还是从消息服务器中拉取消息,不同于 Pull 的是 Push 首先要注册消费监听器,当监听器处触发后才开始消费消息。
Tip: GItHub https://github.com/JavaFamily 有一线大厂面经和面试考察点脑图,也有个人联系方式。
消息领域模型
我上面说过他跟Dubbo像不是我瞎说的,就连他的注册过程都很像Dubbo的服务暴露过程。
是不是觉得很简单,但是你同时也产生了好奇心,每一步是怎么初始化启动的呢/p>
帅丙呀就知道大家都是求知欲极强的人才,这不我都准备好了,我们一步步分析一下。
主要是人才群里的仔要求我写出来。。。(文末有进群方式)
NameService启动流程
在org.apache.rocketmq.namesrv目录下的NamesrvStartup这个启动类基本上描述了他的启动过程我们可以看一下代码:
-
第一步是初始化配置
-
创建NamesrvController实例,并开启两个定时任务:
-
每隔10s扫描一次Broker,移除处于不激活的Broker;
-
每隔10s打印一次KV配置。
Producer是消息发送方,那他怎么发送的呢/p>
通过轮训,Producer轮训某个Topic下面的所有队列实现发送方的负载均衡
大家去看Broker的源码的话会发现,他的初始化流程很冗长,会根据配置创建很多线程池主要用来发送消息、拉取消息、查询消息、客户端管理和消费者管理,也有很多定时任务,同时也注册了很多请求处理器,用来发送拉取消息查询消息的。
Consumer
不说了直接怼图吧!要死了,下次我还是做扫盲,写点爽文吧555
消费端会通过RebalanceService线程,10秒钟做一次基于Topic下的所有队列负载。
面试常见问题分析
他的优缺点是啥
RocketMQ优点:
-
单机吞吐量:十万级
-
可用性:非常高,分布式架构
-
消息可靠性:经过参数优化配置,消息可以做到0丢失
-
功能支持:MQ功能较为完善,还是分布式的,扩展性好
-
支持10亿级别的消息堆积,不会因为堆积导致性能下降
-
源码是java,我们可以自己阅读源码,定制自己公司的MQ,可以掌控
-
天生为金融互联 领域而生,对于可靠性要求很高的场景,尤其是电商里面的订单扣款,以及业务削峰,在大量交易涌入时,后端可能无法及时处理的情况
-
RoketMQ在稳定性上可能更值得信赖,这些业务场景在阿里双11已经经历了多次考验,如果你的业务有上述并发场景,建议可以选择RocketMQ
RocketMQ缺点:
-
支持的客户端语言不多,目前是java及c++,其中c++不成熟
-
区活跃度不是特别活跃那种
-
没有在 mq 核心中去实现JMS等接口,有些系统要迁移需要修改大量代码
消息去重
去重原则:使用业务端逻辑保持幂等性
幂等性:就是用户对于同一操作发起的一次请求或者多次请求的结果是一致的,不会因为多次点击而产生了副作用,数据库的结果都是唯一的,不可变的。
只要保持幂等性,不管来多少条重复消息,最后处理的结果都一样,需要业务端来实现。
去重策略:保证每条消息都有唯一编 (比如唯一流水 ),且保证消息处理成功与去重表的日志同时出现。
建立一个消息表,拿到这个消息做数据库的insert操作。给这个消息做一个唯一主键(primary key)或者唯一约束,那么就算出现重复消费的情况,就会导致主键冲突,那么就不再处理这条消息。
消息重复
消息领域有一个对消息投递的QoS定义,分为:
- 最多一次(At most once)
- 至少一次(At least once)
- 仅一次( Exactly once)
QoS:Quality of Service,服务质量
几乎所有的MQ产品都声称自己做到了At least once。
既然是至少一次,那避免不了消息重复,尤其是在分布式 络环境下。
比如: 络原因闪断,ACK返回失败等等故障,确认信息没有传送到消息队列,导致消息队列不知道自己已经消费过该消息了,再次将该消息分发给其他的消费者。
不同的消息队列发送的确认信息形式不同,例如RabbitMQ是发送一个ACK确认消息,RocketMQ是返回一个CONSUME_SUCCESS成功标志,Kafka实际上有个offset的概念。
RocketMQ没有内置消息去重的解决方案,最新版本是否支持还需确认。
消息的可用性
当我们选择好了集群模式之后,那么我们需要关心的就是怎么去存储和复制这个数据,RocketMQ对消息的刷盘提供了同步和异步的策略来满足我们的,当我们选择同步刷盘之后,如果刷盘超时会给返回FLUSH_DISK_TIMEOUT,如果是异步刷盘不会返回刷盘相关信息,选择同步刷盘可以尽最大程度满足我们的消息不会丢失。
除了存储有选择之后,我们的主从同步提供了同步和异步两种模式来进行复制,当然选择同步可以提升可用性,但是消息的发送RT时间会下降10%左右。
RocketMQ采用的是混合型的存储结构,即为Broker单个实例下所有的队列共用一个日志数据文件(即为CommitLog)来存储。
而Kafka采用的是独立型的存储结构,每个队列一个文件。
这里帅丙认为,RocketMQ采用混合型存储结构的缺点在于,会存在较多的随机读操作,因此读的效率偏低。同时消费消息需要依赖ConsumeQueue,构建该逻辑消费队列需要一定开销。
RocketMQ 刷盘实现
Broker 在消息的存取时直接操作的是内存(内存映射文件),这可以提供系统的吞吐量,但是无法避免机器掉电时数据丢失,所以需要持久化到磁盘中。
刷盘的最终实现都是使用NIO中的 MappedByteBuffer.force() 将映射区的数据写入到磁盘,如果是同步刷盘的话,在Broker把消息写到CommitLog映射区后,就会等待写入完成。
异步而言,只是唤醒对应的线程,不保证执行的时机,流程如图所示。
我们可使用Hash取模法,让同一个订单发送到同一个队列中,再使用同步发送,只有同个订单的创建消息发送成功,再发送支付消息。这样,我们保证了发送有序。
RocketMQ的topic内的队列机制,可以保证存储满足FIFO(First Input First Output 简单说就是指先进先出),剩下的只需要消费者顺序消费即可。
RocketMQ仅保证顺序发送,顺序消费由消费者业务保证!!!
这里很好理解,一个订单你发送的时候放到一个队列里面去,你同一个的订单 Hash一下是不是还是一样的结果,那肯定是一个消费者消费,那顺序是不是就保证了/p>
真正的顺序消费不同的中间件都有自己的不同实现我这里就举个例子,大家思路理解下。
分布式事务:
Half Message(半消息)
是指暂不能被Consumer消费的消息。Producer 已经把消息成功发送到了 Broker 端,但此消息被标记为状态,处于该种状态下的消息称为半消息。需要 Producer
对消息的后,Consumer才能去消费它。
消息回查
由于 络闪段,生产者应用重启等原因。导致 Producer 端一直没有对 Half Message(半消息) 进行 二次确认。这是Brock服务器会定时扫描,会
主动询问 Producer端 该消息的最终状态(Commit或者Rollback),该消息即为 消息回查。
订单 +业务场景,组成一个唯一主键,你插入数据库只能成功第一个,后续的都会 错的, 违反唯一主键的错误。
还有就是有人疑惑为啥不直接就不判断就等他插入的时候 错,丢掉后续的就好了/p>
你要知道 错有很多种,你哪里知道不是数据库挂了的错者别的运行时异常/p>
不过你如果可以做到抛特定的异常也可以,反正我们要减少数据库的 错,如果并发大,像我现在负责的系统都是10W+QPS,那日志会打满疯狂 警的。(就是正常情况我们都经常 警)
解决问题的思路有很多,喷我可以,讲清楚问题,讲清楚你的理由。
很多大家都只是单方面的知识摄入,就这样还要喷我,还有一上来就问我为啥今天没发文章,我欠你的工作日上班,周六周日都怼上去了,时间有限啊,哥哥。
大家都有自己的事情,写文章也耗时耗脑,难免出错,还望理解。
日常求赞
好了各位,以上就是这篇文章的全部内容了,能看到这里的人呀,都是人才。
我后面会每周都更新几篇和互联 常用技术栈相关的文章,非常感谢人才们能看到这里,如果这个文章写得还不错,觉得「敖丙」我有点东西的话 求点赞?? 求关注?? 求分享?? 对暖男我来说真的 非常有用!!!
创作不易,各位的支持和认可,就是我创作的最大动力,我们下篇文章见!
文章知识点与官方知识档案匹配,可进一步学习相关知识Java技能树首页概览91738 人正在系统学习中
声明:本站部分文章及图片源自用户投稿,如本站任何资料有侵权请您尽早请联系jinwei@zod.com.cn进行处理,非常感谢!
-