RabbitMQ采坑笔记

QueueingConsumer过时

QueueingConsumer在Rabbitmq客户端3.x版本中用的如火如荼,但是在4.x版本开初就被标记为@Deprecated。
Consumer的消费模式有Pull 和 Push两种,而经常用到的就是Push模式,Push模式在3.x的用法demo如下:

QueueingConsumer consumer = new QueueingConsumer(channel);
channel.basicQos(1);
channel.basicConsume(QUEUE_NAME, false, "consumer_zzh",consumer);
 
while (true) {
    QueueingConsumer.Delivery delivery = consumer.nextDelivery();
    String message = new String(delivery.getBody());
    System.out.println(" [X] Received '" + message + "'");
    channel.basicAck(delivery.getEnvelope().getDeliveryTag(),false);
    break;
}

在官方文档中推荐使用继承DefaultConsumer的方式:

boolean autoAck = false;
channel.basicConsume(queueName, autoAck, "myConsumerTag",
     new DefaultConsumer(channel) {
         @Override
         public void handleDelivery(String consumerTag,
                                    Envelope envelope,
                                    AMQP.BasicProperties properties,
                                    byte[] body)
             throws IOException
         {
             String routingKey = envelope.getRoutingKey();
             String contentType = properties.getContentType();
             long deliveryTag = envelope.getDeliveryTag();
             // (process the message components here ...)
             channel.basicAck(deliveryTag, false);
         }
});

QueueingConsumer内部其实是一个LinkBlockingQueue,它将从broker端接受到的信息先暂存到这个LinkBlockingQueue中,然后消费端程序在从这个LinkBlockingQueue中take出消息。试下一下,如果我们不take消息或者说take的非常慢,那么LinkBlockingQueue中的消息就会越来越多,最终造成内存溢出。
这个问题可以通过设置Basic.Qos来很好的解决。
DefaultConsumer还有一下方法:

  • handleShutdownSignal方法 当Channel与Conenction关闭的时候会调用,
  • handleCancelOk方法会在其它方法之前调用,返回消费者标签
    handleCancelOk与handleCancel消费者可以显式或者隐式的取水订单的时候调用,也可以通过channel.basicCancel方法来显式的取消一个消费者订阅
    首先触发handleConsumeOk方法,之后触发handleDelivery方法,最后才触发handleCancelOk方法
	@Override
    public void handleConsumeOk(String consumerTag) {
        this._consumerTag = consumerTag;
    }

 	@Override
    public void handleCancelOk(String consumerTag) {
        // no work to do
    }

  	@Override
    public void handleCancel(String consumerTag) throws IOException {
        // no work to do
    }

   @Override
    public void handleShutdownSignal(String consumerTag, ShutdownSignalException sig) {
        // no work to do
    }

  	@Override
    public void handleRecoverOk(String consumerTag) {
        // no work to do
    }

进入basicConsume方法内部看一看,他有三个实现但最终都是ChannerlN.class下的 public String basicConsume(String queue, final boolean autoAck, String consumerTag, boolean noLocal, boolean exclusive, Map arguments, final Consumer callback) throws IOException {}方法(下面代码块中的源码1)在最终执行,我们可以看到其只调用了Consumer的HandleConsumeOk方法,其方法实现如源码2;

//自己的consumer
 DefaultConsumer defaultConsumer = new DefaultConsumer(channel) {
                @Override
                public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
                    String routingKey = envelope.getRoutingKey();
                    String contentType = properties.getContentType();
                    long deliveryTag = envelope.getDeliveryTag();
                    // (process the message components here ...)
                    channel.basicAck(deliveryTag, false);
                    log.info("i'm ack message:{}", properties.getCorrelationId());
                }
            };
            /**
             * 回调 autoAck 默认是false
             */
            channel.basicConsume(DurableConfig.QUEUE_NAME, defaultConsumer);
//源码1
public String basicConsume(String queue, final boolean autoAck, String consumerTag, boolean noLocal, boolean exclusive, Map arguments, final Consumer callback) throws IOException {
        Method m = (new com.rabbitmq.client.AMQP.Basic.Consume.Builder()).queue(queue).consumerTag(consumerTag).noLocal(noLocal).noAck(autoAck).exclusive(exclusive).arguments(arguments).build();
        BlockingRpcContinuation k = new BlockingRpcContinuation(m) {
            public String transformReply(AMQCommand replyCommand) {
                String actualConsumerTag = ((ConsumeOk)replyCommand.getMethod()).getConsumerTag();
                ChannelN.this._consumers.put(actualConsumerTag, callback);
                ChannelN.this.metricsCollector.basicConsume(ChannelN.this, actualConsumerTag, autoAck);
                ChannelN.this.dispatcher.handleConsumeOk(callback, actualConsumerTag); /**在初始化调用了这个*/
                return actualConsumerTag;
            }
        };
        this.rpc(m, k);

        try {
            if (this._rpcTimeout == 0) {
                return (String)k.getReply();
            } else {
                try {
                    return (String)k.getReply(this._rpcTimeout);
                } catch (TimeoutException var11) {
                    throw this.wrapTimeoutException(m, var11);
                }
            }
        } catch (ShutdownSignalException var12) {
            throw wrap(var12);
        }
    }
