工作中,有这样一个需求,A服务需要调用B服务的逻辑来完成后续操作,但是不知道B服务的处理时间。因此A服务设计一个超时时间来请求B服务,在超时时间内没有收到B服务的反馈,就算该请求失败了。
设计逻辑为:A服务设置请求超时时间,在超时时间内,A服务锁定线程,等待B服务反馈,B服务执行完成,通过mq通知A服务,A服务将线程唤醒,接着往下执行
A服务代码如下:
// 用于存储当前任务的等待锁
private static final HashMap<Long, CountDownLatch> WAIT_LATCH_MAP = new HashMap<>();
// 用于存储任务执行结果或异常信息 Key: taskId, Value: Exception
private static final ConcurrentHashMap<Long, Exception> TASK_RESULT_MAP = new ConcurrentHashMap<>();
long taskId = 0;
try {
// 2. 触发异步生成任务
AjaxResult result = SpringUtils.getBean(RemoteCmService.class).generateSyncFile(neMoi);
taskId = (Long) result.get("data");
// 1. 创建等待锁
CountDownLatch latch = new CountDownLatch(1);
synchronized (WAIT_LATCH_MAP) {
WAIT_LATCH_MAP.put(taskId, latch);
}
// 清理之前的结果记录
TASK_RESULT_MAP.remove(taskId);
// 3. 等待 MQ 消息通知 (超时时间设为 2 分钟)
log.info("Waiting for MQ notification for taskId: {}", taskId);
boolean completed = latch.await(2, TimeUnit.MINUTES);
if (!completed) {
log.error("Timeout waiting for generateSyncFile result for taskId: {}", taskId);
throw new RelationException("生成配置文件超时:2分钟内未收到MQ完成通知");
}
// 4. 检查是否有失败记录
Exception taskException = TASK_RESULT_MAP.get(taskId);
if (taskException != null) {
log.error("Generate sync file failed for taskId: {}", taskId, taskException);
throw new RelationException("生成配置文件失败: " + taskException.getMessage());
}
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
throw new RelationException("等待生成文件时被中断");
} finally {
// 清理锁和结果记录
synchronized (WAIT_LATCH_MAP) {
WAIT_LATCH_MAP.remove(taskId);
}
TASK_RESULT_MAP.remove(taskId);
}
/**
* 供 MQ 监听器调用的静态方法,用于释放等待锁并记录结果
*
* @param taskId 任务ID字符串 (与 createFile 中的 taskKey 对应)
* @param neMoi 网元MOI
* @param success 是否成功
*/
public static void notifyFileGenerationResult(Long taskId, String neMoi, boolean success) {
CountDownLatch latch;
synchronized (WAIT_LATCH_MAP) {
latch = WAIT_LATCH_MAP.get(taskId);
}
if (latch != null) {
if (!success) {
log.error("Received failure notification for neMoi: {}, taskId: {}", neMoi, taskId);
// 记录失败异常,供主线程检查
TASK_RESULT_MAP.put(taskId, new Exception("生成配置文件失败"));
} else {
log.info("Received success notification for neMoi: {}, taskId: {}", neMoi, taskId);
}
// 释放锁,唤醒主线程
latch.countDown();
} else {
log.warn("No waiting thread found for taskId: {}, neMoi: {}", taskId, neMoi);
}
}
B服务代码如下:
private void notifyTaskFinish(boolean isSucceed) {
if (!this.isSonTask) {
this.syncMgr.finish(this.taskId, isSucceed);
}
}
public void finish(long taskId, boolean isSucceed) {
SyncControllerInfo syncControllerInfo = null;
if (syncingNeMap != null) {
syncControllerInfo = syncingNeMap.get(taskId);
if (syncControllerInfo != null) {
// syncingNeMap.remove(taskId); // 注意:如果后续还需要查询状态,不要立即移除,或者由消费者确认后移除
syncControllerInfo.setProgress(100);
if (isSucceed) {
log.info("moi=" + syncControllerInfo.getNeMoi() + ", SyncTask succeed.");
syncControllerInfo.setResult(SimplifiedSyncTaskApp.SUCCESS);
} else {
log.error("moi=" + syncControllerInfo.getNeMoi() + ", SyncTask failed.");
syncControllerInfo.setResult(SimplifiedSyncTaskApp.FAIL);
}
}
}
// 发送 MQ 消息通知外部系统(如 VM 模块)
if (syncControllerInfo != null) {
sendFinishMqMessage(taskId, isSucceed, syncControllerInfo.getNeMoi());
} else {
log.warn("Finish called but no SyncControllerInfo found for taskId: {}", taskId);
// 即使找不到内存中的对象,也可能需要发送一个基本的完成消息,取决于业务需求
// sendFinishMqMessage(taskId, isSucceed, null);
}
}
private void sendFinishMqMessage(long taskId, boolean isSucceed, String neMoi) {
try {
// 构建消息内容,可以使用 JSON 或自定义对象
JSONObject jsonObject = new JSONObject();
jsonObject.set("taskId", taskId);
jsonObject.set("success", isSucceed);
jsonObject.set("neMoi", neMoi);
String messageBody = jsonObject.toString();
Destination destination = new ActiveMQTopic(TopicConstant.CM_GENERATE_FILE_RESULT_TOPIC);
SpringUtils.getBean(JmsMessagingTemplate.class).convertAndSend(destination, messageBody);
log.info("Sent MQ message for sync task finish. taskId={}, success={}, neMoi={}", taskId, isSucceed, neMoi);
} catch (Exception e) {
log.error("Failed to send MQ message for sync task finish. taskId={}", taskId, e);
}
}
监听服务如下:
@Slf4j
@Component
public class CmGenerateFileResultListener {
@JmsListener(destination = TopicConstant.CM_GENERATE_FILE_RESULT_TOPIC, containerFactory = "topicListener")
public void onGenerateFileResult(String message) {
try {
log.info("Received CM generate file result: {}", message);
JSONObject jsonObject = JSONUtil.parseObj(message);
String neMoi = jsonObject.getStr("neMoi");
Long taskId = Long.parseLong(jsonObject.getStr("taskId")); //
boolean isSuccess = "true".equalsIgnoreCase(jsonObject.getStr("success"));
// 调用 PreActiveContainPlatCreator 的静态方法通知主线程
PreActiveContainPlatCreator.notifyFileGenerationResult(taskId ,neMoi, isSuccess);
} catch (Exception e) {
log.error("Error processing CM generate file result message: {}", message, e);
}
}
}