86-java-rabbitMQ(3)-rabbitMQ的消息可靠性/集群搭建

7 篇文章 0 订阅
4 篇文章 0 订阅

本文架构
在这里插入图片描述

具体可参考文章:
https://www.cnblogs.com/linjiqin/p/12683076.html
https://blog.csdn.net/u013256816/article/details/60875666/

消息可靠性
生产者端

如何保证生产者消息能投递到rabbitMQ服务端?

1.事务 + 持久化exchange,queue,message
说明:

  • 通过事务,来保证生产者将消息投递到服务端
  • 性能比较差
/**
     * 生产者---开启事务的方式发送消息,确保服务端收到消息
     * 事务模式控制发送消息的确认
     * 效率最低
     */
    @Test
    public void producer_transaction() throws IOException, TimeoutException {
        //1.通过util对象创建connection对象
        Connection connection = ConnUtil.getConnection();

        //2.获取channel
        Channel channel = connection.createChannel();

        //3.声明队列,这里就需要声明两个队列了,
        /**
         * queue:声明队列的名称
         * durable: 是否持久化 ----> 持久化queue
         * exclusive: 是否独占连接,如果连接关闭则自动删除,如果将此参数设置为true可用于临时队列的创建
         * autoDelete: 是否自动删除此队列,如果将自参数和exclusive都设置为true就可实现临时队列.
         * arguments: 可以设置一个队列的扩展参数,比如: 设置队列存活时间
         */
        channel.queueDeclare(QUEUE_INFORM_EMAIL,true,false,false,null);
        channel.queueDeclare(QUEUE_INFORM_SMS,true,false,false,null);

        //4 声明交换机
        /**
         * 参数明细
         * 参数1: 交换机名称
         * 参数2: 交换机类型
         *      fanout: 对应发布订阅模式
         *      direct: 对应routing路由模式
         *      topic: 对应通配符模式
         *      header: 对应header模式
         *  参数3: 是否持久化  ---->持久化exchange
         */
        channel.exchangeDeclare(EXCHANGE_TOPIC_INFORM,BuiltinExchangeType.TOPIC,true);

        //5.将2个队列绑定在交换机上面
        /**
         * 参数1: 队列名字
         * 参数2: 交换机名字
         * 路由key: 在发布订阅模式中使用空串;在路由模式和通配符模式中会根据路由key将消息发送到指定的queue中去
         */
        channel.queueBind(QUEUE_INFORM_EMAIL,EXCHANGE_TOPIC_INFORM,ROUTING_EMAIL);
        channel.queueBind(QUEUE_INFORM_SMS,EXCHANGE_TOPIC_INFORM,ROUTING_SMS);
        //同一个queue可以绑定多个routingKey
        //如果两个queue绑定相同的exchange交换机,相同的routingKey,则我们就可以实现发发布订阅的功能
        //只需要我们发送消息时,指定发送到这个交换机的这个routingKey下节课
        //channel.queueBind(QUEUE_INFORM_EMAIL,EXCHANGE_ROUTING_INFORM,"inform");
        //channel.queueBind(QUEUE_INFORM_SMS,EXCHANGE_ROUTING_INFORM,"inform");

        //6.发送消息
        /**
         * channel.basicPublish();
         * String exchange, String routingKey, BasicProperties props, byte[] body
         * 1.exchange: 交换机,如果传递""空串表示使用默认的交换机
         * 2.routingKey: 路由key,交换机根据路由key来将消息转发到指定的队列,如果使用默认的交换机,routingKey设置队列的名字
         * 3.props: 消息的属性,这里使用  MessageProperties.PERSISTENT_TEXT_PLAIN ---->表示持久化消息
         *      消息的属性有这些内容
         *      public BasicProperties(
         *             3.1 String contentType,//消息类型如:text/plain
         *             3.2 String contentEncoding,//编码
         *             3.3 Map<String,Object> headers,
         *             3.4 Integer deliveryMode,//1:不持久化  2:持久化消息
         *             3.5 Integer priority,//优先级
         *             3.6 String correlationId,
         *             3.7 String replyTo,//反馈队列
         *             3.8 String expiration,//expiration到期时间
         *             ...
         * 4.body: 消息的内容
         */
        try {
            String msg_email = "发送消息到email!!";
            String msg_sms = "发送消息到sms!!!";
            /**
             * 因为这里采用的是发布订阅模式,我们把消息是发给交换机,交换机去发给queue,所以这里指定交换机及即可
             * ---->1.开启事务
             * ---->2.持久化消息
             */
            channel.txSelect();//开启事务
            //MessageProperties.PERSISTENT_TEXT_PLAIN 这是把消息持久化了,服务器宕机重启,消息依然存在
            channel.basicPublish(EXCHANGE_TOPIC_INFORM,"inform.email",MessageProperties.PERSISTENT_TEXT_PLAIN,msg_email.getBytes());
            //这是没有持久化,服务器宕机重启,消息就会丢失
            channel.basicPublish(EXCHANGE_TOPIC_INFORM,"inform.sms",null,msg_sms.getBytes());
            System.out.println("发送成功!!消息内容:" + msg_email );
            System.out.println("发送成功!!消息内容:" + msg_sms );
            channel.txCommit();//提交事务

        }catch (Exception e){
            e.printStackTrace();
            channel.txRollback();//事务回滚
        }finally {
            channel.close();
        }
    }

