什么是消息队列
一篇文章告诉你什么是消息队列。我入门消息队列的笔记。
来源:mp.weixin.qq.com
一、什么是消息队列?
消息队列,一般我们会简称它为 MQ(Message Queue),就是很直白的简写。
我们先不管"消息(Message)“这个词,来看看"队列(Queue)”。队列是一种先进先出的数据结构。在 Java 里边,已经实现了不少队列了。
那为什么还需要消息队列(MQ)这种中间件呢??其实这个问题,跟之前我学 Redis 的时候很像。Redis 是一个以 key-value 形式存储的内存数据库,明明我们可以使用类似 HashMap 这种实现类就可以达到类似的效果了,那还为什么要用 Redis?
消息队列可以简单理解为:把要传输的数据放在队列中。
科普:
- 把数据放到消息队列叫做生产者
- 从消息队列里边取数据叫做消费者
二、为什么要用消息队列?
2.1 解耦
现在我有一个系统A,系统A可以产生一个 userId,然后有系统B和系统C都需要这个 userId 去做相关的操作:
public class SystemA {
// 系统B和系统C的依赖
SystemB systemB = new SystemB();
SystemC systemC = new SystemC();
// 系统A独有的数据userId
private String userId = "Java3y";
public void doSomething() {
// 系统B和系统C都需要拿着系统A的userId去操作其他的事
systemB.SystemBNeed2do(userId);
systemC.SystemCNeed2do(userId);
}
}
某一天,系统B的负责人说不再使用了,于是系统A就得改代码删掉调用…
又过了几天,系统D的负责人说要使用 userId,系统A又得改代码…
每天被各种系统负责人骚扰,改来改去…
解决方案: 系统A将 userId 写到消息队列中,系统C和系统D从消息队列中拿数据。
好处:
- 系统A只负责把数据写到队列中,谁想要或不想要这个数据,系统A一点都不关心
- 即便系统D不想要了,系统B又突然想要了,都跟系统A无关,代码不用改
- 系统D即使挂了或请求超时,都跟系统A无关,只跟消息队列有关
2.2 异步
系统A同步调用系统B、C、D:
public void doOrder() {
userId = this.order();
systemB.SystemBNeed2do(userId); // 300ms
systemC.SystemCNeed2do(userId); // 300ms
systemD.SystemDNeed2do(userId); // 300ms
// 共计:50 + 300 + 300 + 300 = 950ms
}
系统A做主要业务(订单下单),系统B/C/D是非主要业务(发短信等)。
解决方案: 系统A执行完以后,将 userId 写到消息队列中,然后直接返回。其它操作异步处理。
效果: 从 950ms 降为 100ms(异步)
2.3 削峰/限流
大促期间并发量高,比如每秒 3000 个请求。假设只有两台机器,每台每次只能处理 1000 个请求。
多余的 1000 个请求可能把整个系统搞崩。
解决方案: 写到消息队列中。系统B和系统C根据自己的处理能力去消息队列中拿数据,这样即使 8000 个请求,也只是把请求放在消息队列里,由系统自己控制,不会把整个系统搞崩。
三、使用消息队列有什么问题?
3.1 高可用
无论用消息队列来做解耦、异步还是削峰,消息队列肯定不能是单机的。单机消息队列挂了,那整个系统几乎就不可用了。
所以,消息队列都是得集群/分布式的。
3.2 数据丢失问题
我们将数据写到消息队列上,系统B和C还没来得及取消息队列的数据,就挂掉了。如果没有任何措施,我们的数据就丢了。
学过 Redis 的都知道,Redis 可以将数据持久化到磁盘上。同样地,消息队列中的数据也需要存在别的地方:
- 磁盘?
- 数据库?
- Redis?
- 分布式文件系统?
- 同步存储还是异步存储?
3.3 消费者怎么得到消息队列的数据?
- push:生产者将数据放到消息队列中,消息队列有数据了,主动叫消费者去拿
- pull:消费者不断去轮询消息队列,看看有没有新的数据
3.4 其它问题
- 消息重复消费了怎么办?
- 想保证消息是绝对有顺序的怎么做?
虽然消息队列给我们带来了很多好处,但同时引入消息队列也会提高系统的复杂性。市面上已经有不少消息队列了,每种都有各自的特点,选取哪种 MQ 还得好好斟酌。