RabbitMQ消费端消费机制

一、确认机制

第一种

 public static void getMessage() throws Exception {
        Connection connection = ConnectionUtil.getConnection();
        final Channel channel = connection.createChannel();
        // channel.queueDeclare(ConnectionUtil.QUEUE_NAME,true,false,false,null);
        DefaultConsumer deliverCallback = new DefaultConsumer(channel) {
            @Override
            public void handleDelivery(String consumerTag, Envelope envelope,
                                       AMQP.BasicProperties properties, byte[] body) throws IOException {
                System.out.println(new String(body, "UTF-8"));
                //表示消息消费成功
                System.out.println("消息消费成功");
                //手工确认,第一个参数是消息的id,第二个参数是批量标识(ture标识批量)
                //channel.basicAck(envelope.getDeliveryTag(),false);
            }
        };
        //消费,消息从ready状态,变成unacked状态
		//channel.basicConsume(ConnectionUtil.QUEUE_NAME, deliverCallback);
        //消费自动确认,一旦选择这种模式,消费发送到消费端,不管消息是否被消费端正常消费,都会将队列中的消息删除掉
		channel.basicConsume(ConnectionUtil.QUEUE_NAME,true, deliverCallback);
        //手工确认
        channel.basicConsume(ConnectionUtil.QUEUE_NAME,false, deliverCallback);
    }

第二种 springboot 注解方式
在配置类定义监听器容器工厂,设置手工确认

 //监听器容器工厂
    @Bean
    public SimpleRabbitListenerContainerFactory simpleRabbitListenerContainerFactory(){
        SimpleRabbitListenerContainerFactory simpleRabbitListenerContainerFactory = new SimpleRabbitListenerContainerFactory();
        //首先将连接工厂注入进来
        simpleRabbitListenerContainerFactory.setConnectionFactory(connectionFactory());
        //设置手工确认
        simpleRabbitListenerContainerFactory.setAcknowledgeMode(AcknowledgeMode.MANUAL);
        return simpleRabbitListenerContainerFactory;
    }

消费端监听队列

	//containerFactory指定监听器容器,来决定监听器是否自动确认
 @RabbitListener(queues = "queue4",containerFactory = "simpleRabbitListenerContainerFactory")
    public void get(String message ){
        System.out.println(new String(message.getBody(),"utf-8"));
        System.out.println("消费者1");
    }

第三种 javabean方式

    @Bean
    public SimpleMessageListenerContainer simpleMessageListenerContainer(){
        SimpleMessageListenerContainer simpleMessageListenerContainer = new SimpleMessageListenerContainer();
        simpleMessageListenerContainer.setConnectionFactory(connectionFactory());
        //设置消息监听器
        simpleMessageListenerContainer.setMessageListener(new MessageListener() {
            @Override
            public void onMessage(Message message) {
                try {
                    System.out.println(new String(message.getBody(),"utf-8"));
                    System.out.println("消息监听器-javaconfig");
                } catch (UnsupportedEncodingException e) {
                    e.printStackTrace();
                }
            }
        });
        //添加要监听的队列名集合
        simpleMessageListenerContainer.addQueueNames("queue4","queue1");
        //添加要监听的队列
		//simpleMessageListenerContainer.addQueues(new Queue("queue4"));
        //设置手工确认
        //simpleMessageListenerContainer.setAcknowledgeMode(AcknowledgeMode.MANUAL);
        return simpleMessageListenerContainer;

    }

二、拒绝机制

	//containerFactory指定监听器容器,来决定监听器是否自动确认
    @RabbitListener(queues = "queue4",containerFactory = "simpleRabbitListenerContainerFactory")
    public void getMessage(Message message, Channel channel) throws IOException {
        //这里可以用fastjson解序列化
        System.out.println(new String(message.getBody(),"utf-8"));
        //根据库存业务操作决定消息确认或者退回
        if(repertoryOp()){
            //channel根据消息配置中的id确认消息,第二个参数表示,是否是批量处理。
            //如果是批量处理,可以再多少条之后,提交一次即可,保证最后一笔消息id被提交,前面的也就相当于提交了。
            channel.basicAck(message.getMessageProperties().getDeliveryTag(),false);
            System.out.println("消息确认");
        }else{
            //消息退回
            //这个可以批量退回
            //第一个参数:消息id,第二个参数:是否批量退回,第三个参数:是否退回消息队列(否则丢弃)
            channel.basicNack(message.getMessageProperties().getDeliveryTag(),false,true);
            //单条退回
			//channel.basicReject(message.getMessageProperties().getDeliveryTag(),false);
            System.out.println("消息退回");
        }
        System.out.println("消费者4");
    }

    private boolean repertoryOp(){
        return true;
    }

三、消息预取
一般正常多个消费端消费队列时,会默认采用轮询机制,把所有队列中的消息直接平均分配到各个消费端。这个时候,有可能不同的消费端消费能力不同,造成有的消费端很快消费完,别的消费端迟迟消费不完的情况。
注意:这种方式,队列中的消息,是直接压到消费端,队列中不存在消息。

