7、RabbitMQ学习笔记—消息投递保障-1

消息如何保障100%投递成功

什么是生产端的可靠性投递?
  • 保障消息的成功发出
  • 保障MQ节点的成功接收
  • 发送端收到MQ节点(Broker)的确认应答
  • 完善的消息进行补偿机制
BAT/TMD 互联网大厂的解决方案
  • 消息落库,对消息状态进行达标
    在这里插入图片描述
    1、消息入库(业务库、消息库),大场景并发一般不考虑事务,影响效率。采用补偿的方式确认消息发送
    2、消息发送
    3、确认应答(confirm listener 异步监听)
    4、更改消息状态为成功发送
    5、从消息库获取未成功消息
    6、再次发送
    7、设定重发次数上限,超出上限设为发送失败
    保障MQ如果采用上述第一种方法,是否适用于高并发场景?
  • 消息的延迟投递,做二次确认,回调检查(主流方案)
    主要目的。减少数据库的读写操作。
    在这里插入图片描述
    UpstreamServer:上游系统生产端
    DownstreamServer : 下游系统消费端
    MQserver:消息中间件(rabbitm)
    Callback Server:回调服务
    消息首先完成业务入库
    1、发送消息
    2、再次发送(延迟消息投递)
    3、消费端监听,接收消息
    4、返回消息接收确认(新消息)
    5、callback监听确认消息(消息入库)
    6、callback监听延迟消息,检查消息库
    7、RPC进行未完成的消息再次投递
幂等性

在海量订单产生的业务高峰期,如何避免消息的重复消费问题?

  • 唯一id+指纹码 机制,利用数据库主键去重
    1、select count(1)from t_table where id = 唯一id+指纹码
    2、好处:实现简单
    3、坏处:高并发下数据库写入的性能瓶颈
    4、解决方案:跟进id进行分库分表进行算法路由
  • 利用redis的原子性操作
    1、是否需要进行数据库入库,如果入库,关键解决的问题是数据库和缓存如何做到原子性,一致性。
    2、如果不落库,那么都存储到缓存,如何设定当时同步策略
Confirm
  • 消息的确认,是指生产者投递消息后,如果Broker收到消息,测绘给我们生产者一个应答。
  • 生产者进行接收应答,用来确认这条消息是否正常的发送到broker,这种方式也是消息的可靠性投递的核心保障
    在这里插入图片描述
    如何实现confirm确认消息
    1、在channel上开启确认模式:channel.confirmSelect();
    2、在channel上添加监听:addConfirmListener,监听成功和失败的返回结果,根据具体的结果对消息进行重发、或记录日志等后续处理。
    创建生产着
public static final String EXCHANGE_NAME = "test_exchange_confirm";
    public static final String ROUTING_KEY = "routingkey.confirm";
    public static void main(String[] args) throws IOException, TimeoutException {
        // 获取连接
        Connection connection = ConnectionsUtils.getConnection();
        // 创建信道
        Channel channel = connection.createChannel();
        //开启确认模式
        channel.confirmSelect();
        // 发送消息
        String message = "test message for confirm";
        channel.basicPublish(EXCHANGE_NAME, ROUTING_KEY, null, message.getBytes());
        System.out.println("发送成功:" + message);
        // 添加确认监听
        channel.addConfirmListener(new ConfirmListener() {
            /**
             * @param deliveryTag 消息唯一标签
             * @param multiple 批量
             */
            @Override
            public void handleAck(long deliveryTag, boolean multiple) throws IOException {
                System.out.println("---ack---");
            }
            @Override
            public void handleNack(long deliveryTag, boolean multiple) throws IOException {
                System.out.println("---no-ack---");
            }
        });
    }

创建消费者

