消息队列中点对点与发布订阅区别
背景知识
JMS一个在 Java标准化组织(JCP)内开发的标准(代号JSR 914)。2001年6月25日,Java消息服务发布JMS 1.0.2b,2002年3月18日Java消息服务发布 1.1.
Java消息服务(Java Message Service,JMS)应用程序接口是一个Java平台中关于面向消息中间件(MOM)的API,用于在两个应用程序之间,或分布式系统中发送消息,进行异步通信。
点对点与发布订阅最初是由JMS定义的。这两种模式主要区别或解决的问题就是发送到队列的消息能否重复消费(多订阅)
1.JMS中定义
JMS规范目前支持两种消息模型:点对点(point to point, queue)和发布/订阅(publish/subscribe,topic)。
点对点:
消息生产者生产消息发送到queue中,然后消息消费者从queue中取出并且消费消息。这里要注意:
消息被消费以后,queue中不再有存储,所以消息消费者不可能消费到已经被消费的消息。
Queue支持存在多个消费者,但是对一个消息而言,只会有一个消费者可以消费。
发布/订阅
消息生产者(发布)将消息发布到topic中,同时有多个消息消费者(订阅)消费该消息。和点对点方式不同,发布到topic的消息会被所有订阅者消费。
2.二者分析与区别
2.1 点对点模式
生产者发送一条消息到queue,只有一个消费者能收到。
2.2 发布订阅模式
发布者发送到topic的消息,只有订阅了topic的订阅者才会收到消息。 ### 小结 queue实现了负载均衡,一个消息只能被一个消费者接受,当没有消费者可用时,这个消息会被保存直到有 一个可用的消费者,一个queue可以有很多消费者,他们之间实现了负载均衡, 所以Queue实现了一个可靠的负载均衡。 topic实现了发布和订阅,当你发布一个消息,所有订阅这个topic的服务都能得到这个消息,所以从1到N个订阅者都能得到一个消息的拷贝, 只有在消息代理收到消息时有一个有效订阅时的订阅者才能得到这个消息的拷贝。
疑问
发布订阅模式下,能否实现订阅者负载均衡消费呢?当发布者消息量很大时,显然单个订阅者的处理能力是不足的。实际上现实场景中是多个订阅者节点组成一个订阅组负载均衡消费topic消息即分组订阅,
这样订阅者很容易实现消费能力线性扩展。
3 流行消息队列的消息模型比较
传统企业型消息队列ActiveMQ遵循了JMS规范,实现了点对点和发布订阅模型,但其他流行的消息队列RabbitMQ、Kafka并没有遵循老态龙钟的JMS规范,是通过什么方式实现消费负载均衡、多订阅呢?
3.1 RabbitMQ
RabbitMQ实现了AQMP协议,AQMP协议定义了消息路由规则和方式。生产端通过路由规则发送消息到不同queue,消费端根据queue名称消费消息。此外RabbitMQ是向消费端推送消息,订阅关系和消费状态保存在服务端。
生产端发送一条消息通过路由投递到Queue,只有一个消费者能消费到。
当RabbitMQ需要支持多订阅时,发布者发送的消息通过路由同时写到多个Queue,不同订阅组消费此消息。
RabbitMQ既支持内存队列也支持持久化队列,消费端为推模型,消费状态和订阅关系由服务端负责维护,消息消费完后立即删除,不保留历史消息。所以支持多订阅时,消息会多个拷贝。
3.2 Kafka
Kafka只支持消息持久化,消费端为拉模型,消费状态和订阅关系由客户端端负责维护,消息消费完后不会立即删除,会保留历史消息。因此支持多订阅时,消息只会存储一份就可以了。
同一个订阅组会消费topic所有消息,每条消息只会被同一个订阅组的一个消费节点消费,同一个订阅组内不同消费节点会消费不同消息
在执行下列代码时就可以去mq的控制台上查看消息内容及属性啦
mq控制台详解
example
package com.youxue.my.time_task.util;
import org.apache.activemq.ActiveMQConnection;
import org.apache.activemq.ActiveMQConnectionFactory;
import javax.jms.*;
/**
* <p>
* activeMQ 工具类
* <p/>
*
* @author dx
* @since 2020/6/23 17:59
*/
public class ActiveMQUtil {
/**
* JMS发送公共消息
*/
public static void jmsMessage(){
// 创建连接工厂
ConnectionFactory connectionFactory = new ActiveMQConnectionFactory(
ActiveMQConnectionFactory.DEFAULT_USER,
ActiveMQConnectionFactory.DEFAULT_PASSWORD,
ActiveMQConnectionFactory.DEFAULT_BROKER_URL);
Connection connection = null;
try {
// 创建连接对象
connection = connectionFactory.createConnection();
// 启动连接
connection.start();
// 创建session会话
Session session = connection.createSession(Boolean.FALSE, Session.AUTO_ACKNOWLEDGE);
// 设置当前消息在消息队列中存储的名称
Destination destination = session.createQueue("hello word");
// Destination destination = session.createQueue("04044044444404");
// 创建消息发送者/提供者
MessageProducer messageProducer = session.createProducer(destination);
// 设置持久化模式
messageProducer.setDeliveryMode(DeliveryMode.NON_PERSISTENT);
// 可同时发送多条信息
sendMessage(session,messageProducer);
// 发送消息
// TextMessage textMessage = session.createTextMessage();
// textMessage.setText("来了老弟?");
// messageProducer.send(textMessage);
} catch (Exception e) {
e.printStackTrace();
} finally {
// 释放资源
if(null != connection){
try {
connection.close();
} catch (JMSException e) {
e.printStackTrace();
}
}
}
}
/**
* JMS 接收消息
* 接收消息时类似于socket的客户端,将会一直处于接收状态,
* 除非关闭应用,或超出session会话时间
*
* ☆JMS 消息,只能被一台服务器接收一次,
* ☆消息被接收后,该消息会被删除,
* ☆其他所有服务器都不能再接收到之前发送的消息
*
* ☆使用JMS接收 ptp 发送的消息
* ☆ java.lang.ClassCastException: org.apache.activemq.command.ActiveMQMapMessage cannot be cast to javax.jms.TextMessage
* ☆两者消息类型不一致,直接抛出异常
*/
public static void receive(){
// 创建连接工厂
ConnectionFactory connectionFactory = new ActiveMQConnectionFactory(
ActiveMQConnectionFactory.DEFAULT_USER,
ActiveMQConnectionFactory.DEFAULT_PASSWORD,
ActiveMQConnectionFactory.DEFAULT_BROKER_URL);
Connection connection = null;
Session session = null;
try {
// 创建连接对象
connection = connectionFactory.createConnection();
// 启动连接
connection.start();
// 创建session会话
session = connection.createSession(Boolean.FALSE, Session.AUTO_ACKNOWLEDGE);
// 创建一个消息队列
Destination destination = session.createQueue("hell world");
// Destination destination = session.createQueue("04044044444404");
// 创建消息制作者
MessageConsumer consumer = session.createConsumer(destination);
while (true) {
// 接收数据的时间(等待) 100 ms
Message message = consumer.receive(1000 * 100);
TextMessage text = (TextMessage) message;
if (text != null) {
System.out.println("接收:" + text.getText());
} else {
break;
}
}
// 提交会话
session.commit();
} catch (JMSException e) {
e.printStackTrace();
} finally {
// 关闭释放资源
if (session != null) {
try {
session.close();
} catch (JMSException e) {
e.printStackTrace();
}
}
if (connection != null) {
try {
connection.close();
} catch (JMSException e) {
e.printStackTrace();
}
}
}
}
/**
* JMS 发送消息
*
* @param session
* @param messageProducer
* @throws Exception
*/
public static void sendMessage(Session session, MessageProducer messageProducer) throws Exception {
for (int i = 0; i < 50; i++) {
String message = "发送消息第" + (i + 1) + "条";
TextMessage text = session.createTextMessage(message);
System.out.println(message);
messageProducer.send(text);
}
}
/**
* 点对点(point-to-point)发送信息
*/
public static void pointToPoint(){
QueueConnection queueConnection = null;
QueueSession queueSession = null;
// 创建链接工厂
QueueConnectionFactory factory = new ActiveMQConnectionFactory(
ActiveMQConnection.DEFAULT_USER,
ActiveMQConnection.DEFAULT_PASSWORD,
ActiveMQConnection.DEFAULT_BROKER_URL);
try {
queueConnection = factory.createQueueConnection();
queueSession = queueConnection.createQueueSession(Boolean.TRUE, Session.AUTO_ACKNOWLEDGE);
// 创建一个消息队列
Queue queue = queueSession.createQueue("first-queue");
// 创建一个消息发送者/制造者
QueueSender sender = queueSession.createSender(queue);
// 设置持久化
sender.setDeliveryMode(DeliveryMode.NON_PERSISTENT);
// 发送消息
ptpSendMessage(queueSession, sender);
// 提交会话
queueSession.commit();
} catch (JMSException e) {
e.printStackTrace();
}finally {
// 释放资源
if(null != queueConnection){
try {
queueConnection.close();
} catch (JMSException e) {
e.printStackTrace();
}
}
if(null != queueSession){
try {
queueSession.close();
} catch (JMSException e) {
e.printStackTrace();
}
}
}
}
/**
* 点对点接收消息
* ☆point-to-point 消息,短时间内可以被同一服务器重复接收
*
*/
public static void ptpReceive(){
QueueConnection queueConnection = null;
QueueSession queueSession = null;
// 创建链接工厂
QueueConnectionFactory factory = new ActiveMQConnectionFactory(
ActiveMQConnection.DEFAULT_USER,
ActiveMQConnection.DEFAULT_PASSWORD,
ActiveMQConnection.DEFAULT_BROKER_URL);
try {
queueConnection = factory.createQueueConnection();
queueConnection.start();
queueSession = queueConnection.createQueueSession(Boolean.TRUE, Session.AUTO_ACKNOWLEDGE);
Queue queue = queueSession.createQueue("first-queue");
QueueReceiver receiver = queueSession.createReceiver(queue);
receiver.setMessageListener(msg -> {
if (msg != null) {
MapMessage map = (MapMessage) msg;
try {
System.out.println(map.getLong("time") + "我接收了#" + map.getString("text"));
} catch (JMSException e) {
e.printStackTrace();
}
}
});
// 休眠100ms再关闭
// Thread.sleep(1000 * 100);
// 提交会话
queueSession.commit();
} catch (JMSException e) {
e.printStackTrace();
} finally {
try {
if(null != queueConnection){
queueConnection.close();
}
if(null != queueSession){
queueSession.close();
}
} catch (JMSException e) {
e.printStackTrace();
}
}
}
/**
* 点对点发送消息
* @param queueSession
* @param queueSender
* @throws JMSException
*/
public static void ptpSendMessage(QueueSession queueSession, QueueSender queueSender) throws JMSException {
for (int i = 0; i < 50; i++) {
String message = "发送了第消息" + (i + 1) + "条";
MapMessage map = queueSession.createMapMessage();
map.setString("text", message);
map.setLong("time", System.currentTimeMillis());
System.out.println(map);
queueSender.send(map);
}
}
/**
* Topic主题发布/订阅 发送消息
*/
public static void topicMessage(){
TopicConnection topicConnection = null;
TopicSession topicSession = null;
// 创建链接工厂
TopicConnectionFactory factory = new ActiveMQConnectionFactory(
ActiveMQConnection.DEFAULT_USER,
ActiveMQConnection.DEFAULT_PASSWORD,
ActiveMQConnection.DEFAULT_BROKER_URL);
try {
topicConnection = factory.createTopicConnection();
topicSession = topicConnection.createTopicSession(Boolean.TRUE, Session.AUTO_ACKNOWLEDGE);
// 创建一个消息队列
Topic topic = topicSession.createTopic("topic-queue");
// 创建一个消息发送者/制造者
TopicPublisher topicPublisher = topicSession.createPublisher(topic);
// 设置持久化
topicPublisher.setDeliveryMode(DeliveryMode.NON_PERSISTENT);
// 发送消息
topicSendMessage(topicSession, topicPublisher);
// 提交会话
topicSession.commit();
} catch (Exception e) {
e.printStackTrace();
} finally {
// 释放资源
if(null != topicConnection){
try {
topicConnection.close();
} catch (JMSException e) {
e.printStackTrace();
}
}
if(null != topicSession){
try {
topicSession.close();
} catch (JMSException e) {
e.printStackTrace();
}
}
}
}
/**
* Topic主题发布/订阅 接收消息
*
* ☆只有当接收消息的客户端正在运行时,发布消息的客户端再发送消息,订阅者才能接收到发布的消息
*
* ☆例: 发布者发送消息时订阅者不在线(未运行),
* 订阅者上线时则不能接收发布者之前发送的消息
*
*/
public static void topicReceive(){
TopicConnection topicConnection = null;
TopicSession topicSession = null;
// 创建链接工厂
TopicConnectionFactory factory = new ActiveMQConnectionFactory(
ActiveMQConnection.DEFAULT_USER,
ActiveMQConnection.DEFAULT_PASSWORD,
ActiveMQConnection.DEFAULT_BROKER_URL);
try {
topicConnection = factory.createTopicConnection();
topicConnection.start();
topicSession = topicConnection.createTopicSession(Boolean.TRUE, Session.AUTO_ACKNOWLEDGE);
Topic topic = topicSession.createTopic("topic-queue");
TopicSubscriber topicSubscriber = topicSession.createSubscriber(topic);
topicSubscriber.setMessageListener(msg -> {
if (msg != null) {
MapMessage map = (MapMessage) msg;
try {
System.out.println(map.getLong("time") + "接收第#" + map.getString("text"));
} catch (JMSException e) {
e.printStackTrace();
}
}
});
// 休眠100ms再关闭
Thread.sleep(1000 * 100);
// 提交会话
topicSession.commit();
} catch (Exception e) {
e.printStackTrace();
} finally {
if(null != topicConnection){
try {
topicConnection.close();
} catch (JMSException e) {
e.printStackTrace();
}
}
if(null != topicSession){
try {
topicSession.close();
} catch (JMSException e) {
e.printStackTrace();
}
}
}
}
/**
* Topic主题发布/订阅 发送消息
*
* @param topicSession 会话
* @param topicPublisher 发布者
* @throws Exception
*/
public static void topicSendMessage(TopicSession topicSession, TopicPublisher topicPublisher) throws Exception {
for (int i = 0; i < 50; i++) {
String message = "发送第消息" + (i + 1) + "条";
MapMessage map = topicSession.createMapMessage();
map.setString("text", message);
map.setLong("time", System.currentTimeMillis());
System.out.println(map);
topicPublisher.send(map);
}
}
public static void main(String[] args) {
// jms发送消息
// jmsMessage();
// jms接收消息
// receive();
// point-to-point发送消息
// pointToPoint();
// point-to-point接收消息
// ptpReceive();
// topic发布/订阅发送消息
// topicMessage();
// topic发布/订阅接收消息
topicReceive();
}
}