2.普通confirm + 持久化exchange,queue,message

说明:

  • 这是消息投递开启confirm确认,通过查询消息的投递状态,来保证消息投递的到rabbitMQ服务器的可靠性.
  • 性能比事务稍微好一点
 /**
     * 生产者---以confirm模式,确保服务端收到消息
     * 普通模式的confirm
     * 效率仅仅比事务快一点
     */
    @Test
    public void producer_confirm_普通() throws Exception {
        Connection connection = ConnUtil.getConnection();
        Channel channel = connection.createChannel();

        channel.queueDeclare(QUEUE_INFORM_EMAIL,true,false,false,null);
        channel.queueDeclare(QUEUE_INFORM_SMS,true,false,false,null);
        channel.exchangeDeclare(EXCHANGE_TOPIC_INFORM,BuiltinExchangeType.TOPIC,true);

        channel.queueBind(QUEUE_INFORM_EMAIL,EXCHANGE_TOPIC_INFORM,ROUTING_EMAIL);
        channel.queueBind(QUEUE_INFORM_SMS,EXCHANGE_TOPIC_INFORM,ROUTING_SMS);

        String msg_email = "发送消息到email!!";
        String msg_sms = "发送消息到sms!!!";

        //开启普通模式的confirm
        channel.confirmSelect();
        channel.basicPublish(EXCHANGE_TOPIC_INFORM,"inform.email",MessageProperties.PERSISTENT_TEXT_PLAIN,msg_email.getBytes());
        //检查是否服务端给我们确认消息是否发送成功了
        if(!channel.waitForConfirms()){
            System.out.println("消息发送失败了");
        }

        System.out.println("发送成功!!消息内容:" + msg_email );
        System.out.println("发送成功!!消息内容:" + msg_sms );

        channel.close();
    }

3.批量confirm + 持久化exchange,queue,message

说明:

  • 这是消息投递开启confirm确认,通过查询消息的投递状态,来保证消息投递的到rabbitMQ服务器的可靠性.
  • 性能比普通confirm好的多.
 /**
     * 生产者---以confirm模式,确保服务端收到消息
     * 批量模式的confirm
     * 效率次之
     */
    @Test
    public void producer_confirm_批量() throws Exception {
        Connection connection = ConnUtil.getConnection();
        Channel channel = connection.createChannel();

        channel.queueDeclare(QUEUE_INFORM_EMAIL,true,false,false,null);
        channel.queueDeclare(QUEUE_INFORM_SMS,true,false,false,null);
        channel.exchangeDeclare(EXCHANGE_TOPIC_INFORM,BuiltinExchangeType.TOPIC,true);

        channel.queueBind(QUEUE_INFORM_EMAIL,EXCHANGE_TOPIC_INFORM,ROUTING_EMAIL);
        channel.queueBind(QUEUE_INFORM_SMS,EXCHANGE_TOPIC_INFORM,ROUTING_SMS);

        String msg_email = "发送消息到email!!";
        String msg_sms = "发送消息到sms!!!";

        //开启普通模式的confirm
        channel.confirmSelect();
        for(int i = 0; i < 10; i ++){
            channel.basicPublish(EXCHANGE_TOPIC_INFORM,"inform.email",MessageProperties.PERSISTENT_TEXT_PLAIN,msg_email.getBytes());
        }
        //批量发送之后,检查是否服务端给我们确认消息是否发送成功了
        if(!channel.waitForConfirms()){
            System.out.println("消息发送失败了");
        }

        System.out.println("发送成功!!消息内容:" + msg_email );
        System.out.println("发送成功!!消息内容:" + msg_sms );

        channel.close();
    }

