RabbitMq(二) RabbitMq可靠性投递与实践经验

RabbitMq可靠性投递与实践经验

可靠性投递

消息丢失
在这里插入图片描述

如图所示,4个地方可能会出现消息丢失的情况

1、生产者发消息到broker,需要消费者应答

服务端天威延迟或者队列满了会倒是消息发送失败
RabbitMq提供了两种服务端确认模式
1、事务模式
利用txCommit 提交消息
异常时txRollback回滚
缺点:阻塞式。,一条消息没有发送完毕,不能发送下一条消息,降低了性能
try {
            channel.txSelect();
            // 发送消息,发布了4条,但只确认了3条
            // String exchange, String routingKey, BasicProperties props, byte[] body
            channel.basicPublish("", QUEUE_NAME, null, (msg).getBytes());
            channel.txCommit();
            channel.basicPublish("", QUEUE_NAME, null, (msg).getBytes());
            channel.txCommit();
            channel.basicPublish("", QUEUE_NAME, null, (msg).getBytes());
            channel.txCommit();
            channel.basicPublish("", QUEUE_NAME, null, (msg).getBytes());
            int i =1/0;
            channel.txCommit();
            channel.basicPublish("", QUEUE_NAME, null, (msg).getBytes());
            channel.txCommit();
            System.out.println("消息发送成功");
        } catch (Exception e) {
            channel.txRollback();
            System.out.println("消息已经回滚");
        }
在Springboot中的设置
	RabbitTemplate.setChannelTransacted(true)
	
2、Confirm模式
	本质上是mq发送了ack的应答,确认模式有三种
	1、普通确认模式
// 开启发送方确认模式
        channel.confirmSelect();

        channel.basicPublish("", QUEUE_NAME, null, msg.getBytes());
        // 普通Confirm,发送一条,确认一条
        if (channel.waitForConfirms()) {
            System.out.println("消息发送成功" );
        }else{
            System.out.println("消息发送失败");
        }
2、批量确认模式
批量发送,有一条失败,全部失败
try {
            channel.confirmSelect();
            for (int i = 0; i < 5; i++) {
                // 发送消息
                // String exchange, String routingKey, BasicProperties props, byte[] body
                channel.basicPublish("", QUEUE_NAME, null, (msg +"-"+ i).getBytes());
            }
            // 批量确认结果,ACK如果是Multiple=True,代表ACK里面的Delivery-Tag之前的消息都被确认了
            // 比如5条消息可能只收到1个ACK,也可能收到2个(抓包才看得到)
            // 直到所有信息都发布,只要有一个未被Broker确认就会IOException
            channel.waitForConfirmsOrDie();
            System.out.println("消息发送完毕,批量确认成功");
        } catch (Exception e) {
            // 发生异常,可能需要对所有消息进行重发
            e.printStackTrace();
        }
3、异步确认
已经确认的消息,会被剔除掉
// 用来维护未确认消息的deliveryTag
        final SortedSet<Long> confirmSet = Collections.synchronizedSortedSet(new TreeSet<Long>());

        // 这里不会打印所有响应的ACK;ACK可能有多个,有可能一次确认多条,也有可能一次确认一条
        // 异步监听确认和未确认的消息
        // 如果要重复运行,先停掉之前的生产者,清空队列
        channel.addConfirmListener(new ConfirmListener() {
            public void handleNack(long deliveryTag, boolean multiple) throws IOException {
                System.out.println("Broker未确认消息,标识:" + deliveryTag);
                if (multiple) {
                    // headSet表示后面参数之前的所有元素,全部删除
                    confirmSet.headSet(deliveryTag + 1L).clear();
                } else {
                    confirmSet.remove(deliveryTag);
                }
                // 这里添加重发的方法
            }
            public void handleAck(long deliveryTag, boolean multiple) throws IOException {
                // 如果true表示批量执行了deliveryTag这个值以前(小于deliveryTag的)的所有消息,如果为false的话表示单条确认
                System.out.println(String.format("Broker已确认消息,标识:%d,多个消息:%b", deliveryTag, multiple));
                if (multiple) {
                    // headSet表示后面参数之前的所有元素,全部删除
                    confirmSet.headSet(deliveryTag + 1L).clear();
                } else {
                    // 只移除一个元素
                    confirmSet.remove(deliveryTag);
                }
                System.out.println("未确认的消息:"+confirmSet);
            }
        });

        // 开启发送方确认模式
        channel.confirmSelect();
        for (int i = 0; i < 10; i++) {
            long nextSeqNo = channel.getNextPublishSeqNo();
            // 发送消息
            // String exchange, String routingKey, BasicProperties props, byte[] body
            channel.basicPublish("", QUEUE_NAME, null, (msg +"-"+ i).getBytes());
            confirmSet.add(nextSeqNo);
        }
        System.out.println("所有消息:"+confirmSet);

