问题描述
用Redis实现消息队列,消息发送到Stream中后没有被正常消费,堆积在里面
@Override
public void consume(ObjectRecord<String, MyMessage> message) {
ObjectMapper objectMapper = new ObjectMapper();
try {
MQLogs mqLogs = objectMapper.readValue(message.getValue().getContent(), MQLogs.class);
JdeWorkOrderIssuanceDto dto = objectMapper.readValue(mqLogs.getContent(), JdeWorkOrderIssuanceDto.class);
String token = stringRedisTemplate.opsForValue().get(GlobalConstant.JDE_TOKEN);
dto.setToken(token);
Call<JdeRootBean<JdeWorkOrderIssuanceBean>> response = RetrofitUtil.getRetrofit(jdeConfig.getUrl())
.create(HttpService.class)
.workOrderIssuance(dto);
Response<JdeRootBean<JdeWorkOrderIssuanceBean>> execute = response.execute();
MQLogs mqLogs1 = mqlogsDao.findById(mqLogs.getId()).orElseThrow(() -> new IllegalStateException("消息日志不存在"));
if (execute.code() == 200) {
mqLogs1.setStatus(1);
}
mqLogs1.setTime(mqLogs1.getTime() + 1);
mqLogs1.setUpdatedAt(LocalDateTime.now());
mqlogsDao.save(mqLogs1);
this.stringRedisTemplate.opsForStream().delete(message);
} catch (IOException e) {
throw new RuntimeException(e);
}
}
}
原因分析:
正常运行一天后出现阻塞情况,后面多次检查发现注释掉网络请求部分问题解决
解决方案:
当时想到由于第三方提供的接口响应时间过长,有时几分钟都无法响应,于是想到使用异步的方式执行来解决问题:
CompletableFuture.supplyAsync(() -> {
try {
// 发送网络请求
Call<JdeRootBean<JdeWorkOrderIssuanceBean>> response = RetrofitUtil.getRetrofit(jdeConfig.getUrl())
.create(HttpService.class)
.workOrderIssuance(dto);
Response<JdeRootBean<JdeWorkOrderIssuanceBean>> execute = response.execute();
// 处理网络请求响应
if (execute.code() == 200) {
// 更新消息状态等操作
MQLogs mqLogs1 = mqlogsDao.findById(mqLogs.getId()).orElseThrow(() -> new IllegalStateException("消息日志不存在"));
mqLogs1.setStatus(1);
mqLogs1.setTime(mqLogs1.getTime() + 1);
mqLogs1.setUpdatedAt(LocalDateTime.now());
mqlogsDao.save(mqLogs1);
}
return execute;
} catch (IOException e) {
throw new CompletionException(e);
}
}).thenAccept(response -> {
// 处理成功后的逻辑
log.info("HTTP请求成功: {}", response);
// 删除消息
this.stringRedisTemplate.opsForStream().delete(message);
}).exceptionally(ex -> {
// 处理异常情况
log.error("HTTP请求失败: {}", ex.getMessage(), ex);
return null;
});
实际修改后发现还是会出现消息未被消费的情况于是在之前的基础上增加了超时时间
// 异步执行网络请求
CompletableFuture.supplyAsync(() -> {
try {
// 发送网络请求
Call<JdeRootBean<JdeWorkOrderIssuanceBean>> response = RetrofitUtil.getRetrofit(jdeConfig.getUrl())
.create(HttpService.class)
.workOrderIssuance(dto);
Response<JdeRootBean<JdeWorkOrderIssuanceBean>> execute = response.execute();
// 处理网络请求响应
if (execute.code() == 200) {
// 更新消息状态等操作
MQLogs mqLogs1 = mqlogsDao.findById(mqLogs.getId()).orElseThrow(() -> new IllegalStateException("消息日志不存在"));
mqLogs1.setStatus(1);
mqLogs1.setTime(mqLogs1.getTime() + 1);
mqLogs1.setUpdatedAt(LocalDateTime.now());
mqlogsDao.save(mqLogs1);
}
return execute;
} catch (IOException e) {
throw new CompletionException(e);
}
}).completeOnTimeout(null, 5, TimeUnit.MINUTES) // 设置超时时间为5分钟
.thenAccept(response -> {
// 处理成功后的逻辑
log.info("HTTP请求成功: {}", response);
// 删除消息
this.stringRedisTemplate.opsForStream().delete(message);
}).exceptionally(ex -> {
// 处理异常情况
log.error("HTTP请求失败: {}", ex.getMessage(), ex);
return null;
});
修改过后,问题依旧,百思不得其解,询问GPT得到修改方案如下
@Override
public void consume(ObjectRecord<String, MyMessage> message) {
ObjectMapper objectMapper = new ObjectMapper();
try {
MQLogs mqLogs = objectMapper.readValue(message.getValue().getContent(), MQLogs.class);
JdeWorkOrderIssuanceDto dto = objectMapper.readValue(mqLogs.getContent(), JdeWorkOrderIssuanceDto.class);
String token = jdeTokenService.getToken();
dto.setToken(token);
ScheduledExecutorService executorService = Executors.newSingleThreadScheduledExecutor();
AtomicBoolean completed = new AtomicBoolean(false);
CompletableFuture.runAsync(() -> {
try {
Call<Map<String, Object>> response = RetrofitUtil.getRetrofit(jdeConfig.getUrl())
.create(HttpService.class)
.workOrderIssuance(dto);
Response<Map<String, Object>> execute = response.execute();
if (completed.compareAndSet(false, true)) {
processResponse(execute, mqLogs, message, executorService);
}
} catch (IOException e) {
if (completed.compareAndSet(false, true)) {
handleException(mqLogs, e, message, executorService);
}
}
}, executorService);
executorService.schedule(() -> {
if (completed.compareAndSet(false, true)) {
handleTimeout(mqLogs, message, executorService);
}
}, 5, TimeUnit.MINUTES);
} catch (IOException e) {
throw new RuntimeException(e);
}
}
private void processResponse(Response<Map<String, Object>> response, MQLogs mqLogs, ObjectRecord<String, MyMessage> message, ScheduledExecutorService executorService) {
int statusCode = response.code();
if (response.isSuccessful() && statusCode == 200) {
Map<String, Object> body = response.body();
if (body != null) {
mqLogs.setStatus(1);
}
}
closeExecutorServiceAndDeleteMessage(mqLogs, message, executorService);
}
private void handleException(MQLogs mqLogs, Exception e, ObjectRecord<String, MyMessage> message, ScheduledExecutorService executorService) {
mqLogs.setStatus(0); // 或者设置其他表示失败的状态码
closeExecutorServiceAndDeleteMessage(mqLogs, message, executorService);
}
private void handleTimeout(MQLogs mqLogs, ObjectRecord<String, MyMessage> message, ScheduledExecutorService executorService) {
mqLogs.setStatus(0); // 或者设置其他表示超时的状态码
closeExecutorServiceAndDeleteMessage(mqLogs, message, executorService);
}
private void closeExecutorServiceAndDeleteMessage(MQLogs mqLogs, ObjectRecord<String, MyMessage> message, ScheduledExecutorService executorService) {
mqLogs.setTime(mqLogs.getTime() + 1);
mqLogs.setUpdatedAt(LocalDateTime.now());
mqlogsDao.save(mqLogs);
this.stringRedisTemplate.opsForStream().delete(message);
executorService.shutdown(); // 在处理结束后关闭executorService
}
修改后问题得到好转,但是问题还是会出现,继续排查问题,换通义千问追问得到答案为:Retrofit 中使用的execute
方法会同步执行,并阻塞当前线程,并不能完成异步操作,修改为enqueue
则可以实现异步效果.
至此问题解决