4.异步confirm + 持久化exchange,queue,message

说明:

  • 这是消息投递开启confirm确认,通过异步回调确认机制,来保证消息投递的到rabbitMQ服务器的可靠性.
  • 性能最好.
 /**
     * 生产者---以confirm模式,确保服务端收到消息
     * 异步模式的confirm
     * 效率最高
     */
    @Test
    public void producer_confirm_异步() throws Exception {
        Connection connection = ConnUtil.getConnection();
        Channel channel = connection.createChannel();

        channel.queueDeclare(QUEUE_INFORM_EMAIL,true,false,false,null);
        channel.queueDeclare(QUEUE_INFORM_SMS,true,false,false,null);
        channel.exchangeDeclare(EXCHANGE_TOPIC_INFORM,BuiltinExchangeType.TOPIC,true);

        channel.queueBind(QUEUE_INFORM_EMAIL,EXCHANGE_TOPIC_INFORM,ROUTING_EMAIL);
        channel.queueBind(QUEUE_INFORM_SMS,EXCHANGE_TOPIC_INFORM,ROUTING_SMS);

        String msg_email = "发送消息到email!!";
        String msg_sms = "发送消息到sms!!!";

        //开启普通模式的confirm
        channel.confirmSelect();
        //添加异步的confirm确认的监听
        channel.addConfirmListener(new ConfirmListener() {
            /**
             * 消息发送成功之后的回调方法
             */
            @Override
            public void handleAck(long deliveryTag, boolean multiple) throws IOException {
                System.out.println("发送成功!!!deliveryTag:" + deliveryTag + ",multiple:" + multiple );
            }

            /**
             * 消息发送失败之后的回调方法
             */
            @Override
            public void handleNack(long deliveryTag, boolean multiple) throws IOException {
                System.out.println("发送失败!!!deliveryTag:" + deliveryTag + ",multiple:" + multiple );
            }
        });


        for(int i = 0; i < 10; i ++){
            channel.basicPublish(EXCHANGE_TOPIC_INFORM,"inform.email",MessageProperties.PERSISTENT_TEXT_PLAIN,msg_email.getBytes());
            channel.basicPublish(EXCHANGE_TOPIC_INFORM,"inform.sms",null,msg_sms.getBytes());
            System.out.println("发送成功!!消息内容:" + msg_email );
            System.out.println("发送成功!!消息内容:" + msg_sms );
        }

        Thread.sleep(3000);

        channel.close();
    }
消费者端

