应用场景
1.商品秒杀
短时间内出现爆发式的用户请求,如果不采取相关的措施,会导致服务器忙不过来,响应超时的问题,轻则会导致服务假死,重则会让服务器直接宕机。
这个时候加上了消息队列,服务器接收到用户的所有请求后,先把这些请求全部写入到消息队列中再排队处理,这样就不会导致同时处理多个请求的情况;如果消息队列长度超过可以承载的最大数量,那么我们可以抛弃当前用户的请求,通知前台用户“页面出错啦,请重新刷新”等提示,这样就会有更好的交互体验。
2.系统解耦
使用了消息队列之后,我们可以把系统的业务功能模块化,实现系统的解耦。
产品需求不断变化,这个时候我们想到了使用消息队列来实现系统的解耦,每个功能的实现独立开,只需要一个订阅或者取消订阅的开关就可以了,当需要增加功能时,只需要打开订阅“用户信息完善”的队列就行,如果过两天不用了,再把订阅的开关关掉就行了。
3.日志记录
常规做法:用户请求-(处理用户请求,记录日志(需完成))-回复前台
使用消息队列:用户请求-处理用户请求-放入消息队列,直接回复前台
消息中间件-RabbitMQ
目前市面上比较常用的 MQ(Message Queue,消息队列)中间件有 RabbitMQ、Kafka、RocketMQ,如果是轻量级的消息队列可以使用 Redis 提供的消息队列
重要概念
生产者:消息的创建者,负责创建和推送数据到服务器
消费者:消息的接收方,用于处理数据和确认消息
代理:RabbitMQ服务本身,它用于扮演“快递”的角色,因为它本身并不生产消息,只是扮演了“快递”的角色,把消息进行暂存和传递。
处理流程图
优点:
支持持久化,RabbitMQ 支持磁盘持久化功能,保证了消息不会丢失;
高并发,RabbitMQ 使用了 Erlang 开发语言,Erlang 是为电话交换机开发的语言,天生自带高并发光环和高可用特性;
支持分布式集群,正是因为 Erlang 语言实现的,因此 RabbitMQ 集群部署也非常简单,只需要启动每个节点并使用 --link 把节点加入到集群中即可,并且 RabbitMQ 支持自动选主和自动容灾;
支持多种语言,比如 Java、.NET、PHP、Python、JavaScript、Ruby、Go 等;
支持消息确认,支持消息消费确认(ack)保证了每条消息可以被正常消费;
它支持很多插件,比如网页控制台消息管理插件、消息延迟插件等,RabbitMQ 的插件很多并且使用都很方便。
消息类型:
direct(默认类型)模式,此模式为一对一的发送方式,也就是一条消息只会发送给一个消费者;
headers 模式,允许你匹配消息的 header 而非路由键(RoutingKey),除此之外 headers 和 direct 的使用完全一致,但因为 headers 匹配的性能很差,几乎不会被用到;
fanout 模式,为多播的方式,会把一个消息分发给所有的订阅者;
topic 模式,为主题订阅模式,允许使用通配符(#、*)匹配一个或者多个消息,我可以使用“cn.mq.#”匹配到多个前缀是“cn.mq.xxx”的消息,比如可以匹配到“cn.mq.rabbit”、“cn.mq.kafka”等消息。
自定义消息队列
我们可以使用Queue 来实现消息队列,Queue 大体可分为以下三类:
双端队列
是Queue的子类也是Queue补充类,头部和尾部支持元素的插入和获取
阻塞队列指的是在元素操作时(添加或删除),如果没有成功,会阻塞等待执行,比如当添加元素时,如果队列元素已满,队列则会阻塞等待直到有空位时再插入;
非阻塞队列,和阻塞队列相反,它会直接返回操作的结果,而非阻塞等待操作,双端队列也属于非阻塞队列。
自定义消息队列代码块
import java.util.LinkedList;
import java.util.Queue;
public class CustomQueue {
private static Queue<String> queue=new LinkedList<>();
public static void main(String[] args) {
producer();
consumer();
}
//生产者
public static void producer(){
queue.add("first-message");
queue.add("second-message");
queue.add("third-message");
}
//消费者
public static void consumer(){
while (!queue.isEmpty())
{
System.out.println(queue.poll());
}
}
}
first-message
second-message
third-message
自定义延迟队列
import lombok.Getter;
import lombok.Setter;
import java.text.DateFormat;
import java.util.Date;
import java.util.concurrent.DelayQueue;
import java.util.concurrent.Delayed;
import java.util.concurrent.TimeUnit;
public class CustomDelayQueue {
private static DelayQueue queue=new DelayQueue();
public static void main(String[] args) throws InterruptedException {
producer();
consumer();
}
//生产者
public static void producer(){
queue.add(new MyDelay(3000,"first-message"));
queue.add(new MyDelay(1000,"second-message"));
}
//消费者
public static void consumer() throws InterruptedException {
System.out.println("开始执行时间:"+DateFormat.getDateTimeInstance().format(new Date()));
while (!queue.isEmpty())
{
System.out.println(queue.take());
}
System.out.println("结束执行时间:"+DateFormat.getDateTimeInstance().format(new Date()));
}
//
static class MyDelay implements Delayed{
long delayTime=System.currentTimeMillis();
@Getter
@Setter
private String msg;
public MyDelay(long delayTime,String msg)
{
this.delayTime=(this.delayTime+delayTime);
this.msg=msg;
}
@Override
public long getDelay(TimeUnit unit) {
return unit.convert(delayTime-System.currentTimeMillis(),TimeUnit.MILLISECONDS);
}
@Override
public int compareTo(Delayed o) {
if(this.getDelay(TimeUnit.MILLISECONDS)>o.getDelay(TimeUnit.MILLISECONDS))
return 1;
else if(this.getDelay(TimeUnit.MILLISECONDS)<o.getDelay(TimeUnit.MILLISECONDS))
return -1;
else return 0;
}
@Override
public String toString() {
return this.msg;
}
}
}
开始执行时间:2020-12-28 19:41:20
second-message
first-message
结束执行时间:2020-12-28 19:41:23