//源码2
  public void handleConsumeOk(final Consumer delegate, final String consumerTag) {
        this.executeUnlessShuttingDown(new Runnable() {
            public void run() {
                try {
                    delegate.handleConsumeOk(consumerTag); /**调用了这个 我们重写的或者默认的 消费者的方法*/
                } catch (Throwable var2) {
                    ConsumerDispatcher.this.connection.getExceptionHandler().handleConsumerException(ConsumerDispatcher.this.channel, var2, delegate, consumerTag, "handleConsumeOk");
                }

            }
        });
    }

自己写的确认方法

@Slf4j
@Service
@RabbitListener(queues = DurableConfig.QUEUE_NAME)
public class DurableConsumer {

    @RabbitHandler
    public void doHandle(String content, Message message, Channel channel, CorrelationData correlationData) {
        try {
            log.info("content {}", content);
            log.info("message {}", message);
            log.info("channel {}", channel);
            log.info("correlationData {}", correlationData.getId());
            channel.basicAck(message.getMessageProperties().getDeliveryTag(), false);
            log.info("*************id:{}", message.getMessageProperties().getCorrelationId());
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

basicConsume方法

  • queue 队列名
  • autoAck 是否自动确认消息,true自动确认,false 不自动要手动调用,建立设置为false
  • arguments 消费者的参数
  • callback 消费者 DefaultConsumer建立使用,重写其中的方法
  • consumerTag 消费者标签,用来区分多个消费者
  • noLocal 设置为true,表示 不能将同一个Conenction中生产者发送的消息传递给这个Connection中 的消费者
  • exclusive 是否排他
String basicConsume(String queue, Consumer callback) throws IOException;
 String basicConsume(String queue, boolean autoAck, Consumer callback) throws IOException;
 String basicConsume(String queue, boolean autoAck, Map arguments, Consumer callback) throws IOException;
String basicConsume(String queue, boolean autoAck, String consumerTag, Consumer callback) throws IOException;
String basicConsume(String queue, boolean autoAck, String consumerTag, boolean noLocal, boolean exclusive, Map arguments, Consumer callback) throws IOException;
  • deliveryTag:该消息的index
  • multiple:是否批量.true:将一次性ack所有小于deliveryTag的消息。
 channel.basicAck(deliveryTag, false);
  • basicRecover:是路由不成功的消息可以使用recovery重新发送到队列中。重新投递并没有所谓的像basicReject中的basicReject的deliveryTag参数,所以重新投递好像是将消费者还没有处理的所有的消息都重新放入到队列中,而不是将某一条消息放入到队列中,与basicReject不同的是,重新投递可以指定投递的消息是否允许当前消费者消费。
  • basicReject:(void basicReject(long deliveryTag, boolean requeue);)是接收端告诉服务器这个消息我拒绝接收,不处理,可以设置是否放回到队列中还是丢掉,而且只能一次拒绝一个消息,官网中有明确说明不能批量拒绝消息,为解决批量拒绝消息才有了basicNack。requeue=true,表示将消息重新放入到队列中,false:表示直接从队列中删除,此时和basicAck(long deliveryTag, false)的效果一样
  • basicNack:可以一次拒绝N条消息,客户端可以设置basicNack方法的multiple参数为true,服务器会拒绝指定了delivery_tag的所有未确认的消息(tag是一个64位的long值,最大值是9223372036854775807)。

传送对象的问题

首先template和家庭的容器都要引入messageConvert

 @Bean
    public RabbitTemplate rabbitTemplate1(@Qualifier("cachingConnectionFactory1") CachingConnectionFactory cachingConnectionFactory, ConfirmCallBackListener confirmCallBackListener, ReturnCallBackListener returnCallBackListener) {
        RabbitTemplate rabbitTemplate = new RabbitTemplate();
        rabbitTemplate.setConnectionFactory(cachingConnectionFactory);
        rabbitTemplate.setMandatory(true);
        rabbitTemplate.setConfirmCallback(confirmCallBackListener);
        rabbitTemplate.setReturnCallback(returnCallBackListener);
        rabbitTemplate.setMessageConverter(new Jackson2JsonMessageConverter());--------------this
        return rabbitTemplate;
    }

    @Bean
    public SimpleRabbitListenerContainerFactory rabbitListenerContainerFactory(@Qualifier("cachingConnectionFactory1") CachingConnectionFactory connectionFactory){
        SimpleRabbitListenerContainerFactory factory = new SimpleRabbitListenerContainerFactory();
        factory.setConnectionFactory(connectionFactory);
        factory.setMessageConverter(new Jackson2JsonMessageConverter()); --------------this
        factory.setAcknowledgeMode(AcknowledgeMode.MANUAL);
        return factory;
    }

其次 不能同时发送一个对象和CorrelationData,不然会在解析参数的时候出错.原因往下看:

@Slf4j
@RestController
public class Producer {
    @Autowired
    @Qualifier("rabbitTemplate1")
    RabbitTemplate rabbitTemplate;

    private Gson gson = new Gson();

    @PostMapping("safe")
    public void sendMessage(String exNAME, String routingKey) {
        Beauty beauty = new Beauty();
        beauty.setAge(18);
        beauty.setWeight(53.2);
        beauty.setName("meili");
        beauty.setIntroduce("beauty");
        String mid = UUID.randomUUID().toString();
        CorrelationData correlationData = new CorrelationData(mid);
        rabbitTemplate.convertAndSend(exNAME, routingKey, beauty);
        log.info("there has a beauty : {}", gson.toJson(beauty));
    }
}

@Slf4j
@Service
@RabbitListener(queues = SafeConfig.QUEUE_NAME, containerFactory = "rabbitListenerContainerFactory")
public class Consumer {

    @RabbitHandler
    public void doHandle(@Payload Beauty content, Message message, Channel channel) {
        try {
            log.info("content {},message {},channel {},correlationData ", content, message, channel);
            channel.basicAck(message.getMessageProperties().getDeliveryTag(), false);
            log.info("----------------id:{}", message.getMessageProperties().getCorrelationId());
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

为什么不能同时存在自定义对象和CorrelationData,同时存在的时候可以看到类似的报错:org.springframework.messaging.converter.MessageConversionException: Cannot convert from [com.hyq.rabbitmq.safemessage.Beauty] to [org.springframework.amqp.rabbit.connection.CorrelationData]
我们根据提示的信息可以走进去源码看看:在解析每一个参数的时候 paramter都是不同的参数,如果你同时传送以上两个参数,循环就是两次一人一次,但是问题在于Object payload = message.getPayload(); 这个每次获取的都是你自定义的对象,所以在convert的时候,就必定有一次正确,有一次时把你的对象转为CorrelationData,所以就会报错,提示你不能那么转换。

protected Object[] getMethodArgumentValues(Message message, Object... providedArgs) throws Exception {
        if (ObjectUtils.isEmpty(this.getMethodParameters())) {
            return EMPTY_ARGS;
        } else {
            MethodParameter[] parameters = this.getMethodParameters();
            Object[] args = new Object[parameters.length];

            for(int i = 0; i < parameters.length; ++i) {
                MethodParameter parameter = parameters[i];
                parameter.initParameterNameDiscovery(this.parameterNameDiscoverer);
                args[i] = findProvidedArgument(parameter, providedArgs);
                if (args[i] == null) {
                    if (!this.resolvers.supportsParameter(parameter)) {
                        throw new MethodArgumentResolutionException(message, parameter, formatArgumentError(parameter, "No suitable resolver"));
                    }

                    try {
                        args[i] = this.resolvers.resolveArgument(parameter, message);
                    } catch (Exception var9) {
                        if (this.logger.isDebugEnabled()) {
                            String error = var9.getMessage();
                            if (error != null && !error.contains(parameter.getExecutable().toGenericString())) {
                                this.logger.debug(formatArgumentError(parameter, error));
                            }
                        }

                        throw var9;
                    }
                }
            }

            return args;
        }
    }


   @Nullable
    public Object resolveArgument(MethodParameter parameter, Message message) throws Exception {
        Payload ann = (Payload)parameter.getParameterAnnotation(Payload.class);
        if (ann != null && StringUtils.hasText(ann.expression())) {
            throw new IllegalStateException("@Payload SpEL expressions not supported by this resolver");
        } else {
            Object payload = message.getPayload();
            if (this.isEmptyPayload(payload)) {
                if (ann != null && !ann.required()) {
                    return null;
                } else {
                    String paramName = this.getParameterName(parameter);
                    BindingResult bindingResult = new BeanPropertyBindingResult(payload, paramName);
                    bindingResult.addError(new ObjectError(paramName, "Payload value must not be empty"));
                    throw new MethodArgumentNotValidException(message, parameter, bindingResult);
                }
            } else {
                Class targetClass = parameter.getParameterType();
                Class payloadClass = payload.getClass();
                if (ClassUtils.isAssignable(targetClass, payloadClass)) {
                    this.validate(message, parameter, payload);
                    return payload;
                } else {
                    if (this.converter instanceof SmartMessageConverter) {
                        SmartMessageConverter smartConverter = (SmartMessageConverter)this.converter;
                        payload = smartConverter.fromMessage(message, targetClass, parameter);
                    } else {
                        payload = this.converter.fromMessage(message, targetClass);
                    }

                    if (payload == null) {
                        throw new MessageConversionException(message, "Cannot convert from [" + payloadClass.getName() + "] to [" + targetClass.getName() + "] for " + message);
                    } else {
                        this.validate(message, parameter, payload);
                        return payload;
                    }
                }
            }
        }
    }
引用

http://www.importnew.com/25242.html
https://www.jianshu.com/p/df231c152754
https://blog.csdn.net/vbirdbest/article/details/78699913

你可能感兴趣的:(java,mq)