.Net NCo 3.0 tRFC重试机制及幂等性实现方案咨询
.Net NCo 3.0 tRFC 可靠重试机制实现方案
核心设计原则
tRFC的幂等性完全依赖事务ID(TID)实现,SAP系统会保证同一个TID最多执行一次,整套重试机制围绕TID的全生命周期状态管理实现,确保无重复执行、无数据丢失。
首先需要扩展你的TidStatus枚举,覆盖全生命周期状态:
public enum TidStatus { Created, // 事务已创建,未提交 Submitted, // 已向SAP发起提交请求,未收到明确结果 Committed, // SAP已确认执行成功 RolledBack, // 已明确执行失败,无需重试 MaxRetryFailed // 超过最大重试次数,需人工介入 }
重试触发时机
- 应用程序启动时,全量扫描数据库中所有非最终状态(Created、Submitted)的TID记录,触发批量重试
- 配置定时轮询任务(建议间隔10~15分钟),扫描所有提交超过30分钟仍未到最终状态的TID记录
- 捕获到SAP通信类异常(如RFC_COMMUNICATION_FAILURE、系统宕机、网络超时)时,不直接标记为失败,更新为Submitted状态留待重试
关键实现逻辑
1. 原提交逻辑优化
你原有代码的异常处理逻辑未区分异常类型,且依赖本地文件存储事务数据可靠性低,优化后代码如下:
private void SubmitTransaction(RfcTransaction trans, TidStore tidStore, String data) { try { // 先将TID状态更新为已提交,避免异常丢失提交记录 tidStore.SetStatus(trans.Tid.TID, TidStatus.Submitted, null); IRfcTable dataTable = stfc_write_to_tcpic.GetTable("TCPICDAT"); dataTable.Append(); dataTable.SetValue(0, data); trans.AddFunction(stfc_write_to_tcpic); stfc_write_to_tcpic = (IRfcFunction)stfc_write_to_tcpic.Clone(); dataTable = stfc_write_to_tcpic.GetTable("TCPICDAT"); dataTable.SetValue(0, data + " -- data of the second function module"); trans.AddFunction(stfc_write_to_tcpic); trans.Commit(_ECCsystem); tidStore.SetStatus(trans.Tid.TID, TidStatus.Committed, null); // 所有本地清理逻辑执行完成后再确认TID,避免本地记录未清理导致重复执行 _ECCsystem.ConfirmTransactionID(trans.Tid); tidStore.DeleteEntry(trans.Tid.TID); } catch (RfcCommunicationException commEx) { // 通信类异常,无法确认SAP执行结果,保留Submitted状态等待重试 tidStore.IncreaseRetryCount(trans.Tid.TID); tidStore.SetStatus(trans.Tid.TID, TidStatus.Submitted, commEx.Message); } catch (RfcAbapException abapEx) { // ABAP层明确返回执行失败,标记为回滚 tidStore.SetStatus(trans.Tid.TID, TidStatus.RolledBack, abapEx.Message); } catch (Exception ex) { // 其他本地异常,可根据业务场景判断是否需要重试 tidStore.SetStatus(trans.Tid.TID, TidStatus.RolledBack, ex.Message); } }
2. 重试逻辑实现
重试前必须先校验TID在SAP端的状态,避免重复提交:
// 单次最大重试次数,可根据业务场景调整 private const int MaxRetryCount = 5; public void RetryPendingTransactions(TidStore tidStore) { var pendingTids = tidStore.GetPendingTids(); // 从数据库获取所有Created、Submitted状态的TID foreach (var tidEntry in pendingTids) { // 加分布式锁/行级锁,避免多实例同时处理同一个TID if (!tidStore.TryLockTid(tidEntry.Tid)) continue; try { if (tidEntry.RetryCount >= MaxRetryCount) { tidStore.SetStatus(tidEntry.Tid, TidStatus.MaxRetryFailed, "超过最大重试次数"); // 触发告警通知人工处理 continue; } // 先查询SAP端TID状态 var tid = new RfcTransactionID(tidEntry.Tid); var sapTidStatus = RfcTransactionManager.CheckTransactionID(_ECCsystem, tid); switch (sapTidStatus) { case RfcTidStatus.Executed: // SAP已执行成功,走本地成功逻辑 tidStore.SetStatus(tidEntry.Tid, TidStatus.Committed, null); _ECCsystem.ConfirmTransactionID(tid); tidStore.DeleteEntry(tidEntry.Tid); break; case RfcTidStatus.NotFound: // SAP未收到该TID的请求,重新提交 var trans = RebuildTransactionFromTidEntry(tidEntry); // 从数据库存储的参数重建RfcTransaction SubmitTransaction(trans, tidStore, tidEntry.Data); break; case RfcTidStatus.InProcess: // SAP仍在处理,跳过本次重试 break; } } catch (Exception ex) { // 重试异常,更新重试次数,等待下一轮 tidStore.IncreaseRetryCount(tidEntry.Tid); } finally { tidStore.UnlockTid(tidEntry.Tid); } } }
注意事项
- 所有事务参数必须和TID绑定持久化到数据库,不要存储在本地文件,避免实例宕机、磁盘损坏导致数据丢失
- 重试时必须复用原有TID,不要生成新的TID,否则会破坏幂等性导致重复执行
- 多实例部署场景必须加分布式锁,避免同一个TID被多个实例同时处理
内容的提问来源于stack exchange,提问作者Mysterious288
相关产品推荐
相关产品推荐

