前言
消息队列千千万万,诸如rocketmq,kafka,activemq,rabbitmq等,互联网上也有很多文章分析这些mq的源码,然而实际上对于一般的开发人员来说,其实更关注的并非是这些mq的内部实现,而是怎么去封装他们,怎么去调用他们的api,对于单单去收发一条消息而言,实际上mq直接的差异并没有想象当中那么大,那么今天,就来看看从,性能(削峰),查问题,分布式事务,业务封装难易度,链路追踪,监控,等多个角度来看看两种主流的对mq的封装,在各个功能上,实现的逻辑有何不同
本地直连mq
第一种形式就是典型的业务代码里面直接引入mq的客户端jar包
远程代理连接mq
有的极少数的单位并非是把收发消息的代码和业务放在一起的,而是单独抽出来,作为一个独立的消息中心,然后业务去调用这个服务,这个服务再去调用mq,同时这个服务自己去订阅mq,然后拿到服务后,再调用消费者业务,这种形式一般消息中心消息会落数据库。
在了解了两种不同性质的设计形式后,我们来看看这两种设计,在各个方面有何区别?在此我们要注意,任何一种设计都有优势和劣势,我们要合理的去看待他~
收发消息性能
实际上,从上图就可以看出,本地直连mq的性能要好过代理连接mq的形式,用代理连接mq,这里会有额外的网络开销,还要落数据库,这里的性能是非常弱的,用代理发送消息的代码,我之前测下来,tps 大概只能在300tps左右,4G双核服务器,原生mycat,线上多布几台,或许对于一般公司存粹走异步的逻辑还是可以的,但是要面对千万级并发肯定是挡不住的,同时消费端消费时,因为也要调用业务,还要记录消费结果,这里的性能也是有消耗的,所以说,对于消息量很大的场景还是推荐本地直连mq的形式~
mq集群部署和消息可靠
我看过很多写mq集群的文章,里面都提到了mq消息的主从同步,实际上,这种情况是针对客户端直连的情况的,如果我们本地不存消息,那么消息的可靠性谁来保证?消息丢了咋办?是的,只能依靠mq本身的集群去保证,这里的学问很多了,比如kafka的备用分片机制,比如rocketmq和activemq的从节点机制备份机制。这里还有一个配置叫做是否绝对可靠,简单来说,就是消息要发到所有 备用分片/备用节点,才算发送成功,当然今天的文章不是来谈论这些的。
而远程代理的形式就没有这么复杂的,因为消息在代理里面落了数据库,所以mq仅仅就是一个通知的作用,mq不需要集群,如果消息在mq里面丢失了,我开定时器把数据库里面未消费的消息捞出来重试即可,当然这种对数据库的性能消耗也是非常厉害的。。。。。
这一点上面,总结一下,mq本地直连对mq集群有高可用要求。而代理的形式则是对数据库的性能有高可用要求,而代理这边对数据库还有分库分表的要求,所以这里不太好说哪一种特别好,只能说技能点 点在了不同的地方,难点不一样。
日常问题排除
在我遇到的所有mq问题中,被问到的最多的问题就是消息为何没有消费? 这里的情况非常多,比如
明明消费没有发出去
消息被消费了,但是业务消费失败了,别人却以为消费成功了
消息真的丢失。。。。(这种还没有遇到过)
。。。。。。
如果是本地直连mq的设计,这里完全是依靠mq自身的消息查询机制来排除机制,既你用的这个mq的管控台必须有根据消息id,或者消息key来查询消息的接口。而会有这种设计的mq必须在存储消息时,消息索引文件里面存一份消息id或者消息key,因为如果仅仅是对于发送消费,实际上索引文件里面只要放消息的offset和消息文件的行位置即可,而我知道目前消息索引内容最多的就是rocketmq,可以原生支持各种消息查询。
而服务端连mq,因为消息落盘,同时消息消费完后修改消息状态,所以问题非常好查,大大节约了开发的排查问题的成本。这也是代理连接mq最大的优势,查问题好查
分布式事务
消息队列除了消峰这个最大的作用之外,第二个用处就是可以解决分布式事务问题,目前世面上面对于消息队列的分布式事务主要分为kafka多条消息的一致性,和rocketmq的消息和本地事务的一致性两种。
对于本地直连mq的系统来说,能否支持以上两种消息中间件的分布式事务完全是依靠mq自身,多条消息一致性,需要mq自身有假发送和最终确认接口。消息和本地事务一致性则是需要mq有预发送,确认发送,取消发送接口,以及最重要的回调接口。不管是哪一种,都需要mq自身携带这些。如果你用了不支持分布式事务的mq还是本地直连的,那你就只能换mq了。
对于代理连mq的情况,倒是可以无视mq的这些,因为对于业务系统来说,用了代理的形式就是意味着,把调用代理的那一步看作了发送消息,代理可以自己去封装这些功能,可以无须依赖mq,这是代理性灵活的地方,但是越是灵活越容易出错,你自己封装回调,一致性接口,万一写错了怎么办?哈哈,这也是有代价的。
消息迁移
上面说到了换mq,那么问题又来了,消息迁移咋搞?其实不妨告诉大家,本人经历的公司都遇到了消息迁移的问题,比如说mq上云,要把我们本来线上的mq切到线上云的mq里面,比如我们本来用的是actviemq后面要切换成kafka。
对于本地直连mq的设计来说,这里其实就是一个关键,双开mq消费端的监听,因为我从mq A 切到了mq B,所以可能会有mq A还有消息没有消费完的问题。系统很大,topic很多的情况下,有可能还只是迁移一部分topic,所以这里的做法就是同时在消费端处开启两种不同mq的监听,一般来说客户端直连的做法,切换mq是一个非常大的工程,生产者业务和消费者业务一定要沟通好,这里和技术无关,和业务有关,就怕你生产者发消息发到了新的mqB,但是消费端那边没有双开,那个时候就还要改配置。。。还有依靠监控。。。。。
对于代理连接mq的设计来说,因为mq只是和代理有关,这里就非常无脑了,直接切即可。。。。。是的你没有看错,无脑直接切到新的mq上面,但是你必须有2个定时器,一个是消息未消费,会重新补发的定时器,一个是消费重试还没有达到最大重试次数的继续重试定时器,必须有这两个定时器才可以切。同时,如果消息量很大,系统有很多消息都没有消费,几百万,几千万消息在数据库里面,这里同时会牵扯到分库性能,调度设计,以及mq的抗压力等等。
总结下来 前者是需要大量的业务沟通成本,而后者则是系统性能考验。也是有利有弊的。
监控mq
mq的监控一般来说无非就几个点,一个topic的消费者没有监听者了,一个topic出现了大量消息堆积,mq挂了,消息消费失败重试太多次(尤其是利用了mq自己的重试机制)
对于本地直连mq的系统来说,这些逻辑都是要调用mq admin接口获取topic列表,然后再调用mq接口,看看这个topic的堆积情况,mq挂不挂,可以看端口,或者自发自收一个心跳消息,等等方法,这种就是主要依靠mq的接口,这种接口一般的mq都是有的,不然本地直连mq会变成伪命题。
而对于代理连接mq的例子来说,监控mq可以用定时器去扫表,为什么呢?一个是数据表里面的信息会很全,给业务的报错比较友好,还有就是如果做了mq的迁移。。。。。这些监控的定时器可以不用改。。。。。。当然代理类比较重要的是,还要多做一个监控,就是这个代理本身也要存货,如果代理都挂了,对于业务来说就是mq都挂了,所以这里要加一个代理服务的监控。
链路追踪
链路追踪两者都没有区别,都是要在框架层给到消息体一个字段,不管你是重新包一个消息体对象,还是其他什么的,客户端直连是在jar包里面做这个逻辑,代理连接也是在代码的客户端做这个动作。没有区别。
消息发送失败
本地直连mq的设计,一般来说消息是存在本地一个文件里面的,然后代码开一条线程去重试。如果是代理的话,则是把发送状态失败的消息状态更新到数据库里面,然后定时器补偿。
实际上单单从发送失败来说,这两种形式的区别不大,就是本地和数据库的区别,而且一般发送失败,要么是mq挂了,要么是建连接慢了,mq挂了会切到另外一台有当前topic路由的机器上面,所以一般不太会出现上千万条消息同时发送失败落在数据库或者本地的情况。
容器化对发送失败的处理
现在很流行容器化,但是容器化有一个特性,就是每次进程启动后,ip会变化,很有可能你进入容器后,你这一次落磁盘的数据,下一次启动后是找不到的,这里本地直连的要注意,最好是能有一个独立的进程可以不断去处理本地文件里面的消息。当然代理这边消息是放在数据库的,可以不用考虑容器的影响。