• rabbitmq之消息模式一


    消息100%的投递

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

    什么是生产端的可靠性投递?

    u 保障消息的成功发出

    保障MQ节点的成功接收

    发送端收到MQ节点(Broker)确认应答

    u 完善的消息进行补偿机制

    BAT/TMD互联网大厂的解决方案:

    u 消息落库,对消息状态进行打标

    u 消息的延迟投递,做二次确认,回调检查

     

    幂等性概念

    幂等性是什么?

    u 我们可以借鉴数据库的乐观锁机制

    比如我们执行一条更新库存的SQL语句

    u Update t_repository set count = count -1,version = version + 1 where version = 1

    u Elasticsearch也是严格遵循幂等性概念,每次数据更新,version+1(博主博客前面有提到)

    消费端-幂等性保障

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

    消费实现幂等性,就意味着,我们的消息永远不会消费多次,即使我们收到了多条一样的消息

    业界主流的幂等性操作

    唯一ID+指纹码机制,利用数据库主键去重

    利用Redis的原子性去实现

    唯一ID+指纹码 机制

    唯一ID+指纹码机制,利用数据库主键去重

    Select count(1) from T_order where ID=唯一ID+指纹码

    好处:实现简单

    坏处:高并发下有数据库写入的性能瓶颈

    解决方案:根据ID进行分库分表进行算法路由

    利用Redis的原子性去实现

    使用Redis进行幂等,需要考虑的问题

    第一:我们是否要进行数据落库,如果落库的话,关键解决的问题是数据库和缓存如何做到原子性?

    第二:如果不进行落库,那么都存储到缓存中,如何设置定时同步策略?

    Confirm确认消息

    理解Confirm消息确认机制

    消息的确认,是指生产者投递消息后,如果Broker收到消息,则会给我们生产者 一个应答。

    生产者进行接收应答,用来确定这条消息是否正常的发送到Broker,这种方式也是消息的可靠性投递的核心保障

     

    如何实现Confirm确认消息?

    第一步:在Channel上开启确认模式:channel.confirmSelect()

    第二步:在channel上添加监听:addConfirmListener,监听成功和失败的返回结果,根据具体的结果对消息进行重新发送、或记录日志等后续处理!

    消费端代码

    package com.jt.rabbit002.confirm;
    
    import com.rabbitmq.client.Channel;
    import com.rabbitmq.client.Connection;
    import com.rabbitmq.client.ConnectionFactory;
    import com.rabbitmq.client.QueueingConsumer;
    
    
    public class Consumer {
        public static void main(String[] args) throws Exception {
            //1 创建ConnectionFactory
            ConnectionFactory connectionFactory = new ConnectionFactory();
            connectionFactory.setHost("192.168.171.132");
            connectionFactory.setPort(5672);
            connectionFactory.setVirtualHost("/");
    
            //2 获取C    onnection
            Connection connection = connectionFactory.newConnection();
    
            //3 通过Connection创建一个新的Channel
            Channel channel = connection.createChannel();
    
            String exchangeName = "test_confirm_exchange";
            String routingKey = "confirm.#";
            String queueName = "test_confirm_queue";
    
            //4 声明交换机和队列 然后进行绑定设置, 最后制定路由Key
            channel.exchangeDeclare(exchangeName, "topic", true);
            channel.queueDeclare(queueName, true, false, false, null);
            channel.queueBind(queueName, exchangeName, routingKey);
    
            //5 创建消费者
            QueueingConsumer queueingConsumer = new QueueingConsumer(channel);
            channel.basicConsume(queueName, true, queueingConsumer);
    
            while(true){
                QueueingConsumer.Delivery delivery = queueingConsumer.nextDelivery();
                String msg = new String(delivery.getBody());
    
                System.err.println("消费端: " + msg);
            }
    
    
        }
    }

    服务提供方代码

    package com.jt.rabbit002.confirm;
    
    import com.rabbitmq.client.Channel;
    import com.rabbitmq.client.ConfirmListener;
    import com.rabbitmq.client.Connection;
    import com.rabbitmq.client.ConnectionFactory;
    
    import java.io.IOException;
    
    
    public class Producer {
        public static void main(String[] args) throws Exception {
            //1 创建ConnectionFactory
            ConnectionFactory connectionFactory = new ConnectionFactory();
            connectionFactory.setHost("192.168.171.132");
            connectionFactory.setPort(5672);
            connectionFactory.setVirtualHost("/");
    
            //2 获取C    onnection
            Connection connection = connectionFactory.newConnection();
    
            //3 通过Connection创建一个新的Channel
            Channel channel = connection.createChannel();
    
    
            //4 指定我们的消息投递模式: 消息的确认模式
            channel.confirmSelect();
    
            String exchangeName = "test_confirm_exchange";
            String routingKey = "confirm.save";
    
            //5 发送一条消息
            String msg = "Hello RabbitMQ Send confirm message!";
            channel.basicPublish(exchangeName, routingKey, null, msg.getBytes());
    
            //6 添加一个确认监听
            channel.addConfirmListener(new ConfirmListener() {
                @Override
                public void handleNack(long deliveryTag, boolean multiple) throws IOException {
                    System.err.println("-------no ack!-----------");
                }
    
                @Override
                public void handleAck(long deliveryTag, boolean multiple) throws IOException {
                    System.err.println("-------ack!-----------");
                }
            });
        }
    }

     

     

    Return返回消息

    Return Listener用于处理一些不可路由的消息!

    正常情况:我们的消息生产者,通过指定一个ExchangeRoutingKey,把消息送达到某一个队列中去,然后我们的消费者监听队列,进行消费处理操作!

    异常情况:在某些情况下,如果我们在发送消息的时候,当前的Exchange不存在或者指定的路由key路由不到,这个时候如果我们需要监听这种不可达的消息,就需要使用Return Listener

    在基础API中有一个关键的配置项

    Mandatory:如果为true,则监听器会接收到路由不可达的消息,然后进行后续处理,如果为false,那么Broker端自动删除该消息!

     

    消费端代码

    package com.jt.rabbit002.returnlistener;
    
    import com.rabbitmq.client.Channel;
    import com.rabbitmq.client.Connection;
    import com.rabbitmq.client.ConnectionFactory;
    import com.rabbitmq.client.QueueingConsumer;
    
    
    public class Consumer {
        public static void main(String[] args) throws Exception {
            ConnectionFactory connectionFactory = new ConnectionFactory();
            connectionFactory.setHost("192.168.171.132");
            connectionFactory.setPort(5672);
            connectionFactory.setVirtualHost("/");
    
            Connection connection = connectionFactory.newConnection();
            Channel channel = connection.createChannel();
    
            String exchangeName = "test_return_exchange";
            String routingKey = "return.#";
            String queueName = "test_return_queue";
    
            channel.exchangeDeclare(exchangeName, "topic", true, false, null);
            channel.queueDeclare(queueName, true, false, false, null);
            channel.queueBind(queueName, exchangeName, routingKey);
    
            QueueingConsumer queueingConsumer = new QueueingConsumer(channel);
    
            channel.basicConsume(queueName, true, queueingConsumer);
    
            while(true){
                QueueingConsumer.Delivery delivery = queueingConsumer.nextDelivery();
                String msg = new String(delivery.getBody());
                System.err.println("消费者: " + msg);
            }
    
        }
    }

    生产端代码

    package com.jt.rabbit002.returnlistener;
    
    import com.rabbitmq.client.*;
    
    import java.io.IOException;
    
    
    public class Producer {
        public static void main(String[] args) throws Exception {
            ConnectionFactory connectionFactory = new ConnectionFactory();
            connectionFactory.setHost("192.168.171.132");
            connectionFactory.setPort(5672);
            connectionFactory.setVirtualHost("/");
    
            Connection connection = connectionFactory.newConnection();
            Channel channel = connection.createChannel();
    
            String exchange = "test_return_exchange";
            String routingKey = "return.save";
            String routingKeyError = "abc.save";
    
            String msg = "Hello RabbitMQ Return Message";
    
    
            channel.addReturnListener(new ReturnListener() {
                @Override
                public void handleReturn(int replyCode, String replyText, String exchange,
                                         String routingKey, AMQP.BasicProperties properties, byte[] body) throws IOException {
    
                    System.err.println("---------handle  return----------");
                    System.err.println("replyCode: " + replyCode);
                    System.err.println("replyText: " + replyText);
                    System.err.println("exchange: " + exchange);
                    System.err.println("routingKey: " + routingKey);
                    System.err.println("properties: " + properties);
                    System.err.println("body: " + new String(body));
                }
            });
    
            //消息投递成功,会被消费者所消费
    //        channel.basicPublish(exchange, routingKey, true, null, msg.getBytes());
            //消息不可达,将触发ReturnListener
             channel.basicPublish(exchange, routingKeyError, true, null, msg.getBytes());
        }
    }

     

    自定义消费者

    我们一般就是在代码中编写while循环,进行consumer.nextDelivery方法进行获取下一条消息,然后进行消费处理!

    但是我们使用自定义的Consumer更加的方便,解耦性更加的强,也是实际工作中最常用的使用方式!

    自定义消费端代码

    package com.jt.rabbit002.consumer;
    
    import com.rabbitmq.client.AMQP;
    import com.rabbitmq.client.Channel;
    import com.rabbitmq.client.DefaultConsumer;
    import com.rabbitmq.client.Envelope;
    
    import java.io.IOException;
    
    
    public class MyConsumer extends DefaultConsumer {
    
    
        public MyConsumer(Channel channel) {
            super(channel);
        }
    
        @Override
        public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
            System.err.println("-----------consume message----------");
            System.err.println("consumerTag: " + consumerTag);
            System.err.println("envelope: " + envelope);
            System.err.println("properties: " + properties);
            System.err.println("body: " + new String(body));
        }
    }

    消费端调用

    package com.jt.rabbit002.consumer;
    
    import com.rabbitmq.client.Channel;
    import com.rabbitmq.client.Connection;
    import com.rabbitmq.client.ConnectionFactory;
    
    
    public class Consumer {
        public static void main(String[] args) throws Exception {
            ConnectionFactory connectionFactory = new ConnectionFactory();
            connectionFactory.setHost("192.168.171.132");
            connectionFactory.setPort(5672);
            connectionFactory.setVirtualHost("/");
    
            Connection connection = connectionFactory.newConnection();
            Channel channel = connection.createChannel();
    
    
            String exchangeName = "test_consumer_exchange";
            String routingKey = "consumer.#";
            String queueName = "test_consumer_queue";
    
            channel.exchangeDeclare(exchangeName, "topic", true, false, null);
            channel.queueDeclare(queueName, true, false, false, null);
            channel.queueBind(queueName, exchangeName, routingKey);
    
            channel.basicConsume(queueName, true, new MyConsumer(channel));
    
    
        }
    }

    生产端调用

    package com.jt.rabbit002.consumer;
    
    import com.rabbitmq.client.Channel;
    import com.rabbitmq.client.Connection;
    import com.rabbitmq.client.ConnectionFactory;
    
    
    public class Producer {
        public static void main(String[] args) throws Exception {
            ConnectionFactory connectionFactory = new ConnectionFactory();
            connectionFactory.setHost("192.168.171.132");
            connectionFactory.setPort(5672);
            connectionFactory.setVirtualHost("/");
    
            Connection connection = connectionFactory.newConnection();
            Channel channel = connection.createChannel();
    
            String exchange = "test_consumer_exchange";
            String routingKey = "consumer.save";
    
            String msg = "Hello RabbitMQ Consumer Message";
    
            for(int i =0; i<5; i ++){
                channel.basicPublish(exchange, routingKey, true, null, msg.getBytes());
            }
    
        }
    }

     

     

     感谢观看

  • 相关阅读:
    Codeforces 787D. Legacy 线段树优化建图+最短路
    Codeforces 1051E. Vasya and Big Integers
    BZOJ3261 最大异或和
    BZOJ3531 SDOI2014 旅行
    洛谷P2468 SDOI 2010 粟粟的书架
    2018 ICPC 焦作网络赛 E.Jiu Yuan Wants to Eat
    HDU6280 From Tree to Graph
    HDU5985 Lucky Coins 概率dp
    (HDU)1334 -- Perfect Cubes (完美立方)
    (HDU)1330 -- Deck (覆盖物)
  • 原文地址:https://www.cnblogs.com/ztbk/p/12097214.html
Copyright © 2020-2023  润新知