各种模式的代码: https://gitee.com/GXQ205153964/rabbitmq.git
spring整合ribbitmq代码 https://gitee.com/GXQ205153964/Spring_RabbitMQ.git
springboot整合RibbitMQ代码 https://gitee.com/GXQ205153964/springboot-rabbitmq.git
消息中间件概述
什么是消息中间件
MQ全称为Message Queue,消息队列是应用程序和应用程序之间的通信方法。
为什么使用MQ
在项目中,可将一些无需即时返回且耗时的操作提取出来,进行**异步处理**,而这种异步处理的方式大大的节省了服务器的请求响应时间,从而提高了系统的吞吐量。
基础架构
开发中消息队列通常有如下应用场景:
- 任务异步处理
将不需要同步处理的并且耗时长的操作由消息队列通知消息接收方进行异步处理。提高了应用程序的响应时间。
- 应用程序解耦合
MQ相当于一个中介,生产方通过MQ与消费方交互,它将应用程序进行解耦合。
- 削峰填谷
如订单系统,在下单的时候就会往数据库写数据。但是数据库只能支撑每秒1000左右的并发写入,并发量再高就容易宕机。低峰期的时候并发也就100多个,但是在高峰期时候,并发量会突然激增到5000以上,这个时候数据库肯定卡死了。
消息被MQ保存起来了,然后系统就可以按照自己的消费能力来消费,比如每秒1000个数据,这样慢慢写入数据库,这样就不会卡死数据库了。
但是使用了MQ之后,限制消费消息的速度为1000,但是这样一来,高峰期产生的数据势必会被积压在MQ中,高峰就被“削”掉了。但是因为消息积压,在高峰期过后的一段时间内,消费消息的速度还是会维持在1000QPS,直到消费完积压的消息,这就叫做“填谷”
AMQP 和 JMS
MQ是消息通信的模型;实现MQ的大致有两种主流方式:AMQP、JMS。
AMQP
AMQP是一种协议,更准确的说是一种binary wire-level protocol(链接协议)。这是其和JMS的本质差别,AMQP不从API层进行限定,而是直接定义网络交换的数据格式。
JMS
JMS即Java消息服务(JavaMessage Service)应用程序接口,是一个Java平台中关于面向消息中间件(MOM)的API,用于在两个应用程序之间,或分布式系统中发送消息,进行异步通信。
AMQP 与 JMS 区别
- JMS是定义了统一的接口,来对消息操作进行统一;AMQP是通过规定协议来统一数据交互的格式
- JMS限定了必须使用Java语言;AMQP只是协议,不规定实现方式,因此是跨语言的。
- JMS规定了两种消息模式;而AMQP的消息模式更加丰富
消息队列产品
市场上常见的消息队列有如下:
- - ActiveMQ:基于JMS
- - ZeroMQ:基于C语言开发
- - RabbitMQ:基于AMQP协议,erlang语言开发,稳定性好
- - RocketMQ:基于JMS,阿里巴巴产品
- - Kafka:类似MQ的产品;分布式消息系统,高吞吐量
RabbitMQ
- RabbitMQ是由erlang语言开发,基于AMQP(Advanced Message Queue 高级消息队列协议)协议实现的消息队列,它是一种应用程序之间的通信方法,消息队列在分布式系统开发中应用非常广泛。
- RabbitMQ官方地址:http://www.rabbitmq.com/
- RabbitMQ提供了6种模式:简单模式,work模式,Publish/Subscribe发布与订阅模式,Routing路由模式,Topics主题模式,RPC远程调用模式(远程调用,不太算MQ;暂不作介绍);
- 官网对应模式介绍:https://www.rabbitmq.com/getstarted.html
安装及配置RabbitMQ
参考下面文章
简单模式
需求:
使用简单模式完成消息传递
步骤:
- 创建工程(生产者消费者)
- 分别添加依赖
- 编写生产者发送消息
- 编写消费者接收消息
具体代码:
生产者和消费者依赖一样
<dependencies>
<dependency>
<groupId>com.rabbitmq</groupId>
<artifactId>amqp-client</artifactId>
<version>5.6.0</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.8.0</version>
<configuration>
<source>1.8</source>
<target>1.8</target>
</configuration>
</plugin>
</plugins>
</build>
生产者代码:
/**
* 发送消息
*
*/
public class Producer_HelloWorld {
public static void main(String[] args) throws Exception {
//创建连接工厂
ConnectionFactory factory = new ConnectionFactory();
//设置参数
factory.setHost("192.168.218.132");//默认是localhost
factory.setPort(5672);
factory.setVirtualHost("gao");//虚拟机名称 默认值 /
factory.setUsername("gao");
factory.setPassword("Aaqwe123");
//创建连接Connection
Connection connection = factory.newConnection();
//创建Channel
Channel channel = connection.createChannel();
//创建队列Queue
/**
*String queue,------------------------------队列名称
* boolean durable, -------------------------是否持久化,当mq重启之后还在
* boolean exclusive, -----------------------1.是否独占 只能有一个消费者监听这队列 2.当Connection关闭时,是否删除队列
* boolean autoDelete,-----------------------是否自动删除,当没有Consumer时,自动删除掉
* Map<String, Object> arguments-------------参数
*/
channel.queueDeclare("hello_world",true,false,false,null);
//发送消息
/**
* basicPublish(String exchange, String routingKey, boolean mandatory, BasicProperties props, byte[] body)
* exchange 交换机名称,简单模式下交换机会使用默认的
* routingKey 路由名称
* mandatory
* props 配置信息
* body 发送消息数据
*/
String body = "hello rabbitmq~~~~~";
channel.basicPublish("","hello_world",null,body.getBytes());
//释放资源
channel.close();
connection.close();
}
}
消费者代码
/**
* 接收消息
*/
public class Consumer_HelloWorld {
public static void main(String[] args) throws Exception {
//创建连接工厂
ConnectionFactory factory = new ConnectionFactory();
//设置参数
factory.setHost("192.168.218.132");//默认是localhost
factory.setPort(5672);
factory.setVirtualHost("gao");//虚拟机名称 默认值 /
factory.setUsername("gao");
factory.setPassword("Aaqwe123");
//创建连接Connection
Connection connection = factory.newConnection();
//创建Channel
Channel channel = connection.createChannel();
//创建队列Queue
/**
*String queue,------------------------------队列名称
* boolean durable, -------------------------是否持久化,当mq重启之后还在
* boolean exclusive, -----------------------1.是否独占 只能有一个消费者监听这队列 2.当Connection关闭时,是否删除队列
* boolean autoDelete,-----------------------是否自动删除,当没有Consumer时,自动删除掉
* Map<String, Object> arguments-------------参数
*/
//如果没有要给名字叫hello_world的队列,则会创建该队列,如果有则不会创建
channel.queueDeclare("hello_world",true,false,false,null);
//接收消息
/**
* 参数:
* String queue, 队列名称
* boolean autoAck, 是否自动确认
* Consumer callback 回调对象
*
*/
Consumer consumer = new DefaultConsumer(channel){
/**
* 回调方法:当收到消息后,会自动执行该方法
* consumerTag:标识
* envelope: 获取一些信息,交换机,路由key
* properties 配置信息
* body 数据
*
*/
@Override
public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
System.out.println("consumerTag:"+consumerTag);
System.out.println("Exchange:"+envelope.getExchange());
System.out.println("Routingkey:"+envelope.getRoutingKey());
System.out.println("properties:"+properties);
System.out.println("body:"+new String(body));
}
};
channel.basicConsume("hello_world",true,consumer);
//不用关闭资源,他是监听
}
}
小结:
简单模式
在上图的模型中,有以下概念:
P:生产者,发送消息的程序
C:消费者,消息的接收者,会一直监听,
Pub/Sub订阅模式
模式说明
queue:消息队列,图中红色部分,类似邮箱,可以缓存消息,生产者向其中发送消息,消费者向其中取出消息
在订阅模型中,多了一个exchange角色,而且过程略有变化:
- P:生产者,也就是要发送消息的程序,但是不再发送到队列中,而是发给X(交换机)
- C:消费者,消息的接受者,会一直等待消息到来。
- Queue:消息队列,接收消息、缓存消息。
- Exchange:交换机,图中的X。一方面,接收生产者发送的消息。另一方面,知道如何处理消息,例如递交给某个特别队列、递交给所有队列、或是将消息丢弃。到底如何操作,取决于Exchange的类型。Exchange有常见以下3种类型:
- Fanout:广播,将消息交给所有绑定到交换机的队列
- Direct:定向,把消息交给符合指定routing key 的队列
- Topic:通配符,把消息交给符合routing pattern(路由模式) 的队列
Exchange(交换机)只负责转发消息,不具备存储消息的能力,因此如果没有任何队列与Exchange绑定,或者没有符合路由规则的队列,那么消息会丢失!
代码:
生产者
public static void main(String[] args) throws Exception {
//创建连接工厂
ConnectionFactory factory = new ConnectionFactory();
//设置参数
factory.setHost("192.168.218.132");//默认是localhost
factory.setPort(5672);
factory.setVirtualHost("gao");//虚拟机名称 默认值 /
factory.setUsername("gao");
factory.setPassword("Aaqwe123");
//创建连接Connection
Connection connection = factory.newConnection();
//创建Channel
Channel channel = connection.createChannel();
String exchangeName = "test_fanout";
//创建交换机
/**
* exchangeDeclareNoWait(String exchange, BuiltinExchangeType type, boolean durable, boolean autoDelete, boolean internal, Map<String, Object> arguments)
* exchange 交换机名称
* type:交换机类型
* DIRECT("direct"), 定向
* FANOUT("fanout"), 扇形(广播)发送消息到每一个队列
* TOPIC("topic"), 通配符方式
* HEADERS("headers"); 参数匹配
* durable 是否持久化
* autoDelete 自动删除
* internal内部使用。一般false
* arguments参数
*/
channel.exchangeDeclare(exchangeName,BuiltinExchangeType.FANOUT,true,false,false,null);
//创建队列
String queue1Name = "test_fanout_queue1";
String queue2Name = "test_fanout_queue2";
channel.queueDeclare(queue1Name,true,false,false,null);
channel.queueDeclare(queue2Name,true,false,false,null);
//创建队列和交换机
/**
* queueBind(String queue, String exchange, String routingKey)
* queue 绑定当前队列名称
* exchange 交换机名称
* routingkey 路由键,绑定规则
* 如果交换机类型为fanout,routingkey设置为""(空)
*/
channel.queueBind(queue1Name,exchangeName,"");
channel.queueBind(queue2Name,exchangeName,"");
String body = "日志信息:张三调用了findAll方法,日志级别:info";
//发送消息
channel.basicPublish(exchangeName,"",null,body.getBytes());
//释放资源
channel.close();
connection.close();
}
消费者
public static void main(String[] args) throws Exception {
//创建连接工厂
ConnectionFactory factory = new ConnectionFactory();
//设置参数
factory.setHost("192.168.218.132");//默认是localhost
factory.setPort(5672);
factory.setVirtualHost("gao");//虚拟机名称 默认值 /
factory.setUsername("gao");
factory.setPassword("Aaqwe123");
//创建连接Connection
Connection connection = factory.newConnection();
//创建Channel
Channel channel = connection.createChannel();
String queue1Name = "test_fanout_queue1";
String queue2Name = "test_fanout_queue2";
//接收消息
/**
* 参数:
* String queue, 队列名称
* boolean autoAck, 是否自动确认
* Consumer callback 回调对象
*
*/
Consumer consumer = new DefaultConsumer(channel){
/**
* 回调方法:当收到消息后,会自动执行该方法
* consumerTag:标识
* envelope: 获取一些信息,交换机,路由key
* properties 配置信息
* body 数据
*
*/
@Override
public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
// System.out.println("consumerTag:"+consumerTag);
// System.out.println("Exchange:"+envelope.getExchange());
// System.out.println("Routingkey:"+envelope.getRoutingKey());
// System.out.println("properties:"+properties);
System.out.println("body:"+new String(body));
System.out.println("将日志信息打印到控制台");
}
};
channel.basicConsume(queue1Name,true,consumer);
//不用关闭资源,他是监听
}
public static void main(String[] args) throws Exception {
//创建连接工厂
ConnectionFactory factory = new ConnectionFactory();
//设置参数
factory.setHost("192.168.218.132");//默认是localhost
factory.setPort(5672);
factory.setVirtualHost("gao");//虚拟机名称 默认值 /
factory.setUsername("gao");
factory.setPassword("Aaqwe123");
//创建连接Connection
Connection connection = factory.newConnection();
//创建Channel
Channel channel = connection.createChannel();
String queue1Name = "test_fanout_queue1";
String queue2Name = "test_fanout_queue2";
//接收消息
/**
* 参数:
* String queue, 队列名称
* boolean autoAck, 是否自动确认
* Consumer callback 回调对象
*
*/
Consumer consumer = new DefaultConsumer(channel){
/**
* 回调方法:当收到消息后,会自动执行该方法
* consumerTag:标识
* envelope: 获取一些信息,交换机,路由key
* properties 配置信息
* body 数据
*
*/
@Override
public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
// System.out.println("consumerTag:"+consumerTag);
// System.out.println("Exchange:"+envelope.getExchange());
// System.out.println("Routingkey:"+envelope.getRoutingKey());
// System.out.println("properties:"+properties);
System.out.println("body:"+new String(body));
System.out.println("将日志信息保存到数据库");
}
};
channel.basicConsume(queue2Name,true,consumer);
//不用关闭资源,他是监听
}
Routing 路由模式
模式说明
路由模式特点:
- 队列与交换机的绑定,不能是任意绑定了,而是要指定一个`RoutingKey`(路由key)
- 消息的发送方在 向 Exchange发送消息时,也必须指定消息的 `RoutingKey`。
- Exchange不再把消息交给每一个绑定的队列,而是根据消息的`Routing Key`进行判断,只有队列的`Routingkey`与消息的 `Routing key`完全一致,才会接收到消息
代码
public static void main(String[] args) throws Exception {
//创建连接工厂
ConnectionFactory factory = new ConnectionFactory();
//设置参数
factory.setHost("192.168.218.132");//默认是localhost
factory.setPort(5672);
factory.setVirtualHost("gao");//虚拟机名称 默认值 /
factory.setUsername("gao");
factory.setPassword("Aaqwe123");
//创建连接Connection
Connection connection = factory.newConnection();
//创建Channel
Channel channel = connection.createChannel();
String exchangeName = "test_direct";
//创建交换机
/**
* exchangeDeclareNoWait(String exchange, BuiltinExchangeType type, boolean durable, boolean autoDelete, boolean internal, Map<String, Object> arguments)
* exchange 交换机名称
* type:交换机类型
* DIRECT("direct"), 定向
* FANOUT("fanout"), 扇形(广播)发送消息到每一个队列
* TOPIC("topic"), 通配符方式
* HEADERS("headers"); 参数匹配
* durable 是否持久化
* autoDelete 自动删除
* internal内部使用。一般false
* arguments参数
*/
channel.exchangeDeclare(exchangeName, BuiltinExchangeType.FANOUT,true,false,false,null);
//创建队列
String queue1Name = "test_direct_queue1";
String queue2Name = "test_direct_queue2";
channel.queueDeclare(queue1Name,true,false,false,null);
channel.queueDeclare(queue2Name,true,false,false,null);
//创建队列和交换机
/**
* queueBind(String queue, String exchange, String routingKey)
* queue 绑定当前队列名称
* exchange 交换机名称
* routingkey 路由键,绑定规则
* 如果交换机类型为fanout,routingkey设置为""(空)
*/
//队列1的绑定
channel.queueBind(queue1Name,exchangeName,"error");
//队列2的绑定
channel.queueBind(queue2Name,exchangeName,"info");
channel.queueBind(queue2Name,exchangeName,"error");
channel.queueBind(queue2Name,exchangeName,"warning");
String body = "日志信息:张三调用了findAll方法,日志级别:info";
//发送消息
channel.basicPublish(exchangeName,"info",null,body.getBytes());
//释放资源
channel.close();
connection.close();
}
消费端
public static void main(String[] args) throws Exception {
//创建连接工厂
ConnectionFactory factory = new ConnectionFactory();
//设置参数
factory.setHost("192.168.218.132");//默认是localhost
factory.setPort(5672);
factory.setVirtualHost("gao");//虚拟机名称 默认值 /
factory.setUsername("gao");
factory.setPassword("Aaqwe123");
//创建连接Connection
Connection connection = factory.newConnection();
//创建Channel
Channel channel = connection.createChannel();
String queue1Name = "test_direct_queue1";
//接收消息
/**
* 参数:
* String queue, 队列名称
* boolean autoAck, 是否自动确认
* Consumer callback 回调对象
*
*/
Consumer consumer = new DefaultConsumer(channel){
/**
* 回调方法:当收到消息后,会自动执行该方法
* consumerTag:标识
* envelope: 获取一些信息,交换机,路由key
* properties 配置信息
* body 数据
*
*/
@Override
public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
// System.out.println("consumerTag:"+consumerTag);
// System.out.println("Exchange:"+envelope.getExchange());
// System.out.println("Routingkey:"+envelope.getRoutingKey());
// System.out.println("properties:"+properties);
System.out.println("body:"+new String(body));
System.out.println("将日志信息打印到控制台");
}
};
channel.basicConsume(queue1Name,true,consumer);
//不用关闭资源,他是监听
}
public static void main(String[] args) throws Exception {
//创建连接工厂
ConnectionFactory factory = new ConnectionFactory();
//设置参数
factory.setHost("192.168.218.132");//默认是localhost
factory.setPort(5672);
factory.setVirtualHost("gao");//虚拟机名称 默认值 /
factory.setUsername("gao");
factory.setPassword("Aaqwe123");
//创建连接Connection
Connection connection = factory.newConnection();
//创建Channel
Channel channel = connection.createChannel();
String queue2Name = "test_direct_queue2";
//接收消息
/**
* 参数:
* String queue, 队列名称
* boolean autoAck, 是否自动确认
* Consumer callback 回调对象
*
*/
Consumer consumer = new DefaultConsumer(channel){
/**
* 回调方法:当收到消息后,会自动执行该方法
* consumerTag:标识
* envelope: 获取一些信息,交换机,路由key
* properties 配置信息
* body 数据
*
*/
@Override
public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
// System.out.println("consumerTag:"+consumerTag);
// System.out.println("Exchange:"+envelope.getExchange());
// System.out.println("Routingkey:"+envelope.getRoutingKey());
// System.out.println("properties:"+properties);
System.out.println("body:"+new String(body));
System.out.println("将日志信息保存到数据库");
}
};
channel.basicConsume(queue2Name,true,consumer);
//不用关闭资源,他是监听
}
小结:
Routing模式要求队列在绑定交换机时要指定routing key,消息会转发到符合 routing key的队列
Topics通配符模式
模式说明:
Topic`类型与`Direct`相比,都是可以根据`RoutingKey`把消息路由到不同的队列。只不过`Topic`类型`Exchange`可以让队列在绑定`Routing key` 的时候**使用通配符**!
- - 红色Queue:绑定的是usa.#,因此凡是以 usa.开头的routing key都会被匹配到
- - 黄色Queue:绑定的是#.news,因此凡是以 .news结尾的routing key都会被匹配
通配符规则
- `#`:匹配一个或多个词
- `*`:匹配不多不少恰好1个词
举例
- `item.#`:能够匹配`item.insert.abc` 或者 `item.insert`
- `item.*`:只能匹配`item.insert`
代码:
Producer_Topics
public static void main(String[] args) throws Exception {
//创建连接工厂
ConnectionFactory factory = new ConnectionFactory();
//设置参数
factory.setHost("192.168.218.132");//默认是localhost
factory.setPort(5672);
factory.setVirtualHost("gao");//虚拟机名称 默认值 /
factory.setUsername("gao");
factory.setPassword("Aaqwe123");
//创建连接Connection
Connection connection = factory.newConnection();
//创建Channel
Channel channel = connection.createChannel();
String exchangeName = "test_topic";
//创建交换机
/**
* exchangeDeclareNoWait(String exchange, BuiltinExchangeType type, boolean durable, boolean autoDelete, boolean internal, Map<String, Object> arguments)
* exchange 交换机名称
* type:交换机类型
* DIRECT("direct"), 定向
* FANOUT("fanout"), 扇形(广播)发送消息到每一个队列
* TOPIC("topic"), 通配符方式
* HEADERS("headers"); 参数匹配
* durable 是否持久化
* autoDelete 自动删除
* internal内部使用。一般false
* arguments参数
*/
channel.exchangeDeclare(exchangeName, BuiltinExchangeType.TOPIC,true,false,false,null);
//创建队列
String queue1Name = "test_topic_queue1";
String queue2Name = "test_topic_queue2";
channel.queueDeclare(queue1Name,true,false,false,null);
channel.queueDeclare(queue2Name,true,false,false,null);
//创建队列和交换机
/**
* 需求:所有error级别的日志存入数据库,所有order系统的日志存入数据库
*
*/
channel.queueBind(queue1Name,exchangeName,"#.error");
channel.queueBind(queue1Name,exchangeName,"order.*");
channel.queueBind(queue2Name,exchangeName,"*.*");
String body = "日志信息:张三调用了findAll方法,日志级别:info";
//发送消息
channel.basicPublish(exchangeName,"goods.info",null,body.getBytes());
//释放资源
channel.close();
connection.close();
}
Consumer_Topic1
public static void main(String[] args) throws Exception {
//创建连接工厂
ConnectionFactory factory = new ConnectionFactory();
//设置参数
factory.setHost("192.168.218.132");//默认是localhost
factory.setPort(5672);
factory.setVirtualHost("gao");//虚拟机名称 默认值 /
factory.setUsername("gao");
factory.setPassword("Aaqwe123");
//创建连接Connection
Connection connection = factory.newConnection();
//创建Channel
Channel channel = connection.createChannel();
String queue1Name = "test_topic_queue1";
//接收消息
/**
* 参数:
* String queue, 队列名称
* boolean autoAck, 是否自动确认
* Consumer callback 回调对象
*
*/
Consumer consumer = new DefaultConsumer(channel){
/**
* 回调方法:当收到消息后,会自动执行该方法
* consumerTag:标识
* envelope: 获取一些信息,交换机,路由key
* properties 配置信息
* body 数据
*
*/
@Override
public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
System.out.println("body:"+new String(body));
System.out.println("将日志信息打印到控制台");
}
};
channel.basicConsume(queue1Name,true,consumer);
//不用关闭资源,他是监听
}
Consumer_Topic2
public static void main(String[] args) throws Exception {
//创建连接工厂
ConnectionFactory factory = new ConnectionFactory();
//设置参数
factory.setHost("192.168.218.132");//默认是localhost
factory.setPort(5672);
factory.setVirtualHost("gao");//虚拟机名称 默认值 /
factory.setUsername("gao");
factory.setPassword("Aaqwe123");
//创建连接Connection
Connection connection = factory.newConnection();
//创建Channel
Channel channel = connection.createChannel();
String queue2Name = "test_topic_queue2";
//接收消息
/**
* 参数:
* String queue, 队列名称
* boolean autoAck, 是否自动确认
* Consumer callback 回调对象
*
*/
Consumer consumer = new DefaultConsumer(channel){
/**
* 回调方法:当收到消息后,会自动执行该方法
* consumerTag:标识
* envelope: 获取一些信息,交换机,路由key
* properties 配置信息
* body 数据
*
*/
@Override
public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
// System.out.println("consumerTag:"+consumerTag);
// System.out.println("Exchange:"+envelope.getExchange());
// System.out.println("Routingkey:"+envelope.getRoutingKey());
// System.out.println("properties:"+properties);
System.out.println("body:"+new String(body));
System.out.println("将日志信息保存到数据库");
}
};
channel.basicConsume(queue2Name,true,consumer);
//不用关闭资源,他是监听
}
小结:
Topic主题模式可以实现 Publish/Subscribe发布与订阅模式 和 Routing路由模式` 的功能;只是Topic在配置routing key 的时候可以使用通配符,显得更加灵活。
Spring整合RabbitMQ
步骤
生产者
- 创建生产者工程
- 添加依赖
- 配置整合
- 编写代码发送消息
消费者
- 创建生产者工程
- 添加依赖
- 配置整合
- 编写消息监听器
代码:
pom.xml生产者和消费者是相同的
<dependencies>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.1.7.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework.amqp</groupId>
<artifactId>spring-rabbit</artifactId>
<version>2.1.8.RELEASE</version>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-test</artifactId>
<version>5.1.7.RELEASE</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.8.0</version>
<configuration>
<source>1.8</source>
<target>1.8</target>
</configuration>
</plugin>
</plugins>
</build>
生产者代码
配置文件
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:rabbit="http://www.springframework.org/schema/rabbit"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
https://www.springframework.org/schema/context/spring-context.xsd
http://www.springframework.org/schema/rabbit
http://www.springframework.org/schema/rabbit/spring-rabbit.xsd">
<!--加载配置文件-->
<context:property-placeholder location="classpath:rabbitmq.properties"/>
<!-- 定义rabbitmq connectionFactory -->
<rabbit:connection-factory id="connectionFactory" host="${rabbitmq.host}"
port="${rabbitmq.port}"
username="${rabbitmq.username}"
password="${rabbitmq.password}"
virtual-host="${rabbitmq.virtual-host}"/>
<!--定义管理交换机、队列-->
<rabbit:admin connection-factory="connectionFactory"/>
<!--
各个参数
id:bean名称
name:queue的名称
auto-declare:自动创建
auto-delete:自动删除。最后一个消费者和该队列断开连接后,自动删除队列
exclusive:是否独占
durable:是否持久化
-->
<!--定义持久化队列,不存在则自动创建;不绑定到交换机则绑定到默认交换机
默认交换机类型为direct,名字为:"",路由键为队列的名称
-->
<rabbit:queue id="spring_queue" name="spring_queue" auto-declare="true"/>
<!-- ~~~~~~~~~~~~~~~~~~~~~~~~~~~~广播;所有队列都能收到消息~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -->
<!--定义广播交换机中的持久化队列,不存在则自动创建-->
<rabbit:queue id="spring_fanout_queue_1" name="spring_fanout_queue_1" auto-declare="true"/>
<!--定义广播交换机中的持久化队列,不存在则自动创建-->
<rabbit:queue id="spring_fanout_queue_2" name="spring_fanout_queue_2" auto-declare="true"/>
<!--定义广播类型交换机;并绑定上述两个队列-->
<rabbit:fanout-exchange id="spring_fanout_exchange" name="spring_fanout_exchange" auto-declare="true">
<rabbit:bindings>
<rabbit:binding queue="spring_fanout_queue_1"/>
<rabbit:binding queue="spring_fanout_queue_2"/>
</rabbit:bindings>
</rabbit:fanout-exchange>
<!-- ~~~~~~~~~~~~~~~~~~~~~~~~~~~~通配符;*匹配一个单词,#匹配多个单词 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -->
<!--定义广播交换机中的持久化队列,不存在则自动创建-->
<rabbit:queue id="spring_topic_queue_star" name="spring_topic_queue_star" auto-declare="true"/>
<!--定义广播交换机中的持久化队列,不存在则自动创建-->
<rabbit:queue id="spring_topic_queue_well" name="spring_topic_queue_well" auto-declare="true"/>
<!--定义广播交换机中的持久化队列,不存在则自动创建-->
<rabbit:queue id="spring_topic_queue_well2" name="spring_topic_queue_well2" auto-declare="true"/>
<rabbit:topic-exchange id="spring_topic_exchange" name="spring_topic_exchange" auto-declare="true">
<rabbit:bindings>
<rabbit:binding pattern="heima.*" queue="spring_topic_queue_star"/>
<rabbit:binding pattern="heima.#" queue="spring_topic_queue_well"/>
<rabbit:binding pattern="itcast.#" queue="spring_topic_queue_well2"/>
</rabbit:bindings>
</rabbit:topic-exchange>
<!--定义rabbitTemplate对象操作可以在代码中方便发送消息-->
<rabbit:template id="rabbitTemplate" connection-factory="connectionFactory"/>
</beans>
rabbitmq.host=192.168.XXX.XXX
rabbitmq.port=5672
rabbitmq.username=gao
rabbitmq.password=XXXXXXXX
rabbitmq.virtual-host=gao
ProducerTest
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = "classpath:spring-rabbitmq-producer.xml")
public class ProducerTest {
// 注入RabbitTemplate
@Autowired
private RabbitTemplate rabbitTemplate;
//发送的消息参考配置文件
@Test
public void testHelloWorld(){
//发送消息
rabbitTemplate.convertAndSend("spring_queue","hello world spring……");
}
/**
* 发送fanout消息
*/
@Test
public void testFanout(){
//发送消息
rabbitTemplate.convertAndSend("spring_fanout_exchange","","spring fanout……");
}
/**
* 发送fanout消息
*/
@Test
public void testTopics(){
//发送消息
rabbitTemplate.convertAndSend("spring_topic_exchange","heima.hehe.haha","spring fanout……");
}
}
消费者代码
配置文件
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:rabbit="http://www.springframework.org/schema/rabbit"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
https://www.springframework.org/schema/context/spring-context.xsd
http://www.springframework.org/schema/rabbit
http://www.springframework.org/schema/rabbit/spring-rabbit.xsd">
<!--加载配置文件-->
<context:property-placeholder location="classpath:rabbitmq.properties"/>
<!-- 定义rabbitmq connectionFactory -->
<rabbit:connection-factory id="connectionFactory" host="${rabbitmq.host}"
port="${rabbitmq.port}"
username="${rabbitmq.username}"
password="${rabbitmq.password}"
virtual-host="${rabbitmq.virtual-host}"/>
<bean id="springQueueListener" class="com.gao.rabbitmq.listener.SpringQueueListener"/>
<!-- <bean id="fanoutListener1" class="com.gao.rabbitmq.listener.FanoutListener1"/>-->
<!-- <bean id="fanoutListener2" class="com.gao.rabbitmq.listener.FanoutListener2"/>-->
<!-- <bean id="topicListenerStar" class="com.gao.rabbitmq.listener.TopicListenerStar"/>-->
<!-- <bean id="topicListenerWell" class="com.gao.rabbitmq.listener.TopicListenerWell"/>-->
<!-- <bean id="topicListenerWell2" class="com.gao.rabbitmq.listener.TopicListenerWell2"/>-->
<rabbit:listener-container connection-factory="connectionFactory" auto-declare="true">
<rabbit:listener ref="springQueueListener" queue-names="spring_queue"/>
<!-- <rabbit:listener ref="fanoutListener1" queue-names="spring_fanout_queue_1"/>-->
<!-- <rabbit:listener ref="fanoutListener2" queue-names="spring_fanout_queue_2"/>-->
<!-- <rabbit:listener ref="topicListenerStar" queue-names="spring_topic_queue_star"/>-->
<!-- <rabbit:listener ref="topicListenerWell" queue-names="spring_topic_queue_well"/>-->
<!-- <rabbit:listener ref="topicListenerWell2" queue-names="spring_topic_queue_well2"/>-->
</rabbit:listener-container>
</beans>
rabbitmq.host=192.168.XXX.XXX
rabbitmq.port=5672
rabbitmq.username=gao
rabbitmq.password=XXXXXXXXXX
rabbitmq.virtual-host=gao
ConsumerTest
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = "classpath:spring-rabbitmq-consumer.xml")
public class ConsumerTest {
@Test
public void test1(){
boolean flag = true;
// while(true){
// }
}
}
SpringQueueListener
public class SpringQueueListener implements MessageListener {
@Override
public void onMessage(Message message) {
//打印消息
System.out.println(new String(message.getBody()));
}
}
springboot整合RabbitMQ
生产者:
步骤
- 创建生产者springBoot工程
- 引入依赖坐标
<dependency>
<groupld>org.springframework.boot</groupld>
<artifactld>spring-boot-starter-amqp</artifactld>
</dependency>
- 编写yml配置,基本信息配置
- 定义交换机,队列以及绑定关系的配置类
- 注入RabbitTemplate,调用方法,完成消息发送
代码:
父项目依赖
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.1.4.RELEASE</version>
</parent>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-amqp</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
</dependency>
</dependencies>
application.yml
spring:
rabbitmq:
host: 192.XXX.XXX.XXX
username: gao
password: XXXXXXXX
virtual-host: gao
port: XXXX
RabbitMQConfig
import org.springframework.amqp.core.*;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
//配置类
@Configuration
public class RabbitMQConfig {
public static final String EXCHANGE_NAME = "boot_topic_exchange";
public static final String QUEUE_NAME = "boot_queue";
//1.交换机
@Bean("bootExchange")
public Exchange bootExchange(){
return ExchangeBuilder.topicExchange(EXCHANGE_NAME).durable(true).build();
}
//2.Queue队列
@Bean("bootQueue")
public Queue bootQueue(){
return QueueBuilder.durable(QUEUE_NAME).build();
}
//3.队列和交互绑定的关系Binding
/**
* 1.知道哪个队列
* 2.知道哪个交换机
* 3.routing key
*/
@Bean
public Binding bindQueueExchange(@Qualifier("bootQueue") Queue queue, @Qualifier("bootExchange") Exchange exchange){
return BindingBuilder.bind(queue).to(exchange).with("boot.#").noargs();
}
}
启动类
@SpringBootApplication
public class ProducerApplication {
public static void main(String[] args) {
SpringApplication.run(ProducerApplication.class);
}
}
ProducerTest
@SpringBootTest
@RunWith(SpringRunner.class)
public class ProducerTest {
//1.注入RabbitTemplate
@Autowired
private RabbitTemplate rabbitTemplate;
@Test
public void testSend(){
rabbitTemplate.convertAndSend(RabbitMQConfig.EXCHANGE_NAME,"boot.haha","boot mq hello~~~~");
}
}
消费者
步骤
- 创建消费者SpringBoot工程
- 引入start依赖坐标
<dependency>
<groupld>org.springframework.boot</groupld>
<artifactld>spring-boot-starter-amqp</artifactld>
</dependency>
- 编写yml配置,基本信息配置
- 定义监听类,使用@RabbitListener注解完成队列监听。
代码
依赖坐标和生产者一样通用父项目pom
启动类
@SpringBootApplication
public class consumerspringboot {
public static void main(String[] args) {
SpringApplication.run(consumerspringboot.class);
}
}
application.yml
spring:
rabbitmq:
host: 192.168.XXX.XXX
username: gao
password: XXXXXXXX
virtual-host: gao
port: 5672
RabbitMQListener
import org.springframework.amqp.core.Message;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.stereotype.Component;
@Component
public class RabbitMQListener {
@RabbitListener(queues = "boot_queue")
public void ListenerQueue(Message message){
System.out.println(new String(message.getBody()));
}
}
小结
- SpringBoot提供了快速整合RabbitMQ的方式
- 基本信息再yml中配置,队列交互机以及绑定关系在配置类中使用Bean的方式配置
- 生产段直接注入RabbitTemplate完成消息发送
- 消费端直接使用@RabbitListener完成消息接收