RabbitMQ高级特性(七)

 

一、消息如何保证100%的投递成功

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

            保证消息的成功发出。

            保证MQj节点的成功接受。

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

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

  •     生产端---可靠性投递。 

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

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

            

二、幂等性

  •       幂等性是什么?

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

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

            UPDATE T_REPS SET COUNT=COUNT-1,VERSION=VERSION+1 WHERE VERSION=1

  •      消费端---幂等性的保障?

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

  •     消费端实现幂等性,就意味着,我们永远不会消费多次,即使我们收到了多条一样的消息。
  •     解决方案
  1.             唯一ID+指纹吗机制,利用数据据库主键去重。

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

                    SELECT COUNT(1) FROM T_ORDER WHERE ID=唯一ID+指纹码

                    好处:实现简单

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

                   解决方案:跟进ID进行分库分表进行算法路由。

   2.             利用redis的原子性去实现。

                   使用redis进行幂等性,需要考虑的问题。 

三、 Cinfirm 确认消息

    理解Confirm 消息确认机制

  •         消息确认,是指生产者投递消息后,如果Broker收到消息      ,则会给我们生产者一个应答
  •         生产者进行接受应答,用来确定这条消息是否正常的发送 到Broker,这种方式也是消息可可靠性投递的核心保障。

        如何实现Confirm确认消息:

  •         第一步:在channel 上开启确认模式:channel.confirmSelect()
  •         在channel上添加监听: addConfirmListenner,监听成功和失败的返回结果,根据具体的结果对消息进行重新发送、或者记录日志等后续处理。
  •        实现代码
  • package wang.chunsen.api.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;
    
    /**
     * @ClassName: Producer
     * @Author: wcs
     * @Description:
     * @Date: Created in 20:31 2018/12/22
     * @Package: wang.chunsen.api.confirm
     * @project: rabbitmq-api
     * @Modified By:
     */
    public class Producer {
    
        public static void main(String[] args) throws Exception {
            //创建一个来连接工厂
            ConnectionFactory connectionFactory = new ConnectionFactory();
            connectionFactory.setHost("192.168.28.128");
            connectionFactory.setPort(5672);
            connectionFactory.setVirtualHost("/");
            //获取connection
            Connection connection = connectionFactory.newConnection();
            //同过connection 创建一个Channel
            Channel channel = connection.createChannel();
            //指定我们的消息投递模式
            channel.confirmSelect();
            String exChangeName = "test_confirm_exchange";
            String routingkey = "confirm.save";
            String message = "hello RabbitMQ Send confirm message!";
            channel.basicPublish(exChangeName, routingkey, null, message.getBytes("UTF-8"));
            //添加一个确认监听
            channel.addConfirmListener(new ConfirmListener() {
                @Override
                public void handleAck(long deliveryTag, boolean multiple) throws IOException {
                    System.err.println("-----------ack!--------------");
                }
    
                @Override
                public void handleNack(long deliveryTag, boolean multiple) throws IOException {
                    System.out.println("------------no  ack----------------");
    
                }
            });
        }
    }
    
    package wang.chunsen.api.confirm;
    
    import com.rabbitmq.client.*;
    
    import java.io.IOException;
    
    /**
     * @ClassName: Consumer
     * @Author: wcs
     * @Description:
     * @Date: Created in 20:31 2018/12/22
     * @Package: wang.chunsen.api.confirm
     * @project: rabbitmq-api
     * @Modified By:
     */
    public class Consumer {
        public static void main(String[] args) throws Exception {
            //创建一个来连接工厂
            ConnectionFactory connectionFactory = new ConnectionFactory();
            connectionFactory.setHost("192.168.28.128");
            connectionFactory.setPort(5672);
            connectionFactory.setVirtualHost("/");
            //获取connection
            Connection connection = connectionFactory.newConnection();
            //同过connection 创建一个Channel
            Channel channel = connection.createChannel();
            String exChangeName = "test_confirm_exchange";
            String routingkey = "confirm.#";
            String queueName = "test_confirm_quque";
            //声明一个exchannge
            channel.exchangeDeclare(exChangeName, "topic", true);
            //创建一个队列
            channel.queueDeclare(queueName, true, false, false, null);
            //进行绑定
            channel.queueBind(queueName, exChangeName, routingkey);
    
            DefaultConsumer defaultConsumer = new DefaultConsumer(channel);
    
            while (true) {
                channel.basicConsume(queueName, true, new DefaultConsumer(channel) {
                    @Override
                    public void handleDelivery(String consumerTag,
                                               Envelope envelope,
                                               AMQP.BasicProperties properties,
                                               byte[] body) throws IOException {
    
                        String message = new String(body, "UTF-8");
                        System.out.println(message);
    
                    }
                });
            }
    
        }
    }
    

     

