消息队列概述与JMS使用

系统间通信概述

一般来说,大型应用通常会被拆分成多个子系统,这些子系统可能会部署在多台机器上,也可能只是一台机器的多个进程中,这样的应用就是分布式应用。而分布式应用的子系统之间并不是完全独立的,它们需要相互通信来共同完成某个功能,这就涉及系统间通信了。

通常有两种方式来实现系统间通信,一种是基于远程过程调用(RPC)的方式,客户端不需要知道调用的具体实现细节,只需直接调用实际存在于远程计算机上的某个对象即可,但调用方式看起来和调用本地应用程序中的对象一样。典型的RPC实现包括Dubbo、Thrift、GRPC等,甚至Rest API。

消息队列概述与JMS使用

另一种是基于消息队列的方式。消息队列的方式是指由应用中的某个系统负责发送信息,由关心这条消息的相应系统负责接收消息,并在收到消息后进行各自系统内的业务处理。消息可以非常简单,比如只包含文本字符串;也可以很复杂,比如包含字节流、字节数组,还可能包含嵌入对象,甚至是Java对象(经过序列化的对象)。

消息发布者只负责将消息发布到消息队列,消息使用者只负责从消息队列中取消息,由消息队列来负责消息的传递,这样发布者和使用者都不用知道对方的存在。

消息队列概述

为什么要使用消息队列

解耦

各模块之间如果存在互相调用的关系,应尽可能解耦合,消息队列就是在模块之间增加了一个中间层来实现解耦,使任何一个模块只关心自己的核心流程,不必关注数据来源或结果。

流量削峰

在系统运行中,可能会出现某一时刻网站请求量爆涨(例如业务活动或DDos攻击),很难在同一时刻处理这么多请求,如果处理不当给数据库带来巨大压力可能会导致雪崩效应。使用消息队列可以将短时间高并发的请求持久化,然后逐步处理,从而改善系统性能。

异步

很多公司会构建一个日志收集系统,由它来统一收集业务日志数据,供离线和在线的分析系统使用。如果每个业务处理请求后都同步处理写日志,可能会造成延迟,甚至出现异常会影响业务服务阻塞,这时候引入消息队列可以异步处理日志消息。

分布式事务

在大型系统中数据库可能会分库分表,分布在多台服务器上,如果一个事务涉及多张表、甚至是多个库分布在不同的机器上,如何处理这个事务呢?一种方法是业界引入的一个处理分布式事务的规范——XA(全局事务管理器),它在事务管理器和资源管理器之间形成通信桥梁,目前主流数据库都提供对XA规范的支持。

但XA的问题是性能不算很好,所以通常使用消息队列来解决这个问题,分布式事务的本质是系统间的通信,配合事件表能实现分布式事务,具体实现这里不做详述。

消息队列的特点

消息队列的思想就是生产者消费者模式,组成就是生产者、阻塞队列、消费者,其中最重要的就是阻塞队列,消息队列也就是一种阻塞队列,JDK中也存在不少该模式体现,例如线程池中就使用了BlockingQueue,关于JDK中的BlockingQueue之前也写过一篇博客研究,可以参考阻塞队列BlockingQueue详解

而生产环境下要求的消息队列就不止JDK中的BlockingQueue这么简单了,在不同的业务场景中,需要消息队列产品能解决诸如消息堆积、消息持久化、可靠投递、消息重复、严格有序、集群等各种问题。

消息堆积

因为生产者和消费者是两个分开处理消息的系统,无法预知两者对消息处理速度的快慢,一旦在某个时间段消费者处理消息的速度没有跟上生产者发送消息的速度,必将导致消息在处理中心逐渐积压而得不到释放。因此,有时需要给消息队列设置一个阈值,将超过阈值的消息不再放入处理,以防止系统资源被耗尽,导致机器挂掉甚至整个消息队列不可用。这就类似于JDK中线程池的拒绝策略。

消息持久化

在消息被放入消息队列后需要将消息暂存下来,消息暂存可以选择将消息放在内存中,也可以选择放到文件、数据库等地方。将消息放在内存中存在的最大问题是,一旦机器宕掉消息将丢失。如果场景需要消息不能丢失,那么势必要将消息持久化。持久化方案有很多种, 比如将消息存到本地文件、分布式文件系统、数据库系统中等。

可靠投递

有些场景必须保证消息不能丢失。这种情况可能发生在出现网络问题、系统宕机等情况下。

消息重复

