rocketmq系列1---基本结构

143 阅读12分钟

rocketmq的大致原理是什么?

从宏观层面来讲,rq这种中间件我们期望的效果是,类似异步线程,比如有个事我不需要立刻拿到结果,我先存mq,等我有空了,你再把这个事通知我。或者可以这么说,mq这种中间件,就是一个集群的异步线程,而且他额外多了一些功能,比如延时xx时间,比如可以分布式的执行。

比如用户领券成功后,我们需要给用户发个信息通知他成功了。那领券成功和发信息在代码可能就是这样的

1:getCoupon();
2:sendMsg();

那sendMsg()一定要跟getcoupon()同时成功或者失败吗?很明显这个跟业务有关系,看业务接不接受即便用户领了券消息没发。咱们假设业务允许这种情况,那如果发消息的接口由于某种故障导致耗时10s,这里就有问题,既然我不关心你的结果,这个是不是可以做成异步?不然你卡着我很难受。能用异步线程吗?完全可以! 那异步线程跟mq区别在哪?好,咱们假设现在有1W用户,咱们的程序代码需要查询所有用户给他们每个人发个券,也就是遍历执行上面的代码1W次。我们大概率会写一个定时器或者接口,触发他去执行,但是这里的问题就是只会有一个机器执行,其他机器资源都浪费了。如果我们比较着急发券,那时间就来不及了。很容易我们就能想到,这里有两个问题可以优化,第一我们可以把查出来的1W个任务,通过某种方法,让所有机器执行,比如现在有10个机器,每个机器去执行1000条。第二,发消息走异步,先发券,消息暂时可以不发。关于拆任务这个,我们可以采用分布式调度任务去做,当然,也可以用mq去做,比如我们查出来10000条数据后,把这些数据一条一条扔到mq。假设mq有10个分片,也是可以做到拆解的效果。至于异步,其实如果已经拆解了任务,我们可以简单的做个异步就行,或者继续把这个发消息的再扔到mq。

rocketmq大致结构如下

image.png 主要分为nameserver,master broker和slave broker。其实很容易理解,我们系统引用了rocketmq的包,想发给他消息,起码得知道mq的地址吧?name server就记录了这些信息,通过nameserver你就知道哪些机器还活着,就可以跟他们建立连接,发送数据。broker很明显是记录数据的,他是mq的核心,master顾名思义,主机器,我们写数据都是跟他交互的。他就想一个快递员,你把东西都给他,在没有消费者(即收件人)的时候,他就先帮你把这些包裹存着。当然快递员可是人手有限,存快递取快递都找他,他很累~所以他找了一个临时工slave broker,他会把包裹复制一份也给他,下次收件人急着要,如果正牌快递员没时间的时候,就会找临时工给你送。

我们来分析几个问题?第一,我们是通过中介(nameserver)要到快递员电话的,如果快递员由于某种原因生病了没法送,电话也关机了,咋搞?那中介怎么知道快递员生病了?让快递员通知?不现实,有时候他遇到危险了根本来不及上报。那就只有一种,中介每隔10s就去打一次电话给快递员,你接电话说明你正常,我打120s你还不接,ok,说明你现在不方便,我会赶紧把你从活跃骑手列表移除。这样顾客来问的时候,我就知道不能把你电话给顾客了。

第二个问题来了。如果快递员失去连接了,怎么办?也很简单,临时工上岗,顶替他位置。由于快递员的包裹都复制给临时工一份,所以他俩其实并没啥区别,找个裁判(Dledger)给你转正,汇报给中介就行,哪怕快递员后来又正常了,现在也得成临时工了...那临时工可不可以有多个?当然可以,一个快递员想找几个临时工都行,只不过每个临时工都要复制包裹也太麻烦了。那如果快递员还没有复制包裹,就车子坏了送不了咋办?很简单,我当着你顾客的面找临时工,不复制完,我就不走,你顾客给我老实等着,假设我没复制完就挂了,顾客起码知道自己这次失败了,再打电话换一个就行。只要不出现 我以为我成功了,但是实际你偷偷失败了这种就问题不大。

什么是messageQueue?

topic的概念其实好理解,就是一个虚拟的,用于区分的,比如送海鲜的快递员算一个topic,但是这个人就只能送海鲜吗?他只不过有一个送海鲜的标签而已。那为啥还有messagequeue的概念?这个从现实理解,你想啊,一个快递员会有自己的站点用来临时存放你的包裹,可是我这个站点空间有限,你总不能指望我这个地方比学校都大吧?(机器是有容量上限的)而且一旦我这里失火了,包裹可全完了。那如果是你,你怎么办?狡兔三窟,找其他快递员合作,比如我们三个快递员合作,如果客户给我三个包裹,咱们一人一个,没毛病吧?什么?怕丢?咱们不还有临时工吗,他也有自己的仓库,跟咱们不在一起,咱们失火了,找他们的就是了啊。你设置了一个生鲜topic,设置了10个messagequeue,本质上就是用topic把数据都归拢一个主题能串在一起有个关系,10个messagequeue就是把数据拆10份存。当然,前提是你真的有10个快递员。这样好处是啥?假设你一个快递员1s最多接收7个包裹,现在你们10个快递员,1s能接收70个顾客的包裹了。

commitLog是什么?