三、 Return 消息机制

  •      return listenner 用于处理一些不可路由的消息
  •      我们消息的生产者,通过指定一个Exchange 和RoutingKey  ,把消息发送到某一个队列中去,然后我们的消费者监听对队列,进行消息的消费处理。
  •     但是在某些情况下,如果我们在发送消息的时候,当前exchange不存在或者指定的路由key路由不到,这个时候如果我们需要监听这种不可达的消息,就要使用Return Listener!

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

            Mandatory: 如果为ture,则监听器会接受不可达到的消息,然后进行处理,如果为false,那么broker端自动删除该消息!

          

package wang.chunsen.api.returnlistener;

import com.rabbitmq.client.*;

import java.io.IOException;

/**
 * @ClassName: Consumer
 * @Author: wcs
 * @Description:
 * @Date: Created in 21:33 2018/12/22
 * @Package: wang.chunsen.api.returnlistener
 * @project: rabbitmq-api
 * @Modified By:
 */
public class Consumer {


    public static void main(String[] args) throws Exception {

        //创建一个来连接工厂
        ConnectionFactory connectionFactory = new ConnectionFactory();
        connectionFactory.setHost("192.168.28.128");
        connectionFactory.setPort(5672);
        connectionFactory.setVirtualHost("/");
        //获取connection
        Connection connection = connectionFactory.newConnection();
        //同过connection 创建一个Channel
        Channel channel = connection.createChannel();
        String exChangeName = "test_return_exchange";
        String routingkey = "return.#";
        String queueName = "test_returnl_quque";
        //声明一个exchannge
        channel.exchangeDeclare(exChangeName, "topic", true);
        //创建一个队列
        channel.queueDeclare(queueName, true, false, false, null);
        //进行绑定
        channel.queueBind(queueName, exChangeName, routingkey);

        while (true) {
            channel.basicConsume(queueName, true, new DefaultConsumer(channel) {
                @Override
                public void handleDelivery(String consumerTag,
                                           Envelope envelope,
                                           AMQP.BasicProperties properties,
                                           byte[] body) throws IOException {

                    String message = new String(body, "UTF-8");
                    System.out.println(message);

                }
            });
        }

    }
}

 

package wang.chunsen.api.returnlistener;

import com.rabbitmq.client.*;

import java.io.IOException;

/**
 * @ClassName: Producer
 * @Author: wcs
 * @Description:
 * @Date: Created in 21:32 2018/12/22
 * @Package: wang.chunsen.api.returnlistener
 * @project: rabbitmq-api
 * @Modified By:
 */
public class Producer {


    public static void main(String[] args) throws Exception {

        //创建一个来连接工厂
        ConnectionFactory connectionFactory = new ConnectionFactory();
        connectionFactory.setHost("192.168.28.128");
        connectionFactory.setPort(5672);
        connectionFactory.setVirtualHost("/");
        //获取connection
        Connection connection = connectionFactory.newConnection();
        //同过connection 创建一个Channel
        Channel channel = connection.createChannel();

        String exChangeName = "test_return_exchange";
        String routingkey = "return.save";
        String routingkeyError = "abc.save";
        String message = "hello RabbitMQ Send 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.out.println("-------handle return-----");
                System.out.println("replyCode" + replyCode);
                System.out.println("exchange" + exchange);
                System.out.println("routingKey" + routingKey);
                System.out.println("properties" + properties);
                System.out.println("body" + new String(body));
            }
        });
        channel.basicPublish(exChangeName, routingkey, true, null, message.getBytes("UTF-8"));


    }
}

四、自定义消费者

      

package wang.chunsen.api.consumer;

import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
import com.rabbitmq.client.ConnectionFactory;

