MQ 是什么,MQ 有哪些模式,MQ是如何和spring 整合的?
提示:以下是本篇文章正文内容,下面案例可供参考
MQ 全称 Message Queue(消息队列),是在消息的传输过程中保存消息的容器。多用于分布式系统之间进行通信。
MQ 相当于一个中介,生成方通过MQ 与 消费方进行交互,它将应用程序进行解耦合
系统的耦合性越高,容错性就越低,可维护性就越差
使用MQ使得应用间解耦,提升容错性和可维护性
将不需要同步处理的并且耗时长的操作由消息队列通知消息接收方进行异步处理。提高了应用程序的响应时间
如订单系统,在下单的时候就会往数据库写数据。但是数据库只能支撑每秒1000左右的并发写入,并发量再高就容易宕机。低峰期的时候并发也就100多个,但是在高峰期时候,并发量会突然激增到5000以上,这个时候数据库肯定卡死了
1. 系统的可用性降低
系统引入的外部依赖越多,系统的稳定性就越差。一旦MQ 宕机就会对系统的业务造成影响。如何保证MQ 的高可用?
2. 系统复杂度提高
QM 的加入增加了系统的复杂度,以前系统间是同步的远程调用,现在是MQ 进行异步调用,如何保证消息没有被重复消费?怎么处理消息丢失情况?怎么保证消息传递的顺序性?
3. 一致性问题
A 系统处理完任务,通过MQ 给B、C、D 三个系统发消息,如果B系统、C系统处理成功,D系统处理失败。如何保证消息处理的一致性?
目前市场有很多的MQ 产品,例如 RabbitMQ 、RocketMQ、ActiveMQ、kafka、ZeroMQ、MetaMQ 等,也有直接用Redis 充当消息队列的案例,而这些消息队列产品各有侧重,在实际选型时,需要结合自身需求以及MQ产品特征,综合考虑
AMQP:
AMQP,即 Advanced Message Queuing Protocol(高级消息队列协议),是一个网络协议,是应用层协议的一个开放标准,为面向消息的中间件设计。基于此协议的客户端与消息中间件可传递消息,遵循此协议,不收客户端和中间件产品和开发语言限制。2006年,AMQP 规范发布。类比HTTP。
JMS:
JMS 即 Java 消息服务(JavaMessage Service)应用程序接口,是一个 Java 平台中关于面向消息中间件的APIJMS 是 JavaEE 规范中的一种,类比JDBC很多消息中间件都实现了JMS规范,例如:ActiveMQ。RabbitMQ 官方没有提供 JMS 的实现包,但是开源社区有
AMQP 与 JMS 区别:
JMS是定义了统一的接口,来对消息操作进行统一;AMQP是通过规定协议来统一数据交互的格式
JMS限定了必须使用Java语言;AMQP只是协议,不规定实现方式,因此是跨语言的。
JMS规定了两种消息模式;而AMQP的消息模式更加丰富
RabbitMQ官方地址:http://www.rabbitmq.com/
2007年,Rabbit 技术公司基于 AMQP 标准开发的 RabbitMQ 1.0 发布。RabbitMQ 采用 Erlang 语言开发。Erlang 语言专门为开发高并发和分布式系统的一种语言,在电信领域使用广泛。
RabbitMQ 基础架构如下图:
RabbitMQ 中的相关概念:
RabbitMQ 提供了6 种模式:简单模式、work 模式,Publish/ Subscrib 发布与订阅模式,Routing 路由模式,Topics 主题模式,RPC 远程调用模式。
官网对应模式介绍:https://www.rabbitmq.com/getstarted.html
这里使用docker 安装:
docker search rabbitmq:management
# management 不可省略(否则打不开 管理端)
docker pull rabbitmq:management
docker run -id --name=rabbitmq
-p 5671:5671 \
-p 5672:5672 \
-p 15672:15672 \
rabbitmq:management
创建成功! 打开浏览器输入 http://ip:15672 即可访问 ,默认用户名:guest,密码:guest
一个生产者,一个消费者,不需要设置交换机(使用默认的交换机)
// 将获取连接的代码封装
package com.geng.utils;
import com.rabbitmq.client.Connection;
import com.rabbitmq.client.ConnectionFactory;
import java.io.IOException;
import java.util.concurrent.TimeoutException;
public class ConnectionUtils {
public static Connection getConnection() throws IOException, TimeoutException {
//1. 创建连接工厂
ConnectionFactory connectionFactory = new ConnectionFactory();
//1.1设置主机地址:默认为localhost
connectionFactory.setHost("192.168.43.6");
//1.2 设置连接端口,默认为 5672
connectionFactory.setPort(5672);
//1.3 设置虚拟主机名称,默认为 /
connectionFactory.setVirtualHost("/gjt");
//1.4 设置连接用户名,默认为 guest
connectionFactory.setUsername("guest");
//1.5 设置连接密码,默认为 guest
connectionFactory.setPassword("guest");
//2. 创建连接
return connectionFactory.newConnection();
}
}
package com.geng.rabbitmq.simple;
import com.geng.utils.ConnectionUtils;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
import com.rabbitmq.client.ConnectionFactory;
import java.io.IOException;
import java.util.concurrent.TimeoutException;
//生产者
public class Produce {
public static String QUEUE_KEY = "simple-queue" ;
public static void main(String[] args) throws IOException, TimeoutException {
Connection connection = ConnectionUtils.getConnection();
//3. 创建频道
Channel channel = connection.createChannel();
//4. 声明队列
/**
* 参数1: 队列名称
* 参数2:是否定义持久化队列
* 参数3:是否独占本次连接
* 参数4:是否在不使用的时候自动删除队列
* 其他参数
*/
channel.queueDeclare(QUEUE_KEY,true,false,false,null);
//5. 要发送的消息
/**
* 参数1:交换机的名称,如果没有则用默认的 default exchange
* 参数2:路由key ,简单模式可以传递队列名称
* 参数3:消息其他属性
* 参数4:消息内容
*/
String message = "good night,耿俊廷";
/**
*
*/
channel.basicPublish("", QUEUE_KEY,null,message.getBytes());
//6. 关闭资源
channel.close();
connection.close();
}
}
package com.geng.rabbitmq.simple;
import com.geng.utils.ConnectionUtils;
import com.rabbitmq.client.*;
import java.io.IOException;
import java.util.concurrent.TimeoutException;
public class ConsumerTest {
public static void main(String[] args) throws IOException, TimeoutException {
//2. 获得连接
Connection connection = ConnectionUtils.getConnection();
//3. 创建通道
Channel channel = connection.createChannel();
//4. 声明队列
channel.queueDeclare(Produce.QUEUE_KEY,true,false,false,null);
//5.消费消息
DefaultConsumer consumer = new DefaultConsumer(channel){
@Override
public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
System.out.println("路由key为:" + envelope.getRoutingKey()); //交换机
System.out.println("交换机为:" + envelope.getExchange());
//消息id
System.out.println("消息id为:" + envelope.getDeliveryTag());
//收到的消息
System.out.println("接收到的消息为:" + new String(body, "utf-8"));
}
};
channel.basicConsume(Produce.QUEUE_KEY,true,consumer);
}
}
一个生产者,多个消费者(竞争关系),不需要设置交换机(使用默认交换机)
package com.geng.rabbitmq.workQueue;
import com.geng.utils.ConnectionUtils;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
import com.rabbitmq.client.ConnectionFactory;
import java.io.IOException;
import java.util.concurrent.TimeoutException;
public class Produce {
public static String QUEUE_KEY = "simple-queue" ;
public static void main(String[] args) throws IOException, TimeoutException {
//2. 获得连接
Connection connection = ConnectionUtils.getConnection();
//3. 创建频道
Channel channel = connection.createChannel();
//4. 声明队列
/**
* 参数1: 队列名称
* 参数2:是否定义持久化队列
* 参数3:是否独占本次连接
* 参数4:是否在不使用的时候自动删除队列
* 其他参数
*/
channel.queueDeclare(QUEUE_KEY,true,false,false,null);
//5. 要发送的消息
/**
* 参数1:交换机的名称,如果没有则用默认的 default exchange
* 参数2:路由key ,简单模式可以传递队列名称
* 参数3:消息其他属性
* 参数4:消息内容
*/
String message = "good night,耿俊廷";
/**
*
*/
for(int i = 0; i < 10; i++){
channel.basicPublish("", QUEUE_KEY,null,(message+i).getBytes());
}
//6. 关闭资源
channel.close();
connection.close();
}
}
package com.geng.rabbitmq.workQueue;
import com.geng.utils.ConnectionUtils;
import com.rabbitmq.client.*;
import java.io.IOException;
import java.util.concurrent.TimeoutException;
public class ConsumerTest {
public static void main(String[] args) throws IOException, TimeoutException {
//或得连接
Connection connection = ConnectionUtils.getConnection();
//3. 创建通道
Channel channel = connection.createChannel();
//4. 声明队列
channel.queueDeclare(Produce.QUEUE_KEY,true,false,false,null);
//5.消费消息
DefaultConsumer consumer = new DefaultConsumer(channel){
@Override
public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
System.out.println("路由key为:" + envelope.getRoutingKey()); //交换机
System.out.println("交换机为:" + envelope.getExchange());
//消息id
System.out.println("消息id为:" + envelope.getDeliveryTag());
//收到的消息
System.out.println("接收到的消息为:" + new String(body, "utf-8"));
}
};
channel.basicConsume(Produce.QUEUE_KEY,true,consumer);
}
}
package com.geng.rabbitmq.workQueue;
import com.geng.utils.ConnectionUtils;
import com.rabbitmq.client.*;
import java.io.IOException;
import java.util.concurrent.TimeoutException;
public class ConsumerTest2 {
public static void main(String[] args) throws IOException, TimeoutException {
//或得连接
Connection connection = ConnectionUtils.getConnection();
//3. 创建通道
Channel channel = connection.createChannel();
//4. 声明队列
channel.queueDeclare(Produce.QUEUE_KEY,true,false,false,null);
//5.消费消息
DefaultConsumer consumer = new DefaultConsumer(channel){
@Override
public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
System.out.println("路由key为:" + envelope.getRoutingKey()); //交换机
System.out.println("交换机为:" + envelope.getExchange());
//消息id
System.out.println("消息id为:" + envelope.getDeliveryTag());
//收到的消息
System.out.println("接收到的消息为:" + new String(body, "utf-8"));
}
};
channel.basicConsume(Produce.QUEUE_KEY,true,consumer);
}
}
需要设置类型为fanout 的交换机,并且交换机和队列进行绑定,当消息发送到交换机后,交换机会将消息发送到绑定的队列
package com.geng.rabbitmq.ps;
import com.geng.utils.ConnectionUtils;
import com.rabbitmq.client.BuiltinExchangeType;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
import java.io.IOException;
import java.util.concurrent.TimeoutException;
public class Produce {
public static String EXCHANGE = "ps-exchange";
public static String QUEUE_KEY1 = "ps-queue1";
public static String QUEUE_KEY2 = "ps-queue2" ;
public static void main(String[] args) throws IOException, TimeoutException {
//2. 获得连接
Connection connection = ConnectionUtils.getConnection();
//3. 创建频道
Channel channel = connection.createChannel();
//4. 声明队列 和 交换机 exchange
/**
* 参数1: 队列名称
* 参数2:是否定义持久化队列
* 参数3:是否独占本次连接
* 参数4:是否在不使用的时候自动删除队列
* 其他参数
*/
channel.queueDeclare(QUEUE_KEY1,true,false,false,null);
channel.queueDeclare(QUEUE_KEY2,true,false,false,null);
channel.exchangeDeclare(EXCHANGE, BuiltinExchangeType.FANOUT);
//队列绑定交换机
channel.queueBind(QUEUE_KEY1, EXCHANGE, "");
channel.queueBind(QUEUE_KEY2, EXCHANGE, "");
//5. 要发送的消息
/**
* 参数1:交换机的名称,如果没有则用默认的 default exchange
* 参数2:路由key ,简单模式可以传递队列名称
* 参数3:消息其他属性
* 参数4:消息内容
*/
String message = "good night,耿俊廷";
/**
*
*/
for(int i = 0; i < 10; i++){
channel.basicPublish(EXCHANGE, "",null,(message+i).getBytes());
}
//6. 关闭资源
channel.close();
connection.close();
}
}
package com.geng.rabbitmq.ps;
import com.geng.utils.ConnectionUtils;
import com.rabbitmq.client.*;
import java.io.IOException;
import java.util.concurrent.TimeoutException;
public class ConsumerTest {
public static void main(String[] args) throws IOException, TimeoutException {
//或得连接
Connection connection = ConnectionUtils.getConnection();
//3. 创建通道
Channel channel = connection.createChannel();
//4. 声明队列
channel.queueDeclare(Produce.QUEUE_KEY1,true,false,false,null);
channel.queueBind(Produce.QUEUE_KEY1,Produce.EXCHANGE,"");
//5.消费消息
DefaultConsumer consumer = new DefaultConsumer(channel){
@Override
public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
System.out.println("路由key为:" + envelope.getRoutingKey()); //交换机
System.out.println("交换机为:" + envelope.getExchange());
//消息id
System.out.println("消息id为:" + envelope.getDeliveryTag());
//收到的消息
System.out.println("接收到的消息为:" + new String(body, "utf-8"));
}
};
channel.basicConsume(Produce.QUEUE_KEY1,true,consumer);
}
}
package com.geng.rabbitmq.ps;
import com.geng.utils.ConnectionUtils;
import com.rabbitmq.client.*;
import java.io.IOException;
import java.util.concurrent.TimeoutException;
public class ConsumerTest2 {
public static void main(String[] args) throws IOException, TimeoutException {
//或得连接
Connection connection = ConnectionUtils.getConnection();
//3. 创建通道
Channel channel = connection.createChannel();
//4. 声明队列
channel.queueDeclare(Produce.QUEUE_KEY2,true,false,false,null);
//队列绑定交换机
channel.queueBind(Produce.QUEUE_KEY2,Produce.EXCHANGE,"");
//5.消费消息
DefaultConsumer consumer = new DefaultConsumer(channel){
@Override
public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
System.out.println("路由key为:" + envelope.getRoutingKey()); //交换机
System.out.println("交换机为:" + envelope.getExchange());
//消息id
System.out.println("消息id为:" + envelope.getDeliveryTag());
//收到的消息
System.out.println("接收到的消息为:" + new String(body, "utf-8"));
}
};
channel.basicConsume(Produce.QUEUE_KEY2,true,consumer);
}
}
需要设置类型为 direct 的交换机,交换机和队列进行绑定,当消息到达交换机后,交换机会根据路由规则(routing key),将消息发送到绑定的对应的队列
package com.geng.rabbitmq.routing;
import com.geng.utils.ConnectionUtils;
import com.rabbitmq.client.BuiltinExchangeType;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
import java.io.IOException;
import java.util.concurrent.TimeoutException;
public class Produce {
public static String EXCHANGE_ROUTE = "route-exchange";
public static String QUEUE_INSERT = "queue-insert";
public static String QUEUE_UPDATE = "queue-update";
public static void main(String[] args) throws IOException, TimeoutException {
//获得连接
Connection connection = ConnectionUtils.getConnection();
//获得通道
Channel channel = connection.createChannel();
/**
* 声明交换机
* 参数1 :交换机名称
* 参数2 :交换机类型 fanout ,direct, topic ,headers
*/
channel.exchangeDeclare(EXCHANGE_ROUTE, BuiltinExchangeType.DIRECT);
/**
* 声明队列
* 参数1: 队列名称
* 参数2: 是否定义持久化队列
* 参数3: 是否独占本次连接
* 参数4: 是否在不使用的时候独占本次连接
* 参数5: 队列其他参数
*/
channel.queueDeclare(QUEUE_INSERT,true,false,false,null);
channel.queueDeclare(QUEUE_UPDATE,true,false,false,null);
//队列绑定交换机(exchange)
channel.queueBind(QUEUE_UPDATE,EXCHANGE_ROUTE,"update");
channel.queueBind(QUEUE_INSERT,EXCHANGE_ROUTE,"insert");
// 发送消息,插入
String message = "新增。路由模式 routing key 为 insert";
/**
* 参数1:交换机名称,如果没有指定则使用默认Default Exchage
* 参数2:路由key,简单模式可以传递队列名称
* 参数3:消息其它属性
* 参数4:消息内容
*/
channel.basicPublish(EXCHANGE_ROUTE,"insert",null,message.getBytes());
//发送消息,修改
String message2 = "修改。 路由模式 routing key 为 update";
channel.basicPublish(EXCHANGE_ROUTE,"update",null,message2.getBytes());
//关闭资源
channel.close();
connection.close();
}
}
package com.geng.rabbitmq.routing;
import com.geng.utils.ConnectionUtils;
import com.rabbitmq.client.*;
import java.io.IOException;
import java.util.concurrent.TimeoutException;
public class Consumer1 {
public static void main(String[] args) throws IOException, TimeoutException {
//获取连接
Connection connection = ConnectionUtils.getConnection();
//创建通道
Channel channel = connection.createChannel();
//声明交换机
channel.exchangeDeclare(Produce.EXCHANGE_ROUTE, BuiltinExchangeType.DIRECT);
//声明队列
channel.queueDeclare(Produce.QUEUE_INSERT,true,false,false,null);
//队列绑定交换机
channel.queueBind(Produce.QUEUE_INSERT,Produce.EXCHANGE_ROUTE,"insert");
//创建消息消费者回调对象
DefaultConsumer consumer = new DefaultConsumer(channel){
@Override
public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
// 路由key
System.out.println("路由key为:" + envelope.getRoutingKey());
//交换机
System.out.println("交换机为:" + envelope.getExchange());
//消息id
System.out.println("消息id:" + envelope.getDeliveryTag());
//消息内容
System.out.println("消费者1-接收到的消息为:" + new String(body, "utf-8"));
}
};
//监听消息
/**
* 参数1: 监听消息队列名称
* 参数2: 是否接收到消息后自动向mq 回复消息接收到了,mq 接收到回复会删除消息
*/
channel.basicConsume(Produce.QUEUE_INSERT,true,consumer);
}
}
package com.geng.rabbitmq.routing;
import com.geng.utils.ConnectionUtils;
import com.rabbitmq.client.*;
import java.io.IOException;
import java.util.concurrent.TimeoutException;
public class Consumer2 {
public static void main(String[] args) throws IOException, TimeoutException {
//获取连接
Connection connection = ConnectionUtils.getConnection();
//创建通道
Channel channel = connection.createChannel();
//声明交换机
channel.exchangeDeclare(Produce.EXCHANGE_ROUTE, BuiltinExchangeType.DIRECT);
//声明队列
channel.queueDeclare(Produce.QUEUE_UPDATE,true,false,false,null);
//队列绑定交换机
channel.queueBind(Produce.QUEUE_UPDATE,Produce.EXCHANGE_ROUTE,"update");
//创建消息消费者回调对象
DefaultConsumer consumer = new DefaultConsumer(channel){
@Override
public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
// 路由key
System.out.println("路由key为:" + envelope.getRoutingKey());
//交换机
System.out.println("交换机为:" + envelope.getExchange());
//消息id
System.out.println("消息id:" + envelope.getDeliveryTag());
//消息内容
System.out.println("消费者1-接收到的消息为:" + new String(body, "utf-8"));
}
};
//监听消息
/**
* 参数1: 交换机名称
* 参数2: 是否接收到消息后自动向mq 回复消息接收到了,mq 接收到回复会删除消息
*/
channel.basicConsume(Produce.QUEUE_UPDATE,true,consumer);
}
}
需要设置类型为topic的交换机,交换机和队列进行绑定,并且指定通配符方式的routing key,当发送消息到交换机后,交换机会根据routing key将消息发送到对应的队列