第四章 activemq
ActiveMQ主要用来解决应用程序之间的通信(消息传递问题),使用mq,应用间通信并不是直接进行宣城调用,而是通过消息队列进行一步通信
应用场景:高并发,对实时性要求不高的分布式应用系统
解决的问题:解耦系统、异步通信、削峰(消除流量的高峰)
1. JMS
JMS(Java Message Service)是javaEE的规范之一,这个规范指出消息的传递必须是异步的、非阻塞的,可以实现系统解耦增加系统的灵活性
1.1 核心API
- ConnectionFactory:连接工场,用于创建Connection;
- Connection:客户端与JMS的一次连接
- Session:客户端与JMS的一次会话,由Connection创建,可以用来创建消息的生产者和消费者,也可以创建消息的目的地和消息;
- Destination:生产者生产消息的目的地,消费者消费消息的来源,由Session创建;
- Queue:队列
- Topic:主题
- MessageProducer:消息的生产者,由Session创建,用于发送MQ消息;
- MessageConsumer:消息的消费者,由Session创建,用于消费消息;
- Message:消息,由Session创建
- TextMessage
- MapMessage
- ObjectMessage
- BytesMessage
- StreamMessage
- MessageListener:消息监听器
1.2 JMS消息类型
- 点对点 (P2P)
一条消息只能呗一个消费者消费,被消费之后自动从消息队列剔除
- 发布/订阅(Pub/Sub)
一条消息可以被多个消费者同时消费,生产者和消费者有时间上的依赖性,也就是生产者在发送消息时,应该至少有一个消费者处于在线状态
2.什么是ActiveMQ
ActiveMQ是最受欢迎的、功能强大的开源消息传递和集成服务器,ActveMQ速度快,支持多语言的客户端和协议,很容易进行企业级集成,支持许多高级特性,完全支持JMS1.1和J2EE1.4;
其它消息队列:
- RabbitMQ:不是JMS实现,实现的AMQP协议;
- RocketMQ:阿里开发的,目前贡献给Apache了,自研的消息协议;
- Kafka:自研的消息协议;
ActiveMQ特点
- 支持多语言客户端和协议,如Java、C、C++、Ruby、Perl、Python、PHP
- 支持许多高级特性,如消息分组、虚拟目的地、通配符、组合目的地;
- 完全支持JMS1.1和J2EE1.4;
- 可以很容易集成到spring应用程序中;
- 通过大部分j2ee服务器的测试,如TomEE、JBoss、WebLogic等;
- 支持高效的JDBC持久化方式;
- 集群的支持;
- …
3.安装与使用
解压完成以后,进入 apache-activemq-5.15.3\bin\win64\
双击activemq.bat 等命令行窗口出现的时候,打开浏览器输入网址
http://localhost:8161/admin
username: admin
password: admin
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-EoHCM0AO-1602493466751)(https://ae01.alicdn.com/kf/H89f01bf6e84649228abc75f91fcf0d41u.png)]
4 第一个实例
- 父工程引入公共依赖
引入依赖的时候可以使用
</dependencyManagement>
管理父项目的依赖,经过此标签管理之后,子工程就可以在需要的时候引入相关的依赖,而不需要引入其他的依赖,且不需要输入版本号,子工程依赖如下所示<dependencies> <dependency> <groupId>javax.jms</groupId> <artifactId>javax.jms-api</artifactId> </dependency> </dependencies>
<dependencies>
<dependency>
<groupId>javax.jms</groupId>
<artifactId>javax.jms-api</artifactId>
<version>2.0.1</version>
</dependency>
<!-- spring-jms -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-jms</artifactId>
<version>5.2.6.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context-support</artifactId>
<version>5.2.6.RELEASE</version>
</dependency>
<dependency>
<groupId>org.apache.activemq</groupId>
<artifactId>activemq-broker</artifactId>
<version>5.15.3</version>
<!-- 排除低版本的jackson -->
<exclusions>
<exclusion>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.apache.activemq</groupId>
<artifactId>activemq-pool</artifactId>
<version>5.15.3</version>
</dependency>
<!-- 引入高版本的jackson-databind -->
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.9.5</version>
</dependency>
</dependencies>
4.1 ActiveMQ 套路
4.1.1创建消息生产者
- 创建连接工厂[ConnectionFactory]
//前两个工作的时候应该会给账号密码,和最后一个是activemq的连接地址
ConnectionFactory factory = new ActiveMQConnectionFactory(
null, null, "tcp://localhost:61616");
- 创建连接对象 并调用 start 方法
//创建完成之后必调用start方法!!!
Connection connection = factory.createConnection();
connection.start();
- 创建session,最重要的就是session
创建队列消息和创建生产者都是由session创建
- 第一个参数: 是否开启事务
- 第二个参数:手动签收还是自动签收
常量 数值 签收方式 Session.AUTO_ACKNOWLEDGE 1 自动签收 Session.CLIENT_ACKNOWLEDGE 2 客户端手动签收
Session session = connection.createSession(false,Session.AUTO_ACKNOWLEDGE)
- 创建Destination,如果队列不存在则自动创建
//1.创建Queue
Queue queue = session.createQueue("hello");
//2.创建Topic队列
Topic topic = session.createTopic("topic");
- 创建生产者
//消息发布者
MessageProduce producer = session.createProdecer(queue);
MessageProduce producer = session.createProdecer(topic);
- 创建消息
消息类型一共有五种,分别是**TextMessage[文本消息]、MapMessage[Map消息,键值对类型]、ObjectMessage**
剩下的两种不是很常用 BytesMessage、StreamMessage
//文本类型消息
TextMessage msg = session.createTextMessage("这是文本类型的消息");
//Map类型消息
MapMessage mapMsg = session.createMapMessage();
mapMsg.setString("name","mapMsg");
mapMsg.setInt("id",1);
...
//ObjectMessage
- 发送消息
producer.send(msg);
- 关闭连接
producer.close();
session.close();
connection.close();
4.2.2 创建消息消费者
- 创建ConnectionFactory
ConnectionFactory factory = new ActiveMQConnectionFactory(null,null,"tcp://localhost:61616");
- 创建Connection**并调用start方法**
Connection connection = factory.createConnection();
connection.start();
- 创建session
//参数1,是否开启事务
//参数2.客户端签收消息的方式
Session session = connection.ceateSession(false,Session.AUTO_ACKNOWLEDGE);
- 创建Destination 获取队列上的消息
//如果消息队列没有名字为queue的队列,则会自动创建一个queue消息
//这里的参数名称对应上边生产者填写的参数一致
Queue queue = session.createQueue("queue");
Topic topic = session.createTopic("topic");
- 创建消费者
MessageConsumer consumer = session.createConsumer(queue);
MessageConsumer csm = session.createConsumer(topic);
- 消费消息
当创建session的时候设置为手动签收的情况下,在消费完消息之后可以选择性的手动签收消息
msg.acknowledge();
//此处的消息可以是其他的版本
//如MapMessage等·
Message message = consumer.receive();
if(message instanceof TextMessage){
TextMessage msg = (TextMessage)message;
System.out.println("消息是:"+ msg.getText());
//客户端手动签收消息
//msg'.acknowledge();
}
如果是Topic的话
如果是topic 的话,就要创建一个消息监听器,来消费消息
//普通版本
consumer.setMessageListener(new MessageListener() {
@Override
public void onMessage(Message message) {
//todo
}
});
//lambda表达式
consumer.setMessageListener(message -> {
if(message instanceof MapMessage){
MapMessage msg = (MapMessage) message;
try {
System.out.println(msg.getInt("id") + msg.getString("topic"));
} catch (JMSException e) {
e.printStackTrace();
}
}
});
- 关闭连接
consumer.close();
session.close();
connection.close();
4.2 java版本的
4.2.1 p2p[点对点]
- 创建生产者
package com.etoak.p2p;
import javax.jms.Connection;
import javax.jms.ConnectionFactory;
import javax.jms.JMSException;
import javax.jms.MessageProducer;
import javax.jms.Queue;
import javax.jms.Session;
import javax.jms.TextMessage;
import org.apache.activemq.ActiveMQConnectionFactory;
public class HelloProducer {
public static void main(String[] args) throws JMSException {
// 1. 创建ConnectionFactory
ConnectionFactory factory = new ActiveMQConnectionFactory(//
null, null, "tcp://localhost:61616");
// 2. 创建Connection, 并且调用start()方法
Connection connection = factory.createConnection();
connection.start();
// 3. 创建Session
// 参数一:是否开启事务
// 参数二:客户端签收消息的方式
Session session = connection.createSession(false, Session.AUTO_ACKNOWLEDGE);
// 4. 创建Destination, 如果不创建的队列不存在,那么自动创建
Queue helloQueue = session.createQueue("hello");
// 5. 创建生产者
MessageProducer producer = session.createProducer(helloQueue);
// 6. 创建消息
for (int i = 0; i < 10; i++) {
TextMessage msg = session.createTextMessage("这是第" + i + "条消息");
// 7. 发送消息
producer.send(msg);
}
producer.close();
session.close();
connection.close();
System.out.println("发送结束");
}
}
- 创建消费者
package com.yanyuyu.queue;
import org.apache.activemq.ActiveMQConnectionFactory;
import javax.jms.*;
public class HelloConsumer {
public static void main(String[] args) throws JMSException {
// 1.创建ConnectionFactory
ConnectionFactory factory = new ActiveMQConnectionFactory(null,null,"tcp://localhost:61616");
// 2.创建Connection
Connection connection = factory.createConnection();
connection.start();
// 3.创建session
Session session = connection.createSession(false,Session.AUTO_ACKNOWLEDGE);
// 4.创建队列
Queue hello = session.createQueue("hello");
// 5.创建消费者
MessageConsumer consumer = session.createConsumer(hello);
// 6.消费消息
Message message = consumer.receive();
if(message instanceof TextMessage){
TextMessage msg = (TextMessage) message;
System.out.println(msg.getText());
}
// 7.关闭连接
consumer.close();
session.close();
connection.close();
}
}
4.2.2 Topic [发布/订阅]
发布此类消息的时候必须要有一个消费者在线,否则是网页端是显示不了订阅消息的
- 创建消息生产者
package com.yanyuyu.topic;
import org.apache.activemq.ActiveMQConnectionFactory;
import javax.jms.*;
public class TopicProducer {
public static void main(String[] args) throws JMSException {
// 1.创建ConnectionFactory
ConnectionFactory factory =
new ActiveMQConnectionFactory(null, null, "tcp://localhost:61616");
// 2.创建Connection
Connection connection = factory.createConnection();
connection.start();
// 3.创建session
Session session = connection.createSession(false,Session.AUTO_ACKNOWLEDGE);
// 4.创建队列
Topic firstTopic = session.createTopic("firstTopic");
// 5.创建消息
MapMessage mapMessage = session.createMapMessage();
mapMessage.setString("topic","firstTopic");
mapMessage.setInt("id",1);
// 6.创建生产者
MessageProducer producer = session.createProducer(firstTopic);
// 7.发送消息
producer.send(mapMessage);
// 8.关闭连接
producer.close();
session.close();
connection.close();
}
}
- 创建消息消费者
package com.yanyuyu.topic;
import org.apache.activemq.ActiveMQConnectionFactory;
import javax.jms.*;
public class TopicConsumer {
public static void main(String[] args) throws JMSException {
//1.创建ConnectionFactory
ConnectionFactory factory = new ActiveMQConnectionFactory(null, null, "tcp://localhost:61616");
//2.创建connection 并调用start方法
Connection connection = factory.createConnection();
connection.start();
//3.创建session
Session session = connection.createSession(false, Session.CLIENT_ACKNOWLEDGE);
//4.创建destination ,获得消息队列
Topic firstTopic = session.createTopic("firstTopic");
//5.创建消费者
MessageConsumer consumer = session.createConsumer(firstTopic);
//6.创建消息监听器
consumer.setMessageListener(message -> {
if(message instanceof MapMessage){
MapMessage msg = (MapMessage) message;
try {
System.out.println(msg.getInt("id") + msg.getString("topic"));
} catch (JMSException e) {
e.printStackTrace();
}
}
});
}
}
4.2.3 selector选择器
- 创建生产者
import javax.jms.Connection;
import javax.jms.ConnectionFactory;
import javax.jms.DeliveryMode;
import javax.jms.JMSException;
import javax.jms.MessageProducer;
import javax.jms.Queue;
import javax.jms.Session;
import javax.jms.TextMessage;
import org.apache.activemq.ActiveMQConnectionFactory;
public class SelectorProducer {
public static void main(String[] args) throws JMSException {
ConnectionFactory factory = new ActiveMQConnectionFactory(//
null, null, "tcp://localhost:61616");
Connection conn = factory.createConnection();
conn.start();
// 修改客户端消息签收方式为手动签收
Session session = conn.createSession(false, Session.CLIENT_ACKNOWLEDGE);
Queue queue = session.createQueue("selector");
MessageProducer producer = session.createProducer(queue);
// 设置消息持久化
producer.setDeliveryMode(DeliveryMode.PERSISTENT);
// 创建两条消息
TextMessage msg = session.createTextMessage();
msg.setText("趵突泉北路6号");
// 设置选择器
msg.setIntProperty("age", 11);
msg.setStringProperty("name", "etoak");
TextMessage msg2 = session.createTextMessage();
msg2.setText("山大路数码港大厦");
// 设置选择器
msg2.setIntProperty("age", 2);
msg2.setStringProperty("name", "etoak");
producer.send(msg);
producer.send(msg2);
producer.close();
session.close();
conn.close();
System.out.println("发送完成");
}
}
- 创建消费者
import java.util.concurrent.TimeUnit;
import javax.jms.Connection;
import javax.jms.ConnectionFactory;
import javax.jms.JMSException;
import javax.jms.Message;
import javax.jms.MessageConsumer;
import javax.jms.MessageListener;
import javax.jms.Queue;
import javax.jms.Session;
import javax.jms.TextMessage;
import org.apache.activemq.ActiveMQConnectionFactory;
public class SelectorConsumer {
public static void main(String[] args) throws JMSException {
// 1. 创建ConnectionFactory
ConnectionFactory factory = new ActiveMQConnectionFactory(//
null, null, "tcp://localhost:61616");
// 2. 创建Connection, 并且调用start()方法
Connection connection = factory.createConnection();
connection.start();
// 3. 创建Session
// 参数一:是否开启事务
// 参数二:客户端签收消息的方式
Session session = connection.createSession(false, //
Session.CLIENT_ACKNOWLEDGE);
// 4. 创建Destination, 是为了获取队列上的消息
Queue helloQueue = session.createQueue("selector");
// 5. 创建消费者
MessageConsumer consumer = session.createConsumer(helloQueue, //
"age = 11 and name = 'etoak' ");
consumer.setMessageListener(message -> {
if (message instanceof TextMessage) {
TextMessage text = (TextMessage) message;
try {
System.out.println(text.getText().toString());
// 手动签收消息,通知队列删除消息
text.acknowledge();
} catch (JMSException e) {
e.printStackTrace();
}
}
});
}
}
4.3 activemq 整合 springmvc
这个工程全部采用配置文件的方式创建,容器没有相关的xml 文件
4.3.1 配置容器
- 创建容器代替web.xml
import org.springframework.web.servlet.support.AbstractAnnotationConfigDispatcherServletInitializer;
import com.etoak.config.AppConfig;
import com.etoak.config.RootConfig;
public class Initializer extends AbstractAnnotationConfigDispatcherServletInitializer{
//spring 容器
@Override
protected Class<?>[] getRootConfigClasses() {
// TODO Auto-generated method stub
return new Class[] {RootConfig.class};
}
/**
* spring mvc 容器
*/
@Override
protected Class<?>[] getServletConfigClasses() {
// TODO Auto-generated method stub
return new Class[] {AppConfig.class};
}
@Override
protected String[] getServletMappings() {
// TODO Auto-generated method stub
return new String[] {"/"};
}
}
- 根据上边填写的内容创建spring容器 RootConfig.java
import java.io.IOException;
import javax.sql.DataSource;
import org.mybatis.spring.SqlSessionFactoryBean;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.FilterType;
import org.springframework.context.annotation.Import;
import org.springframework.context.annotation.PropertySource;
import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
import org.springframework.jdbc.datasource.DataSourceTransactionManager;
import org.springframework.stereotype.Controller;
import org.springframework.stereotype.Repository;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.EnableTransactionManagement;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.servlet.config.annotation.EnableWebMvc;
import com.alibaba.druid.pool.DruidDataSource;
//添加Configuration
@Configuration
@ComponentScan(basePackages = { "com.etoak" }, //
excludeFilters = { @ComponentScan.Filter(type = FilterType.ANNOTATION, //
classes = { Controller.class, RestController.class, //
ControllerAdvice.class, EnableWebMvc.class }) }, //
includeFilters = { @ComponentScan.Filter(type = FilterType.ANNOTATION, //
classes = { Service.class, Repository.class }) })
@PropertySource(value="classpath:jdbc.properties")
@MapperScan(basePackages = {"com.etoak.mapper"}) //<MapperScannerConfigu..>
@EnableTransactionManagement //相当于 <tx:annotation-driven>
@Import(ActiveMQConfig.class)
public class RootConfig {
@Value("${m.driver}")
private String driver;
@Value("${m.url}")
private String url;
@Value("${m.username}")
private String username;
@Value("${m.pwd}")
private String password;
@Bean
public DataSource dataSource() {
DruidDataSource dataSource = new DruidDataSource();
dataSource.setDriverClassName(this.driver);
dataSource.setUrl(this.url);
dataSource.setUsername(this.username);
dataSource.setPassword(this.password);
return dataSource;
}
@Bean
public SqlSessionFactoryBean sessionFactoryBean() {
SqlSessionFactoryBean factory = new SqlSessionFactoryBean();
factory.setDataSource(this.dataSource());
factory.setTypeAliasesPackage("com.etoak");
PathMatchingResourcePatternResolver resovler = new PathMatchingResourcePatternResolver();
try {
factory.setMapperLocations(resovler.getResources("classpath:/mappers/*.xml"));
}catch(IOException e) {
e.printStackTrace();
}
return factory;
}
@Bean
public DataSourceTransactionManager tx() {
return new DataSourceTransactionManager(this.dataSource());
}
}
- 配置activeMQ
package com.etoak.config;
import javax.jms.ConnectionFactory;
import javax.jms.DeliveryMode;
import javax.jms.Session;
import org.apache.activemq.ActiveMQConnectionFactory;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.jms.connection.CachingConnectionFactory;
import org.springframework.jms.core.JmsTemplate;
/**
* spring 整合 ActiveMQ
*/
@Configuration
public class ActiveMQConfig {
@Bean
public ActiveMQConnectionFactory mqFactory() {
ActiveMQConnectionFactory factory = new//
ActiveMQConnectionFactory(null, null, "tcp://localhost:61616");
// 是否允许异步发送
factory.setUseAsyncSend(true);
return factory;
}
@Bean
public ConnectionFactory connectionFactory() {
CachingConnectionFactory factory = new CachingConnectionFactory();
factory.setTargetConnectionFactory(this.mqFactory());
// 缓存session
factory.setSessionCacheSize(20);
return factory;
}
@Bean
public JmsTemplate jmsTemplate() {
JmsTemplate jmsTemplate = new JmsTemplate();
jmsTemplate.setConnectionFactory(this.connectionFactory());
// QOS values (持久化, 优先级, 消息生存时间)
jmsTemplate.setExplicitQosEnabled(true);
// 设置持久化
jmsTemplate.setDeliveryMode(DeliveryMode.PERSISTENT);
// 设置客户端签收消息模式
jmsTemplate.setSessionAcknowledgeMode(Session.CLIENT_ACKNOWLEDGE);
return jmsTemplate;
}
}
4.4 将activemq消息持久化到数据库
X.错误集锦
- java.lang.AbstractMethodError: org.apache.activemq.ActiveMQConnection.createSession()Ljavax/jms/Session;
引发原因:创建session的时候没有填写相应的参数
解决办法:创建session的时候添加相关参数
Session session = connection.createSession(false,Session.AUTO_ACKNOWLEDGE);