2、交换机找不到队列

队列写错了,绑定键写错了等等
1、添加监听器 true+returnListner	回发监听器	,找不到接受队列,就发回给生产者
channel.addReturnListener(new ReturnListener() {
            public void handleReturn(int replyCode,
                                     String replyText,
                                     String exchange,
                                     String routingKey,
                                     AMQP.BasicProperties properties,
                                     byte[] body)
                    throws IOException {
                System.out.println("=========监听器收到了无法路由,被返回的消息============");
                System.out.println("replyText:"+replyText);
                System.out.println("exchange:"+exchange);
                System.out.println("routingKey:"+routingKey);
                System.out.println("message:"+new String(body));
            }
        });
  AMQP.BasicProperties properties = new AMQP.BasicProperties.Builder().deliveryMode(2).
                contentEncoding("UTF-8").build();
channel.exchangeDeclare("TEST_EXCHANGE","topic", false, false, false, null);
channel.basicPublish("TEST_EXCHANGE","qingshan2673",true, properties,"只为更好的你".getBytes());

2、备份交换机

找不到交换机时发送到备份交换机
AMQP.BasicProperties properties = new AMQP.BasicProperties.Builder().deliveryMode(2).
                contentEncoding("UTF-8").build();

        // 备份交换机
        channel.exchangeDeclare("ALTERNATE_EXCHANGE","topic", false, false, false, null);
        channel.queueDeclare("ALTERNATE_QUEUE", false, false, false, null);
        channel.queueBind("ALTERNATE_QUEUE","ALTERNATE_EXCHANGE","#");

        // 在声明交换机的时候指定备份交换机
        Map<String,Object> arguments = new HashMap<String,Object>();
        arguments.put("alternate-exchange","ALTERNATE_EXCHANGE");
        channel.exchangeDeclare("TEST_EXCHANGE","topic", false, false, false, arguments);


        // 发送到了默认的交换机上,由于没有任何队列使用这个关键字跟交换机绑定,所以会被退回
        // 第三个参数是设置的mandatory,如果mandatory是false,消息也会被直接丢弃
        channel.basicPublish("TEST_EXCHANGE","qingshan2673",true, properties,"只为更好的你".getBytes());

3、消息在队列里一直没消息,怎么保持不丢

声明交换机队列时持久化,durable这个参数 ,设置为true
消息持久化
AMQP.BasicProperties properties = new AMQP.BasicProperties.Builder().deliveryMode(2).
            contentEncoding("UTF-8").build();

deliveryMode(2) 表示消息持久化

4、消费者发送ACK

消费者:自动发送ack
channel.basicConsume(QUEUE_NAME, true, consumer);
这里表示接收到消息就返回true,并不会等到消息中的业务处理完才返回ack	
如果需要再业务结束后返回,可以在业务结束后手动调用basicAck方法
业务在全局配置里设置
spring.rabbitmq.listener.simple.acknowledge-mode=auto
manual是手动ack 
none 是自动ack 
auto 不发生异常才会返回ack

mq拒绝策略 
basicReject方法里的requeue=true 表示把消息丢回队列
basicNack 批量拒绝 
if (msg.contains("拒收")){
                    // 拒绝消息
                    // requeue:是否重新入队列,true:是;false:直接丢弃,相当于告诉队列可以直接删除掉
                    // TODO 如果只有这一个消费者,requeue 为true 的时候会造成消息重复消费
                    channel.basicReject(envelope.getDeliveryTag(), true);
                } else if (msg.contains("异常")){
                    // 批量拒绝
                    // requeue:是否重新入队列
                    // TODO 如果只有这一个消费者,requeue 为true 的时候会造成消息重复消费
                    channel.basicNack(envelope.getDeliveryTag(), true, false);
                } else {
                    // 手工应答
                    // 如果不应答,队列中的消息会一直存在,重新连接的时候会重复消费
                    channel.basicAck(envelope.getDeliveryTag(), true);
                }

生产者如何知道消息被消费了

1、消费者受到消息,处理完毕,调用发送者API;太low
2、发送一条响应消息给发送者

补偿机制

如果一致没有收到消费者回发的消息,可以重发消息,间隔时间发送

消息幂等

可以以messageId主键判断是否重复消费

消息顺序性

一个队列只有一个消费者时是顺序消费

集群高可用

搭建

实践经验总结

资源管理

交换机、队列一般是消费者创建

配置文件与命名规范

虚拟机:XXX_Vhost
交换机:XXX_EXCHANGE
队列:QUEUE

信息落库+定时任务重新发送
生产环境运维监控

zabbix+grafana 监控
主要关注:磁盘、内存、连接数

日志追踪

Firehose

面试题

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值