消息队列的初步了解及应用

来源:互联网 发布:非诚勿扰恶搞视频软件 编辑:程序博客网 时间:2024/06/07 07:11

本文内容参考自:
http://blog.csdn.net/heyutao007/article/details/50131089
http://blog.csdn.net/shaobingj126/article/details/50585035

Java消息服务(Java Message Service ,JMS)应用程序接口是一个Java平台关于面向消息中间件(MOM)的API,用于在两个应用程序之间,或分布式系统中发送消息,进行异步通讯。

JMS规范目前支持两种消息模型:点对点(point to point)和发布/订阅(publish/subscribe ,topic)

主要区别:发送到队列的消息能否重复消费(多订阅)

点对点:Queue.不可重复消费

消息生产者生产消息发送到queue 中,然后消息消费者从queue中取出并且消费消息。
消息被消费后,queue中不再存储,所以消息消费者不可能消费到已经被消费的消息。
queue支持存在多个消费者,但是对一个消息而言。只会有一个消费者可以消费

这里写图片描述

p2p(点对点)模式包含三个角色:消息队列(Queue),发送者(Sender),接受者(Receiver),每个消息都被发送到一个特定的队列,接收者从队列中接收消息。队列中保留着消息,直到他们被消费或超时。

特点:
1)每个消息只有一个消费者(Consumer)(即一旦被消费,消息就不再在消息队列中)

2)发送者和接收者之间在时间上没有依赖性,也就是说当发送者发送了消息后,不管接收者有没有正在运行,它不会影响到消息被发送到队列

3)接收者在成功接收消息后需向队列应答成功

如果希望发送的每个消息都会被成功处理的话,那么需要点对点模式

发布/订阅:topic,可以重复消费

消费生产者(发布)将消息发布到topic中,同时有多个消息消费者(订阅)消费该消息。
和点对点方式不同,发布到topic的消息会被所有订阅者消费

这里写图片描述

当发布者消息量很大时,单个订阅者的处理能力不足,可以将多个订阅者节点组成一个订阅组负载均衡消费topic消息即分组订阅。可以看成是一个topic 下有多个Queue,每个Queue是点对点的方式,Queue之间是发布订阅方式。

这里写图片描述

发布/订阅模式包含的三个角色:主题(topic),发布者(Publish),订阅者(Subscriber).多个发布者将消息发送到topic,系统将这些消息传递给多个订阅者

特点:
1)每个消息可以有多个消费者

2)发布者和订阅者有时间上的依赖性,对于某个主题(topic),必须创建一个订阅者后才能消费发布者的消息

3)为了消费消息,订阅者必须保持运行的状态

为了缓和这样严格的时间相关性,JMS允许订阅者创建一个可持久化的订阅,这样即使订阅者没有被激活(运行),也能接收到发布者的消息。

如果希望发送的消息可以不被做任何处理、或者只被一个消费者处理,或者可以被多个消费者处理的话,可以采用Pub/Sub(发布/订阅)

消息消费

在JMS中,消息的产生和消费都是异步的,对于消费来说,JMS的消费者可以通过两种方式来消费消息

1)同步
订阅者或接收者通过receive方法来接收消息,receive方法在接收到消息之前(或超时之前)将一直阻塞

2)异步
订阅者或接收者可以注册一个消息监听器,当消息到达后,系统自动调用监听器的onMessage方法

JNDI,Java命名和目录接口,是一种标准的Java命名系统接口,可以在网络上查找和访问服务。通过指定一个资源名称,该名称对应于数据库或命名服务中的一个记录,同时返回资源连接建立所必须的信息。JNDI在JMS中起到查找和访问发送目标或消息来源的作用。

JMS编程模型

1)ConnectionFactory

创建Connection 对象的工厂,针对两种消息模型,分别有QueueConnectionFactory和TopicConnectionFactory.可以通过JNDI来查找ConnectionFactory对象。

2)Destination

Destination是指消息生产者的消息发送目标或者说消息消费者的消息来源。

Destination 实际上就是:queue或者topic (可以通过JNDI查找)

3)Connection

Connection 表示客户端和JMS系统之间建立的链接(对TCP/IP socket的包装)。Connection 可以产生一个或多个Session.跟ConnectionFactory一样,Connection 也有两种类型,QueueConnection和TopicConnection

4)Session

Session 是操作消息的接口,可以通过session 创建生产者、消费者、消息等。Session提供了事务的功能,当需要用session 发送/接收多个消息时,可以将这些发送/接收动作放到一个事务中。同时也分QueueSession和Topic Session

5)消息的生产者

消息生产者由Session创建,并用于将消息发送到Destination.同时消息生产者分为两种类型:QueueSender和TopicPublisher,可以调用消费生产者的方法(send或publisher)发送消息

6)消息消费者

