【Easylive】项目常见问题解答(自用&持续更新中…) 汇总版
Q: 如果是一个事务里面要去1.写自己的数据库,2.去调接口写别的服务器上的数据库,在这种情况下,我应该先去写自己的数据库还是先去掉接口呢?如果调接口超时这种情况怎么办?
执行顺序建议:先本地后远程
在需要同时操作本地数据库和远程接口的事务中,我建议采用先本地后远程的执行顺序,原因如下:
性能考虑:
• 本地操作通常比远程调用更快完成
• 先完成快速操作可以减少资源锁定时间错误处理:
• 如果本地操作失败,可以避免不必要的远程调用
• 本地操作失败后可直接回滚,不会留下部分完成的状态数据一致性:
• 确保本地系统数据先处于一致状态
• 远程调用作为后续补偿操作的基础
超时处理方案
1. 本地事务+异步通知(推荐方案)
@Transactional
public void processTransaction() {
// 1. 先执行本地数据库操作
localRepository.save(data);
try {
// 2. 尝试同步调用远程接口(设置合理超时时间)
remoteService.callWithTimeout(remoteData, 3, TimeUnit.SECONDS);
} catch (TimeoutException e) {
// 3. 如果超时,将任务放入重试队列
mqService.sendToRetryQueue(remoteData);
// 本地事务仍然提交
}
// 其他本地操作...
}
配套措施:
• 建立消息队列和重试机制
• 监控未完成的远程调用
• 提供人工干预接口
2. 两阶段提交模式(复杂但严格)
实现要点:
- 本地数据库预提交(不实际生效)
- 远程服务预提交(资源预留)
- 确认都成功后最终提交
- 任一失败则回滚两边
超时具体处理策略
1. 短超时+快速失败
// 设置合理的超时时间(根据业务需求调整)
HttpClient client = HttpClient.create()
.responseTimeout(Duration.ofSeconds(2));
2. 重试机制
RetryPolicy retryPolicy = new RetryPolicy()
.withMaxAttempts(3)
.withDelay(1, TimeUnit.SECONDS);
Failsafe.with(retryPolicy)
.get(() -> remoteService.call(remoteData));
3. 补偿事务
@Transactional
public void mainOperation() {
// 记录操作流水
auditLogRepository.logOperationStart();
// 本地数据库操作
localRepository.update(data);
}
// 补偿任务(定时执行)
public void compensateOperation() {
List<UnfinishedOperation> operations = auditLogRepository.findUnfinished();
operations.forEach(op -> {
try {
remoteService.retry(op.getData());
auditLogRepository.markAsCompleted(op.getId());
} catch (Exception e) {
// 报警通知人工处理
alertService.notifyAdmin(op, e);
}
});
}
架构设计建议
最终一致性模式:
• 使用事件溯源(Event Sourcing)
• 实现可靠事件总线服务降级方案:
public void process() { try { remoteService.call(remoteData); } catch (Exception e) { // 降级处理:记录到本地待处理表 fallbackRepository.savePendingTask(remoteData); // 触发告警 monitoringService.alert("Remote call failed", e); } }
监控指标:
• 远程调用成功率
• 平均响应时间
• 待补偿事务数量
• 补偿成功率
决策流程图
总结建议
- 常规场景:采用"先本地后远程+异步重试"方案
- 金融等严格场景:考虑两阶段提交或Saga模式
- 必须保证:
• 本地操作的可回滚性
• 远程调用的幂等性
• 完善的监控报警机制
通过这种设计,即使在远程调用超时的情况下,系统仍能保持最终一致性,同时避免长时间的资源锁定和用户等待。