Node.js环境下双远端API同步更新、失败回滚及多次重试实现问题
实现方案
采用补偿事务(Saga模式)实现跨API的最终一致性,核心逻辑是每一步可回滚操作都对应记录反向操作,任意步骤失败时依次执行反向操作抵消已完成的修改。
现有代码存在的问题
- 未等待两个远程接口拉取完成就执行更新,可能操作未定义的变量
- 更新逻辑将两个API的修改打包,无法区分单个API的更新状态
- 缺少回滚逻辑,部分更新成功后无法撤销已提交的修改
- 重试逻辑作用于整个更新批次,重复执行可能导致数据重复修改
优化后可运行代码
const remote1 = 'https://jsonplaceholder.typicode.com/posts/1' const remote2 = 'https://jsonplaceholder.typicode.com/posts/2' const RETRY_COUNT = 3; // 单操作通用重试工具 async function retry(fn, count = RETRY_COUNT) { for (let i = 0; i < count; i++) { try { return await fn(); } catch (e) { if (i === count - 1) throw e; } } } // 拉取数据+备份原始值用于回滚 async function fetchAndBackup() { const [data1, data2] = await Promise.all([ fetch(remote1).then(res => res.json()), fetch(remote2).then(res => res.json()) ]); // 备份原始值 const backup = { data1OriginalId: data1.id, data2OriginalId: data2.id }; return { data1, data2, backup }; } // 单API更新操作示例,这里替换为你实际的API更新请求 function updateData1(data1, targetId) { return new Promise((resolve, reject) => { setTimeout(() => { // 模拟随机失败,实际场景替换为真实的PUT/POST请求 Math.random() > 0.3 ? (data1.id = targetId, resolve()) : reject(new Error('update data1 failed')); }, 1000); }); } // data1对应的回滚操作 function rollbackData1(data1, originalId) { return new Promise((resolve) => { setTimeout(() => { data1.id = originalId; resolve(); }, 1000); }); } // 单API更新操作示例,替换为实际的API更新请求 function updateData2(data2, targetId) { return new Promise((resolve, reject) => { setTimeout(() => { Math.random() > 0.3 ? (data2.id = targetId, resolve()) : reject(new Error('update data2 failed')); }, 1000); }); } // data2对应的回滚操作 function rollbackData2(data2, originalId) { return new Promise((resolve) => { setTimeout(() => { data2.id = originalId; resolve(); }, 1000); }); } // 主事务流程 async function main() { const { data1, data2, backup } = await fetchAndBackup(); const executedRollbacks = []; try { // 第一步更新data1,失败直接抛错不用回滚 await retry(() => updateData1(data1, 2)); // 记录已完成操作对应的回滚 executedRollbacks.push(() => rollbackData1(data1, backup.data1OriginalId)); // 第二步更新data2 await retry(() => updateData2(data2, 3)); executedRollbacks.push(() => rollbackData2(data2, backup.data2OriginalId)); console.log('全部更新成功'); return 'OK!'; } catch (e) { console.log('更新失败,开始回滚,错误信息:', e.message); // 倒序执行回滚,先回滚最后完成的操作 for (let i = executedRollbacks.length - 1; i >= 0; i--) { // 回滚也做重试,保证回滚成功率 await retry(executedRollbacks[i]); } console.log('全部回滚完成'); return `Tried ${RETRY_COUNT} times and failed`; } } main().then(console.log);
逻辑说明
- 所有更新操作执行前先拉取原始数据做备份,作为回滚的基准值
- 每个更新操作单独重试,避免重复执行已成功的操作
- 每完成一个更新操作就登记对应的回滚函数,失败时倒序执行所有登记的回滚
- 回滚操作也增加重试逻辑,降低回滚失败的概率
内容的提问来源于stack exchange,提问作者Nemo17
相关产品推荐
相关产品推荐

