一、安装
使用docker 进行安装
docker pull rabbitmq:management
docker run -d --name rabbitmq89 -e RABBITMQ_DEFAULT_USER=admin -e RABBITMQ_DEFAULT_PASS=123456 -p 15672:15672 -p 5672:5672 rabbitmq:management
二、RabbitMQ 消息模型
在进行RabbitMQ消息模型之前需要在父项目中添加依赖。
<!--SpringBoot的父项目-->
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.3.8.RELEASE</version>
</parent>
<!--统一添加依赖,子项目可以直接使用-->
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!--SpringBoot集成RabbitMQ-->
<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>
<exclusions>
<exclusion>
<groupId>org.junit.vintage</groupId>
<artifactId>junit-vintage-engine</artifactId>
</exclusion>
</exclusions>
</dependency>
</dependencies>
1、Work queues
(1)yml配置
spring:
rabbitmq:
host: 192.xxx.xx.134
port: 5672
virtual-host: /
username: admin
password: xxxxxx
(2)编写生产者配置
@Component
public class WorkProducer {
@Autowired
private RabbitTemplate rabbitTemplate;
public void sendMessage(){
// 发送mq消息到消息队列中
// 参数1:交换机。 如果是简单模型或工作队列模型,这里是空字符串;使用默认的交换机
// 参数2:路由key。如果是简单模型或工作队列模型,这里传入队列名称
// 参数3:消息内容
rabbitTemplate.convertAndSend("","queue-simple","你好,世界");
}
// 创建队列
@Bean
public Queue createQueue(){
return QueueBuilder.durable("queue-simple").build();
}
// 如果没有自动创建队列,需要配置RabbitAdmin
@Bean
public RabbitAdmin rabbitAdmin(ConnectionFactory connectionFactory) {
RabbitAdmin rabbitAdmin = new RabbitAdmin(connectionFactory);
rabbitAdmin.setAutoStartup(true);
return rabbitAdmin;
}
}
(3)编写消费者
/**
* 消息的消费者,监听队列中的消息
* 监听哪个队列?queue-simple
*/
@Component
public class WorkConsumer {
/**
* @RabbitListener
* 1、消息监听器注解,可以监听指定队列中的消息
* 2、queuesToDeclare 指定要监听的队列,如果队列不存在会自动创建
* 3、queues 也可以指定监听的队列,如果队列不存在不会自动创建
*/
@RabbitListener(queuesToDeclare = @Queue(name = "queue-simple"))
public void handlerMsg(String message) {
System.out.println("消费者消费消息:" + message);
}
}
2、fanout 交换机
(1)消费者
/**
* 消费者
* 消息模型:Publsh/Subscribe 发布订阅
*/
@Component
public class SeatConsumer {
/**
* 监听队列消息
* 创建队列:queue-fanout-seat (如果不存在就创建;如果存在不会更新)
* 创建交换机:exchange-fanout
* 把queue-fanout-seat队列绑定到exchange-fanout交换机
*/
@RabbitListener(bindings = @QueueBinding(
value = @Queue(name = "queue-fanout-seat",durable = "true"),
exchange = @Exchange(name = "exchange-fanout",type = ExchangeTypes.FANOUT)
))
public void seat(String message) {
System.out.println("消费者锁定座位:" + message);
}
}
@Component
public class MemberConsumer {
@RabbitListener(bindings = @QueueBinding(
value = @Queue(name = "queue-fanout-member"),
exchange = @Exchange(name = "exchange-fanout",type = ExchangeTypes.FANOUT)
))
public void handlerMessage(String msg) {
System.out.println("消费者添加积分:"+msg);
}
}
(3)生产者
@Component
public class FanoutProducer {
@Autowired
private RabbitTemplate rabbitTemplate;
public void sendMessage(String userId){
rabbitTemplate.convertAndSend("exchange-fanout","","用户"+userId+"买了票");
}
}
3、direct 交换机
(1)消费者
@Component
public class ErrorConsumer {
@RabbitListener(bindings = @QueueBinding(
value = @Queue(name = "queue-direct-error"),
exchange = @Exchange(name = "direct-log",type = ExchangeTypes.DIRECT),
key = "error"
))
public void handlerMessage(String msg) {
System.out.println("消费者处理错误日志消息:"+msg);
}
}
(2)生产者
@Component
public class DirectProducer {
@Autowired
private RabbitTemplate rabbitTemplate;
public void sendLog(String exchange,String routingKey,String msg){
rabbitTemplate.convertAndSend(exchange,routingKey,msg);
}
}
4、topic 交换机
(1)消费者
@Component
public class C1 {
@RabbitListener(bindings = @QueueBinding(
value = @Queue(name = "Q1"),
exchange = @Exchange(name = "exchange-topic",type = ExchangeTypes.TOPIC),
key = "*.orange.*"
))
public void handlerMsg(String msg) {
System.out.println("消费者Q1:" + msg);
}
}
@Component
public class C2 {
@RabbitListener(bindings = @QueueBinding(
value = @Queue(name = "Q2"),
exchange = @Exchange(name = "exchange-topic",type = ExchangeTypes.TOPIC),
key = {"*.*.rabbit","lazy.#"}
))
public void handlerMsg(String msg) {
System.out.println("消费者Q2:" + msg);
}
}
(2)生产者
@Component
public class TopicProducer {
@Autowired
private RabbitTemplate rabbitTemplate;
public void handlerMessage(String exchange,String routingKey,String msg) {
rabbitTemplate.convertAndSend(exchange,routingKey,msg);
}
}
5、RPC
(1)服务端
@Component
public class Server {
@RabbitListener(bindings = @QueueBinding(
value = @Queue(name = "queue-rpc"),
exchange = @Exchange(name = "exchange-rpc",type = ExchangeTypes.TOPIC),
key = "rpc"
))
public String handlerMsg(String msg) {
System.out.println("服务端获取的数据:" + msg);
return "服务端返回的数据";
}
}
(2)客户端
@Component
public class ClientProducer {
@Autowired
private RabbitTemplate rabbitTemplate;
public void sendMsg(String exchange,String routingKey,String msg) {
Object serverReturn = rabbitTemplate.convertSendAndReceive(exchange, routingKey, msg);
System.out.println("serverReturn = " + serverReturn);
}
}
6、PC
(1)服务端消息可靠性保证
1.yml配置
spring:
rabbitmq:
host: 192.xxx.xx.134
port: 5672
virtual-host: /
username: admin
password: xxxxxx
publisher-confirm-type: correlated
2.配置类
PostConstruct 注释用于在依赖关系注入完成之后需要执行的方法上,以执行任何初始化。此方法必须在将类放入服务之前调用。因为我在这个类中注入了其它服务,因此我使用这个注解在其它服务加载完成后进行初始化.
@Component
public class ConfirmProducer {
@Autowired
private RabbitTemplate rabbitTemplate;
@Autowired
private RabbitConfirm rabbitConfirm;
@PostConstruct
public void init(){
rabbitTemplate.setConfirmCallback(rabbitConfirm);
// true 表示消息未到达队列时会把消息返回给生产者;false会直接丢弃消息
rabbitTemplate.setMandatory(true);
rabbitTemplate.setReturnCallback(rabbitConfirm);
}
public void producerConfirm() {
rabbitTemplate.convertAndSend("exchange-topic-confirm","confirm.msg","确认消息");
}
}
@Component
public class RabbitConfirm implements
RabbitTemplate.ConfirmCallback, RabbitTemplate.ReturnCallback {
@Override
public void confirm(CorrelationData correlationData, boolean ack, String cause) {
System.out.println("correlationData = " + correlationData);
System.out.println("ack = " + ack); //false
System.out.println("cause = " + cause);
if (ack) {
System.out.println("消息发送到交换机确认成功!");
} else {
System.out.println("消息发送到交换机失败,接下来根据业务做处理,比如:重发");
}
}
@Override
public void returnedMessage(Message message, int replyCode, String replyText, String exchange, String routingKey) {
// 消息无法路由到队列时候,再这里可以获取消息内容
System.out.println("=======消息回退:ReturnCallback=======");
System.out.println("消息内容 = " + new String(message.getBody()));
System.out.println("响应编码 = " + replyCode);
System.out.println("消息无法路由的原因 = " + replyText);
System.out.println("交换机 = " + exchange);
System.out.println("路由key = " + routingKey);
}
}
(2)消费端消息可靠性保证
1.yml配置
spring:
rabbitmq:
host: 192.168.12.134
port: 5672
username: admin
password: 123456
virtual-host: /
listener:
simple:
retry:
# 开启重试次数的限制,解决消费者出现异常死循环问题
enabled: true
# 最大重试次数
max-attempts: 3
# 开启消费者手动确认模式 (channel.bacisAck)
acknowledge-mode: manual
2.配置类
死信配置:
@Component
public class DLQConsumer {
@RabbitListener(bindings = @QueueBinding(
value = @Queue(name = "queue-dlx"),
exchange = @Exchange(name = "exchange-dlx",type = ExchangeTypes.TOPIC),
key = "dlx.#"
))
public void handlerMsg(String msg,Channel channel, Message message) throws IOException {
System.out.println("死信队列处理消息:" + msg);
// 消费后消息还在,因为没有确认。
}
}
消费者配置:
@Component
public class ConfirmConsumer {
@RabbitListener(bindings = @QueueBinding(
value = @Queue(name = "queue-confirm",arguments = {
@Argument(name = "x-dead-letter-exchange",value = "exchange-dlx"),
@Argument(name = "x-dead-letter-routing-key",value = "dlx.ooo"),
@Argument(name = "x-message-ttl",value = "100000",type = "java.lang.Long"),
@Argument(name = "x-max-length",value = "3",type = "java.lang.Long"),
}),
exchange = @Exchange(name = "exchange-confirm",type = ExchangeTypes.TOPIC),
key = "confirm.*"
))
public void handlerMsg(String msg, Channel channel, Message message) throws IOException {
try {
//int i=1/0;
System.out.println("消息:" + msg);
// 消费者手动确认消息消费; 参数1:消息id;参数2:是否批量确认
channel.basicAck(message.getMessageProperties().getDeliveryTag(),true);
} catch (Exception e) {
e.printStackTrace();
// 需求:消息消费失败了,希望把消息重投到队列中,再次进行消费,如果还是消费失败就拒绝消息
// 获取消息是否是重投的标记
Boolean redelivered = message.getMessageProperties().getRedelivered();
if (redelivered) {
// 重投消息:可以拒绝不处理..
channel.basicReject(message.getMessageProperties().getDeliveryTag(),false);
System.out.println("消息重投后消费消息失败,拒绝处理");
} else {
// 未重投过:重投
channel.basicNack(message.getMessageProperties().getDeliveryTag(),true,true);
System.out.println("消息消费异常,进行重投!");
}
}
}
}
三、MQ实现延迟消息
MQ实现延迟消息有很多种方法,这里就介绍最简单的一种,那就是通过死信队列实现延迟消息。实现思路:创建普通队列和私信队列两个队列,然后创建一个死信消费者,但是不创建普通队列消费者。这样过期之间一到普通队列里的消息就会跑到死信队列中,便可以实现延迟消费了。
(1)生产者
1、添加依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-amqp</artifactId>
</dependency>
2、yml配置
spring:
rabbitmq:
host: 192.168.12.134
port: 5672
username: admin
password: 123456
virtual-host: /
3、配置类
配置类里面有死信队列和普通队列,在后面写消费者的时候绑定的交换机必须是这里写的死信交换机。
package com.wnxy.sk.product.config;
@Configuration
public class RabbitConfig {
@Bean
public MessageConverter messageConverter() {
return new Jackson2JsonMessageConverter();
}
// 交换机
@Bean("orderExchange")
public Exchange orderExchange() {
return new TopicExchange("order-exchange", true, false);
}
// 队列
@Bean("ttlOrderQueue")
public Queue ttlOrderQueue() {
Map params = new HashMap();
params.put("x-message-ttl", 20000);
params.put("x-dead-letter-exchange", "order-dead-letter-exchange");
params.put("x-dead-letter-routing-key", "dead.letter.order");
return new Queue("ttl-order-queue", true, false, false, params);
}
// 绑定
@Bean
public Binding bindTTLOrderQueue(
@Qualifier("ttlOrderQueue") Queue queue,
@Qualifier("orderExchange") Exchange exchange) {
return BindingBuilder.bind(queue).to(exchange)
.with("order.#").noargs();
}
// 死信交换机
@Bean("orderDeadLetterExchange")
public Exchange orderDeadLetterExchange() {
return new TopicExchange("order-dead-letter-exchange", true, false);
}
// 死信队列
@Bean("orderDeadLetterQueue")
public Queue orderDeadLetterQueue() {
return new Queue("order-dead-letter-queue", true, false, false);
}
// 死信绑定
@Bean
public Binding bindOrderDeadLetterQueue(
@Qualifier("orderDeadLetterQueue") Queue queue,
@Qualifier("orderDeadLetterExchange") Exchange exchange) {
return BindingBuilder.bind(queue)
.to(exchange)
.with("dead.letter.order").noargs();
}
@Bean
public RabbitAdmin rabbitAdmin(
ConnectionFactory factory,
Exchange orderExchange,
Exchange orderDeadLetterExchange,
Queue ttlOrderQueue,
Queue orderDeadLetterQueue) {
RabbitAdmin rabbitAdmin = new RabbitAdmin(factory);
rabbitAdmin.setAutoStartup(true);
rabbitAdmin.declareExchange(orderExchange);
rabbitAdmin.declareExchange(orderDeadLetterExchange);
rabbitAdmin.declareQueue(ttlOrderQueue);
rabbitAdmin.declareQueue(orderDeadLetterQueue);
return rabbitAdmin;
}
}
4、消费者
这里的消费者发送的队列和交换机还是普通的队列和交换机
@Component
public class OrderProducer {
@Autowired
private RabbitTemplate rabbitTemplate;
public void sendOrder(OrderPayDto orderDto){
rabbitTemplate.convertAndSend(
"order-exchange","order."+orderDto.getOrderSn(),orderDto);
}
}
(2)消费者
1、依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-amqp</artifactId>
</dependency>
2、yml配置
spring:
rabbitmq:
host: 192.168.12.134
port: 5672
username: admin
password: 123456
virtual-host: /
listener:
simple:
retry:
max-attempts: 3 #设置最大尝试消费次数
enabled: true #开启重试
# 开启消费者手动确认模式 (channel.bacisAck)
acknowledge-mode: manual
3、配置类
@Bean
public MessageConverter messageConverter(){
return new Jackson2JsonMessageConverter();
}
4、消费者
这里绑定的队列和交换机是死信队列和死信交换机
@Component
@Slf4j
public class OrderConsumer {
@Autowired
private IOrderService orderService;
@Autowired
private RedisTemplate redisTemplate;
@RabbitHandler
@RabbitListener(bindings = @QueueBinding(
value = @Queue(value = "order-dead-letter-queue", durable = "true",
autoDelete = "false", exclusive = "false"),
exchange = @Exchange(value = "order-dead-letter-exchange", durable = "true",
autoDelete = "false", type = ExchangeTypes.TOPIC),
key = "dead.letter.order")
)
public void handleOrder(@Payload OrderPayDto orderPayDto, Channel channel, Message message) {
log.info("消费死信队列中的超时消息 {}",orderPayDto);
//判断从mq中获取到的超时数据,订单的状态是否为待支付,如果为待支付状态,则更新订单失效。
OrderVo orderVo = orderService.findByOrderSn(orderPayDto.getOrderSn());
if (orderVo.getStatus().equals(OrderConstant.ORDER_STATUS_UNPAY)) {//0待付款
log.info("订单失效,修改订单状态为5,无效订单");
LambdaUpdateWrapper<Order> updateWrapper = Wrappers.lambdaUpdate();
updateWrapper.set(Order::getStatus, OrderConstant.ORDER_STATUS_INVALID);//已失效
updateWrapper.eq(Order::getOrderSn, orderPayDto.getOrderSn());
orderService.update(updateWrapper);
//将redis中扣减的库存,还原
redisTemplate.opsForList().leftPush(
"seckill:count:" + orderPayDto.getPromotionId(),
orderPayDto.getProductId());
}
try {
//手动应答
log.info("channel.basicAck start");
channel.basicAck(message.getMessageProperties().getDeliveryTag(), false);
log.info("channel.basicAck end");
} catch (IOException e) {
e.printStackTrace();
}
}
}
四、其它配置
1、生产者创建交换机、队列
(1)配置类
@Configuration
public class RabbitConfig {
/**
* 创建队列: queue-confirm
*/
@Bean("confirmQueue")
public Queue createConfirmQueue() {
Map<String,Object> params = new HashMap<>();
params.put("x-message-ttl",50000);
params.put("x-dead-letter-exchange","exchange-dlx");
params.put("x-dead-letter-routing-key","dlx.ooo");
return new Queue("queue-confirm",true,false,false,params);
}
/**
* 创建交换机:exchange-confirm
*/
@Bean("exchangeConfirm")
public Exchange exchangeConfirm(){
return new TopicExchange("exchange-confirm",true,false);
}
/**
* 绑定:把队列queue-confirm绑定到exchange-confirm
* @Qualifier
* 1、只会根据名称去容器中查找改名称对应的对象
* 2、默认会根据类型去容器中查找
*/
@Bean
public Binding bindConfirmQueue(
@Qualifier("exchangeConfirm") Exchange exchange,
@Qualifier("confirmQueue") Queue queue) {
return BindingBuilder
.bind(queue)
.to(exchange).with("confirm.#").noargs();
}
/**
* 死信交换机:exchange-dlx
*/
@Bean("dlxExchange")
public Exchange dlxExchange(){
return new TopicExchange("exchange-dlx",true,false);
}
/**
* 死信队列:queue-dlx
*/
@Bean("dlxQueue")
public Queue createDLXQueue() {
return new Queue("queue-dlx",true,false,false);
}
/**
* 绑定:把死信队列queue-dlx绑定到死信交换机exchange-dlx
*/
@Bean
public Binding bindDlxQueue(
@Qualifier("dlxExchange") Exchange exchange,
@Qualifier("dlxQueue") Queue queue) {
return BindingBuilder
.bind(queue)
.to(exchange).with("dlx.#").noargs();
}
}
2、发送接收对象
(1)生产者
1.配置类
@Configuration
public class RabbitMQConfig {
// 发送对象时自动转为json的配置类
@Bean
public MessageConverter messageConverter(){
return new Jackson2JsonMessageConverter();
}
}
(2)消费者
下面这个注解写在要接收的对象上面
@Payload
添加配置类
@Configuration
public class RabbitMQConfig {
// 发送对象时自动转为json的配置类
@Bean
public MessageConverter messageConverter(){
return new Jackson2JsonMessageConverter();
}
}