聊聊消息队列
写在前面
最近一两年,总是能听到同行们说 ‘消息队列’这个词,听起来就很高端,但是苦于自己做的是传统项目,对于新技术的应用实在有限,为了不被飞速发展的技术圈淘汰,可以不用,但是不能不会。下面就来看下什么是消息队列,以及消息队列能干什么?有哪些可用的技术框架可以实现它?如何自己写一个消息队列?
首先看消息队列是什么?
其实消息队列也是一项普通的技术,并不高端。我们知道消息队列里面的队列(Queue))是java中的一种数据结构,它的特点是先进先出。那么消息队列,我们就可以这样理解,它就是将要传输的数据(消息)放入队列。我们自定义一个队列来验证是否是先进先出,代码如下:
/**
* 输出结果
* first message.
second message.
third message.
由此可见 队列是先进先出的
* @author jbzhang
*
*/
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());
}
}
}
那么既然队列可以做到,为什么还要单独提出消息队列这样的概念呢?下面我们就来看看消息队列常见的使用场景:
应用耦合:多应用间通过消息队列对同一消息进行处理,避免调用接口失败导致整个过程失败;
这种使用场景如下:
用户下单后,订单系统需要通知库存系统。传统的做法是,订单系统调用库存系统的接口。
订单系统:用户下单后,订单系统完成持久化处理,将消息写入消息队列,返回用户订单下单成功。
库存系统:订阅下单的消息,采用拉/推的方式,获取下单信息,库存系统根据下单信息,进行库存操作。
假如:在下单时库存系统不能正常使用。也不影响正常下单,因为下单后,订单系统写入消息队列就不再关心其他的后续操作了。实现订单系统与库存系统的应用解耦。
异步处理:即多应用对消息队列中同一消息进行处理,应用间并发处理消息,相比串行处理,减少处理时间;
这种使用场景如下:
用户为了使用某个应用,进行注册,系统需要发送注册邮件并验证短信。对这两个操作的处理方式有两种:串行及并行。但是无论并行、串行,你将注册信息写入数据库都要使用一定的时间,但如果直接写进消息队列,将不会耗费什么时间,直接返回注册成功给用户,提高了用户的体验。
限流削峰:广泛应用于秒杀或抢购活动中,避免流量过大导致应用系统挂掉的情况;
这种使用场景如下:
如淘宝聚划算的秒杀活动,一般由于瞬时访问量过大,服务器接收过大,会导致流量暴增,相关系统无法处理请求甚至崩溃。而加入消息队列后,系统可以从消息队列中取数据,相当于消息队列做了一次缓冲。
这样做的好处是,请求先入消息队列,而不是由业务处理系统直接处理,做了一次缓冲,极大地减少了业务处理系统的压力; 队列长度可以做限制,事实上,秒杀时,后入队列的用户无法秒杀到商品,这些请求可以直接被抛弃,返回活动已结束或商品已售完信息;
消息驱动的系统:系统分为消息队列、消息生产者、消息消费者,生产者负责产生消息,消费者(可能有多个)负责对消息进行处理;
这种的使用场景,如我们群聊这种功能,你发出消息供别人查看,此时你就是生产者,读取消息的人就是消费者。
既然消息队列有这么多的使用场景,那有哪些具体的技术框架(消息中间件)呢?
目前在生产环境,使用较多的消息队列有ActiveMQ,RabbitMQ,ZeroMQ,Kafka,MetaMQ,RocketMQ等。
其实消息中间件有很多,各有各的优势,但是它们的原理都是相同,我们学会一种的实现思路,就可以理解其他消息中间件的原理。
我主要学习了RabbitMQ,下面来介绍我的总结:
RabbitMQ 是一个老牌开源的消息中间件,它实现了标准的 AMQP(Advanced Message Queuing Protocol,高级消息队列协议)消息中间件,使用 Erlang 语言开发,支持集群部署,和多种客户端语言混合调。
RabbitMQ 中有 3 个重要的概念:生产者、消费者和代理。
生产者:消息的创建者,负责创建和推送数据到消息服务器。
消费者:消息的接收方,用于处理数据和确认消息。
代理:也就是 RabbitMQ 服务本身,它用于扮演“快递”的角色,因为它本身并不生产消息,只是扮演了“快递”的角色,把消息进行暂存和传递。
它们的运行流程,如下图所示:
RabbitMQ 具备以下几个优点:
支持持久化,RabbitMQ 支持磁盘持久化功能,保证了消息不会丢失;
高并发,RabbitMQ 使用了 Erlang 开发语言,Erlang 是为电话交换机开发的语言,天生自带高并发光环和高可用特性;
支持分布式集群,正是因为 Erlang 语言实现的,因此 RabbitMQ 集群部署也非常简单,只需要启动每个节点并使用 --link 把节点加入到集群中即可,并且 RabbitMQ 支持自动选主和自动容灾;
支持多种语言,比如 Java、.NET、PHP、Python、JavaScript、Ruby、Go 等;
支持消息确认,支持消息消费确认(ack)保证了每条消息可以被正常消费;
支持很多插件,比如网页控制台消息管理插件、消息延迟插件等,RabbitMQ 的插件很多并且使用都很方便。
RabbitMQ 的消息类型,分为以下四种:
direct(默认类型)模式,此模式为一对一的发送方式,也就是一条消息只会发送给一个消费者;
headers 模式,允许你匹配消息的 header 而非路由键(RoutingKey),除此之外 headers 和 direct 的使用完全一致,但因为 headers 匹配的性能很差,几乎不会被用到;
fanout 模式,为多播的方式,会把一个消息分发给所有的订阅者;
topic 模式,为主题订阅模式,允许使用通配符(#、*)匹配一个或者多个消息,我可以使用“cn.mq.#”匹配到多个前缀是“cn.mq.xxx”的消息,比如可以匹配到“cn.mq.rabbit”、“cn.mq.kafka”等消息。
关于RabbitMQ的具体使用方法,我会在另外一篇文章中进行总结,这里不再赘述,下面贴上文章的链接(未完成)
我们介绍完了消息队列的概念,使用场景及相关的中间件,下面我们来思考如何自己写一个消息队列?
实现思路如下: