-
消息队列(Message Queue)
- 消息队列是消息中间件的一种实现方式
- 常用的消息队列: RabbitMQ、RocketMQ、ActiveMQ、Kafka、ZeroMQ、MetaMQ 等,redis、mysql...也可实现
-
什么是消息中间件
- 面向消息的中间件(MessageOrlented MiddlewareMOM)
- 是将 信息与信息之间的联系践行一种存储或者管理的技术,这就是中间件技术
- A发送者将消息发送给消息服务器,消息服务器将其存放在若千队列中,在合适
的时候再将消息转发给B接收者 - MQ是异步处理的,当生产者向消费者发送大量消息,都会缓存在队列中,排队消费,并且消息队列是可以持久化的。所以由于消息是可缓存的,消息中间件也是解决高并发的一种手段
- 这种模式下,他们不需要同步执行,发送和接收都是异步通讯的,不许等待。
-
消息中间件产生的背景
- 两个服务(A、B)之间进行通讯时,当A在向B发送数据时,必须等B服务完成处理返回结果时,A才能继续执行,这是基于请求和响应的同步过程
- 若此时服务崩溃、网络问题导致请求不可达了,此时客户就会受到影响
- 这就是同步产生的问题,那就是2个服务的进程都必须是正常运行的,否则可能就会出现上述问题。
1、问: 针对同步接口中,接口的重复提交问题该怎么解决?
答:使用token
2、问:同步过程中,由于外因(客户端网络慢)、外因(服务器压力大响应慢),假设A中的数据需要同步到B,但迟迟未能同步,就产生了数据的幂等性问题。这该怎么解决?
答:A调用B,如果B没有及时响应。A项目默认有3次重试补偿机制,将该信息存放在日志表(补偿表)中,使用定时任务每天晚上进行健康检查数据,再自己手动补偿,但这种补偿不是实时的。 - 消息中间件较好的解决上述问题。
-
JMS介绍
- 什么是jms:
JMS是java的消息服务,JMS的客户端之间可以通过JMS服务进行异步的消息传输 - 什么是消息模型
a、点对点消息模型
生产者发送一条消息到queue,只有一个消费者能收到。
b、发布订阅消息模型
发布者发送到topic的消息,只有订阅了topic的订一个可用的消费者,一个queue可以有很多消费者,他们之间实现了负载均衡, 所以Queue实现了一个可靠的负载均衡。 topic实现了发布和订阅,当你发布一个消息,所有订阅这个topic的服务都能得到这个消息,所以从1到N个订阅者都能得到一个消息的拷贝, 只有在消息代理收到消息时有一个有效订阅时的订阅者才能得到这个消息的拷贝。
-
应用场景
- 如下图所示: 用户注册、订单修改库存、日志存储
-
linux安装active mq
- 下载linux的activemq, 地址为: http://activemq.apache.org/components/classic/download/
- 将下载的文件上传到服务器
- 解压 tar -zxvf apache-activemq-5.11.0-bin.tar.gz
- 进入解压后的bin目录
- 启动 ./activemq start
- 查看状态 ./activemq status
- 在阿里云服务器添加安全规则 网络和安全组->安全组配置->配置规则->添加安全组->配置如下 添加8161和61616端口
- 重启访问 地址:http://47.99.241.91:8161/ 回车
- 点击页面的 Manage ActiveMQ broker
- 输入账号和密码 默认账号:admin 密码:admin
-
java点对点通讯--例子
-
创建一个maven项目
-
在pom.xml中引入其依赖
<dependencies> <!-- apache.activemq依赖--> <dependency> <groupId>org.apache.activemq</groupId> <artifactId>activemq-core</artifactId> <version>5.7.0</version> </dependency> </dependencies>
-
创建生产者Producter.java
import javax.jms.Connection; import javax.jms.ConnectionFactory; import javax.jms.MessageProducer; import javax.jms.Queue; import javax.jms.Session; import javax.jms.TextMessage; import org.apache.activemq.ActiveMQConnection; import org.apache.activemq.ActiveMQConnectionFactory; /** * 点对点通讯-生产者 * @author 123456 * */ public class Producter { public static final String QUEUE_URL = "tcp://47.99.241.91:61616"; // url public static final String QUEUE_NAME = "myqueue"; // 名称 public static void main(String[] args) throws Exception { createQueue(); } public static void createQueue() throws Exception { // 使用默认的账户和密码, 获取ActiveMQ的会话工厂 ConnectionFactory connectionFactory = new ActiveMQConnectionFactory(ActiveMQConnection.DEFAULT_USER, ActiveMQConnection.DEFAULT_PASSWORD, QUEUE_URL); // 创建连接 Connection connection = connectionFactory.createConnection(); // 启动连接 connection.start(); // 创建Session 是否开启事务->false:自动提交 true:手动commit提交到mq, 签收方式 Session session = connection.createSession(false, Session.AUTO_ACKNOWLEDGE); Queue queue = session.createQueue(QUEUE_NAME); // 消息生产者 MessageProducer producer = session.createProducer(queue); // 创建、发送消息 for (int i = 0; i < 5; i++) { TextMessage textMessage = session.createTextMessage("hello active mq"+i); producer.send(textMessage); System.out.println("生产者发送消息:---"+textMessage.getText()); //session.commit(); // connection.createSession(true, Session.AUTO_ACKNOWLEDGE);为true手动commit } // 关闭连接 session.close(); connection.close(); } }
-
创建消费者Receiver.java
package com.spdb.receiver; import javax.jms.Connection; import javax.jms.ConnectionFactory; import javax.jms.MessageConsumer; import javax.jms.Queue; import javax.jms.Session; import javax.jms.TextMessage; import org.apache.activemq.ActiveMQConnection; import org.apache.activemq.ActiveMQConnectionFactory; /** * * @author 123456 * */ public class Receiver { public static final String QUEUE_URL = "tcp://47.99.241.91:61616"; // url public static final String QUEUE_NAME = "myqueue"; // 名称 public static void main(String[] args) throws Exception { createQueue(); } public static void createQueue() throws Exception { // 使用默认的账户和密码, 获取ActiveMQ的会话工厂 ConnectionFactory connectionFactory = new ActiveMQConnectionFactory(ActiveMQConnection.DEFAULT_USER, ActiveMQConnection.DEFAULT_PASSWORD, QUEUE_URL); // 创建连接 Connection connection = connectionFactory.createConnection(); // 启动连接 connection.start(); // 创建Session 是否开启事务->false:自动提交 true:手动commit提交到mq, 签收方式 Session session = connection.createSession(true, Session.AUTO_ACKNOWLEDGE); Queue queue = session.createQueue(QUEUE_NAME); // 消息消费者 MessageConsumer consumer = session.createConsumer(queue); // 等待消息 while(true){ TextMessage textMessage = (TextMessage)consumer.receive(); if (textMessage != null){ System.out.println("消费者获取消息:---"+textMessage.getText()); }else{ break; } //session.commit(); // connection.createSession(true, Session.AUTO_ACKNOWLEDGE);为true手动commit } // 关闭连接 session.close(); connection.close(); } }
-
启动activemq-->admin/admin密码-->queues(上面的信息说明可以百度)注意查看记录
-
启动Producter.java查看activemq记录
-
启动Receiver.java查看activemq记录
-
Session session = connection.createSession(param1, param2);方法讲解:
a、param1 是设置事务, 取值有 :
true 自动提交事务 此时param2的值忽略,会被jms服务器设置为SESSION_TRANSACTED
false 手动提交事务 此时param2的值可为下面3个:自动签收、手动签收、不是必须签收其中一个。
b、param2 是设置, 取值有:
Session.AUTO_ACKNOWLEDGE // 自动签收,发送后就自动默认签收, 此模式不安全
Session.CLIENT_ACKNOWLEDGE // 手动签收。客户端收到消息后,调用textMessage.acknowledge();方法签收
Session.DUPS_OK_ACKNOWLEDGE // 不是必须签收。允许重复确认,消息可能会重复发送。此模式消息会重复
Session.SESSION_TRANSACTED
-
JMS消息可靠机制
- 使用手动签收Session.CLIENT_ACKNOWLEDGE 模式
- 使用active MQ的message ID来判断
- 使用消息的推送记录来判断
-
java发布订阅-例子
- 创建maven项目
- 在pom.xml中引入依赖
<dependencies> <!-- apache.activemq依赖--> <dependency> <groupId>org.apache.activemq</groupId> <artifactId>activemq-core</artifactId> <version>5.7.0</version> </dependency> </dependencies>
-
创建TopProducter.java
import javax.jms.Connection; import javax.jms.ConnectionFactory; import javax.jms.MessageProducer; import javax.jms.Session; import javax.jms.TextMessage; import javax.jms.Topic; import org.apache.activemq.ActiveMQConnection; import org.apache.activemq.ActiveMQConnectionFactory; /** * 发布订阅-生产者 * @author 123456 * */ public class TopProducter { public static final String QUEUE_URL = "tcp://47.99.241.91:61616"; // url public static final String QUEUE_NAME = "mytop"; // 名称 public static void main(String[] args) throws Exception { createQueue(); } public static void createQueue() throws Exception { // 使用默认的账户和密码, 获取ActiveMQ的会话工厂 ConnectionFactory connectionFactory = new ActiveMQConnectionFactory(ActiveMQConnection.DEFAULT_USER, ActiveMQConnection.DEFAULT_PASSWORD, QUEUE_URL); // 创建连接 Connection connection = connectionFactory.createConnection(); // 启动连接 connection.start(); // 创建Session 是否开启事务->false:自动提交 true:手动commit提交到mq, 签收方式 Session session = connection.createSession(false, Session.AUTO_ACKNOWLEDGE); Topic topic = session.createTopic(QUEUE_NAME); // 消息生产者 MessageProducer producer = session.createProducer(topic); // 创建、发送消息 for (int i = 0; i < 5; i++) { TextMessage textMessage = session.createTextMessage("hello active mq"+i); producer.send(textMessage); System.out.println("生产者发送消息:---"+textMessage.getText()); //session.commit(); // connection.createSession(true, Session.AUTO_ACKNOWLEDGE);为true手动commit } // 关闭连接 session.close(); connection.close(); } }
-
创建TopReceiver.java
import javax.jms.Connection; import javax.jms.ConnectionFactory; import javax.jms.MessageConsumer; import javax.jms.Session; import javax.jms.TextMessage; import javax.jms.Topic; import org.apache.activemq.ActiveMQConnection; import org.apache.activemq.ActiveMQConnectionFactory; /** * 发布订阅-消费者 * @author 123456 * */ public class TopReceiver { public static final String QUEUE_URL = "tcp://47.99.241.91:61616"; // url public static final String QUEUE_NAME = "mytop"; // 名称 public static void main(String[] args) throws Exception { createQueue(); } public static void createQueue() throws Exception { // 使用默认的账户和密码, 获取ActiveMQ的会话工厂 ConnectionFactory connectionFactory = new ActiveMQConnectionFactory(ActiveMQConnection.DEFAULT_USER, ActiveMQConnection.DEFAULT_PASSWORD, QUEUE_URL); // 创建连接 Connection connection = connectionFactory.createConnection(); // 启动连接 connection.start(); // 创建Session 是否开启事务->false:自动提交 true:手动commit提交到mq, 签收方式 Session session = connection.createSession(true, Session.AUTO_ACKNOWLEDGE); Topic topic = session.createTopic(QUEUE_NAME); // 消息消费者 MessageConsumer consumer = session.createConsumer(topic); // 等待消息 while(true){ TextMessage textMessage = (TextMessage)consumer.receive(); if (textMessage != null){ System.out.println("消费者获取消息:---"+textMessage.getText()); }else{ break; } //session.commit(); // connection.createSession(true, Session.AUTO_ACKNOWLEDGE);为true手动commit } // 关闭连接 session.close(); connection.close(); } }
-
启动登录active mq
-
启动TopProducter.java、TopReceiver.java
-
登录mq查看记录
-
springboot整合active MQ
- 使用场景
现有2个不同系统,需要将他们的数据进行同步
- 生产者
- 创建一个生产者springboot项目
- 在pom.xml中引入依赖
<?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <groupId>com.spdb</groupId> <artifactId>activemq1</artifactId> <version>0.0.1-SNAPSHOT</version> <name>activemq1</name> <description>Demo project for Spring Boot</description> <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>2.1.4.RELEASE</version> <relativePath/> <!-- lookup parent from repository --> </parent> <properties> <java.version>1.8</java.version> </properties> <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-activemq</artifactId> </dependency> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> </dependency> <!-- 配置alibaba JSON依赖 --> <dependency> <groupId>com.alibaba</groupId> <artifactId>fastjson</artifactId> <version>1.2.46</version> </dependency> </dependencies> <build> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> </plugin> </plugins> </build> </project>
- 在resources目录的application.properties中配置信息
spring.activemq.broker-url=tcp://47.99.241.91:61616 spring.activemq.password=admin spring.activemq.user=admin queueName=spdb
- 创建一个实体类User.java
import lombok.Data; /** * Create by wangxb * 2019-05-06 22:19 */ @Data public class User { private Long id; private String name; private Integer age; public User(Long id, String name, Integer age) { this.id = id; this.name = name; this.age = age; } }
- 创建QueueConfig.java
import org.apache.activemq.command.ActiveMQQueue; import org.springframework.beans.factory.annotation.Value; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import javax.jms.Queue; /** * Create by wangxb * 2019-05-06 22:29 */ @Configuration public class QueueConfig { @Value("${queueName}") // 读取application.properties配置文件 private String queueName; @Bean public Queue createQueue(){ System.out.println("-----------------创建ActiveMQQueue---------------------"); return new ActiveMQQueue(queueName); } }
- 创建Producer.java
import com.alibaba.fastjson.JSONObject; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.jms.core.JmsMessagingTemplate; import org.springframework.scheduling.annotation.EnableScheduling; import org.springframework.scheduling.annotation.Scheduled; import org.springframework.stereotype.Component; import javax.jms.Queue; /** * Create by wangxb * 2019-05-06 22:33 */ @Component @EnableScheduling // 定时任务 public class Producer { @Autowired private JmsMessagingTemplate jmsMessagingTemplate; @Autowired private Queue queue; private Integer age = 18; @Scheduled(fixedDelay = 5000) // 每个5秒跑一次该方法 public void send(){ age++; User user = new User(System.currentTimeMillis(), "admin"+age, age); String message = JSONObject.toJSONString(user); jmsMessagingTemplate.convertAndSend(queue, message); System.out.println("生产者发送消息: "+message); } }
- 在启动类中启动
import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; @SpringBootApplication public class Activemq1Application { public static void main(String[] args) { SpringApplication.run(Activemq1Application.class, args); } }
- 启动
- 登录activemq查看记录
- 消费者
- 在创建一个sprinboot项目
- 在pom.xml中引入依赖
<?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <groupId>com.spdb</groupId> <artifactId>activemq1</artifactId> <version>0.0.1-SNAPSHOT</version> <name>activemq1</name> <description>Demo project for Spring Boot</description> <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>2.1.4.RELEASE</version> <relativePath/> <!-- lookup parent from repository --> </parent> <properties> <java.version>1.8</java.version> </properties> <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-activemq</artifactId> </dependency> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> </dependency> <!-- 配置alibaba JSON依赖 --> <dependency> <groupId>com.alibaba</groupId> <artifactId>fastjson</artifactId> <version>1.2.46</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-jms</artifactId> <version>5.1.6.RELEASE</version> <scope>compile</scope> </dependency> </dependencies> <build> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> </plugin> </plugins> </build> </project>
- 在resources目录的application.properties
spring.activemq.broker-url=tcp://47.99.241.91:61616 spring.activemq.password=admin spring.activemq.user=admin queueName=spdb server.port=8181
- 创建User.java实体类,属性和上面一样
- 创建消费者Consumer.java
import org.springframework.jms.annotation.JmsListener; import org.springframework.stereotype.Component; /** * Create by wangxb * 2019-05-07 6:54 */ @Component public class Consumer { @JmsListener(destination = "${queueName}") // 使用jms注解监听 public void receive(String msg) { System.out.println("监听器收到msg:" + msg); } }
- 创建springboot启动类
import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; @SpringBootApplication public class Activemq2Application { public static void main(String[] args) { SpringApplication.run(Activemq2Application.class, args); } }
- 启动
- 启动mq,查看记录