处于学习阶段以下都是个人理解总结,不合适的地方希望各位指出,谢谢!
前言
系统之间通讯的方式有很多种,目前我知道的可以分成两大类。
- 实时的通讯,例如webService,doubbo等,实时的应用场景就是通讯结果需要实时的反应,例如A系统中点击搜索时,数据需要从B系统获取,这种场景是需要实时的。
- 异步的通讯,例如ActiveMQ,Kafka,RabitiMQ等。异步的应用场景就是通讯结果不需要实时的反应,例如从A系统中要发送1亿条数据给B系统,因为一次性发送数据量太大,需要分批次发送,这时A系统中发送消息的代码可能就是一个循环发送,当一次发送完成(B系统收到数据时)才能进行下一次循环!完全有可能的情况是,这时候网络不好,B系统暂时没有接收到数据,那么A系统的这个循环只能处于阻塞状态。为了解决类似这种问题,有了异步通讯方式,A系统只需要把数据丢入队列中,不需要B系统接收到,再执行下一次发送,B系统也不需要A发送数据了就马上接收。
A系统和B系统实时通讯如下图
A系统和B系统异步通讯如下图:
JMS
1.什么是JMS
JMS(Java Messaging Service)是Java平台上有关面向消息中间件的技术规范,它便于消息系统中的Java应用程序进行消息交换,并且通过提供标准的产生、发送、接收消息的接口简化企业应用的开发。
JMS本身只定义了一系列的接口规范,是一种与厂商无关的 API,用来访问消息收发系统。它类似于 JDBC(java Database Connectivity):这里,JDBC 是可以用来访问许多不同关系数据库的 API,而 JMS 则提供同样与厂商无关的访问方法,以访问消息收发服务。
2.JMS支持发送的消息5种类型
- TextMessage–一个字符串对象
- MapMessage–一套名称-值对
- ObjectMessage–一个序列化的 Java 对象
- BytesMessage–一个字节的数据流
- StreamMessage – Java 原始值的数据流
3.JMS消息传递类型
1.queue队列(点对点)
一个生产者放入队列的消息只会被一个消费者消费(目前我给我的感觉是随机的,听别人说是先到先得)。
即使多个消费者在监听指定的消息队列,也只有一个消费者去消费
应用场景:A系统发送请求更新B系统中的数据,B系统是部署了集群的,这时,每一个B系统都会监听这个队列,
更新方法只需要执行一次就可以了,不需要集群中的每一个B系统都执行一次更新方法,那么就采用queue模式。
2.topic订阅/消费者(广播机制)
一个生产者放入队列的消息会被监听这个消息队列的所有消费者消费。应用场景:A系统请求更新静态B系统中的静态资源,B系统是部署了集群的,
这时,每一个B系统都会监听这个队列,因为是静态资源,如果这个集群中的B系统只有一个更新了,
那么肯定是不合理的,需要集群中的所有B系统都更新一次,那么就采用topic模式。
ActiveMQ
1.什么是ActiveMQ
消息队列实现的一种平台(个人理解是这样的)。如果类比jdbc是一组API去操作不同的关系型数据库的话,那么jms是一组API去操作不同的消息队列,activeMQ就是一种消息队列。
2.ActiveMQ安装(linux环境)
下载地址:http://activemq.apache.org/components/classic/download/
上传安装包,解压,运行bin目录中的activemq就好了
启动和停止命令:
./activemq start (启动)
./activemq stop (停止)
启动成功后访问管理页面:
Java程序使用JMS操作ActiveMQ
queue模式生产者
public void queueSendMessage() throws JMSException {
//采用tcp协议,注意!
String borkerURL = "tcp://192.168.220.128:61616";
ConnectionFactory activeMQConnectionFactory = new ActiveMQConnectionFactory(borkerURL);
Connection connection = activeMQConnectionFactory.createConnection();
//打开连接!
connection.start();
//创建session
Session session = connection.createSession(false, Session.AUTO_ACKNOWLEDGE);
//创建队列
Queue queue = session.createQueue("test-queue");
//创建消息生产者
MessageProducer producer = session.createProducer(queue);
//创建消息
TextMessage textMessage = session.createTextMessage("i come from test-queue");
//发送消息
producer.send(textMessage);
//关闭资源
producer.close();
session.close();
connection.close();
}
queue模式消费者
public void queueConsumeMessage() throws JMSException, IOException {
String borkerURL = "tcp://192.168.220.128:61616";
ConnectionFactory connectionFactory = new ActiveMQConnectionFactory(borkerURL);
Connection connection = connectionFactory.createConnection();
//打开连接!
connection.start();
//创建session
Session session = connection.createSession(false, Session.AUTO_ACKNOWLEDGE);
//创建队列,这里队列的名称 是你要消费的队列名称
String queueName = "test-queue";
Queue queue = session.createQueue(queueName);
//创建消费者
MessageConsumer consumer = session.createConsumer(queue);
//监听消息
consumer.setMessageListener(message -> {
//这里的message是Message类型 需要类型转换 才会有对应的成员属性
TextMessage textMessage = (TextMessage) message;
try {
System.out.println("我是1号消费者,消费的消息是:" + textMessage.getText());
} catch (JMSException e) {
e.printStackTrace();
}
}
);
//等待键盘输入
System.in.read();
//关闭连接
consumer.close();
session.close();
connection.close();
}
topic模式生产者
public void topicSendMessage() throws JMSException {
String brokerURL = "tcp://192.168.220.128:61616";
ConnectionFactory connectionFactory = new ActiveMQConnectionFactory(brokerURL);
Connection connection = connectionFactory.createConnection();
connection.start();
//创建session
Session session = connection.createSession(false, Session.AUTO_ACKNOWLEDGE);
//创建topic
Topic topic = session.createTopic("test-topic");
//创建消息生产者
MessageProducer producer = session.createProducer(topic);
//创建消息
TextMessage textMessage = session.createTextMessage("i come from topic" + (LocalDateTime.now().toString()));
//发送消息
producer.send(textMessage);
//关闭资源
producer.close();
session.close();
connection.close();
}
topic模式消费者
public void topicConsumeMessage() throws JMSException, IOException {
String brokerURL = "tcp://192.168.220.128:61616";
ConnectionFactory connectionFactory = new ActiveMQConnectionFactory(brokerURL);
Connection connection = connectionFactory.createConnection();
connection.start();
//创建session
Session session = connection.createSession(false, Session.AUTO_ACKNOWLEDGE);
//创建需要消费的主题
String topicName="test-topic";
Topic topic = session.createTopic(topicName);
//创建消费者
MessageConsumer consumer = session.createConsumer(topic);
//设置监听消费消息
consumer.setMessageListener(message -> {
TextMessage textMessage = (TextMessage) message;
try {
System.out.println("我是1号消费者,消费的信息是:"+textMessage.getText());
} catch (JMSException e) {
e.printStackTrace();
}
});
//等待键盘输入
System.in.read();
//关闭连接
consumer.close();
session.close();
connection.close();
}
Spring中使用JmsTemplate操作ActiveMQ
1.生产者
生产者配置文件
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans" xmlns:context="http://www.springframework.org/schema/context"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:amq="http://activemq.apache.org/schema/core"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd
http://activemq.apache.org/schema/core
http://activemq.apache.org/schema/core/activemq-core-5.8.0.xsd">
<context:component-scan base-package="com.ithaha.producer"></context:component-scan>
<!-- ActiveMQ 连接工厂 -->
<!-- 真正可以产生Connection的ConnectionFactory,由对应的 JMS服务厂商提供-->
<!-- 如果连接网络:tcp://ip:61616;未连接网络:tcp://localhost:61616 以及用户名,密码-->
<amq:connectionFactory id="amqConnectionFactory" brokerURL="tcp://192.168.220.128:61616"/>
<!-- Spring Caching连接工厂 -->
<!-- Spring用于管理真正的ConnectionFactory的ConnectionFactory -->
<bean id="connectionFactory" class="org.springframework.jms.connection.CachingConnectionFactory">
<!-- 目标ConnectionFactory对应真实的可以产生JMS Connection的ConnectionFactory -->
<property name="targetConnectionFactory" ref="amqConnectionFactory"></property>
<!-- Session缓存数量 -->
<property name="sessionCacheSize" value="100"/>
</bean>
<!-- Spring JmsTemplate 的消息生产者 start-->
<!-- 定义JmsTemplate的Queue类型 -->
<bean id="jmsQueueTemplate" class="org.springframework.jms.core.JmsTemplate">
<!-- 这个connectionFactory对应的是我们定义的Spring提供的那个ConnectionFactory对象 -->
<constructor-arg ref="connectionFactory"/>
<!-- 非pub/sub模型(发布/订阅),即队列模式 -->
<property name="pubSubDomain" value="false"/>
</bean>
<!-- 定义JmsTemplate的Topic类型 -->
<bean id="jmsTopicTemplate" class="org.springframework.jms.core.JmsTemplate">
<!-- 这个connectionFactory对应的是我们定义的Spring提供的那个ConnectionFactory对象 -->
<constructor-arg ref="connectionFactory"/>
<!-- pub/sub模型(发布/订阅) -->
<property name="pubSubDomain" value="true"/>
</bean>
<!--Spring JmsTemplate 的消息生产者 end-->
</beans>
生产者java代码
package com.ithaha.producer;
import org.springframework.jms.core.JmsTemplate;
import org.springframework.stereotype.Component;
import javax.annotation.Resource;
@Component
public class QueueProducer {
@Resource(name = "jmsQueueTemplate")
private JmsTemplate jmsQueueTemplate;
public void sendTextMessage(String destinationName, final String message) {
jmsQueueTemplate.send(destinationName, (session) -> session.createTextMessage(message)
);
}
}
package com.ithaha.producer;
import org.springframework.jms.core.JmsTemplate;
import org.springframework.stereotype.Component;
import javax.annotation.Resource;
@Component
public class TopicProducer {
@Resource(name = "jmsTopicTemplate")
private JmsTemplate jmsTopicTemplate;
public void sendTextMessage(String destinationName, final String message) {
jmsTopicTemplate.send(destinationName, session -> session.createTextMessage(message));
}
}
生产者测试代码
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = "classpath:spring-activemq-producer.xml")
public class ProducerTest {
@Autowired
private QueueProducer queueProducer;
@Autowired
private TopicProducer topicProducer;
@Test
public void queueSendTextMessage(){
String message = "你好消费者!"+ LocalDateTime.now().toString();
queueProducer.sendTextMessage("update_data_queue",message);
System.out.println("消息已发送!");
}
@Test
public void topicSendTextMessage(){
String message = "你好消费者!"+ LocalDateTime.now().toString();
topicProducer.sendTextMessage("generator_page_topic",message);
System.out.println("消息已发送!");
}
}
3.消费者配置文件
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans" xmlns:context="http://www.springframework.org/schema/context" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:amq="http://activemq.apache.org/schema/core"
xmlns:jms="http://www.springframework.org/schema/jms"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd
http://www.springframework.org/schema/jms
http://www.springframework.org/schema/jms/spring-jms.xsd
http://activemq.apache.org/schema/core
http://activemq.apache.org/schema/core/activemq-core-5.8.0.xsd">
<context:component-scan base-package="com.ithaha.consumer"></context:component-scan>
<!-- ActiveMQ 连接工厂 -->
<!-- 真正可以产生Connection的ConnectionFactory,由对应的 JMS服务厂商提供-->
<!-- 如果连接网络:tcp://ip:61616;未连接网络:tcp://localhost:61616 以及用户名,密码-->
<amq:connectionFactory id="amqConnectionFactory" brokerURL="tcp://192.168.220.128:61616"/>
<!-- Spring Caching连接工厂 -->
<!-- Spring用于管理真正的ConnectionFactory的ConnectionFactory -->
<bean id="connectionFactory" class="org.springframework.jms.connection.CachingConnectionFactory">
<!-- 目标ConnectionFactory对应真实的可以产生JMS Connection的ConnectionFactory -->
<property name="targetConnectionFactory" ref="amqConnectionFactory"></property>
<!-- Session缓存数量 -->
<property name="sessionCacheSize" value="100"/>
</bean>
<!-- 消息消费者 start-->
<!-- 定义Queue监听器 -->
<jms:listener-container destination-type="queue" container-type="default" connection-factory="connectionFactory" acknowledge="auto">
<jms:listener destination="update_data_queue" ref="queueConsumer1"/>
<jms:listener destination="update_data_queue" ref="queueConsumer1"/>
</jms:listener-container>
<!-- 定义Topic监听器 -->
<jms:listener-container destination-type="topic" container-type="default" connection-factory="connectionFactory" acknowledge="auto">
<jms:listener destination="generator_page_topic" ref="topicConsumer1"/>
<jms:listener destination="generator_page_topic" ref="topicConsumer2"/>
</jms:listener-container>
<!-- 消息消费者 end -->
</beans>
消费者java代码
@Component(value = "topicConsumer1")
public class TopicConsumer1 implements MessageListener {
public void onMessage(Message message) {
try {
TextMessage textMessage = (TextMessage) message;
System.out.println("TopicConsumer1接收到消息:"+textMessage.getText());
} catch (JMSException e) {
e.printStackTrace();
}
}
}
@Component(value = "topicConsumer2")
public class TopicConsumer2 implements MessageListener {
public void onMessage(Message message) {
try {
TextMessage textMessage = (TextMessage) message;
System.out.println("TopicConsumer2接收到消息:"+textMessage.getText());
} catch (JMSException e) {
e.printStackTrace();
}
}
}
@Component(value = "queueConsumer1")
public class QueueConsumer1 implements MessageListener {
public void onMessage(Message message) {
try {
TextMessage textMessage = (TextMessage) message;
System.out.println("QueueConsumer1接收到消息:"+textMessage.getText());
} catch (JMSException e) {
e.printStackTrace();
}
}
}
*/
@Component(value = "queueConsumer2")
public class QueueConsumer2 implements MessageListener {
public void onMessage(Message message) {
try {
TextMessage textMessage = (TextMessage) message;
System.out.println("QueueConsumer2接收到消息:"+textMessage.getText());
} catch (JMSException e) {
e.printStackTrace();
}
}
}
测试代码
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = "classpath:spring-activemq-queue-consumer.xml")
public class ConsumerTest {
@Test
public void consume() throws IOException {
//等待键盘输入 让消费者保持监听消息队列状态
System.in.read();
}
}
总结
- 监听:生产者将消息放入队列后,消费者是监听队列
- 消费者监听的队列时使用Destination指定监听哪个队列
- 在发送消息的代码里有个session概念,是获取一系列操作的对象的基础对象
- 在监听消息时,需要实现MessageListener接口