7.发布确认模式
三种策略:
1.单次确认,并等待超时时间,超时时间内返回结果;超时则抛出异常
实现简单;会大大降低吞吐量,但是延时可接受
2.批量确认,积累到一定次数再等待返回
会很大提升效率和吞吐量;但是出错排查困难
3.同步处理,新建一个同步集合,正常返回就从队列中remove
最佳实践,有效利用资源,错误可以控制;但是实现复杂,需要正确编码
生产者:
import com.becolette.amqp.rabbit.simple.MqConnection;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.ConfirmCallback;
import com.rabbitmq.client.Connection;
import java.time.Duration;
import java.util.UUID;
import java.util.concurrent.ConcurrentNavigableMap;
import java.util.concurrent.ConcurrentSkipListMap;
import java.util.function.BooleanSupplier;
/**
* @author Becolette
* @description 消息发送
* @datetime 2020/9/29 9:49
*/
public class PublisherConfirms {
private static final int MESSAGE_COUNT = 5000;
private static final String INDIVIDUAL_QUEUE_NAME = "individual_queue";
public static void main(String[] args) throws Exception {
publishMessagesIndividually();
//publishMessagesInBatch();
//handlePublishConfirmsAsynchronously();
}
static void publishMessagesIndividually() throws Exception {
try (Connection connection = MqConnection.createConnection("localhost", 5672, "guest", "guest", "/")) {
Channel ch = connection.createChannel();
ch.queueDeclare(INDIVIDUAL_QUEUE_NAME, false, false, true, null);
ch.confirmSelect();
long start = System.nanoTime();
for (int i = 0; i < MESSAGE_COUNT; i++) {
String body = String.valueOf(i);
ch.basicPublish("", INDIVIDUAL_QUEUE_NAME, null, body.getBytes());
ch.waitForConfirmsOrDie(5_000);
}
long end = System.nanoTime();
System.out.format("Published %,d messages individually in %,d ms%n", MESSAGE_COUNT, Duration.ofNanos(end - start).toMillis());
}
}
static void publishMessagesInBatch() throws Exception {
try (Connection connection = MqConnection.createConnection("localhost", 5672, "guest", "guest", "/")) {
Channel ch = connection.createChannel();
String queue = UUID.randomUUID().toString();
ch.queueDeclare(queue, false, false, true, null);
ch.confirmSelect();
int batchSize = 100;
int outstandingMessageCount = 0;
long start = System.nanoTime();
for (int i = 0; i < MESSAGE_COUNT; i++) {
String body = String.valueOf(i);
ch.basicPublish("", queue, null, body.getBytes());
outstandingMessageCount++;
if (outstandingMessageCount == batchSize) {
ch.waitForConfirmsOrDie(5_000);
outstandingMessageCount = 0;
}
}
if (outstandingMessageCount > 0) {
ch.waitForConfirmsOrDie(5_000);
}
long end = System.nanoTime();
System.out.format("Published %,d messages in batch in %,d ms%n", MESSAGE_COUNT, Duration.ofNanos(end - start).toMillis());
}
}
static void handlePublishConfirmsAsynchronously() throws Exception {
try (Connection connection = MqConnection.createConnection("localhost", 5672, "guest", "guest", "/")) {
Channel ch = connection.createChannel();
String queue = UUID.randomUUID().toString();
ch.queueDeclare(queue, false, false, true, null);
ch.confirmSelect();
ConcurrentNavigableMap<Long, String> outstandingConfirms = new ConcurrentSkipListMap<>();
ConfirmCallback cleanOutstandingConfirms = (sequenceNumber, multiple) -> {
if (multiple) {
ConcurrentNavigableMap<Long, String> confirmed = outstandingConfirms.headMap(
sequenceNumber, true
);
confirmed.clear();
} else {
outstandingConfirms.remove(sequenceNumber);
}
};
ch.addConfirmListener(cleanOutstandingConfirms, (sequenceNumber, multiple) -> {
String body = outstandingConfirms.get(sequenceNumber);
System.err.format(
"Message with body %s has been nack-ed. Sequence number: %d, multiple: %b%n",
body, sequenceNumber, multiple
);
cleanOutstandingConfirms.handle(sequenceNumber, multiple);
});
long start = System.nanoTime();
for (int i = 0; i < MESSAGE_COUNT; i++) {
String body = String.valueOf(i);
outstandingConfirms.put(ch.getNextPublishSeqNo(), body);
ch.basicPublish("", queue, null, body.getBytes());
}
if (!waitUntil(Duration.ofSeconds(60), () -> outstandingConfirms.isEmpty())) {
throw new IllegalStateException("All messages could not be confirmed in 60 seconds");
}
long end = System.nanoTime();
System.out.format("Published %,d messages and handled confirms asynchronously in %,d ms%n", MESSAGE_COUNT, Duration.ofNanos(end - start).toMillis());
}
}
static boolean waitUntil(Duration timeout, BooleanSupplier condition) throws InterruptedException {
int waited = 0;
while (!condition.getAsBoolean() && waited < timeout.toMillis()) {
Thread.sleep(100L);
waited = +100;
}
return condition.getAsBoolean();
}
}
消费者:
import com.becolette.amqp.rabbit.simple.MqConnection;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
import com.rabbitmq.client.DeliverCallback;
/**
* @author Becolette
* @description 消息接受
* @datetime 2020/10/1 14:15
*/
public class Subscriber {
private static final String INDIVIDUAL_QUEUE_NAME = "individual_queue";
public static void main(String[] argv) throws Exception {
Connection connection = MqConnection.createConnection("localhost", 5672, "guest", "guest", "/");
Channel channel = connection.createChannel();
channel.queueDeclare(INDIVIDUAL_QUEUE_NAME, false, false, true, null);
System.out.println(" [*] Waiting for messages. To exit press CTRL+C");
channel.basicQos(1);
DeliverCallback deliverCallback = (consumerTag, delivery) -> {
String message = new String(delivery.getBody(), "UTF-8");
System.out.println(" [x] Received '" + message + "'");
channel.basicAck(delivery.getEnvelope().getDeliveryTag(), false);
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
};
channel.basicConsume(INDIVIDUAL_QUEUE_NAME, false, deliverCallback, consumerTag -> {
});
}
}