JMS(消息中间件)
1 消息中间件
消息中间件适用于需要可靠的数据传送的分布式环境。采用消息中间件机制的系统中,不同的对象之间通过传递消息来激活对方的事件,完成相应的操作。发送者将消息发送给消息服务器,消息服务器将消息存放在若干队列中,在合适的时候再将消息转发给接收者。消息中间件能在不同平台之间通信,它常被用来屏蔽掉各种平台及协议之间的特性,实现应用程序之间的协同,其优点在于能够在客户和服务器之间提供同步和异步的连接,并且在任何时刻都可以将消息进行传送或者存储转发,这也是它比远程过程调用更进一步的原因。
MQ采用异步调动的方式,解除耦合(程序不启动也能运行),提高了运行性能(不用担心处理时间,处理时间的长短不影响程序性能,
常用的消息中间件:
-
ActiveMQ
是Apache出品,最流行的,能力强劲的开源消息总线。ActiveMQ是一个完全支持JMS1.1和JSEE1.4规范的JMS Provider实现。我们在本次课程中介绍ActiveMQ的使用。
-
RabbitMQ
AMQP协议的领导实现,支持多种场景。淘宝的MySQL集群内部有使用它进行通讯,OpenStack开源云平台的通信组件,最先在金融行业得到运用。
-
ZeroMQ
史上最快的消息队列系统
-
Kafka
Apache下的一个子项目。特点:高吞吐,在一台普通的服务器上既可以达到10W/s的吞吐速率;完全的分布式系统,适合处理海量数据。
2 简介
JMS即Java消息服务(Java Message Service)应用程序接口,是一个Java平台中关于面向消息中间件(MOM)的API,用于在两个应用程序之间,或分布式系统中发送消息,进行异步通信。Java消息服务是一个与具体平台无关的API,绝大多数MOM提供商都对JMS提供支持。
JMS本身只定义了一系列的接口规范,是一种与厂家无关的API,用来访问消息收发系统。它类似于JDBC,JDBC是可以用来访问许多不同关系数据库API_(:з」∠) _ ,而JMS则提供同样与厂商无关的访问方法,以访问消息收发服务。许多厂商目前都支持JMS。
JMS定义了五种不同的消息格式,以及调用的消息类型,允许你发送并接收一些不同类型的数据,提供现有消息格式的一些级别的兼容性。
- TextMessage -- 一个字符串对象(常见,也最简单)
- MapMessage -- 一套键值对(传多个信息)
- ObjectMessage -- 一个序列化的Java对象(传递实现了可序列化接口的类)
- BytesMessage -- 一个字节的数据流(文件、音频)
- StreamMessage -- Java原始值的数据流
2.1 JMS消息传递类型
对于消息的传递有两种类型:
-
一种是点对点模式,即一个生产者和一个消费者一一对应。
一个或多个生产者把产品放在队列上,(一个)消费者在队列的另一边接收数据
-
一种是发布/订阅模式,即一个生产者产生的消息进行发送后,可以由多个消费者进行接收。
一个或多个生产者产生在Topic(主题),所有消费者都能同时(广播)接收到该Topic(主题)
3 ActiveMQ
3.1 安装ActiveMQ
在官网下载ActiveMQ
sftp上传到linux
tar zxvf tar.gz文件(解压缩)
chmod 777 解压后的文件夹 (给该文件夹赋予最高权限,任何用户都可以访问)
cd 文件夹/bin
chmod 755 activemq
./activemq start 启动
访问连接 http://IP地址:8161
访问红线部分,输入默认账号密码admin
3.2 入门Demo (点对点模式)
-
引入依赖
org.apache.activemq activemq-client 5.15.10 -
编写一个生产者
import org.apache.activemq.ActiveMQConnectionFactory; import javax.jms.*; /** * 点对点关系 */ public class QueueProducer { public static void main(String[] args) throws JMSException { //1.创建连接工厂 ConnectionFactory connectionFactory = new ActiveMQConnectionFactory("tcp://192.168.234.129:61616"); //2.创建连接 Connection connection = connectionFactory.createConnection(); //3.启动连接 connection.start(); //4.获取session(会话对象) /** * boolean:是否启动事务,如果为true,代表commit的时候才提交事务 * int:消息的确认方式 AUTO_ACKNOWLEDGE = 1(自动确认); CLIENT_ACKNOWLEDGE = 2(客户端手动确认); * DUPS_OK_ACKNOWLEDGE = 3(自动批量确认); SESSION_TRANSACTED = 0(事务提交并确认) */ Session session = connection.createSession(false, Session.AUTO_ACKNOWLEDGE); //5.创建队列对象,传入队列的名字 Queue queue = session.createQueue("test-queue"); //6.创建消息生产者对象 MessageProducer producer = session.createProducer(queue); //7.创建消息对象(文本消息) TextMessage textMessage = session.createTextMessage("欢迎使用activeMQ"); //8.生产者发送消息 producer.send(textMessage); //9.关闭资源 producer.close(); session.close(); connection.close(); } }
-
运行并查看结果
-
编写一个消费者
import org.apache.activemq.ActiveMQConnectionFactory; import javax.jms.*; import java.io.IOException; public class QueueConsumer { public static void main(String[] args) throws JMSException, IOException { //1.创建连接工厂 ConnectionFactory connectionFactory = new ActiveMQConnectionFactory("tcp://192.168.234.129:61616"); //2.创建连接 Connection connection = connectionFactory.createConnection(); //3.启动连接 connection.start(); Session session = connection.createSession(false, Session.AUTO_ACKNOWLEDGE); //5.创建队列对象,传入队列的名字 Queue queue = session.createQueue("test-queue"); //6.创建消息消费者对象 MessageConsumer consumer = session.createConsumer(queue); //7.设置监听,当有消息产生的时候就消费消息 consumer.setMessageListener(new MessageListener() { @Override public void onMessage(Message message) { TextMessage textMessage = (TextMessage) message; try { System.out.println("提取的消息:" + textMessage.getText()); } catch (JMSException e) { e.printStackTrace(); } } }); //8.等待键盘输入,目的是让消费者程序不终止执行 System.in.read(); //9.关闭资源 consumer.close(); session.close(); connection.close(); } }
-
运行后发现消息被消费,以及消费者数+1
-
其他细节
- 此时消费者由于System.in.read(); 就没有被终止程序,也就是一直处于监听状态
- 如果此时再次有生产者生产时,处于监听状态的消费者会立马消费
- 当一个队列有多个消费者时,如果有消息产生,只会有一个消费者得到消息
-
点对点小结
- 当消息只需要消费一次时,可以使用点对点的方式(比如搜索服务)
3.3 发布/订阅模式
-
编写生产者
import org.apache.activemq.ActiveMQConnectionFactory; import javax.jms.*; import java.io.IOException; /** * 发布订阅模式的生产者 */ public class TopicProducer { public static void main(String[] args) throws JMSException, IOException { //1.创建连接工厂 ConnectionFactory connectionFactory = new ActiveMQConnectionFactory("tcp://192.168.234.129:61616"); //2.创建连接 Connection connection = connectionFactory.createConnection(); //3.启动连接 connection.start(); Session session = connection.createSession(false, Session.AUTO_ACKNOWLEDGE); //5.创建主题对象,传入主题的名字 Topic topic = session.createTopic("test-topic"); //6.创建消息生产者对象 MessageProducer producer = session.createProducer(topic); //7.创建消息对象(文本消息) TextMessage textMessage = session.createTextMessage("欢迎使用activeMQ"); //8.生产者发送消息 producer.send(textMessage); //9.关闭资源 producer.close(); session.close(); connection.close(); } }
-
运行后查看结果
-
编写消费者
import org.apache.activemq.ActiveMQConnectionFactory; import javax.jms.*; import java.io.IOException; public class TopicConsumer { public static void main(String[] args) throws JMSException, IOException { //1.创建连接工厂 ConnectionFactory connectionFactory = new ActiveMQConnectionFactory("tcp://192.168.234.129:61616"); //2.创建连接 Connection connection = connectionFactory.createConnection(); //3.启动连接 connection.start(); Session session = connection.createSession(false, Session.AUTO_ACKNOWLEDGE); //5.创建队列对象,传入队列的名字 Topic topic = session.createTopic("test-topic"); //6.创建消息消费者对象 MessageConsumer consumer = session.createConsumer(topic); //7.设置监听,当有消息产生的时候就消费消息 consumer.setMessageListener(new MessageListener() { @Override public void onMessage(Message message) { TextMessage textMessage = (TextMessage) message; try { System.out.println("提取的消息:" + textMessage.getText()); } catch (JMSException e) { e.printStackTrace(); } } }); //8.等待键盘输入,目的是让消费者程序不终止执行 System.in.read(); //9.关闭资源 consumer.close(); session.close(); connection.close(); } }
-
运行但发现之前发送的主题没有被消费,说明发布/订阅模式下
- 当有主题发送出来时,就会向在场的消费者进行广播
- 新进的消费者不能收到以前发布的主题
4 Spring整合JMS
4.1 点对点模式
- 先做生产者
-
导入依赖包
5.1.5.RELEASE junit junit 4.12 test org.springframework spring-test ${spring-version} org.springframework spring-context ${spring-version} org.springframework spring-jms ${spring-version} org.apache.activemq activemq-client 5.15.10 spring-context org.springframework org.apache.geronimo.specs geronimo-jms_1.1_spec javax.jms javax.jms-api 2.0.1 -
编写配置文件applicationContext-jms.xml(此处先编写生产者的配置文件)
-
编写生产者,里面要写生产的逻辑
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.jms.core.JmsTemplate; import org.springframework.jms.core.MessageCreator; import org.springframework.stereotype.Component; import javax.jms.Destination; import javax.jms.JMSException; import javax.jms.Message; import javax.jms.Session; @Component public class QueueProducer { @Autowired private JmsTemplate jmsTemplate; @Autowired private Destination queueTextDestination; /** * 发送文本信息 * @param text */ public void sendTextMessage(final String text) { jmsTemplate.send(queueTextDestination, new MessageCreator() { @Override public Message createMessage(Session session) throws JMSException { return session.createTextMessage(text); } }); } }
-
编写测试类,测试运行效果
import org.junit.Test; import org.junit.runner.RunWith; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.test.context.ContextConfiguration; import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; import spring.activemq.demo.QueueProducer; //需要导入配置文件,让spring框架能使用配置文件中的配置 @RunWith(SpringJUnit4ClassRunner.class) @ContextConfiguration(locations = "classpath:applicationContext-jms.xml") public class TestQueue { @Autowired private QueueProducer queueProducer; @Test public void testSend() { queueProducer.sendTextMessage("spring JMS 点对点"); } }
- 编写消费者
-
自定义监听方法
import javax.jms.JMSException; import javax.jms.Message; import javax.jms.MessageListener; import javax.jms.TextMessage; public class MyMessageListener implements MessageListener { @Override public void onMessage(Message message) { TextMessage textMessage = (TextMessage) message; try { System.out.println("接收到消息:" + textMessage.getText()); } catch (JMSException e) { e.printStackTrace(); } } }
-
修改配置文件,加入消费者
但要注意加入的消费者在导入配置文件后会自动启动,可以生产者和消费者分成两个不同的配置文件
这里为了方便就不另建配置文件和测试类
-
编写测试类测试消费者
@Test public void textGetQueue() { try { //添加暂停语句, System.in.read(); } catch (IOException e) { e.printStackTrace(); } }
4.2 发布/订阅模式
- 编写生产者
-
编写配置文件,此处为了方便沿用点对点模式的配置类,但要先注释掉点对点模式的消费者监听器
(个人认为一个消费者可以类比为一个监听器了)
-
编写生产者类
对比于点对点模式的生产者,只是目的类型有变化,可以看出点对点模式和发布/订阅模式的connectionFactory是一样的(至少可以从配置类文件可以看出)
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.jms.core.JmsTemplate; import org.springframework.jms.core.MessageCreator; import org.springframework.stereotype.Component; import javax.jms.Destination; import javax.jms.JMSException; import javax.jms.Message; import javax.jms.Session; @Component public class TopicProducer { @Autowired private JmsTemplate jmsTemplate; @Autowired private Destination topicTextDestination; /** * 发送文本信息 * @param text */ public void sendTextMessage(final String text) { jmsTemplate.send(topicTextDestination, new MessageCreator() { @Override public Message createMessage(Session session) throws JMSException { return session.createTextMessage(text); } }); } }
-
编写测试类
import org.junit.Test; import org.junit.runner.RunWith; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.test.context.ContextConfiguration; import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; import spring.activemq.demo.TopicProducer; @RunWith(SpringJUnit4ClassRunner.class) @ContextConfiguration(locations = "classpath:applicationContext-jms.xml") public class TestTopic { @Autowired private TopicProducer topicProducer; @Test public void testSend() { topicProducer.sendTextMessage("spring JMS 发布/订阅"); } }
运行后可以通过浏览器查看结果
- 编写消费者
-
修改配置文件,加上消费者的监听器
从配置文件上看,两种模式的不同也只是目标类型上的不同,和生产者的情况有点类似
-
添加测试类方法
@Test public void textGetQueue() { try { //添加暂停语句, System.in.read(); } catch (IOException e) { e.printStackTrace(); } }