public static final String EXCHANGE_NAME = "test_exchange_confirm";
    public static final String EXCHANGE_TYPE = "direct";
    public static final String ROUTING_KEY = "routingkey.confirm";
    public static final String QUEUE_NAME = "test_queue_confirm";
    public static void main(String[] args) throws IOException, TimeoutException {
        // 获取连接
        Connection connection = ConnectionsUtils.getConnection();
        // 创建信道
        Channel channel = connection.createChannel();
        // 声明交换机
        channel.exchangeDeclare(EXCHANGE_NAME, EXCHANGE_TYPE, true, false, false ,null);
        // 声明队列
        channel.queueDeclare(QUEUE_NAME, false, false, false, null);
        // 绑定
        channel.queueBind(QUEUE_NAME, EXCHANGE_NAME, ROUTING_KEY);
        // 创建消费者进行监听获取消息
        DefaultConsumer consumer = new DefaultConsumer(channel){
            @Override
            public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
                System.out.println("接收消息message: " + new String(body));
            }
        };
        channel.basicConsume(QUEUE_NAME, true, consumer);
    }

启动消费者进行监听,进行消息发送
在这里插入图片描述
在这里插入图片描述

Return

return listener 用于处理一些不可路由的消息

  • 消息生产者通过指定一个exchange和routingkey吧消息发送到某个队列中,然后小给这进行监听队列,进行消费处理
  • 如果在发送消息的时候,当前的exchange不存在或者指定的routingkey路由不到,这个时候如果我们需要监听这种不可达消息,就需要使用return listener。
    基础api的关键配置项
  • Mandatory:如果为true,则监听器会接收到路由不可达的消息,然后进行后续的处理,如果为false,那么broker端会自动删除该消息
    在这里插入图片描述
    创建生产者
public static final String EXCHANGE_NAME = "test_exchange_return";
//    public static final String ROUTING_KEY = "routingkey.return";
    public static final String ROUTING_KEY = "routingkey.return.abc";
    public static void main(String[] args) throws IOException, TimeoutException {
        // 获取连接
        Connection connection = ConnectionsUtils.getConnection();
        // 创建信道
        Channel channel = connection.createChannel();
        //开启确认模式
        channel.confirmSelect();
        // 发送消息
        String message = "test message for return";
        channel.addReturnListener(new ReturnListener() {
            @Override
            public void handleReturn(int replyCode, String replyText, String exchange,
                                     String routingKey, AMQP.BasicProperties properties, byte[] body) throws IOException {
                System.out.println("---return---");
                System.out.println("replyCode: " + replyCode);
                System.out.println("replyText: " + replyText);
                System.out.println("exchange: " + exchange);
                System.out.println("routingKey: " + routingKey);
                System.out.println("properties: " + properties);
                System.out.println("body: " + new String(body));
            }
        });
        channel.basicPublish(EXCHANGE_NAME, ROUTING_KEY,true,  null, message.getBytes());
        System.out.println("发送成功:" + message);
    }

创建消费者

public static final String EXCHANGE_NAME = "test_exchange_return";
    public static final String EXCHANGE_TYPE = "topic";
    public static final String ROUTING_KEY = "routingkey.*";
    public static final String QUEUE_NAME = "test_queue_return";
    public static void main(String[] args) throws IOException, TimeoutException {
        // 获取连接
        Connection connection = ConnectionsUtils.getConnection();
        // 创建信道
        Channel channel = connection.createChannel();
        // 声明交换机
        channel.exchangeDeclare(EXCHANGE_NAME, EXCHANGE_TYPE, true, false, false ,null);
        // 声明队列
        channel.queueDeclare(QUEUE_NAME, false, false, false, null);
        // 绑定
        channel.queueBind(QUEUE_NAME, EXCHANGE_NAME, ROUTING_KEY);
        // 创建消费者进行监听获取消息
        DefaultConsumer consumer = new DefaultConsumer(channel){
            @Override
            public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
                System.out.println("接收消息message: " + new String(body));
            }
        };
        channel.basicConsume(QUEUE_NAME, true, consumer);
    }

我们采用topic模式的exchange,消费端采用的routingkey是routingkey.*,也就是说生产者发送ROUTING_KEY = "routingkey.return"的时候,消费者可以接受到消息,换成ROUTING_KEY = "routingkey.return.abc“的时候,就可以接收到返回的信息,前提是生产者发送消息时 mandatory参数一定要设置为true,否则会被自动删除消息。我们运行看下结果

  • 使用ROUTING_KEY = “routingkey.return”
    在这里插入图片描述
    在这里插入图片描述
  • 使用ROUTING_KEY = “routingkey.return.abc”
    在这里插入图片描述
    在这里插入图片描述
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值