如果快递员收到一个快递就去分类摆好,那他一天就别干啥了,天天收拾吧!最简单粗暴的就是拿到快递就直接扔仓库,问题就是到时候怎么找啊?别急,我们不是设置了messagequeue吗,假设这个仓库有2个messagequeue,我再找俩账本跟他对应(consumerQueue),扔到仓库的时候,顺便瞅一眼这个对应哪个messagequeue,把扔的位置记录到账本里。这个直接无脑堆的地方叫commitLog,属于仓库的一部分,最多1g,多了会再开一个commitlog。假如用户想取快递,找对应topic,对应的consumerqueue就知道当时存的所有数据位置。甚至还可以弄个offset概念,想读哪些数据就读哪些。

rocketmq是怎么存储数据的?

刚才说了,commitlog其实是一个顺序追加的日志文件,那这里我们就不禁要思考,是每来一个请求就去落磁盘吗?回到现实中,你快递员只有把包裹存到你的仓库并且已经给了临时工一份,才能基本保障包裹不会丢失(虽然也存在大家都丢的情况,但是几率实在太低,而且一般都会设置2个以上临时工)。好了,现在客户给你一个包裹,说我这个超级重要,你不能丢。你会怎么办?那肯定是先让客户等等,我先去存仓库,给临时工都存上一份,等都搞定了,再跟客户说,稳了。当然,这种速度很不友好,但是,稳!那如果客户想让你送包裹,并且说尽快送,丢了没事,重点是要快!你要知道,除了他,还有其他小区的客户,你想快,就不能拿一个包裹回去存一趟了。肯定是先收了包裹跟他说好了,然后迅速去下一个客户,先临时堆在大门口,等差不多了就一块存仓库。

对应的细节就是,你可以设置mq每一次都落到磁盘并且复制给slave节点才算成功,也可以发到master内存那边就算成功,然后定时或者满足数据量就去刷一次磁盘。当然,现实中稳定+快的方式就是发给master内存,并且超过从节点一半的机器比如3个从节点有2个也接受到了同步消息,虽然这个时候大家都在内存,满足快,但是4个机器同时挂了的几率实在低,所以稳定性也有保障。

Dledger的raft两阶段提交协议是什么

commitlog都给Dledger管理,leader的日志首先是uncommit状态,然后leader会把消息发给所有slave节点,当一半以上的返回成功,leader的uncommit就会变成commit状态,然后他在给slave节点发送消息,让他们也改成commit。那如果master挂了怎么办,从节点选取!大家怎么选老大?每个节点会投票自己当老大,第一轮肯定失败,因为每个人都投自己,那失败了就会随机休眠xxms,这个时候第一个醒的,投票自己,其他节点醒了看到已经有人发起了投票,就不会再投自己了。跟随这个人投票。如果这一轮还是没选出来,那再次随机就行了。

rocketmq的网络通信架构

一个典型的reactor线程模型是有一个主线程负责连接,接收请求,返回响应,n个线程去解析请求,封装请求,m个线程去做业务逻辑处理,比如存数据,取数据。

rq则做了进一步处理,他有一个reactor主线程负责建立连接,一个reactor线程池去处理监听,比如有没有可读,可写事件,拿到事件后会扔到worker线程池,让他们处理预处理,比如按照协议解析请求,组装数据等,拿到真实数据后,再扔到业务线程池去处理真实操作,比如把数据存起来啊,取出来数据之类的。

mmap和sendfile区别

先解释下正常的拷贝。

image.png 1:先从磁盘拷贝到内核缓冲区,从用户态切换内核态,再通过cpu把数据从内核态复制用户空间,并切换到用户态,ok现在你想发送数据,就要把数据拷贝到socket缓冲区并切成内核态,再通过DMA把数据复制到协议引擎。

这里有用户态-内核态-用户态-内核态-用户态 4次切换 DMA从磁盘复制到内核缓冲区,CPU把内核缓冲区复制到用户空间,CPU把用户空间再复制到socket缓冲区,DMA再复制到协议引擎。 4次复制。

2:mmap拷贝:会建立内存映射,用户缓冲区和内核缓冲区共享映射数据。也就是说少了一次复制。

3:sendfile:用户缓冲区不要了,也不用把数据复制到socket缓冲区,只需要复制一些offset和length,那这里就是用户态-内核态-用户态两次切换。复制的话,也就磁盘到内核缓冲区以及内核缓冲到协议引擎。两次复制,两次切换。

事务消息怎么回事?

我们先聊聊最终一致性的事务一般怎么做。

1:上游发送待确认消息

2:可靠消息服务保存这个数据为待确认

3:上游数据执行本地事务

4:上游发送确认/删除消息

5:可靠服务改为已发送/已删除,并且发mq,这个要在一个事务

6:下游服务拿到消息

7:下游服务执行本地数据库

8:对消息进行手动ack

9:发送消息给可靠消息服务通知已完成

这里的可靠服务,我们假设带入rocketmq,他要做的就是拿到上游的消息,存一个half消息,这个消息不在messagequeue对应的consumerqueue里,不然下游就能看到了,他先放其他地方,放完了上游也感知到half发送成功了,就开始执行本地事务,如果成功了,mq就知道了,就会把消息放到该呆的位置,上游这就算成发送了。那如果上游还没发确认消息就挂了呢?或者本地事务已经成功了,但是由于某种原因没通知到mq咋办?很简单,搞个定时器看,如果超时了,我是回滚还是确认?问谁?问上游啊,你得提供一个接口让我问一下,你这个好没好?好了我就改已发送,不然我就删除了。上游挂了都没法问咋办?你总有正常的时候吧,不正常我就一直问,最终一定能问出来。