对于这种情况,可以采用消息预取的机制,每次消费端从队列中预取一定数量的消息,处理完后再次预取一定量消息再次消费。这时候,消费端只能采用手工确认的方式。
这里生产者模拟生产一百条消息:

public static void sendByExchange(String message) throws Exception {
        Connection connection = ConnectionUtil.getConnection();
        Channel channel = connection.createChannel();
        //生产者适合创建交换机和绑定,至于队列在生产者或者消费者创建都可以
        // 声明exchange
//        channel.exchangeDeclare(ConnectionUtil.EXCHANGE_NAME, BuiltinExchangeType.FANOUT);
        channel.exchangeDeclare("te", BuiltinExchangeType.FANOUT);
        //声明队列
        // 第二个参数表示队列是否持久化(跟消息持久化不同),
        // 第三个参数表示是否是排他队列,表示这个队列只能绑定在这个链接上。
        // 第四个参数,表示是否队列为空时删除,一般结合第三个参数为ture使用true
        // 第五个参数为队列配置参数集合
        channel.queueDeclare("queue4", true, false, false, null);
//        channel.queueDeclare(ConnectionUtil.QUEUE_NAME, true, false, false, null);
        //交换机和队列绑定
        channel.queueBind("queue4","te", "direct.key");
//        channel.queueBind(ConnectionUtil.QUEUE_NAME, ConnectionUtil.EXCHANGE_NAME, "test");
//        channel.basicPublish(ConnectionUtil.EXCHANGE_NAME, "", null,
//                message.getBytes());
        for(int i = 0; i < 100; i++){
            channel.basicPublish("te", "direct.key", null,
                    (message + i).getBytes());
            System.out.println("发送的信息为:" + message + i);
        }

        channel.close();
        connection.close();
    }

消费端有两个消费者消费消息:
消费者1

public static void getMessage() throws Exception {
        Connection connection = ConnectionUtil.getConnection();
        final Channel channel = connection.createChannel();
        DefaultConsumer deliverCallback = new DefaultConsumer(channel) {
            @Override
            public void handleDelivery(String consumerTag, Envelope envelope,
                                       AMQP.BasicProperties properties, byte[] body) throws IOException {
                System.out.println(new String(body, "UTF-8"));
                //表示消息消费成功
				System.out.println("消费者1消费消息成功");
                //手工确认,第一个参数是消息的id,第二个参数是批量标识(ture标识批量)
                channel.basicAck(envelope.getDeliveryTag(),false);
            }
        };

        //手工确认
        channel.basicConsume("queue4",false, deliverCallback);
    }

消费者2

public static void getMessage() throws Exception {
        Connection connection = ConnectionUtil.getConnection();
        final Channel channel = connection.createChannel();
        DefaultConsumer deliverCallback = new DefaultConsumer(channel) {
            @Override
            public void handleDelivery(String consumerTag, Envelope envelope,
                                       AMQP.BasicProperties properties, byte[] body) throws IOException {
                  //模拟较长时间的业务逻辑
                 Thread.sleep(50);
                System.out.println(new String(body, "UTF-8"));
                //表示消息消费成功
               System.out.println("消费者2消费消息成功");
                //手工确认,第一个参数是消息的id,第二个参数是批量标识(ture标识批量)
                channel.basicAck(envelope.getDeliveryTag(),false);
            }
        };
   
        //手工确认
        channel.basicConsume("queue4",false, deliverCallback);
    }

执行结果:
会发现队列中没有消息的任何状态,消费者1会分配到50个消息(0,2,4,6~98),消费者2也会分配到50个消息(1,3,5,…99).但是消费者2会执行较长时间,而消费者1会很快消费完。

如果消费者中增加预取处理:

  //消息预取
 channel.basicQos(1);

会发现消费者1会直接消费掉99条消息,而消费者2只消费一条消息。能够很好充分利用消费端消费能力。
springboot设置消息预取:

@Bean
public SimpleRabbitListenerContainerFactory
	simpleRabbitListenerContainerFactory(ConnectionFactory connectionFactory){
	SimpleRabbitListenerContainerFactory simpleRabbitListenerContainerFactory =
	new SimpleRabbitListenerContainerFactory();
	simpleRabbitListenerContainerFactory.setConnectionFactory(connectionFactory);
	//手动确认消息
	simpleRabbitListenerContainerFactory.setAcknowledgeMode(AcknowledgeMode.MANUAL);
	//设置消息预取的数量
	simpleRabbitListenerContainerFactory.setPrefetchCount(1);
	return simpleRabbitListenerContainerFactory;
}

关于这个预取的数量如何设置呢?
我们发现 如果设置为1 能极大的利用客户端的性能(我消费完了就可以赶紧消
费下一条 不会导致忙的很忙 闲的很闲) 但是, 我们每消费一条消息 就要通知一次rabbitmq 然后再取出新的消息, 这样对于rabbitmq的性能来讲 是非常不合理的 所以这个参数要根据业务情况设置。
这里发现预取的数据量从1~2500条,性能越高,可靠性越低,中间值为500。
一般预取一定的数量消息,然后批量确认。

你可能感兴趣的:(学习)