1.死信队列
死信队列:DLX,dead-letter-exchange
利用DLX,当消息在一个队列中变成死信 (dead message) 之后,它能被重新publish到另一个Exchange,
这个Exchange就是DLX
消息成为死信的三种情况:
1. 队列消息长度到达限制;
2. 消费者拒接消费消息,basicNack/basicReject,并且不把消息重新放入原目标队列,requeue=false;
3. 原队列存在消息过期设置,消息到达超时时间未被消费;
如何实现死信队列呢?
(1)给队列绑定死信交换机
给队列设置参数:x-dead-letter-exchange 和 x-dead-letter-routing-key
代码实现如下:
先创建死信交换机,死信队列,普通交换机,普通队列,然后将普通队列与普通交换机绑定,死信队列与死信交换机绑定,为普通队列设置参数,为了消息过期后,将消息发送到死信交换机上
package com.wyj;
import org.springframework.amqp.core.*;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
/**
* @author 王逸君
* @version 1.0
* @date 2021/4/22 19:08
*/
@Configuration
public class Config {
private final String EXCHANGE="exchange";
private final String QUEUE="queue";
private final String DEAD_EXCHANGE="dead_exchange";
private final String DEAD_QUEUE="dead_queue";
//队列
@Bean
public Queue queue(){
return QueueBuilder
.durable(QUEUE)
.withArgument("x-dead-letter-exchange",DEAD_EXCHANGE)//设置死信交换机
.withArgument("x-dead-letter-routing-key","error")//设置队列与死信交换机的rootingkey
.build();
}
//死信队列
@Bean
public Queue deadQueue(){
return QueueBuilder.durable(DEAD_QUEUE).build();
}
//创建交换机
@Bean
public Exchange exchange(){
return ExchangeBuilder.directExchange(EXCHANGE).build();
}
//创建死信交换机
@Bean
public Exchange deadExchange(){
return ExchangeBuilder.directExchange(DEAD_EXCHANGE).build();
}
//绑定交换机与队列
@Bean
public Binding binding(){
return BindingBuilder.bind(queue()).to(exchange()).with("error").noargs();
}
//绑定死信队列与死信交换机
@Bean
public Binding deadBinding(){
return BindingBuilder.bind(deadQueue()).to(deadExchange()).with("error").noargs();
}
}
接着创建消费者,进行测试:
package com.wyj.listener;
import com.rabbitmq.client.Channel;
import org.springframework.amqp.core.Message;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Component;
import java.io.IOException;
/**
* @author 王逸君
* @version 1.0
* @date 2021/4/22 20:36
*/
@Component
public class Mylistener {
@Autowired
private RedisTemplate redisTemplate;
@RabbitListener(queues = "queue")
public void listener(Message message, Channel channel) throws Exception{
//判断消息是否被消费了
Object o = redisTemplate.opsForValue().get(message.getMessageProperties().getDeliveryTag());
if (o==null){
//执行业务代码
System.out.println("完成业务功能");
//设置消息的唯一标识
redisTemplate.opsForValue().set(message.getMessageProperties().getDeliveryTag(),"wyj");
try {
channel.basicAck(message.getMessageProperties().getDeliveryTag(),true);
} catch (IOException e) {
channel.basicNack(message.getMessageProperties().getDeliveryTag(),true,true);
}
}else {
channel.basicAck(message.getMessageProperties().getDeliveryTag(),true);
}
}
}
2.延迟队列
rabbitMQ没有直接实现延迟队列,我们可以通过设置死信队列,以及对消息设置过期时间来实现延迟队列,即使用DLX和TTL实现延迟队列
代码实现如下:
生产者:
package com.wyj;
import org.springframework.amqp.core.*;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
/**
* @author 王逸君
* @version 1.0
* @date 2021/4/22 19:08
*/
@Configuration
public class Config {
private final String EXCHANGE="exchange";
private final String QUEUE="queue";
private final String DEAD_EXCHANGE="dead_exchange";
private final String DEAD_QUEUE="dead_queue";
//队列
@Bean
public Queue queue(){
return QueueBuilder
.durable(QUEUE)
.withArgument("x-message-ttl",10000)//设置过期时间
.withArgument("x-max-length",10)//设置消息的最大存储条数
.withArgument("x-dead-letter-exchange",DEAD_EXCHANGE)//设置死信交换机
.withArgument("x-dead-letter-routing-key","error")//设置队列与死信交换机的rootingkey
.build();
}
//死信队列
@Bean
public Queue deadQueue(){
return QueueBuilder.durable(DEAD_QUEUE).build();
}
//创建交换机
@Bean
public Exchange exchange(){
return ExchangeBuilder.directExchange(EXCHANGE).build();
}
//创建死信交换机
@Bean
public Exchange deadExchange(){
return ExchangeBuilder.directExchange(DEAD_EXCHANGE).build();
}
//绑定交换机与队列
@Bean
public Binding binding(){
return BindingBuilder.bind(queue()).to(exchange()).with("error").noargs();
}
//绑定死信队列与死信交换机
@Bean
public Binding deadBinding(){
return BindingBuilder.bind(deadQueue()).to(deadExchange()).with("error").noargs();
}
}
3.消息的幂等性保障
幂等性:一次和多次请求某一个资源对于资源本身应该具有同样的效果,也就是说,其任意多次执行对资源本
身所产生的的影响均与一次执行的影响相同,保证消息不会被重复消费。
如何实现消息的幂等性?
(1)消费者获取到消息后先根据id查询redis/db中是否存在该消息
(2)如果存在,则正常消费,消费完毕后写入redis/db
(3)如果不存在,则证明消息被消费过,直接丢弃
代码实现如下:
消费者:
package com.wyj.listener;
import com.rabbitmq.client.Channel;
import org.springframework.amqp.core.Message;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Component;
import java.io.IOException;
/**
* @author 王逸君
* @version 1.0
* @date 2021/4/22 20:36
*/
@Component
public class Mylistener {
@Autowired
private RedisTemplate redisTemplate;
@RabbitListener(queues = "queue")
public void listener(Message message, Channel channel) throws Exception{
//判断消息是否被消费了
Object o = redisTemplate.opsForValue().get(message.getMessageProperties().getDeliveryTag());
if (o==null){
//执行业务代码
System.out.println("完成业务功能");
//设置消息的唯一标识
redisTemplate.opsForValue().set(message.getMessageProperties().getDeliveryTag(),"wyj");
try {
channel.basicAck(message.getMessageProperties().getDeliveryTag(),true);
} catch (IOException e) {
channel.basicNack(message.getMessageProperties().getDeliveryTag(),true,true);
}
}else {
channel.basicAck(message.getMessageProperties().getDeliveryTag(),true);
}
}
}
4.rabbitMQ伪集群的搭建
(1)停止rabbitMQ服务
service rabbitmq-server stop
(2)开启第一个节点
RABBITMQ_NODE_PORT=5673 RABBITMQ_NODENAME=rabbit1 rabbitmq-server start
(3)开启第二个节点
RABBITMQ_NODE_PORT=5674 RABBITMQ_SERVER_START_ARGS="-rabbitmq_management listener [{port,15674}]" RABBITMQ_NODENAME=rabbit2 rabbitmq-server start
(4)设置主从关系。将rabbit1设置为主节点,rabbit2设置为从节点
先操作rabbit1
[root@super ~]# rabbitmqctl -n rabbit1 stop_app
Stopping node rabbit1@super ...
[root@super ~]# rabbitmqctl -n rabbit1 reset
Resetting node rabbit1@super ...
[root@super ~]# rabbitmqctl -n rabbit1 start_app
Starting node rabbit1@super ...
再操作rabbit2
[root@super ~]# rabbitmqctl -n rabbit2 stop_app
Stopping node rabbit2@super ...
[root@super ~]# rabbitmqctl -n rabbit2 reset
Resetting node rabbit2@super ...
[root@super ~]# rabbitmqctl -n rabbit2 join_cluster rabbit1@'localhost' ###''内是主机名换成自己的
Clustering node rabbit2@super with rabbit1@super ...
[root@super ~]# rabbitmqctl -n rabbit2 start_app
Starting node rabbit2@super ...