消息消费者由Session创建,用于接收Destination 的消息。两种类型:QueueReceiver和TopicSubscriber.可分别通过session 的createReceiver(Queue)或CreateSubscriber(Topic)来创建,当然也可以通过Session 的createDurableSubscriber方法来创建持久化的订阅者

7)MessageListener

消息监听器。如果注册了消息监听器。一旦消息到达,将自动调用监听器的onMessage方法


消息队列中间件是分布式系统中重要的组件,主要解决应用耦合,异步消息,流量削峰等问题。实现高性能、高可用性和最终一致性架构。是大型分布式系统不可缺少的中间件。

目前在生产环境,使用较多的消息队列有ActiveMQ,RabbitMQ,ZeroMQ,Kafka,RocketMQ等

消息队列应用场景

1,异步处理

用户注册后发送注册邮件和注册短信

这里写图片描述

2,应用解耦

用户下单后,订单系统通知库存系统

这里写图片描述

订单系统:用户下单后,订单系统完成持久化处理,将消息写入消息队列,返回用户订单下单成功。库存系统:订阅下单的消息,采用拉/推的方式,获取下单信息,库存系统根据下单信息,进行库存操作

假如在下单时库存系统不能正常使用,也不会影响正常下单,因为下单后,订单系统写入消息队列就不再关心其他的后续操作了。从而实现了订单系统与库存系统的应用解耦。

3,流量削峰

一般在秒杀或团枪活动中使用,秒杀活动,一般因为流量过大,导致应用挂掉,解决这个问题可以加入消息队列
1,可以控制活动的人数
2,可以缓解短时间内高流量压垮应用

这里写图片描述

1)用户的请求,服务器接收后,首先写入消息队列。加入消息队列长度超过最大数量,则直接抛弃用户请求或跳转到错误页面2)秒杀业务根据消息队列中的请求信息,再做后续处理

4,日志处理

将消息队列用在日志处理中,比如Kafka的应用,解决大量日志传输的问题

这里写图片描述

日志采集客户端,负责日志数据采集,定时写入Kafka队列Kafka消息队列,负责日志数据的接收,存储和转发。日志处理应用,订阅并消费kafka队列中的日志数据

5,消息通讯

消息队列一般都内置了高效的通信机制,因此也可以用在消息通讯中,比如实现点对点的消息,或者聊天室

1)点对点

这里写图片描述

客户端A和客户端B使用同一队列,进行消息通讯

2)聊天室

这里写图片描述

客户端A,客户端B,客户端N订阅同一主题,进行消息发布和接收,实现类似聊天室效果

以上实际是消息队列的两种消息模式,点对点和发布订阅模式


公司项目使用了阿里云的消息队列服务。下面以一个简单的需求为例:

用户执行操作后,需要记录用户行为,并判断是否发放荣誉勋章。

实现:在用户操作后,将相关请求放入队列,并采用拉的方式,用定时任务拉取队列中的消息,取到消息后则执行相关操作。

下面不介绍具体逻辑实现代码,仅贴出封装好的队列操作工具类(此处采用了点对点的模型)

public class MessageServerUtil<T> {    private static final Logger logger = LoggerFactory.getLogger(MessageServerUtil.class);    private Class<T> typeParameterClass;    private CloudQueue queue = null;    private String receiptHandle = null;    /**     * 构造函数初始化 T     *     * @param typeParameterClass     */    public MessageServerUtil(Class<T> typeParameterClass, String queryName) {        this.typeParameterClass = typeParameterClass;        //封装好的获取MnsClient,也可以在此处直接创建。参数:"YourAccessId", "YourAccessKey", "MNSEndpoint"        this.queue = MyConfig.config.getMnsClient().getQueueRef(queryName);        if (this.queue == null) {            throw new RuntimeException("初始化 query 失败");        }    }    /**     * 取得新消息     *     * @return     */    public T getMessage() {        Message popMsg = queue.popMessage();        if (popMsg != null) {            receiptHandle = popMsg.getReceiptHandle();            Gson gson = new Gson();            T t = gson.fromJson(popMsg.getMessageBodyAsString(), typeParameterClass);            return t;        } else {            return null; // 没有新消息        }    }    /**     * 发送消息     *     * @param t     */    public void send(T t) {        Gson gson = new Gson();        Message message = new Message();        message.setMessageBody(gson.toJson(t));        Message putMsg = queue.putMessage(message);    }    /**     * 发送延时消息     *     * @param t     */    public void send(T t, int delaySeconds) {        Gson gson = new Gson();        Message message = new Message();        message.setMessageBody(gson.toJson(t));        message.setDelaySeconds(delaySeconds);        Message putMsg = queue.putMessage(message);    }    /**     * 删除旧消息; 取得消息不为空时使用     */    public void delete() {        if (this.receiptHandle != null) {            queue.deleteMessage(this.receiptHandle);        }    }}