RabbitMq概述与工作模式深度剖析
windows下安装RabbitMQ Server
安装了一整天都没好,心累。最开始在windows下安装,安装的有问题。又在cent os下安装,依然没有成功,满满的挫败感。晚上又试了一下,终于安装成功了
如下图:
RabbitMQ的安装的注意事项
- RabbitMQ 和redis一样,也是采用c/s架构 由服务端与客户端组成, 我们需要安装它的服务端
- RabbitMQ服务端是由Erlang语言编写,所以我们必须先安装Erlang语言的环境
- 注意版本一定要对应,否则安装注定失败,给一个官网的说明。笔者安装的Erlang版本是otp_win64_21.0.1。RabbitMQ Server的版本是rabbitmq-server-3.7.12
- 安装完Erlang环境之后,需要配置path环境变量。
- 安装的 RabbitMQ Server\rabbitmq_server-3.7.12\sbin 目录下面 执行一条cmd命令:rabbitmq-plugins enable rabbitmq_management
演示删除guest用户并新增用户
线上环境下一定要把guest用户(当然 guest这个用户只能本机才能登陆)删掉并且新加一个用户
添加Virtual host级别的权限
给用户设置Virtual host的权限,正则匹配都是* 也就是所有权限
Linux下安装RabbitMQ Server
- 1.将下面这三个安装包上传到/usr/local/rabbitmq目录下
erlang-18.3-1.el7.centos.x86_64.rpm
rabbitmq-server-3.6.5-1.noarch.rpm
socat-1.7.3.2-1.1.el7.x86_64.rpm
- 安装Erlang
rpm -ivh erlang-18.3-1.el7.centos.x86_64.rpm
#查询erlang安装的软件包名称
rpm -q -a | grep "erlang"
#查询rpm包安装到哪里
rpm -ql erlang-21.2.4-1.el7.centos.x86_64
#卸载软件,参数e的作用是使rpm进入卸载模式。对名为[package name]的软件包进行卸载
rpm -e erlang-21.2.4-1.el7.centos.x86_64
#由于系统中各个软件包之间相互有依赖关系。如果因存在依赖关系而不能卸载,rpm将给予提示并停止卸载。你可以使用如下的命令来忽略依赖关系,直接开始卸载:
rpm -e erlang-21.2.4-1.el7.centos.x86_64 -nodeps
安装时发现之前已经装过erlang,需要把之前安装的卸载掉,重新安装,因为erlang的版本和rabbitmq的版本需要配套使用,两者强关联。
- 安装RabbitMQ
rpm -ivh socat-1.7.3.2-1.1.el7.x86_64.rpm
rpm -ivh rabbitmq-server-3.6.5-1.noarch.rpm
- 开启管理界面及配置
rabbitmq-plugins enable rabbitmq_management
- 启动服务
service rabbitmq-server start # 启动服务
service rabbitmq-server stop # 停止服务
service rabbitmq-server restart # 重启服务
- 设置配置文件
cd /usr/share/doc/rabbitmq-server-3.6.5/
cp rabbitmq.config.example /etc/rabbitmq/rabbitmq.config
vim /usr/lib/rabbitmq/lib/rabbitmq_server-3.6.5/ebin/rabbit.app
rabbitmq控制面板的使用
- 创建用户
- 创建虚拟机
- 虚拟机关联用户
MQ的基本概念
为什么要使用消息队列
解耦
异步
削峰
消息队列缺点
- 系统可用性降低
- 系统复杂度提高
- 一致性问题
常见消息中间件的区别
AMQP的基本概念
AMQP,即 Advanced Message Queuing Protocol(高级消息队列协议),是一个网络协议,是应用层协议的一个开放标准,为面向消息的中间件设计。基于此协议的客户端与消息中间件可传递消息,并不受客户端/中间件不同产品,不同的开发语言等条件的限制。2006年,AMQP 规范发布。类比HTTP。
RabbitMQ 基础架构
-
Broker:接收和分发消息的应用,RabbitMQ Server就是 Message Broker。以RabbitMQ 为例:信道(channel)、队列(queue)、交换器(exchange)、路由键(routing key)、绑定(binding key)、虚拟主机(vhost)等
-
Virtual host:出于多租户和安全因素设计的,把 AMQP 的基本组件划分到一个虚拟的分组中,类似于网络中的 namespace 概念。当多个不同的用户使用同一个 RabbitMQ server 提供的服务时,可以划分出多个vhost,每个用户在自己的 vhost 创建 exchange/queue 等
-
Exchange:message 到达 broker 的第一站,根据分发规则,匹配查询表中的 routing key,分发消息到queue 中去。常用的类型有:direct (point-to-point), topic (publish-subscribe) and fanout (multicast)
-
Queue:消息最终被送到这里等待 consumer 取走
-
Binding:exchange 和 queue 之间的虚拟连接,binding 中可以包含 routing key。Binding 信息被保存到 exchange 中的查询表中,用于 message 的分发依据
-
Connection:publisher/consumer 和 broker 之间的 TCP 连接
-
Channel:如果每一次访问 RabbitMQ 都建立一个 Connection,在消息量大的时候建立 TCP Connection的开销将是巨大的,效率也较低。Channel 是在 connection 内部建立的逻辑连接,如果应用程序支持多线程,通常每个thread创建单独的 channel 进行通讯,AMQP method 包含了channel id 帮助客户端和message broker 识别 channel,所以 channel 之间是完全隔离的。Channel 作为轻量级的 Connection 极大减少了操作系统建立 TCP connection 的开销
-
生产者(producer):创建消息,发布到代理服务器(Message Broker)
-
消费者(consumer):连接到代理服务器,并订阅到队列(queue)上,代理服务器将发送消息给一个订阅的/监听的消费者,消费者其只能接收消息的一部分:有效载荷(playload)
-
消息(Message):由有效载荷(playload)和标签(label)组成。其中有效载荷即传输的数据。
RabbitMQ 的工作模式
基本消息模型(简单模式)
生产者将消息发送到队列,消费者从队列中获取消息,队列是存储消息的缓冲区。
案例
- 引入pom依赖
<dependency>
<groupId>com.rabbitmq</groupId>
<artifactId>amqp-client</artifactId>
<version>5.3.0</version>
</dependency>
<dependency>
<groupId>com.google.code.gson</groupId>
<artifactId>gson</artifactId>
<version>2.8.5</version>
</dependency>
- 消费者
package com.baiqi.rabbitmq.helloworld;
import com.baiqi.rabbitmq.utils.RabbitConstant;
import com.baiqi.rabbitmq.utils.RabbitUtils;
import com.rabbitmq.client.*;
import java.io.IOException;
import java.util.concurrent.TimeoutException;
public class Consumer {
public static void main(String[] args) throws IOException, TimeoutException {
//获取TCP长连接
Connection conn = RabbitUtils.getConnection();
//创建通信“通道”,相当于TCP中的虚拟连接
Channel channel = conn.createChannel();
//创建队列,声明并创建一个队列,如果队列已存在,则使用这个队列
//第一个参数:队列名称ID
//第二个参数:是否持久化,false对应不持久化数据,MQ停掉数据就会丢失
//第三个参数:是否队列私有化,false则代表所有消费者都可以访问,true代表只有第一次拥有它的消费者才能一直使用,其他消费者不让访问
//第四个:是否自动删除,false代表连接停掉后不自动删除掉这个队列
//其他额外的参数, null
channel.queueDeclare(RabbitConstant.QUEUE_HELLOWORLD,false, false, false, null);
//从MQ服务器中获取数据
//创建一个消息消费者
//第一个参数:队列名
//第二个参数代表是否自动确认收到消息,false代表手动编程来确认消息,这是MQ的推荐做法
//第三个参数要传入DefaultConsumer的实现类
channel.basicConsume(RabbitConstant.QUEUE_HELLOWORLD, false, new Reciver(channel));
}
}
class Reciver extends DefaultConsumer {
private Channel channel;
//重写构造函数,Channel通道对象需要从外层传入,在handleDelivery中要用到
public Reciver(Channel channel) {
super(channel);
this.channel = channel;
}
@Override
public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
String message = new String(body);
System.out.println("消费者接收到的消息:"+message);
System.out.println("消息的TagId:"+envelope.getDeliveryTag());
//false只确认签收当前的消息,设置为true的时候则代表签收该消费者所有未签收的消息
channel.basicAck(envelope.getDeliveryTag(), false);
}
}
- 生产者
package com.baiqi.rabbitmq.helloworld;
import com.baiqi.rabbitmq.utils.RabbitConstant;
import com.baiqi.rabbitmq.utils.RabbitUtils;
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 Producer {
public static void main(String[] args) throws IOException, TimeoutException {
//获取TCP长连接
Connection conn = RabbitUtils.getConnection();
//创建通信“通道”,相当于TCP中的虚拟连接
Channel channel = conn.createChannel();
//创建队列,声明并创建一个队列,如果队列已存在,则使用这个队列
//第一个参数:队列名称ID
//第二个参数:是否持久化,false对应不持久化数据,MQ停掉数据就会丢失
//第三个参数:是否队列私有化,false则代表所有消费者都可以访问,true代表只有第一次拥有它的消费者才能一直使用,其他消费者不让访问
//第四个:是否自动删除,false代表连接停掉后不自动删除掉这个队列
//其他额外的参数, null
channel.queueDeclare(RabbitConstant.QUEUE_HELLOWORLD,false, false, false, null);
String message = "hello白起666";
//四个参数
//exchange 交换机,暂时用不到,在后面进行发布订阅时才会用到
//队列名称
//额外的设置属性
//最后一个参数是要传递的消息字节数组
channel.basicPublish("", RabbitConstant.QUEUE_HELLOWORLD, null,message.getBytes());
channel.close();
conn.close();
System.out.println("===发送成功===");
}
}
- 工具类
package com.baiqi.rabbitmq.utils;
import com.rabbitmq.client.Connection;
import com.rabbitmq.client.ConnectionFactory;
public class RabbitUtils {
private static ConnectionFactory connectionFactory = new ConnectionFactory();
static {
connectionFactory.setHost("192.168.1.104");
connectionFactory.setPort(5672);//5672是RabbitMQ的默认端口号
connectionFactory.setUsername("yemuxia");
connectionFactory.setPassword("123456");
connectionFactory.setVirtualHost("/yemuxia_vir");
}
public static Connection getConnection(){
Connection conn = null;
try {
conn = connectionFactory.newConnection();
return conn;
} catch (Exception e) {
throw new RuntimeException(e);
}
}
}
package com.baiqi.rabbitmq.utils;
public class RabbitConstant {
public static final String QUEUE_HELLOWORLD = "helloworld";
public static final String QUEUE_SMS = "sms";
public static final String EXCHANGE_WEATHER = "weather";
public static final String EXCHANGE_WEATHER_ROUTING = "weather_routing";
public static final String QUEUE_BAIDU = "baidu";
public static final String QUEUE_SINA = "sina";
public static final String EXCHANGE_WEATHER_TOPIC = "weather_topic";
}
work消息模型(工作队列模式)
工作队列或者竞争消费者模式
让多个消费者绑定到一个队列,共同消费队列中的消息。对于任务过重或任务较多情况使用工作队列可以提高任务处理的速度。
案例
模拟乘客预定车票,发送短信服务
- 定义短信bean类
package com.baiqi.rabbitmq.workqueue;
public class SMS {
private String name;
private String mobile;
private String content;
public SMS(String name, String mobile, String content) {
this.name = name;
this.mobile = mobile;
this.content = content;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getMobile() {
return mobile;
}
public void setMobile(String mobile) {
this.mobile = mobile;
}
public String getContent() {
return content;
}
public void setContent(String content) {
this.content = content;
}
}
- 定义三个消费者
package com.baiqi.rabbitmq.workqueue;
import com.baiqi.rabbitmq.utils.RabbitConstant;
import com.baiqi.rabbitmq.utils.RabbitUtils;
import com.rabbitmq.client.*;
import java.io.IOException;
/**
*
* 消费者
*/
public class SMSSender1 {
public static void main(String[] args) throws IOException {
Connection connection = RabbitUtils.getConnection();
final Channel channel = connection.createChannel();
channel.queueDeclare(RabbitConstant.QUEUE_SMS, false, false, false, null);
//如果不写basicQos(1),则自动MQ会将所有请求平均发送给所有消费者
//basicQos,MQ不再对消费者一次发送多个请求,而是消费者处理完一个消息后(确认后),在从队列中获取一个新的
channel.basicQos(1);//处理完一个取一个
channel.basicConsume(RabbitConstant.QUEUE_SMS , false , new DefaultConsumer(channel){
@Override
public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
String jsonSMS = new String(body);
System.out.println("SMSSender1-短信发送成功:" + jsonSMS);
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
channel.basicAck(envelope.getDeliveryTag() , false);
}
});
}
}
package com.baiqi.rabbitmq.workqueue;
import com.baiqi.rabbitmq.utils.RabbitConstant;
import com.baiqi.rabbitmq.utils.RabbitUtils;
import com.rabbitmq.client.*;
import java.io.IOException;
/**
* 消费者
*/
public class SMSSender2 {
public static void main(String[] args) throws IOException {
Connection connection = RabbitUtils.getConnection();
final Channel channel = connection.createChannel();
channel.queueDeclare(RabbitConstant.QUEUE_SMS, false, false, false, null);
//如果不写basicQos(1),则自动MQ会将所有请求平均发送给所有消费者
//basicQos,MQ不再对消费者一次发送多个请求,而是消费者处理完一个消息后(确认后),在从队列中获取一个新的
channel.basicQos(1);//处理完一个取一个
channel.basicConsume(RabbitConstant.QUEUE_SMS , false , new DefaultConsumer(channel){
@Override
public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
String jsonSMS = new String(body);
System.out.println("SMSSender2-短信发送成功:" + jsonSMS);
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
channel.basicAck(envelope.getDeliveryTag() , false);
}
});
}
}
package com.baiqi.rabbitmq.workqueue;
import com.baiqi.rabbitmq.utils.RabbitConstant;
import com.baiqi.rabbitmq.utils.RabbitUtils;
import com.rabbitmq.client.*;
import java.io.IOException;
/**
* 消费者
*/
public class SMSSender3 {
public static void main(String[] args) throws IOException {
Connection connection = RabbitUtils.getConnection();
final Channel channel = connection.createChannel();
channel.queueDeclare(RabbitConstant.QUEUE_SMS, false, false, false, null);
//如果不写basicQos(1),则自动MQ会将所有请求平均发送给所有消费者
//basicQos,MQ不再对消费者一次发送多个请求,而是消费者处理完一个消息后(确认后),在从队列中获取一个新的
channel.basicQos(1);//处理完一个取一个
channel.basicConsume(RabbitConstant.QUEUE_SMS , false , new DefaultConsumer(channel){
@Override
public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
String jsonSMS = new String(body);
System.out.println("SMSSender3-短信发送成功:" + jsonSMS);
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
channel.basicAck(envelope.getDeliveryTag() , false);
}
});
}
}
- 定义生产者
package com.baiqi.rabbitmq.workqueue;
import com.baiqi.rabbitmq.utils.RabbitConstant;
import com.baiqi.rabbitmq.utils.RabbitUtils;
import com.google.gson.Gson;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
import java.io.IOException;
import java.util.concurrent.TimeoutException;
/**
*
* 发送者
*/
public class OrderSystem {
public static void main(String[] args) throws IOException, TimeoutException {
Connection connection = RabbitUtils.getConnection();
Channel channel = connection.createChannel();
channel.queueDeclare(RabbitConstant.QUEUE_SMS, false, false, false, null);
for(int i = 1 ; i <= 100 ; i++) {
SMS sms = new SMS("乘客" + i, "13900000" + i, "您的车票已预订成功");
String jsonSMS = new Gson().toJson(sms);
channel.basicPublish("" , RabbitConstant.QUEUE_SMS , null , jsonSMS.getBytes());
}
System.out.println("发送数据成功");
channel.close();
connection.close();
}
}
订阅模型(三类)
1、1个生产者,多个消费者
2、每一个消费者都有自己的一个队列
3、生产者没有将消息直接发送到队列,而是发送到了交换机
4、每个队列都要绑定到交换机
5、生产者发送的消息,经过交换机到达队列,实现一个消息被多个消费者获取的目的
Exchange类型有以下几种:交换机需要手动创建
- Fanout:广播,将消息交给所有绑定到交换机的队列
- Direct:定向,把消息交给符合指定routing key 的队列
- Topic:通配符,把消息交给符合routing pattern(路由模式) 的队列
- Headers:当消息发送到RabbitMQ时会取到该消息的headers与Exchange绑定时指定的键值对进行匹配,headers属性是一个键值对,可以是Hashtable,键值对的值可以是任何类型。而fanout,direct,topic 的路由键都需要要字符串形式的。(不常用)
根据交换机的类型将订阅模型又细分为三类。
订阅模型-Fanout
主要特点是广播模式, 队列的消费者都能拿到消息。实现一条消息被多个消费者消费
案例
- 创建交换机
- 创建消费端
package com.baiqi.rabbitmq.pubsub;
import com.baiqi.rabbitmq.utils.RabbitConstant;
import com.baiqi.rabbitmq.utils.RabbitUtils;
import com.rabbitmq.client.*;
import java.io.IOException;
/**
* 消费者
*/
public class Sina {
public static void main(String[] args) throws IOException {
//获取TCP长连接
Connection connection = RabbitUtils.getConnection();
//获取虚拟连接
final Channel channel = connection.createChannel();
//声明队列信息
channel.queueDeclare(RabbitConstant.QUEUE_SINA, false, false, false, null);
//queueBind用于将队列与交换机绑定
//参数1:队列名 参数2:交互机名 参数三:路由key(暂时用不到)
channel.queueBind(RabbitConstant.QUEUE_SINA, RabbitConstant.EXCHANGE_WEATHER, "");
channel.basicQos(1);
channel.basicConsume(RabbitConstant.QUEUE_SINA , false , new DefaultConsumer(channel){
@Override
public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
System.out.println("新浪天气收到气象信息:" + new String(body));
channel.basicAck(envelope.getDeliveryTag() , false);
}
});
}
}
package com.baiqi.rabbitmq.pubsub;
import com.baiqi.rabbitmq.utils.RabbitConstant;
import com.baiqi.rabbitmq.utils.RabbitUtils;
import com.rabbitmq.client.*;
import java.io.IOException;
/**
* 消费者
*/
public class BiaDu {
public static void main(String[] args) throws IOException {
//获取TCP长连接
Connection connection = RabbitUtils.getConnection();
//获取虚拟连接
final Channel channel = connection.createChannel();
//声明队列信息
channel.queueDeclare(RabbitConstant.QUEUE_BAIDU, false, false, false, null);
//queueBind用于将队列与交换机绑定
//参数1:队列名 参数2:交互机名 参数三:路由key(暂时用不到)
channel.queueBind(RabbitConstant.QUEUE_BAIDU, RabbitConstant.EXCHANGE_WEATHER, "");
channel.basicQos(1);
channel.basicConsume(RabbitConstant.QUEUE_BAIDU , false , new DefaultConsumer(channel){
@Override
public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
System.out.println("新浪天气收到气象信息:" + new String(body));
channel.basicAck(envelope.getDeliveryTag() , false);
}
});
}
}
- 创建生产端
package com.baiqi.rabbitmq.pubsub;
import com.baiqi.rabbitmq.utils.RabbitConstant;
import com.baiqi.rabbitmq.utils.RabbitUtils;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
import java.io.IOException;
import java.util.Scanner;
/**
* 发布者
*/
public class WeatherBureau {
public static void main(String[] args) throws Exception {
Connection connection = RabbitUtils.getConnection();
String input = new Scanner(System.in).next();
Channel channel = connection.createChannel();
//第一个参数交换机名字 其他参数和之前的一样
channel.basicPublish(RabbitConstant.EXCHANGE_WEATHER,"" , null , input.getBytes());
channel.close();
connection.close();
}
}
订阅模型-Direct
P:生产者,向Exchange发送消息,发送消息时,会指定一个routing key。
X:Exchange(交换机),接收生产者的消息,然后把消息递交给 与routing key完全匹配的队列
C1:消费者,其所在队列指定了需要routing key 为 error 的消息
C2:消费者,其所在队列指定了需要routing key 为 info、error、warning 的消息
案例
- 创建交换机
- 创建消费者和生产者
package com.baiqi.rabbitmq.routing;
import com.baiqi.rabbitmq.utils.RabbitConstant;
import com.baiqi.rabbitmq.utils.RabbitUtils;
import com.rabbitmq.client.*;
import java.io.IOException;
/**
* 消费者
*/
public class BiaDu {
public static void main(String[] args) throws IOException {
Connection connection = RabbitUtils.getConnection();
final Channel channel = connection.createChannel();
channel.queueDeclare(RabbitConstant.QUEUE_BAIDU, false, false, false, null);
//queueBind用于将队列与交换机绑定
//参数1:队列名 参数2:交互机名 参数三:路由key
channel.queueBind(RabbitConstant.QUEUE_BAIDU, RabbitConstant.EXCHANGE_WEATHER_ROUTING, "china.hunan.changsha.20201127");
channel.queueBind(RabbitConstant.QUEUE_BAIDU, RabbitConstant.EXCHANGE_WEATHER_ROUTING, "china.hebei.shijiazhuang.20201128");
channel.basicQos(1);
channel.basicConsume(RabbitConstant.QUEUE_BAIDU , false , new DefaultConsumer(channel){
@Override
public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
System.out.println("百度天气收到气象信息:" + new String(body));
channel.basicAck(envelope.getDeliveryTag() , false);
}
});
}
}
package com.baiqi.rabbitmq.routing;
import com.baiqi.rabbitmq.utils.RabbitConstant;
import com.baiqi.rabbitmq.utils.RabbitUtils;
import com.rabbitmq.client.*;
import java.io.IOException;
/**
* 消费者
*/
public class Sina {
public static void main(String[] args) throws IOException {
//获取TCP长连接
Connection connection = RabbitUtils.getConnection();
//获取虚拟连接
final Channel channel = connection.createChannel();
//声明队列信息
channel.queueDeclare(RabbitConstant.QUEUE_SINA, false, false, false, null);
//指定队列与交换机以及routing key之间的关系
channel.queueBind(RabbitConstant.QUEUE_SINA, RabbitConstant.EXCHANGE_WEATHER_ROUTING, "us.cal.lsj.20201127");
channel.queueBind(RabbitConstant.QUEUE_SINA, RabbitConstant.EXCHANGE_WEATHER_ROUTING, "china.hubei.wuhan.20201127");
channel.queueBind(RabbitConstant.QUEUE_SINA, RabbitConstant.EXCHANGE_WEATHER_ROUTING, "us.cal.lsj.20201128");
channel.queueBind(RabbitConstant.QUEUE_SINA, RabbitConstant.EXCHANGE_WEATHER_ROUTING, "china.henan.zhengzhou.20201012");
channel.basicQos(1);
channel.basicConsume(RabbitConstant.QUEUE_SINA , false , new DefaultConsumer(channel){
@Override
public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
System.out.println("新浪天气收到气象信息:" + new String(body));
channel.basicAck(envelope.getDeliveryTag() , false);
}
});
}
}
package com.baiqi.rabbitmq.routing;
import com.baiqi.rabbitmq.utils.RabbitConstant;
import com.baiqi.rabbitmq.utils.RabbitUtils;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Scanner;
/**
* 发布者
*/
public class WeatherBureau {
public static void main(String[] args) throws Exception {
Map area = new LinkedHashMap<String, String>();
area.put("china.hunan.changsha.20201127", "中国湖南长沙20201127天气数据");
area.put("china.hubei.wuhan.20201127", "中国湖北武汉20201127天气数据");
area.put("china.hunan.zhuzhou.20201127", "中国湖南株洲20201128天气数据");
area.put("us.cal.lsj.20201127", "美国加州洛杉矶20201127天气数据");
area.put("china.hebei.shijiazhuang.20201128", "中国河北石家庄20201128天气数据");
area.put("china.hubei.wuhan.20201128", "中国湖北武汉20201128天气数据");
area.put("china.henan.zhengzhou.20201128", "中国河南郑州20201128天气数据");
area.put("us.cal.lsj.20201128", "美国加州洛杉矶20201128天气数据");
Connection connection = RabbitUtils.getConnection();
Channel channel = connection.createChannel();
Iterator<Map.Entry<String, String>> itr = area.entrySet().iterator();
while (itr.hasNext()) {
Map.Entry<String, String> me = itr.next();
//第一个参数交换机名字 第二个参数作为 消息的routing key
channel.basicPublish(RabbitConstant.EXCHANGE_WEATHER_ROUTING,me.getKey() , null , me.getValue().getBytes());
}
channel.close();
connection.close();
}
}
- 运行
订阅模型-Topic
Topic 类型的 Exchange 与 Direct 相比,都是可以根据 RoutingKey 把消息路由到不同的队列。只不过 Topic 类型 Exchange 可以让队列在绑定 Routing key 的时候使用通配符。
通配符规则:
- #:匹配一个或多个词
- *:匹配不多不少恰好 1 个词
案例
- 创建交换机
- 创建消费者和生产者
package com.baiqi.rabbitmq.topic;
import com.baiqi.rabbitmq.utils.RabbitConstant;
import com.baiqi.rabbitmq.utils.RabbitUtils;
import com.rabbitmq.client.*;
import java.io.IOException;
/**
* 消费者
*/
public class BiaDu {
public static void main(String[] args) throws IOException {
Connection connection = RabbitUtils.getConnection();
final Channel channel = connection.createChannel();
channel.queueDeclare(RabbitConstant.QUEUE_BAIDU, false, false, false, null);
//queueBind用于将队列与交换机绑定
//参数1:队列名 参数2:交互机名 参数三:路由key
channel.queueBind(RabbitConstant.QUEUE_BAIDU, RabbitConstant.EXCHANGE_WEATHER_TOPIC, "*.*.*.20201127");
// channel.queueBind(RabbitConstant.QUEUE_BAIDU, RabbitConstant.EXCHANGE_WEATHER_ROUTING, "china.hebei.shijiazhuang.20201128");
channel.basicQos(1);
channel.basicConsume(RabbitConstant.QUEUE_BAIDU , false , new DefaultConsumer(channel){
@Override
public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
System.out.println("百度天气收到气象信息:" + new String(body));
channel.basicAck(envelope.getDeliveryTag() , false);
}
});
}
}
package com.baiqi.rabbitmq.topic;
import com.baiqi.rabbitmq.utils.RabbitConstant;
import com.baiqi.rabbitmq.utils.RabbitUtils;
import com.rabbitmq.client.*;
import java.io.IOException;
/**
* 消费者
*/
public class Sina {
public static void main(String[] args) throws IOException {
//获取TCP长连接
Connection connection = RabbitUtils.getConnection();
//获取虚拟连接
final Channel channel = connection.createChannel();
//声明队列信息
channel.queueDeclare(RabbitConstant.QUEUE_SINA, false, false, false, null);
//指定队列与交换机以及routing key之间的关系
channel.queueBind(RabbitConstant.QUEUE_SINA, RabbitConstant.EXCHANGE_WEATHER_TOPIC, "us.#");
channel.basicQos(1);
channel.basicConsume(RabbitConstant.QUEUE_SINA , false , new DefaultConsumer(channel){
@Override
public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
System.out.println("新浪天气收到气象信息:" + new String(body));
channel.basicAck(envelope.getDeliveryTag() , false);
}
});
}
}
package com.baiqi.rabbitmq.topic;
import com.baiqi.rabbitmq.utils.RabbitConstant;
import com.baiqi.rabbitmq.utils.RabbitUtils;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.Map;
/**
* 发布者
*/
public class WeatherBureau {
public static void main(String[] args) throws Exception {
Map area = new LinkedHashMap<String, String>();
area.put("china.hunan.changsha.20201127", "中国湖南长沙20201127天气数据");
area.put("china.hubei.wuhan.20201127", "中国湖北武汉20201127天气数据");
area.put("china.hunan.zhuzhou.20201127", "中国湖南株洲20201127天气数据");
area.put("us.cal.lsj.20201127", "美国加州洛杉矶20201127天气数据");
area.put("china.hebei.shijiazhuang.20201128", "中国河北石家庄20201128天气数据");
area.put("china.hubei.wuhan.20201128", "中国湖北武汉20201128天气数据");
area.put("china.henan.zhengzhou.20201128", "中国河南郑州20201128天气数据");
area.put("us.cal.lsj.20201128", "美国加州洛杉矶20201128天气数据");
Connection connection = RabbitUtils.getConnection();
Channel channel = connection.createChannel();
Iterator<Map.Entry<String, String>> itr = area.entrySet().iterator();
while (itr.hasNext()) {
Map.Entry<String, String> me = itr.next();
//第一个参数交换机名字 第二个参数作为 消息的routing key
channel.basicPublish(RabbitConstant.EXCHANGE_WEATHER_TOPIC,me.getKey() , null , me.getValue().getBytes());
}
channel.close();
connection.close();
}
}
- 运行
RabbitMQ消息确认机制
RabbitMQ在传递消息的过程中充当了代理人(Broker)的角色,那生产者(Producer)怎样知道消息被正确投递到Broker了呢?
- RabbitMQ提供了监听器(Listener)来接收消息投递的状态。
- 消息确认涉及两种状态:Confirm与Return。
Confirm
Confirm代表生产者将消息送到Broker时产生的状态,后续会出现两种情况:
- ack代表Broker已经将数据接收。
- nack代表Broker拒收消息。原因有多种,队列已满,限流,IO异常….
Return
Return代表消息被Broker正常接收(ack)后,但Broker没有对应的队列进行投递时产生的状态,消息被退回给生产者。
注意:上面两种状态只代表生产者与Broker之间消息投递的情况。与消费者是否接收/确认消息无关。
案例
- 创建消费者,生产者
package com.baiqi.rabbitmq.confirm;
import com.baiqi.rabbitmq.utils.RabbitConstant;
import com.baiqi.rabbitmq.utils.RabbitUtils;
import com.rabbitmq.client.*;
import java.io.IOException;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.concurrent.TimeoutException;
public class WeatherBureau {
public static void main(String[] args) throws IOException, TimeoutException {
Map area = new LinkedHashMap<String, String>();
area.put("china.hunan.changsha.20201127", "中国湖南长沙20201127天气数据");
area.put("china.hubei.wuhan.20201127", "中国湖北武汉20201127天气数据");
area.put("china.hunan.zhuzhou.20201127", "中国湖南株洲20201127天气数据");
area.put("us.cal.lsj.20201127", "美国加州洛杉矶20201127天气数据");
area.put("china.hebei.shijiazhuang.20201128", "中国河北石家庄20201128天气数据");
area.put("china.hubei.wuhan.20201128", "中国湖北武汉20201128天气数据");
area.put("china.henan.zhengzhou.20201128", "中国河南郑州20201128天气数据");
area.put("us.cal.lsj.20201128", "美国加州洛杉矶20201128天气数据");
Connection connection = RabbitUtils.getConnection();
Channel channel = connection.createChannel();
//开启confirm监听模式
channel.confirmSelect();
channel.addConfirmListener(new ConfirmListener() {
public void handleAck(long l, boolean b) throws IOException {
//第二个参数代表接收的数据是否为批量接收,一般我们用不到。
System.out.println("消息已被Broker接收,Tag:" + l );
}
public void handleNack(long l, boolean b) throws IOException {
System.out.println("消息已被Broker拒收,Tag:" + l);
}
});
channel.addReturnListener(new ReturnCallback() {
public void handle(Return r) {
System.err.println("===========================");
System.err.println("Return编码:" + r.getReplyCode() + "-Return描述:" + r.getReplyText());
System.err.println("交换机:" + r.getExchange() + "-路由key:" + r.getRoutingKey() );
System.err.println("Return主题:" + new String(r.getBody()));
System.err.println("===========================");
}
});
Iterator<Map.Entry<String, String>> itr = area.entrySet().iterator();
while (itr.hasNext()) {
Map.Entry<String, String> me = itr.next();
//Routing key 第二个参数相当于数据筛选的条件
//第三个参数为:mandatory true代表如果消息无法正常投递则return回生产者,如果false,则直接将消息放弃。
channel.basicPublish(RabbitConstant.EXCHANGE_WEATHER_TOPIC,me.getKey() ,true, null , me.getValue().getBytes());
}
//如果关闭则无法进行监听,因此此处不需要关闭
/*channel.close();
connection.close();*/
}
}
package com.baiqi.rabbitmq.confirm;
import com.baiqi.rabbitmq.utils.RabbitConstant;
import com.baiqi.rabbitmq.utils.RabbitUtils;
import com.rabbitmq.client.*;
import java.io.IOException;
public class Sina {
public static void main(String[] args) throws IOException {
Connection connection = RabbitUtils.getConnection();
final Channel channel = connection.createChannel();
channel.queueDeclare(RabbitConstant.QUEUE_SINA, false, false, false, null);
channel.queueBind(RabbitConstant.QUEUE_SINA, RabbitConstant.EXCHANGE_WEATHER_TOPIC, "us.#");
channel.basicQos(1);
channel.basicConsume(RabbitConstant.QUEUE_SINA , false , new DefaultConsumer(channel){
@Override
public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
System.out.println("腾讯天气收到气象信息:" + new String(body));
channel.basicAck(envelope.getDeliveryTag() , false);
}
});
}
}
package com.baiqi.rabbitmq.confirm;
import com.baiqi.rabbitmq.utils.RabbitConstant;
import com.baiqi.rabbitmq.utils.RabbitUtils;
import com.rabbitmq.client.*;
import java.io.IOException;
public class Baidu {
public static void main(String[] args) throws IOException {
Connection connection = RabbitUtils.getConnection();
final Channel channel = connection.createChannel();
channel.queueDeclare(RabbitConstant.QUEUE_BAIDU, false, false, false, null);
//queueBind用于将队列与交换机绑定
//参数1:队列名 参数2:交互机名 参数三:路由key
channel.queueBind(RabbitConstant.QUEUE_BAIDU, RabbitConstant.EXCHANGE_WEATHER_TOPIC, "*.*.*.20201127");
//channel.queueUnbind(RabbitConstant.QUEUE_BAIDU, RabbitConstant.EXCHANGE_WEATHER_TOPIC, "*.*.*.20201127");
//*.hebei.*.*
channel.basicQos(1);
channel.basicConsume(RabbitConstant.QUEUE_BAIDU , false , new DefaultConsumer(channel){
@Override
public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
System.out.println("百度天气收到气象信息:" + new String(body));
channel.basicAck(envelope.getDeliveryTag() , false);
}
});
}
}
案例演示
入门案例演示
- 1.创建两个java项目,并导入依赖包
- 2.编写ConnectionUtil类
package example;
import com.rabbitmq.client.Connection;
import com.rabbitmq.client.ConnectionFactory;
public class ConnectionUtil {
public static final String QUEUE_NAME = "testQueue";
public static final String EXCHANGE_NAME = "exchange";
public static Connection getConnection() throws Exception{
//创建一个连接工厂
ConnectionFactory connectionFactory = new ConnectionFactory();
//设置rabbitmq 服务端所在地址 我这里在本地就是本地
connectionFactory.setHost("127.0.0.1");
//设置端口号,连接用户名,虚拟地址等
connectionFactory.setPort(5672);
connectionFactory.setUsername("test_admin");
connectionFactory.setPassword("123456");
connectionFactory.setVirtualHost("testhost");
return connectionFactory.newConnection();
}
}
- 3.在生产者java项目中编写消息生产者类
package example;
import com.rabbitmq.client.*;
public class Producer {
public static void main(String[] args) throws Exception{
sendByExchange("Hello rabbitMQ!");
}
public static void sendByExchange(String message) throws Exception {
Connection connection = ConnectionUtil.getConnection();
Channel channel = connection.createChannel();
//声明队列
channel.queueDeclare(ConnectionUtil.QUEUE_NAME,true,false,false,null);
// 声明exchange
channel.exchangeDeclare(ConnectionUtil.EXCHANGE_NAME, "fanout");
//交换机和队列绑定
channel.queueBind(ConnectionUtil.QUEUE_NAME, ConnectionUtil.EXCHANGE_NAME, "");
channel.basicPublish(ConnectionUtil.EXCHANGE_NAME, "", null, message.getBytes());
System.out.println("发送的信息为:" + message);
channel.close();
connection.close();
}
}
- 4.在消费者java项目中编写消息消费者类
package example;
import com.rabbitmq.client.*;
import java.io.IOException;
public class Consumer {
public static void main(String[] args) throws Exception{
getMessage();
}
public static void getMessage() throws Exception {
Connection connection = ConnectionUtil.getConnection();
Channel channel = connection.createChannel();
// channel.queueDeclare(ConnectionUtil.QUEUE_NAME,true,false,false,null);
//定义消费者
DefaultConsumer deliverCallback = new DefaultConsumer(channel) {
@Override
public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
System.out.println(new String(body, "UTF-8"));
}
};
//开始消费
channel.basicConsume(ConnectionUtil.QUEUE_NAME, deliverCallback);
}
}
- 5.运行生产者项目,查看rabbitMQ管理面板
- 6.运行消费者项目,查看管理面板
- 7.开启消费者端自动确认,重启服务,并查看管理面板
String basicConsume(String queue, boolean autoAck, Consumer callback) throws IOException;
//开始消费,并自动确认
channel.basicConsume(ConnectionUtil.QUEUE_NAME,true,deliverCallback);
- 8.开启消费者端手动确认,重启服务
void basicAck(long deliveryTag, boolean multiple) throwsIOException;
DefaultConsumer deliverCallback = new DefaultConsumer(channel) {
@Override
public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
System.out.println(new String(body, "UTF-8"));
System.out.println("消息消费成功");
//手动确认
channel.basicAck(envelope.getDeliveryTag(),false);
}
};
fanout交换器的使用案例
- 声明交换器的类型为fanout(广播,绑定的队列都可以收到消息),并为其绑定两个队列testQueue1,testQueue2
package example;
import com.rabbitmq.client.*;
public class Producer {
public static void main(String[] args) throws Exception{
sendByExchange("Hello rabbitMQ!");
}
public static void sendByExchange(String message) throws Exception {
Connection connection = ConnectionUtil.getConnection();
Channel channel = connection.createChannel();
// 声明exchange
channel.exchangeDeclare(ConnectionUtil.EXCHANGE_NAME, "fanout");
//此交换机绑定两个队列
channel.queueBind("testQueue1", ConnectionUtil.EXCHANGE_NAME, "");
channel.queueBind("testQueue2", ConnectionUtil.EXCHANGE_NAME, "");
channel.basicPublish(ConnectionUtil.EXCHANGE_NAME, "", null, message.getBytes());
System.out.println("发送的信息为:" + message);
channel.close();
connection.close();
}
}
package example;
import com.rabbitmq.client.*;
import java.io.IOException;
public class Consumer1 {
public static void main(String[] args) throws Exception{
getMessage();
}
public static void getMessage() throws Exception {
Connection connection = ConnectionUtil.getConnection();
Channel channel = connection.createChannel();
channel.queueDeclare("testQueue1",true,false,false,null);
//定义消费者
DefaultConsumer deliverCallback = new DefaultConsumer(channel) {
@Override
public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
System.out.println(new String(body, "UTF-8"));
}
};
//开始消费
channel.basicConsume("testQueue1",true, deliverCallback);
}
}
package example;
import com.rabbitmq.client.*;
import java.io.IOException;
public class Consumer2 {
public static void main(String[] args) throws Exception{
getMessage();
}
public static void getMessage() throws Exception {
Connection connection = ConnectionUtil.getConnection();
Channel channel = connection.createChannel();
channel.queueDeclare("testQueue2",true,false,false,null);
//定义消费者
DefaultConsumer deliverCallback = new DefaultConsumer(channel) {
@Override
public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
System.out.println(new String(body, "UTF-8"));
}
};
//开始消费
channel.basicConsume("testQueue2",true, deliverCallback);
}
}
- 先运行消费者程序,再运行生产者程序
direct交换器的使用案例
- 声明交换器类型为direct,设置路由键为info.user
package example;
import com.rabbitmq.client.*;
public class Producer {
public static void main(String[] args) throws Exception{
sendByExchange("Hello rabbitMQ!");
}
public static void sendByExchange(String message) throws Exception {
Connection connection = ConnectionUtil.getConnection();
Channel channel = connection.createChannel();
//删除交换机
channel.exchangeDelete(ConnectionUtil.EXCHANGE_NAME);
// 声明exchange
channel.exchangeDeclare(ConnectionUtil.EXCHANGE_NAME, "direct");
//此交换机绑定两个队列
channel.queueBind("testQueue1", ConnectionUtil.EXCHANGE_NAME, "info.user");
channel.queueBind("testQueue2", ConnectionUtil.EXCHANGE_NAME, "error.user");
channel.basicPublish(ConnectionUtil.EXCHANGE_NAME, "info.user", null, message.getBytes());
System.out.println("发送的信息为:" + message);
channel.close();
connection.close();
}
}
- 消费者程序不用变
topic交换器的使用案例
- 声明交换器类型为topic,并绑定三个队列
package example;
import com.rabbitmq.client.*;
public class Producer {
public static void main(String[] args) throws Exception{
sendByExchange();
}
public static void sendByExchange() throws Exception {
String message = "";
Connection connection = ConnectionUtil.getConnection();
Channel channel = connection.createChannel();
//删除交换机
channel.exchangeDelete(ConnectionUtil.EXCHANGE_NAME);
// 声明exchange
channel.exchangeDeclare(ConnectionUtil.EXCHANGE_NAME, BuiltinExchangeType.TOPIC);
//此交换机绑定三个队列
channel.queueBind("testQueue1", ConnectionUtil.EXCHANGE_NAME, "debug.*.B");
channel.queueBind("testQueue2", ConnectionUtil.EXCHANGE_NAME, "error.#");
channel.queueBind("testQueue3", ConnectionUtil.EXCHANGE_NAME, "*.email.*");
//模拟数据并发送
String[] str1 = new String[]{"error","debug","info"};
String[] str2 = new String[]{"user","order","email"};
String[] str3 = new String[]{"A","B","C"};
for (int i = 0; i < 3; i++) {
for (int j = 0; j < 3; j++) {
for (int k = 0; k < 3; k++) {
message = str1[i]+"."+str2[j]+"."+str3[k];
System.out.println(message);
channel.basicPublish(ConnectionUtil.EXCHANGE_NAME, message, null, message.getBytes());
}
}
}
channel.close();
connection.close();
}
}
- 消费者1,2,3代码类似,队列名称修改一下
package example;
import com.rabbitmq.client.*;
import java.io.IOException;
public class Consumer1 {
public static void main(String[] args) throws Exception{
getMessage();
}
public static void getMessage() throws Exception {
Connection connection = ConnectionUtil.getConnection();
Channel channel = connection.createChannel();
channel.queueDeclare("testQueue1",true,false,false,null);
//定义消费者
DefaultConsumer deliverCallback = new DefaultConsumer(channel) {
@Override
public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
System.out.println(new String(body, "UTF-8"));
}
};
//开始消费
channel.basicConsume("testQueue1",true, deliverCallback);
}
}
- 运行结果如下
消费者1:
消费者2:
消费者3:
在SpringBoot项目中使用rabbitMQ
- 1.创建订单服务(rabbit_order)和库存服务(rabbit_stock),并引入以下依赖
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-amqp</artifactId>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.54</version>
</dependency>
</dependencies>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>Finchley.SR2</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
- 2.订单服务代码编写如下:
AppConfig类
package com.example.config;
import org.springframework.amqp.core.Binding;
import org.springframework.amqp.core.BindingBuilder;
import org.springframework.amqp.core.DirectExchange;
import org.springframework.amqp.core.Queue;
import org.springframework.amqp.rabbit.connection.CachingConnectionFactory;
import org.springframework.amqp.rabbit.connection.ConnectionFactory;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.boot.web.embedded.tomcat.TomcatServletWebServerFactory;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
@Configuration
@ComponentScan("com")
public class RabbitmqConfig {
@Bean
public ConnectionFactory connectionFactory(){
CachingConnectionFactory connectionFactory = new CachingConnectionFactory("localhost",5672);
connectionFactory.setUsername("test_admin");
connectionFactory.setPassword("123456");
connectionFactory.setVirtualHost("testhost");
//是否开启消息确认机制
connectionFactory.setPublisherConfirms(true);
return connectionFactory;
}
/**
* 使用direct类型的交换器
* @return
*/
@Bean
public DirectExchange defaultExchange() {
return new DirectExchange("directExchange");
}
@Bean
public Queue queue() {
//名字是否持久化
return new Queue("testQueue4", true);
}
@Bean
public Binding binding() {
//绑定一个队列to: 绑定到哪个交换机上面 with:绑定的路由建(routingKey)
return BindingBuilder.bind(queue()).to(defaultExchange()).with("direct.key");
}
@Bean
public TomcatServletWebServerFactory tomcatServletWebServerFactory(){
TomcatServletWebServerFactory tomcatServletWebServerFactory
=new TomcatServletWebServerFactory(8081);
return tomcatServletWebServerFactory;
}
@Bean
public RabbitTemplate rabbitTemplate(ConnectionFactory connectionFactory){
RabbitTemplate rabbitTemplate = new RabbitTemplate(connectionFactory);
return rabbitTemplate;
}
}
Rabbit工具类
package com.example.util;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
@Component
public class RabbitmqMessageSend {
@Autowired
RabbitTemplate rabbitTemplate;
/**
* String exchange, String routingKey, final Object object
* @param exchange
* @param routingKey
* @param message
*/
public void sendMessage(String exchange,String routingKey,String message){
rabbitTemplate.convertAndSend(exchange, routingKey, message);
}
}
订单Controller
package com.example.controller;
import com.example.util.RabbitmqMessageSend;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class OrderController {
@Autowired
RabbitmqMessageSend rabbitmqMessageSend;
@RequestMapping("/order.do")
public Object order(String exchange,String routingKey,String message){
rabbitmqMessageSend.sendMessage(exchange,routingKey,message);
return "下单成功";
}
}
- 3.订单服务运行结果如下(连续调用两次接口)
- 4库存服务代码如下(AppConfig和订单服务类似)
package com.example.util;
import org.springframework.amqp.core.Message;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.stereotype.Component;
@Component
public class RabbitmqUtil {
@RabbitListener(queues = "testQueue4")
public void get(Message message) throws Exception{
System.out.println(new String(message.getBody(),"UTF-8"));
System.out.println("消费者1");
}
}
- 5.库存服务运行结果如下
总结
RabbitMQ如何解决重复消费的问题
思路:给业务数据增加“是否消费”的字段。消费者在消费数据之前先查询业务数据进行判断该业务数据是否被消费过,如果未消费,则进行消费。如果已消费,则丢弃该消息。
例如:订单数据记录表增加state字段状态,0表示未消费,1表示已消费
RabbitMQ什么时候选择消费端自动确认,什么时候选择手动确认
根据应用场景选择
例如:如果使用rabbitMQ处理日志的话,不影响系统的正常使用,可以使用消费端自动确认。
如果使用rabbitMQ处理减库存的业务操作,则需要手动确认。订单服务,库存服务。先处理订单服务,再处理减库存服务。
RabbitMQ如何保证消息可靠性
确保rabbitMQ消息的可靠性要做到三点:
- publisher confirms(发布方确认):
确保producer到broker节点消息的可靠性。如可能发生消息到了broker但是还没有投递到queue,broker突然宕机这种情况; - message持久化:
将message存储到硬盘,如果是未持久化的消息会存在内存中,broker宕机后重启内存中的消息会丢失 - acknowledgement(consumer确认):
该机制能够保证,只有consumer成功消费了消息,才将其从queue中移除。