起因不再多说,可看前一篇文章《RabbitMQ —— 四、单 Channel 消费之 ArrayBlockingQueue》,本文是将内部的缓冲队列换成了 Disruptor。
package com.xugy.apprabbit.module.amqp.core;
import com.lmax.disruptor.BlockingWaitStrategy;
import com.lmax.disruptor.RingBuffer;
import com.lmax.disruptor.WorkHandler;
import com.lmax.disruptor.dsl.Disruptor;
import com.lmax.disruptor.dsl.ProducerType;
import com.rabbitmq.client.*;
import com.xugy.apprabbit.boot.autoconfigure.amqp.AmqpProperties;
import com.xugy.apprabbit.module.amqp.AmqpEntry;
import com.xugy.apprabbit.module.amqp.Messagelistener;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.amqp.core.Message;
import java.io.IOException;
import java.util.Arrays;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.stream.Collectors;
/**
* @Author [email protected]
*/
public class AmqpCoreListener {
protected static final Logger logger = LoggerFactory.getLogger(AmqpCoreListener.class);
protected final ExecutorService executor = Executors.newCachedThreadPool();
protected final AmqpProperties amqpProperties;
protected final Channel channel;
protected final String consumer;
protected final String queue;
protected final int ringBufferSize;
protected final boolean isAutoAck;
protected AmqpReceiver amqpReceiver;
public AmqpCoreListener(Channel channel, String queue, String consumer, AmqpProperties amqpProperties) {
this.amqpProperties = amqpProperties;
this.channel = channel;
this.queue = queue;
this.consumer = consumer;
this.isAutoAck = amqpProperties.isAutoAck();
int ringSize = 1;
while (ringSize < amqpProperties.getQos()) ringSize = ringSize << 1;
ringSize = ringSize >> 1;
this.ringBufferSize = ringSize;
}
public AmqpCoreListener start() {
this.amqpReceiver = new AmqpReceiver();
return this;
}
protected Disruptor disruptorConsumer() {
Disruptor disruptor1 = disruptorCallback();
Disruptor disruptor = new Disruptor(() -> new AmqpEntry(), ringBufferSize, Executors.defaultThreadFactory(), ProducerType.SINGLE, new BlockingWaitStrategy());
disruptor.handleEventsWithWorkerPool(Arrays.stream(new AmqpConsumer[amqpProperties.getCurrent()]).map(x -> new AmqpConsumer(disruptor1)).collect(Collectors.toList()).toArray(new AmqpConsumer[0]));
disruptor.start();
return disruptor;
}
protected Disruptor disruptorCallback() {
Disruptor disruptor = new Disruptor(() -> new AmqpEntry(), ringBufferSize, Executors.defaultThreadFactory(), ProducerType.MULTI, new BlockingWaitStrategy());
disruptor.handleEventsWithWorkerPool(new AmqpCallbacker());
disruptor.start();
return disruptor;
}
/***********************************************/
/******** 消息生产 ********/
/***********************************************/
class AmqpProducer {
public AmqpProducer() {
}
}
/***********************************************/
/******** 开始接收 ********/
/***********************************************/
class AmqpReceiver {
AmqpReceiver() {
executor.execute(() -> {
try {
channel.basicQos(amqpProperties.getQos());
} catch (IOException e) {
throw new RuntimeException(">>>>> AmqpCoreListener init: when basicQos Exception: {}", e);
}
RingBuffer<AmqpEntry> ringBuffer = disruptorConsumer().getRingBuffer();
final Consumer consumer = new DefaultConsumer(channel) {
@Override
public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) {
long sequence = ringBuffer.next();
try {
AmqpEntry amqpEntry = ringBuffer.get(sequence);
amqpEntry.setMessage(new Message(body, null));
amqpEntry.setEnvelope(envelope);
} finally {
ringBuffer.publish(sequence);
}
}
};
try {
channel.basicConsume(queue, false, consumer);
} catch (IOException e) {
logger.error(">>>>> AmqpCoreListener basicConsume Exception: ");
e.printStackTrace();
}
});
}
}
/***********************************************/
/******** 消息消费 ********/
/***********************************************/
class AmqpConsumer implements WorkHandler<AmqpEntry> {
protected final Messagelistener messageListener;
protected final RingBuffer<AmqpEntry> ringBuffer;
AmqpConsumer(Disruptor callbacker) {
this.ringBuffer = callbacker.getRingBuffer();
try {
messageListener = (Messagelistener) Class.forName(consumer).getDeclaredConstructor().newInstance();
} catch (Exception e) {
throw new RuntimeException(e);
}
messageListener.setCallbackRingBuffer(this.ringBuffer);
}
/**
* Callback to indicate a unit of work needs to be processed.
*
* @param amqpEntry published to the {@link RingBuffer}
*/
@Override
public void onEvent(AmqpEntry amqpEntry) {
try {
messageListener.onMessage(amqpEntry);
amqpEntry.setAck(Boolean.TRUE);
} catch (Exception e) {
amqpEntry.setAck(Boolean.FALSE);
e.printStackTrace();
} finally {
if (isAutoAck) {
long sequence = ringBuffer.next();
try {
AmqpEntry amqpEntry1 = ringBuffer.get(sequence);
amqpEntry1.setEnvelope(amqpEntry.getEnvelope());
amqpEntry1.setAck(amqpEntry.isAck());
} finally {
ringBuffer.publish(sequence);
}
}
}
}
}
/***********************************************/
/******** 消息确认 ********/
/***********************************************/
class AmqpCallbacker implements WorkHandler<AmqpEntry> {
/**
* Callback to indicate a unit of work needs to be processed.
*
* @param amqpEntry published to the {@link RingBuffer}
*/
@Override
public void onEvent(AmqpEntry amqpEntry) throws IOException {
if (amqpEntry.isAck())
channel.basicAck(amqpEntry.getEnvelope().getDeliveryTag(), false);
else
channel.basicNack(amqpEntry.getEnvelope().getDeliveryTag(), false, true);
}
}
}
这里的等待策略使用了 BlockingWaitStrategy,因经测试,其他几种等待策略,即便是 yield,也会导致 cpu 空转,负载飙升,消息吞吐量会从 1万+ 降到 1千多一点。