/**
 * @ClassName: Producer
 * @Author: wcs
 * @Description:
 * @Date: Created in 21:32 2018/12/22
 * @Package: wang.chunsen.api.returnlistener
 * @project: rabbitmq-api
 * @Modified By:
 */
public class Producer {


    public static void main(String[] args) throws Exception {

        //创建一个来连接工厂
        ConnectionFactory connectionFactory = new ConnectionFactory();
        connectionFactory.setHost("192.168.28.128");
        connectionFactory.setPort(5672);
        connectionFactory.setVirtualHost("/");
        //获取connection
        Connection connection = connectionFactory.newConnection();
        //同过connection 创建一个Channel
        Channel channel = connection.createChannel();

        String exChangeName = "test_consumer_exchange";
        String routingkey = "consumer.save";

        String message = "hello RabbitMQ Send return message!";

        for (int i = 0; i < 10; i++) {
            channel.basicPublish(exChangeName, routingkey, true, null, message.getBytes("UTF-8"));
        }


    }
}
package wang.chunsen.api.consumer;

import com.rabbitmq.client.*;

import java.io.IOException;

/**
 * @ClassName: Consumer
 * @Author: wcs
 * @Description:
 * @Date: Created in 21:33 2018/12/22
 * @Package: wang.chunsen.api.returnlistener
 * @project: rabbitmq-api
 * @Modified By:
 */
public class Consumer {


    public static void main(String[] args) throws Exception {

        //创建一个来连接工厂
        ConnectionFactory connectionFactory = new ConnectionFactory();
        connectionFactory.setHost("192.168.28.128");
        connectionFactory.setPort(5672);
        connectionFactory.setVirtualHost("/");
        //获取connection
        Connection connection = connectionFactory.newConnection();
        //同过connection 创建一个Channel
        Channel channel = connection.createChannel();
        String exChangeName = "test_consumer_exchange";
        String routingkey = "consumer .#";
        String queueName = "test_consumer_quque";
        //声明一个exchannge
        channel.exchangeDeclare(exChangeName, "topic", true);
        //创建一个队列
        channel.queueDeclare(queueName, true, false, false, null);
        //进行绑定
        channel.queueBind(queueName, exChangeName, routingkey);

        while (true) {
            channel.basicConsume(queueName, true, new MyConsumer(channel) {
                @Override
                public void handleDelivery(String consumerTag,
                                           Envelope envelope,
                                           AMQP.BasicProperties properties,
                                           byte[] body) throws IOException {

                    String message = new String(body, "UTF-8");
                    System.out.println(message);

                }
            });
        }

    }
}

 

package wang.chunsen.api.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;

/**
 * @ClassName: MyConsumer
 * @Author: wcs
 * @Description:
 * @Date: Created in 10:53 2018/12/23
 * @Package: wang.chunsen.api.consumer
 * @project: rabbitmq-api
 * @Modified By:
 */
public class MyConsumer extends DefaultConsumer {

    public MyConsumer(Channel channel) {
        super(channel);
    }

    /**
     * No-op implementation of {@link Consumer#handleDelivery}.
     *
     * @param consumerTag
     * @param envelope
     * @param properties
     * @param body
     */
    @Override
    public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
        super.handleDelivery(consumerTag, envelope, properties, body);
    }
}

五、消费端限流

   什么是消费端限流?

   巨量的消息瞬间去拿不推送过来,但是我们单个客户端无法同时处理这么多的消息。

  •    RabbitMQ提供了一种qos(服务质量保证)功能,即在非自动确认消息的前提下,如果一定数目的消息(通过基于consume或者channel设置QOS的值)未被确认之前,不进行消费新的消息。
  •     void BasicQos(uint prefecchsize,ushort prefetchCount, bool glbal);

        prefetchSize: 0

        prefetchCount:会告诉RabbitMQ不要同时给一个消费者推送多于N个消息,即一旦有N个消息还没有被(ack),则该consume将block掉,直到有消息ack

       global:true\false 是将上面设置应用于channel 简单点来说,就是上面限制channel级别的还是consumer级别的。

       prefecthSize 和global 这两项,rabbitmq没有实现,暂且不研究prefetch_count在no_ask=false的情况下生效,及在自动应答的情况下这两个值是不生效的。

 

package wang.chunsen.api.limit;

import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
import com.rabbitmq.client.ConnectionFactory;

