什么是消息队列
消息队列:一般我们会简称它为MQ(Message Queue)。其主要目的是通讯。
ps:消息队列是以日志的形式将数据顺序存储到磁盘当中。通常我们说从内存中IO读写数据的速度要快于从硬盘中IO读写的速度是对于随机的写入和读取。但是对于这种顺序存储的形式,在磁盘和内存中的操作速度是差不多的。
消息队列的作用
消息队列的三个主要作用:异步、削峰、解耦(很重要)。
我们以张三给李四送货物为例来形象的解释一下这三个作用。
在没有引入消息队列之前这个任务需要张三和李四两个人见面并进行货物的提交,引入消息队列之后相当于在两人之间多了一个快递站。张三把货物放到快递站,李四有时间的时候再去快递站取走快递即可。消息队列就相当于上述例子中的快递站。
异步:在没有快递站之前,两人完成货物的提交必须要两人见面,但是在引入快递站(消息队列)后,无需两人必须见面,张三将货物放在快递站后,便可便可认为李四可以接收到货物,李四在一个自己合适的时间去快递站取走货物便可完成货物的提交。
削峰:如果张三一次有很多货物要给李四提交,这会使得李四一次拿走这么多货物的压力很大。但是有了快递站(消息队列)后,李四可以一部分一部分的慢慢拿走货物。
解耦:再引入快递站之前,货物提交的操作对两人的要求是必须约定好时间两人同时到场才能执行,在引入快递站之后。张三可以把货物放下快递站,李四可以选择时间去拿走货物。二者的耦合度降低。
传统的流程设计与采用消息队列后的对比
传统的设计模式如下图,属于经典的串行化调用。这种设计模式的优势在于,代码简单,出现问题时容易定位到问题。
但是,我们从高性能,高并发,高可用这三个方面去评价一下这个设计会发现它存在许多劣势。
-
高性能:由于串行化的设计,业务的处理需要从上到下一步一步执行。假设每次网络传输耗时200ms,业务处理需要20ms,完成上面那些操作需要耗时2s,对于用户来说等待时间过长,用户体验也会很差,如果用户下单后的操作越来越多,耗时只会越来越高。
-
高并发: 这些操作都是由一个线程(主线程)去执行这些操作,所以当我们的QPS(系统每秒钟收到的请求)如果很高的话,很容易造成超时。
-
高可用:这些服务假如有一个服务挂掉(宕机或者网络波动),就意味着我这个请求失败了,这样用户体验会极差,用户会频繁看到支付失败。
在引入消息队列后,流程如下所示
用户下单后,将相关信息放入消息队列当中,其他业务可以同时从消息队列中拿到相关信息进行处理。其中用户下单环节进行消息的生产,被称作生产者(producer)。而右侧调用的各种业务来接收消息,被称作消费者(consumer)。
我们接着从高性能,高并发,高可用这三个方面去评价引入消息队列后的设计。
-
高可用:当我系统里的一个模块宕机了,不会影响到我其他服务。(可以通过数据补偿或者分布式事务来保证数据最终一致性)
-
高性能:用户下单,将下单所需要的数据都放到消息队列里,就直接返回了,所有耗时相当于就是网络传输所耗时。
-
高并发:由于消息队列不处理任何业务上的逻辑,所有他支持的并发是百万级别的。假如有100万个用户下单,100万的数据放到消息队列里,连接消息队列的服务慢慢消费即可,也不至于造成瞬间有百万请求进来,将我的服务压垮。
消息队列的优缺点
优点
- 解耦:就像高可用里面说的一样,发淘金币服务挂了,关下单什么关系,发淘金币服务挂了,我还是可以正常下单,只不过后期可以数据补偿或者分布式事务去解决这个问题。
- 削峰:比如说我平时服务就只能支撑几万的qps,像淘宝京东那种秒杀,那时候服务突然打进来,那服务就会直接被压死了。但是如果采用消息队列,这秒杀进来的所有的请求都不会直接打到具体服务上,都会先打到消息队列里,然后我后面的服务再慢慢消费。(可以看看淘宝京东双11秒杀的时候,是不是有的时候慢是慢了点,但是服务起码没挂。等我秒杀结束之后,服务还能正常运转。)
- 异步:连接消息队列的服务可以异步去执行。而且每次多增加一个步骤,我下单的代码是不需要动的,只需要再增加一个消费者即可。
缺点
1,增加了系统复杂性。
所以说如果说你的业务量不大,并发也不高,就没必要使用消息队列。
2,事务问题。
事务问题其实是分布式系统肯定会存在的一个问题,只不过消息队列更严重一些。也就是说用了消息队列就代表接受了不实时性,只需要保证最终一致性就行了。一般解决方案有两种,第一种就是采用分布式事务,这个下单的里涉及的所有服务放到一个事务里面,要么都成功,要么都失败。第二种就是,消费者做好合理的数据补偿措施,比如说,消息重试,人工刷数据等等。
3,可用性
刚才讲了解耦,其实是系统的各个模块之间的解耦,但是这些模块都和消息队列关联,万一消息队列挂了,就真的下不了单了。为了保证可用性,我们可以采用消息队列集群,前端流量限流等。
常见的消息队列
springboot使用kafka
引入依赖
<dependency>
<groupId>org.springframework.kafka</groupId>
<artifactId>spring-kafka</artifactId>
</dependency>
配置application.yml文件
kafka:
bootstrap-servers: 自己的ip
consumer:
group-id: myname
生产者类
java
@RestController
public class KafkaController {
@Resource
private KafkaTemplate kafkaTemplate;
@GetMapping("/produce")
public String produce(String message){
kafkaTemplate.send("order",message);
return "消息发送成功";
}
}
消费者类
java
@Component
public class KafkaListener {
@org.springframework.kafka.annotation.KafkaListener(topics = {"order"})
public void listen(ConsumerRecord<String,String> record){
System.out.println("卡夫卡推送的消息:"+record.value());
System.out.println("我已经收到消息!!");
}
}