有些消息队列为了支持消息可靠投递,会选择在接收到消息后先持久化到本地,然后发送给消费者。当消息发送失败或者不知道是否发送成功时(比如超时),消息的状态是待发送,定时任务不停地轮询所有的待发送消息,最终保证消息不会丢失,这就带来了消息可能会重复的问题。

严格有序

在某些场景中会有需要按照生产消息的顺序来消费的情形,这就要消息队列提供有序消息的保证.

集群

消息队列服务器也可能出现宕机问题,要保证高可用需要集群支持。

消息队列协议

常见的消息队列:RabbitMQ, ActiveMQ, Kafka, RocketMQ

消息队列的规范协议有AMQP、MQTT、STOMP、XMPP四种,这些协议是类似于HTTP的比较底层的通信协议,而Java引入了JMS(Java Message Service,Java消息服务)为这些协议提供了更高一层的抽象,给Java开发者提供了一个使用消息队列一致的接口。下面主要介绍JMS的体系模型。

JMS点对点模型

JMS按其规范分为点对点(Point-to-Point)和发布订阅(Publish/Subscribe)两种形式。点对点就是将一个系统的消息发布到指定的另外一个系统,这样另外一个系统就能获得消息,从而处理对应的业务逻辑。

注意点对点模型在JMS中体现为QueueConneciton

点对点模型的特点:
+ 每条消息只有一个接收者,消息一旦被接收就不再保留在消息队列中了。一个队列中可能会有多个接收者在监听,但是消息只能被队列中的一个接收者接收。
+ 发送者和接收者之间在时间上没有依赖,当消息被发送之后,不管接收者有没有在运行,都不会影响消息被发送到队列中
+ 消息存在先后顺序。一个队列会按照消息服务器将消息放入队列中的顺序把它们传送给接收者。当消息已经被接收时就会从队列头部将它们删除(除非使用了消息优先级)。
+ 当接收者收到消息时,会发送确认收到通知。

JMS发布/订阅模型

发布订阅模式是一个系统约定将消息发布到一个主题(Topic)中,然后各个系统就能够通过订阅这个主题,根据发送过来的信息处理对应的业务。在更多的时候,开发者往往更多地使用发布订阅模式,因为可以进行更多的扩展,使得更多的系统能够监控得到消息。

注意点对点模型在JMS中体现为TopicConneciton

发布/订阅模型的特点:
+ 每条消息可以有多个订阅者。
+ 发布者和订阅者之间有时间上的依赖。一般情况下,某个主题的订阅者需要在创建了订阅之后才能接收到消息,而且为了接收消息订阅者必须保持运行的状态。
+ JMS允许订阅者创建一个可持久化的订阅,这样即使订阅者没有运行也能接收到所订阅的消息。
+ 每条消息都会传送给该主题下的所有订阅者。
+ 通常发布者不会知道也意识不到哪一个订阅者正在接收消息。

JMS使用

JMS在编程中体现了不同的接口:

JMS1.1

ConnectionFactory

是创建Connection的工厂,根据不同的消息类型用户可选择用QueueConnectionFactory和TopicConnectionFactory。

Destination

指消息目的地,要么是队列(Queue)要么是主题(Topic)

Connection

客户端与JMS系统之间建立的连接,分为QueueConnection和TopicConnectionFactory。一个Connection可以产生多个Session。

Session

实际操作消息的接口,表示一个单线程的上下文(会话)。可以通过Session创建生产者、消费者、消息,还提供了事务操作。Session也分为QueueSession和TopicSession。

MessageProducer

消息生产者,由Session创建,将消息发送到Destination。消费者可以同步或异步接收这些消息,
消息生产者也分为QueueSender和TopicSender。

MessageComsumer

消息消费者,由Session创建,接收来自Destination的消息。分为QueueReceiver和TopicReceiver。

Message

消息,就是被传送的对象

MessageListener

消息监听器,在消息到达时自动调用onMessage方法。

JMS2.0

JMS2.0对API做了简化:

JMSContext替换Connection和Session

JMSProducer替换MessageProducer,支持链式操作传递消息。

JMSConsumer替换MessageComsumer。

原创文章,作者:彭晨涛,如若转载,请注明出处:https://www.codetool.top/article/%e6%b6%88%e6%81%af%e9%98%9f%e5%88%97%e6%a6%82%e8%bf%b0%e4%b8%8ejms%e4%bd%bf%e7%94%a8/

发表评论

电子邮件地址不会被公开。