兔子,火箭和乌鸦(消息队列JMS分析)
为什么要有一个专门的消息队列?
自己写一个会有什么问题?
只不过是个队列而已,自己也可以用mongodb做一个;能有多难?
首先在mongodb中创建多个表,消息来了根据表名代表不同的队列分;消费端监听对应的表,每次拿头一个消息,然后删除这个消息。
重复消费问题
如果消费端集群同时消费一个消息,这种情况会发生吗?消息队列分集群都是主备+分片,只有主服务器会做写操作,也就只有主服务器会去消费,那重复读问题是怎么来的?
几个消费者同时读取一个消息,这是可能的,读了之后要做修改操作,如果修改成功才算消费成功,其他的回滚,这不就能避免重复读吗?
又或者读的时候就加悲观锁,也可以保证只被一个消费者消费。
还有一种情况,使用fanout类型交换器,广播模式,给所有消费者发消息,这就造成了重复消费。(广播模式存在的意义是啥?)
消息重复的情况必然存在
在 MQTT 协议中,给出了三种传递消息时能够提供的服务质量标准,这三种服务质量从低到高依次是:
At most once: 至多一次。消息在传递时,最多会被送达一次。换一个说法就是,没什么消息可靠性保证,允许丢消息。一般都是一些对消息可靠性要求不太高的监控场景使用,比如每分钟上报一次机房温度数据,可以接受数据少量丢失。
At least once: 至少一次。消息在传递时,至少会被送达一次。也就是说,不允许丢消息,但是允许有少量重复消息出现。
Exactly once:恰好一次。消息在传递时,只会被送达一次,不允许丢失也不允许重复,这个是最高的等级。
使用幂等就可以解决了
同一条消息,必然是相同的信息,那就可以用唯一键来防止数据库的重复插入。
消息队列怎么保存数据?需要搭配Redis一起吗?
一般存在内存中,消息太多会往磁盘存,读取磁盘消息影响效率。自己有自己的储存方式(文件系统),不需要别的工具帮忙。
延迟队列比定时触发好的原因?
定时触发需要去数据库搜索到期的数据,而延迟队列只需要查看队列尾部的数据有没有到期。
延迟队列中整个队列的延迟时间都是一样的,所以必然是队列尾部的先到期,所以只需要检查尾部消息即可。
这样看好像mongodb也可以做到,根据延迟时间建表,然后查询第一条数据有没有到期。
怎么做一个延迟队列?
先做一个没人用的队列,设置超时时间和超时后会去的死信对列;这个死信队列才是我们要处理的队列。
比如:等待队列,超时时间是10分钟,超时后去闹钟队列;服务器A会去闹钟队列拉去闹钟消息,闹钟队列也会自己推消息到服务器A;10分钟的定时闹钟就做好了
队列集群思路
主备,备机就只是备份,不参与消息读取,防止消息重复读。分片/分区后每个分区只被一个消费者消费,也是防止重复读。
消费消息伴随着修改,所以主从读写分离没什么意义(没有单纯的读操作)。
顺序消息怎么实现?
保证发送时顺序,消息要等得到队列接收确认再继续发送;还要保证消费时顺序,发送到同一个分区,可以使用相同的分区key。
最常用的,削峰填谷,做秒杀,怎么做?
首先数据获取控制,刷新页面,给IP限流,每个IP一分钟之内刷新30次。
然后是数据操作控制,就是将秒杀申请存到消息队列,然后逐条处理;使用锁机制,hash取模200(假如有200个名额),名额唯一键,直接向数据库写入(MySql的机制,在内存中先处理,再I/O,大量失败并不会影响性能,需要满足一定成功操作才会强制刷盘),满足秒杀数量后,就可以全部拒绝了。
JMS规范(Java Message Service)
JMS即Java消息服务(Java Message Service)应用程序接口,是一个Java平台中关于面向消息中间件(MOM,Message oriented Middleware)的API,用于在两个应用程序之间,或分布式系统中发送消息,进行异步通信。与具体平台无关的API,绝大多数MOM提供商都支持。
AMQP协议
AMQP全称高级消息队列协议(Advanced Message Queuing Protocol),是一种标准,类似于JMS,兼容JMS协议。
队列事务
有事务,但是事物消耗大,不推荐使用
发送确认机制,回调通知机制
使用回调函数可以知道有没有成功发送到队列,但是有没有成功被消费要自己去查数据库了(反正我没找到现成的方法)。
最后一个问题,自己用mongodb写一个和专业的mq有什么区别?
mongodb不会自己推消息,但是kafka也不会自己推消息,rocketmq做的是伪推送,只有rabbitmq有真正的推送消息;所以推消息不是必须的。
集群,只不过是主备模式,也很好做。
队列优先级,这个好像不好做,也不是做不了;表名加个参数。
订阅,这个也可以做,mongodb的表名是可以自己设置的;根据客户端的订阅来设置表名即可。
这样看来自己用mongodb做一个简单的JMS实现是没问题了。
有一些复杂功能,实现起来就很麻烦了;
rabbit的Topic(主题)类型,可以用A.B的key匹配AC和DB的队列。
高并发情况下的处理,自己写起来也比较麻烦,甚至考虑不够全面,还不如直接用成熟的第三方产品。
一个不成熟的想法,消息队列是不是时间机器?
有一个业务,分10个步骤,每个步骤都要等待1分钟;用队列的话可以一次性做完,可以利用以往数据概率分析先预测他每一步的结果,然后直接填写下一步,然后发送到队列,慢慢消费,节省用户10分钟。可以把分散的业务统一。
三大消息队列的区别
兔子
唯一一个老老实实用队列储存的,队列头插入,队列尾消费,消费完了就移除消息。一个经典又传统的队列。AMQP(Advanced Message Queuing Protocol)协议的标准实现。
储存方式是以append的方式写到一个文件中,文件满了就新建一个,有删除的消息先做记录,到一定比例后合并相邻的文件的有效消息。
关于索引,每个队列都有相应的索引,索引使用顺序的段文件来存储;难道是循环遍历的方式去查询索引的?没有使用二叉树而是这样线形设计的话,索引数量应该不会很多。
特点是:灵活,性能好,高并发,管理界面很好用;吞吐量到万级(相比其他两个偏小)
交换器类型
Fanout:广播
Direct:路由,发给key匹配的队列
Topic:主题,路由模式上做了进一步扩展,以存在两种特殊字符“*”和“#”,用于模糊匹配
Headers(不实用):headers类型的交换器性能很差,不实用。
使用广播模式不会重复消费?
火箭和乌鸦的共同点
rocketmq设计上参考了kafka
数据结构用的是数组,需要往磁盘上存数据;消费的时候有偏移量,标识出消费到第几个消息了。
顺序写提高磁盘写入和读取效率,一个大文件比很多小文件读写的快就是这个道理。
可用性都非常高
火箭
特点:国产,java编写,可靠性,吞吐量十万级,java开发看得懂;管理界面不太好用,需要最少4G磁盘,配置清理磁盘比较麻烦。
也是储存到文件里;
储存结构,消息和索引分开储存。和rabbitmq差不多。
有消息事务,2pc模式;
集群下就像一个微服务,有自己的注册中心NameServer;
消息有推拉。(推实际上还是变种的拉,利用长轮询和监听,本身不具有推功能)
消息可以过滤。(被过滤的消息存到被过滤队列吗?客户端也是自己写的,无用的消息不发送不就行了)
Oneway发送:只发送请求不等待应答;异步发送又是什么?异步发送会有回调函数,还是有应答;同步发送就是直接等待应答。
乌鸦kafka
“卡夫卡”在捷克语里是“寒鸦”的意思。
几乎是另一种工具了,起初用来做数据收集;依赖磁盘,所以优化了磁盘的存储读取;批量处理,对于大量数据有绝对优势。
特点:极大的吞吐量(优化后达到千万级),批量处理导致没那么灵活,单个消息会有延迟。
储存:按主题归类,主题又可以分为多个分区,每个分区存在一个日志文件里,
文件系统那么好用,干嘛还要数据库?
//todo
索引:偏移量索引,时间戳索引;没有查询队列到索引?直接按主题查询吗?
//todo
分区和消费组,为了统一管理消费者,会组成一个消费组,来规划消息消费,每个分区只会被一个消费者消费,保证了不会重复消费。
横向扩展,分区可增加,消费者也可以增加。
消息只拉不推。
ISR数据一致性
集群搭建需要zookeeper,不搭建集群也要zookeeper,下载kafka就送zookeeper。
主备切换数据一致性怎么保证?