/**
 * @ClassName: Producer
 * @Author: wcs
 * @Description:
 * @Date: Created in 21:32 2018/12/22
 * @Package: wang.chunsen.api.returnlistener
 * @project: rabbitmq-api
 * @Modified By:
 */
public class Producer {


    public static void main(String[] args) throws Exception {

        //创建一个来连接工厂
        ConnectionFactory connectionFactory = new ConnectionFactory();
        connectionFactory.setHost("192.168.28.128");
        connectionFactory.setPort(5672);
        connectionFactory.setVirtualHost("/");
        //获取connection
        Connection connection = connectionFactory.newConnection();
        //同过connection 创建一个Channel
        Channel channel = connection.createChannel();

        String exChangeName = "test_qos_exchange";
        String routingkey = "qos.save";

        String message = "hello RabbitMQ Send return message!";

        for (int i = 0; i < 10; i++) {
            channel.basicPublish(exChangeName, routingkey, true, null, message.getBytes("UTF-8"));
        }


    }
}
package wang.chunsen.api.limit;

import com.rabbitmq.client.AMQP;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.DefaultConsumer;
import com.rabbitmq.client.Envelope;

import java.io.IOException;

/**
 * @ClassName: MyConsumer
 * @Author: wcs
 * @Description:
 * @Date: Created in 10:53 2018/12/23
 * @Package: wang.chunsen.api.consumer
 * @project: rabbitmq-api
 * @Modified By:
 */
public class MyConsumer extends DefaultConsumer {

    private Channel channel;


    public MyConsumer(Channel channel) {
        super(channel);
        this.channel = channel;
    }

    /**
     * No-op implementation of {@link Consumer#handleDelivery}.
     *
     * @param consumerTag
     * @param envelope
     * @param properties
     * @param body
     */
    @Override
    public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
        super.handleDelivery(consumerTag, envelope, properties, body);

        channel.basicQos((int) envelope.getDeliveryTag(), false);
    }
}

 

package wang.chunsen.api.limit;

import com.rabbitmq.client.*;

import java.io.IOException;

/**
 * @ClassName: Consumer
 * @Author: wcs
 * @Description:
 * @Date: Created in 21:33 2018/12/22
 * @Package: wang.chunsen.api.returnlistener
 * @project: rabbitmq-api
 * @Modified By:
 */
public class Consumer {


    public static void main(String[] args) throws Exception {

        //创建一个来连接工厂
        ConnectionFactory connectionFactory = new ConnectionFactory();
        connectionFactory.setHost("192.168.28.128");
        connectionFactory.setPort(5672);
        connectionFactory.setVirtualHost("/");
        //获取connection
        Connection connection = connectionFactory.newConnection();
        //同过connection 创建一个Channel
        Channel channel = connection.createChannel();
        String exChangeName = "test_qos_exchange";
        String routingkey = "qos.#";
        String queueName = "test_qos_quque";
        //声明一个exchannge
        channel.exchangeDeclare(exChangeName, "topic", true);
        //创建一个队列
        channel.queueDeclare(queueName, true, false, false, null);
        //进行绑定
        channel.queueBind(queueName, exChangeName, routingkey);


        //auto ACK 设置为false

        channel.basicQos(0, 1, false);
        while (true) {
            channel.basicConsume(queueName, false, new MyConsumer(channel) {
                @Override
                public void handleDelivery(String consumerTag,
                                           Envelope envelope,
                                           AMQP.BasicProperties properties,
                                           byte[] body) throws IOException {

                    String message = new String(body, "UTF-8");
                    System.out.println(message);

                }
            });
        }

    }
}

 

六、消息的ACK与重回队列

  消费端的手工ACK和NACK(自动签收,手工签收)。

  •   消费端进行消费的时候,如果由于业务异常我们可以进行日志记录,然后进行补偿。
  •   如果由于服务器宕机等严重问题,那我们就需要手工Ack保证消费端消费成功。

  消费端的重回队列

  •    消费端重回队列是为了对没有处理成功的消息,把消息重新投递给Broker
  •    一般我们在实际应用中,都会关闭 重回队列,也就是设置为false

  CODING

七、TTL消息

  •   TTL是Time To Live 的缩写,业务就是生存时间
  •   RabbitMq 支持消息的过期时间,在消息发送时可以进行指定
  •   RabbitMQ 支持队列的过期时间,从消息发送入队列开始计算,只要超过队列超时时间配置,那么消息会自动清除。

