在一次探讨的过程中,听到有人使用Redisson延迟队列遇到了丢消息的问题。
于是打算自己做一个小测试,看看是否会有这种情况发生。
环境&版本
- Redis 6.2.6(standalone)
- Redisson 3.16.4(Netty 4.1.70)
- JDK 17
测试
模拟2个生产端共发送10万延迟消息。
4个消费端轮流消费,检查是否能够完整拉取到10万个消息。
反复测试5次。
消息体
模拟一个简单的消息对象。
package cn.mrxionge.idemo.redisdq;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
@Data
@NoArgsConstructor
@AllArgsConstructor
class MsgDto {
private String id;
private String tag;
private String content;
}
消费端
模拟4个客户端不停地从队列中拉取并消费消息。
并且在每消费100个消息的时候,打印一次当前已消费的总数。
package cn.mrxionge.idemo.redisdq;
import org.redisson.Redisson;
import org.redisson.api.RBlockingQueue;
import org.redisson.api.RedissonClient;
import org.redisson.codec.JsonJacksonCodec;
import org.redisson.config.Config;
import java.util.concurrent.atomic.AtomicInteger;
public class RConsumer {
public static void main(String[] args) throws InterruptedException {
Config config = new Config();
config.useSingleServer().setAddress("redis://127.0.0.1:6379").setPassword("123456").setDatabase(0);
AtomicInteger msgCount = new AtomicInteger();
for (int i = 0; i < 4; i++) {
String name = "0" + i;
new Thread(() -> {
try {
RedissonClient client = Redisson.create(config);
System.out.printf("消费者 %s 启动%n", name);
RBlockingQueue<MsgDto> queue = client.getBlockingQueue("delayed_msg_queue", JsonJacksonCodec.INSTANCE);
while (true) {
queue.take();
if (msgCount.incrementAndGet() % 100 == 0) {
System.out.printf("当前消费总数 %s%n", msgCount.get());
}
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}).start();
}
}
}
生产端
模拟2个客户端不停地向延迟队列中添加消息,每个客户端发5万个,共10万消息。
消息的延迟时间随机生成,延迟时间分别是5,10,15,20,25,30秒。
package cn.mrxionge.idemo.redisdq;
import cn.hutool.core.codec.Base64;
import cn.hutool.core.util.IdUtil;
import org.redisson.Redisson;
import org.redisson.api.RDelayedQueue;
import org.redisson.api.RedissonClient;
import org.redisson.codec.JsonJacksonCodec;
import org.redisson.config.Config;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
import static cn.hutool.core.util.RandomUtil.randomInt;
import static cn.hutool.core.util.RandomUtil.randomString;
public class RProvider {
public static void main(String[] args) throws InterruptedException {
Config config = new Config();
config.useSingleServer().setAddress("redis://127.0.0.1:6379").setPassword("123456").setDatabase(0);
AtomicInteger msgCount = new AtomicInteger();
CountDownLatch latch = new CountDownLatch(2);
for (int i = 0; i < 2; i++) {
String name = "0" + i;
new Thread(() -> {
int count = 0;
RedissonClient client = Redisson.create(config);
System.out.printf("生产者 %s 启动%n", name);
RDelayedQueue<MsgDto> queue = client.getDelayedQueue(client.getQueue("delayed_msg_queue", JsonJacksonCodec.INSTANCE));
for (int j = 0; j < 50000; j++) {
MsgDto msgDto = new MsgDto(IdUtil.objectId(), "MSG_TAG_" + randomInt(1, 9), Base64.encode(randomString(10)));
queue.offer(msgDto, randomInt(1, 7) * 5L, TimeUnit.SECONDS);
count++;
}
System.out.printf("生产者 %s 发送 %s 条消息%n", name, count);
msgCount.addAndGet(count);
latch.countDown();
}).start();
}
latch.await();
System.out.printf("生产者共发送 %s 条消息%n", msgCount.get());
}
}
结果
消费端控制台输出
消费者 03 启动
消费者 01 启动
消费者 00 启动
消费者 02 启动
当前消费总数 100
当前消费总数 200
当前消费总数 300
··· ···
··· ···
当前消费总数 99800
当前消费总数 99900
当前消费总数 100000
生产端控制台输出
生产者 00 启动
生产者 01 启动
生产者 01 发送 50000 条消息
生产者 00 发送 50000 条消息
生产者共发送 100000 条消息
反复测试5次,均没有发生丢失消息的情况。
如果在使用的过程中,担心会有消息丢失,我们可以在生产端和消费端都打印一下消息对象。这样在出问题的时候,我们可以根据日志来定位问题。