如何确保消费者消费消息的时候,消息不会丢失?
问题说明:

  • 1.如果开启自动回复,如果客户端收到消息了,就会自动回复服务端收到消息,服务端就会自动删除这个消息; 但是如果在处理业务流程的时候出现问题,这个消息最后在业务中处理未成功,则这个消息将会丢失.
  • 2.如果改为手动应答,不是在收到消息的时候就回复确认,而一般在我们的业务中是:收到消息,并且处理完成这个业务流程之后,再确认消息.这样就能保证我们消息不会丢失
 @Test
    public void consumerSMS() throws IOException, TimeoutException, InterruptedException {
        publishSuvscirbeConsume(QUEUE_INFORM_SMS,EXCHANGE_TOPIC_INFORM,ROUTING_SMS);
    }

    /**
     * 这是提取公共的发布订阅的consumer方法
     */
    public void publishSuvscirbeConsume(String queue,String exchange,String routingKey) throws IOException, TimeoutException, InterruptedException {
        //1.通过util对象创建connection对象
        Connection connection = ConnUtil.getConnection();

        //2.获取channel
        Channel channel = connection.createChannel();
        //3.声明队列
        /**
         * 这里可以不用声明队列,但是要确定该队列一定是在mq中存在的,不然就会代码报错.
         * queue:声明队列的名称
         * durable: 是否持久化
         * exclusive: 是否独占连接,如果连接关闭则自动删除,如果将此参数设置为true可用于临时队列的创建
         * autoDelete: 是否自动删除此队列,如果将自参数和exclusive都设置为true就可实现临时队列.
         * arguments: 可以设置一个队列的扩展参数,比如: 设置队列存活时间
         */
        channel.queueDeclare(queue,true,false,false,null);

        //4.声明交换机,并设置属性持久化
        channel.exchangeDeclare(exchange,BuiltinExchangeType.TOPIC,true);
        //5.绑定queue到exchange上
        channel.queueBind(queue,exchange,routingKey);

        //声明一个消费者
        DefaultConsumer consumer = new DefaultConsumer(channel){
            /**
             * 接收到消息时,调用此方法
             * @param consumerTag 消费者标签
             * @param envelope  信封---其实就是mq对于此消息的封装的东西,比如消息id,exchange等
             * @param properties 消息属性
             * @param body 消息内容
             * @throws IOException
             */
            @Override
            public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
                //交换机
                String exchange = envelope.getExchange();
                long deliveryTag = envelope.getDeliveryTag();
                System.out.println("exchange:" + exchange);
                System.out.println("deliveryTag:" + deliveryTag);

                //接收消息
                String msg = new String(body, "utf-8");
                System.out.println("receive msg: "+ msg);

                /**
                 * 开启了手动确认
                 *   1.如果消费者不确认,则该queue中的消息进入unacked状态,并且不会被删除,再次去拉取消息的时候依然可以消费这些消息
                 *   2.如果消费者确认,则该queue中的消息将被删除.
                 */
                channel.basicAck(envelope.getDeliveryTag(),false);
            }
        };

        //4.监听队列,消费消息
        /**
         * String queue, boolean autoAck, Consumer callback
         * 参数明细:
         * queue: 队列名
         * autoAck: 自动回复,当消费者受到消息之后,要告诉mq已接收到消息
         *        true:表示开启自动回复,如果客户端收到消息了,就会自动回复服务端收到消息,服务端就会自动删除这个消息;
         *              但是如果在处理业务流程的时候出现问题,这个消息最后在业务中处理未成功,则这个消息将会丢失.
         *        false:改为手动应答,不是在收到消息的时候就回复确认,而一般在我们的业务中是:
         *              收到消息,并且处理完成这个业务流程之后,再确认消息.
         * callback: 消费方法,当前消费者受到消息后要执行的方法.
         */
        channel.basicConsume(queue,false,consumer);
        //这是模拟,客户端阻塞,因为不这样做,客户端程序直接执行完毕,则不能执行consumer中的handleDelivery回调方法.
        //阻塞当前线程
        while (true){

        }
    }
rabbitMQ服务端

服务端如何保证,在服务器故障的情况下,消息不会丢失?

  • 1.exchange,queue,message在声明和传入的时候就持久化.
  • 2.服务器采用高可用的镜像集群模式,如果一个服务器宕机,其他服务器照样可以提供服务.
  • 3.如果服务器全部宕机,我们还有持久化的方式,保证消息的不会丢失.
  • 4.由于rabbitMQ持久化需要一定间隔时间,我们在持久化可能对于间隔时间内的消息不能持久化到,我们还可以采用镜像队列的方式,其实这个就是rabbitMQ高可用集群的模式.

综上所述,服务端保证消息的不会丢失,总结:

  • 配置高可用的rabbitMQ镜像集群(这个是服务器搭建).
  • 传入的exchange,queue,message一定要持久化(这个是业务调用).

rabbitMQ的集群搭建

具体参考视频:
https://www.bilibili.com/video/BV1dE411K7MG?p=17

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值