八、死信队列

  •      DLX也是一个正常的Exchange,和一般的Exchange没有区别,他能在任何的队列上被指定,实际上就是设置某个死信队列的属性
  •    当这个队列中有死信时,RabbitMQ就会自动的将这个消息重新发布到设置的Exchange上去,进而被路由到另一个队列中。
  •    可以监听这个队列中消息做相应的处理,这个特性可以弥补RabbitMq3.0以前支持的immediate 参数功能。

   

   死信队列:DLX , Dead-Letter-Exchange

  •   利用DLX,当消息子一个队列中变成死信之后,他能被重新publish到另一个Exchange,这个Exchange就是DL

   消息变成死信队列有以下集中情况

   消息被拒绝(basic.reject/basic.nack)并且requeue=false

   消息TTL过期

   队列达到最大长度

  死信队列的设置:

  首先需要设置死信队列的exchange 和queue,然后进行绑定:

  •    Exchange :dlx.exchange
  •    Queue: dlx.queue
  •    RoutingKey:#

 然后我们进行正常声明交换机、队列、绑定、只不过我们需要在队列上加一个参数即可:arguments.put("x-dead-letter-exchange","dlx.exchange).

  这样消息在过期,requeue,队列在达到最大的长度时,消息可以之浩劫路由到死信队列。

  

package com.bfxy.rabbitmq.api.ack;

import java.util.HashMap;
import java.util.Map;

import com.rabbitmq.client.AMQP;
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.11.76");
		connectionFactory.setPort(5672);
		connectionFactory.setVirtualHost("/");
		
		Connection connection = connectionFactory.newConnection();
		Channel channel = connection.createChannel();
		
		String exchange = "test_ack_exchange";
		String routingKey = "ack.save";
		
		
		
		for(int i =0; i<5; i ++){
			
			Map<String, Object> headers = new HashMap<String, Object>();
			headers.put("num", i);
			
			AMQP.BasicProperties properties = new AMQP.BasicProperties.Builder()
					.deliveryMode(2)
					.contentEncoding("UTF-8")
					.headers(headers)
					.build();
			String msg = "Hello RabbitMQ ACK Message " + i;
			channel.basicPublish(exchange, routingKey, true, properties, msg.getBytes());
		}
		
	}
}
package com.bfxy.rabbitmq.api.ack;

import java.io.IOException;

import com.rabbitmq.client.AMQP;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.DefaultConsumer;
import com.rabbitmq.client.Envelope;

public class MyConsumer extends DefaultConsumer {


	private Channel channel ;
	
	public MyConsumer(Channel channel) {
		super(channel);
		this.channel = channel;
	}

	@Override
	public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
		System.err.println("-----------consume message----------");
		System.err.println("body: " + new String(body));
		try {
			Thread.sleep(2000);
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
		if((Integer)properties.getHeaders().get("num") == 0) {
			channel.basicNack(envelope.getDeliveryTag(), false, true);
		} else {
			channel.basicAck(envelope.getDeliveryTag(), false);
		}
		
	}


}
package com.bfxy.rabbitmq.api.ack;

import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
import com.rabbitmq.client.ConnectionFactory;
import com.rabbitmq.client.QueueingConsumer;
import com.rabbitmq.client.QueueingConsumer.Delivery;

public class Consumer {

	
	public static void main(String[] args) throws Exception {
		
		
		ConnectionFactory connectionFactory = new ConnectionFactory();
		connectionFactory.setHost("192.168.11.76");
		connectionFactory.setPort(5672);
		connectionFactory.setVirtualHost("/");
		
		Connection connection = connectionFactory.newConnection();
		Channel channel = connection.createChannel();
		
		
		String exchangeName = "test_ack_exchange";
		String queueName = "test_ack_queue";
		String routingKey = "ack.#";
		
		channel.exchangeDeclare(exchangeName, "topic", true, false, null);
		channel.queueDeclare(queueName, true, false, false, null);
		channel.queueBind(queueName, exchangeName, routingKey);
		
		// 手工签收 必须要关闭 autoAck = false
		channel.basicConsume(queueName, false, new MyConsumer(channel));
		
		
	}
}

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值