rabbitMQ04死信队列 ,延迟队列,消息幂等性保障,rabbitMQ集群,RabbitMQ镜像集群配置,HaProxy负载均衡RabbitMQ
死信队列
死信队列,英文缩写:DLX 。Dead Letter Exchange(死信交换机),当消息成为Dead message后,可以被重新发送到另一个交换机,这个交换机就是DLX。
消息成为死信的三种情况:
- 队列消息长度到达限制;
- 消费者拒接消费消息,basicNack/basicReject,并且不把消息重新放入原目标队列,requeue=false(不重复发送);
- 原队列存在消息过期设置,消息到达超时时间未被消费;
队列绑定死信交换机:
给队列设置参数: x-dead-letter-exchange 和 x-dead-letter-routing-key
用代码建立交换机队列,并绑定(以下代码就是指代死信队列的图)
package com.zz;
import org.springframework.amqp.core.*;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class RabbitConfig {
private final String EXCHANGE = "exchange";
// 死信交换机
private final String DEAD_EXCHANGE = "dead_exchange";
private final String QUEUE = "queue";
// 死信队列
private final String DEAD_QUEUE = "dead_queue";
@Bean
public Queue queue() {
// durable 要不要持久化 withArgument有什么参数
return QueueBuilder.durable(QUEUE).withArgument("x-message-ttl", 20000)
.withArgument("x-max-length", 10)
.withArgument("x-dead-letter-exchange", DEAD_EXCHANGE)
.withArgument("x-dead-letter-routing-key", "error")
.build();
// x-message-ttl 给消息设置过期时间
// x-max-length 最大长度
// x-dead-letter-exchange 表示要绑定的死亡路由器
// x-dead-letter-routing-key 表示路由key 后面参数表示按照error绑定
// build()创建
}
//
//死信队列
@Bean
public Queue dead_queue() {
return QueueBuilder.durable(DEAD_QUEUE).build();
}
// 普通交换机
@Bean
public Exchange exchange() {
return ExchangeBuilder.directExchange(EXCHANGE).build();
}
//死信交换机
@Bean
public Exchange dead_exchange() {
return ExchangeBuilder.directExchange(DEAD_EXCHANGE).build();
}
// 绑定普通交换机与队列
@Bean
public Binding binding() {
// noargs 表示没有其他参数
// 这里的error要跟可以不跟上面queue()里得error对应
return BindingBuilder.bind(queue()).to(exchange()).with("error").noargs();
}
@Bean
public Binding dead_binding() {
// 这里的error要跟上面queue()里得error对应
return BindingBuilder.bind(dead_queue()).to(dead_exchange()).with("error").noargs();
}
}
测试消息到达超时时间
queue队列20秒结束后就会将值转移到dead_queue队列
代码:
@SpringBootTest
public class TestRabbit {
@Resource
private RabbitTemplate rabbitTemplate;
@Test
public void testDeadQueue(){
rabbitTemplate.convertAndSend("exchange","error","这个将要新建成功");
}
}
执行前:
执行后:
队列消息长度到达限制
queue队列添加10个经过20秒后结束后就会将值转移到dead_queue队列
代码:
// 测试最大长度10就转移到死信队列
@Test
public void testDeadQueue1(){
for (int i = 0; i < 10; i++) {
rabbitTemplate.convertAndSend("exchange","error","这个时10给消息");
}
}
执行前:
执行后:
消费者拒接消费消息,basicNack/basicReject,并且不把消息重新放入原目标队列,requeue=false(不重复发送);
消费者拒接消费消息,调用这个方法basicNack,下列的false表示不重新发送,然后就直接进入到dead_exchange队列中
channel.basicNack(msg.getMessageProperties().getDeliveryTag(),true,false);
package com.zz;
import com.rabbitmq.client.Channel;
import org.springframework.amqp.core.Message;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.stereotype.Component;
import java.io.IOException;
@Component
public class MyListener {
@RabbitListener(queues = "queue")
public void listener(Message msg, Channel channel){
System.out.println(new String(msg.getBody()));
try {
// 如果出现异常,则调用channel.basicNack()方法,让其自动重新发送消息。
channel.basicNack(msg.getMessageProperties().getDeliveryTag(),true,false);
} catch (IOException e) {
e.printStackTrace();
}
}
}
执行前:
执行后:
注意:
1.死信交换机和死信队列和普通的没有区别。死信交换机主要接收队列的消息,普通交换机接收生产者消息。
2.当消息成为死信后,如果该队列绑定了死信交换机,则消息会被死信交换机重新路由到死信队列
延迟队列
延迟队列,即消息进入队列后不会立即被消费,只有到达指定时间后,才会被消费。
需求:
1.下单后,30分钟未支付,取消订单,回滚库存。
2.新用户注册成功7天后,发送短信问候.
实现方式:
1.定时器
2.延迟队列
但是,在RabbitMQ中并未提供延迟队列功能。
但是可以使用:TTL+死信队列组合实现延迟队列的效果.
测试类:
@Test
public void testDeadQueue2(){
rabbitTemplate.convertAndSend("exchange","error","下单成功");
}
模拟死信队列20秒之后把消息传到库存:
@Component
public class MyListener {
@RabbitListener(queues = "dead_queue")
private void listener1(Message msg,Channel channel){
System.out.println("30分钟没有出来订单,取消订单并回滚库存");
//消费的是死信队列的消息
try {
//从队列中删除该消息。
channel.basicAck(msg.getMessageProperties().getDeliveryTag(),true);
} catch (IOException e) {
// 如果出现异常,则调用channel.basicNack()方法,让其自动重新发送消息。
try {
channel.basicNack(msg.getMessageProperties().getDeliveryTag(),true,true);
} catch (IOException ioException) {
ioException.printStackTrace();
}
e.printStackTrace();
}
}
}
消息幂等性保障
**幂等性:就是用户对于同一操作发起的一次请求或者多次请求的结果是一致的,不会因为多次点击而产生了副作用。**举个最简单的例子,那就是支付,用户购买商品后支付,支付扣款成功,但是返回结果的时候网络异常,此时钱已经扣了,用户再次点击按钮,此时会进行第二次扣款,返回结果成功,
幂等性: 无论执行多少次,得到的结果和第一次都是相同的。 根据id=2查询. 删除 id=1.
保证消息不被重复消费。
注意:以下的setnx没用,切记误导。
如何保障消息的幂等性:
引入redis依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
代码:
// 消息幂等性
@Resource
private RedisTemplate redisTemplate;
@RabbitListener(queues = "queue")
public void listener3(Message msg, Channel channel)throws Exception {
//先从redis中查看是否有这个标识
Object o = redisTemplate.opsForValue().get(msg.getMessageProperties().getDeliveryTag());
// 判断是否为null
if (o==null){
try {
// 业务代码
System.out.println("完成业务功能");
// 为空则就添加到redis
redisTemplate.opsForValue().set(msg.getMessageProperties().getDeliveryTag(),"zzz");
// 从队列中删除
channel.basicAck(msg.getMessageProperties().getDeliveryTag(),true);
} catch (IOException e) {
// 异常会再次发送信息
channel.basicNack(msg.getMessageProperties().getDeliveryTag(),true,true);
e.printStackTrace();
}
}else {
// 从队列中删除
channel.basicAck(msg.getMessageProperties().getDeliveryTag(),true);
}
}
rabbitMQ集群—一台主机启动多个rabbitMQ 伪集群
先停止rabbitMQ服务
service rabbitmq-server stop
(1)开启第一个节点 5672表示java的默认端口号,我这里写的是5673 节点名为rabbit1 不写就是12672
RABBITMQ_NODE_PORT=5673 RABBITMQ_NODENAME=rabbit1 rabbitmq-server start
(2)开启第二个节点 15674表示图形化界面端口号 节点名为rabbit2
RABBITMQ_NODE_PORT=5674 RABBITMQ_SERVER_START_ARGS="-rabbitmq_management listener [{port,15674}]" RABBITMQ_NODENAME=rabbit2 rabbitmq-server start
(3)设置主从关系:
rabbit1操作作为主节点:
rabbitmqctl -n rabbit1 stop_app
rabbitmqctl -n rabbit1 reset
rabbitmqctl -n rabbit1 start_app
rabbit2操作为从节点:
rabbitmqctl -n rabbit2 stop_app
rabbitmqctl -n rabbit2 reset
rabbitmqctl -n rabbit2 join_cluster rabbit1@‘zzz’ ###’'内是主机名换成自己的
rabbitmqctl -n rabbit2 start_app
测试:
在主机上rabbit1新建一个队列,rabbit2上也会同步这个队列。
RabbitMQ镜像集群配置
上面已经完成RabbitMQ默认集群模式,但并不保证队列的高可用性,尽管交换机、绑定这些可以复制到集群里的任何一个节点,但是队列内容不会复制。虽然该模式解决一项目组节点压力,但队列节点宕机直接导致该队列无法应用,只能等待重启,所以要想在队列节点宕机或故障也能正常应用,就要复制队列内容到集群里的每个节点,必须要创建镜像队列。
镜像队列是基于普通的集群模式的,然后再添加一些策略,所以你还是得先配置普通集群,然后才能设置镜像队列,我们就以上面的集群接着做。
第一步:
第二步:
测试:默认rabbit1端口为15672,rabbit2端口为15674
我把其中rabbit1断掉,在rabbit2上添加一条数据,再把rabbit1连接上,就会在rabbit1同步rabbit2上刚添加的信息。
HaProxy负载均衡RabbitMQ
安装HaProxy
tar -zxvf haproxy-1.6.5.tar.gz -C /usr/local
//进入目录、进行编译、安装
cd /usr/local/haproxy-1.6.5
make TARGET=linux31 PREFIX=/usr/local/haproxy
make install PREFIX=/usr/local/haproxy
mkdir /etc/haproxy
//创建haproxy配置文件
vi /etc/haproxy/haproxy.cfg
haproxy.cfg配置文件如下
#logging options
global
log 127.0.0.1 local0 info
maxconn 5120
chroot /usr/local/haproxy
uid 99
gid 99
daemon
quiet
nbproc 20
pidfile /var/run/haproxy.pid
defaults
log global
mode tcp
option tcplog
option dontlognull
retries 3
option redispatch
maxconn 2000
contimeout 5s
clitimeout 60s
srvtimeout 15s
#front-end IP for consumers and producters
listen rabbitmq_cluster
# haproxy暴漏的端口号
bind 0.0.0.0:5672
mode tcp
#balance url_param userid
#balance url_param session_id check_post 64
#balance hdr(User-Agent)
#balance hdr(host)
#balance hdr(Host) use_domain_only
#balance rdp-cookie
#balance leastconn
#balance source //ip
balance roundrobin
# haproxy代理的rabbit服务
server node1 127.0.0.1:5673 check inter 5000 rise 2 fall 2
server node2 127.0.0.1:5674 check inter 5000 rise 2 fall 2
listen stats
# haproxy的图形化界面
bind 192.168.213.181:8100
mode http
option httplog
stats enable
stats uri /rabbitmq-stats
stats refresh 5s
**注意:**这里直接赋值上面的内容,会少前面几个单词
配置文件解释如下:
开启Haproxy
开启Haproxy
/usr/local/haproxy/sbin/haproxy -f /etc/haproxy/haproxy.cfg
//查看haproxy进程状态
ps -ef | grep haproxy
访问如下地址对mq节点进行监控
http://192.168.31.214:8100/rabbitmq-statss
如下页面: