package core.framework.impl.queue; import com.rabbitmq.client.AMQP; import com.rabbitmq.client.Channel; import com.rabbitmq.client.Consumer; import com.rabbitmq.client.ConsumerCancelledException; import com.rabbitmq.client.Envelope; import com.rabbitmq.client.QueueingConsumer; import com.rabbitmq.client.ShutdownSignalException; import com.rabbitmq.utility.Utility; import core.framework.api.log.ActionLogContext; import core.framework.api.log.Markers; import core.framework.api.util.StopWatch; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.io.IOException; import java.io.UncheckedIOException; import java.util.Deque; import java.util.LinkedList; import java.util.Queue; import java.util.concurrent.ConcurrentLinkedQueue; import java.util.concurrent.TimeoutException; import java.util.concurrent.locks.LockSupport; /** * @author neo */ public class RabbitMQConsumer implements Consumer, AutoCloseable { private final Logger logger = LoggerFactory.getLogger(RabbitMQConsumer.class); private final QueueingConsumer.Delivery stopSignal = new QueueingConsumer.Delivery(null, null, null); private final Queue<QueueingConsumer.Delivery> deliveries = new ConcurrentLinkedQueue<>(); private final Channel channel; private final String queue; private final long slowOperationThresholdInNanos; private final Thread consumerThread; private volatile ShutdownSignalException shutdown; private volatile ConsumerCancelledException cancelled; // refer to com.rabbitmq.client.QueueingConsumer public RabbitMQConsumer(Channel channel, String queue, int prefetchCount, long slowOperationThresholdInNanos) { this.channel = channel; this.queue = queue; this.slowOperationThresholdInNanos = slowOperationThresholdInNanos; try { channel.basicQos(prefetchCount); channel.basicConsume(queue, false, this); // QOS only works with manual ack } catch (IOException e) { throw new UncheckedIOException(e); } consumerThread = Thread.currentThread(); } @Override public void handleShutdownSignal(String consumerTag, ShutdownSignalException shutdown) { this.shutdown = shutdown; deliveries.add(stopSignal); LockSupport.unpark(consumerThread); } @Override public void handleRecoverOk(String consumerTag) { } @Override public void handleConsumeOk(String consumerTag) { } @Override public void handleCancelOk(String consumerTag) { } @Override public void handleCancel(String consumerTag) throws IOException { cancelled = new ConsumerCancelledException(); deliveries.add(stopSignal); LockSupport.unpark(consumerThread); } @Override public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) { if (shutdown != null) throw Utility.fixStackTrace(shutdown); deliveries.add(new QueueingConsumer.Delivery(envelope, properties, body)); LockSupport.unpark(consumerThread); } public QueueingConsumer.Delivery nextDelivery() throws ShutdownSignalException, ConsumerCancelledException, InterruptedException { while (true) { QueueingConsumer.Delivery delivery = poll(); if (delivery != null) { return delivery; } else { park(); } } } public Deque<QueueingConsumer.Delivery> nextDeliveries(int maxSize) throws ShutdownSignalException, ConsumerCancelledException, InterruptedException { while (true) { QueueingConsumer.Delivery delivery = poll(); if (delivery != null) { Deque<QueueingConsumer.Delivery> deliveries = new LinkedList<>(); deliveries.add(delivery); while (deliveries.size() < maxSize) { delivery = poll(); if (delivery == null) break; deliveries.add(delivery); } return deliveries; } else { park(); } } } private QueueingConsumer.Delivery poll() { QueueingConsumer.Delivery delivery = deliveries.poll(); if (stopSignal.equals(delivery) || shutdown != null || cancelled != null) { if (stopSignal.equals(delivery)) deliveries.add(stopSignal); if (shutdown != null) throw Utility.fixStackTrace(shutdown); if (cancelled != null) throw Utility.fixStackTrace(cancelled); } return delivery; } private void park() throws InterruptedException { LockSupport.park(); if (Thread.interrupted()) throw new InterruptedException(); } public void acknowledgeAll(long deliveryTag) { acknowledge(deliveryTag, true); } public void acknowledge(long deliveryTag) { acknowledge(deliveryTag, false); } private void acknowledge(long deliveryTag, boolean multiple) { StopWatch watch = new StopWatch(); try { channel.basicAck(deliveryTag, multiple); } catch (IOException e) { throw new UncheckedIOException(e.getMessage(), e); } finally { long elapsedTime = watch.elapsedTime(); ActionLogContext.track("rabbitMQ", elapsedTime); logger.debug("acknowledge, queue={}, deliveryTag={}, multiple={}, elapsedTime={}", queue, deliveryTag, multiple, elapsedTime); if (elapsedTime > slowOperationThresholdInNanos) { logger.warn(Markers.errorCode("SLOW_RABBITMQ"), "slow rabbitMQ operation, elapsedTime={}", elapsedTime); } } } @Override public void close() { try { channel.close(); } catch (ShutdownSignalException e) { logger.debug("connection is closed", e); } catch (IOException | TimeoutException e) { logger.warn("failed to close channel", e); } } }