ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

ABAP异步RFC并发编程实战:从原理到高性能框架设计

ABAP异步RFC并发编程实战:从原理到高性能框架设计 1. 从“等待”到“并行”为什么我们需要异步RFC在ABAP开发里调用一个远程函数RFC是再常见不过的操作。无论是跨系统获取数据还是调用一个耗时较长的后台处理函数我们通常都会用CALL FUNCTION ... DESTINATION这种同步方式。代码执行到这里就停下来眼巴巴地等着远端的系统处理完毕把结果传回来然后才能继续往下走。这个过程就像你去银行柜台办业务取个号然后只能干坐着等叫号期间什么也做不了。这种“同步等待”模式在大多数场景下没问题但当遇到一些特殊情况时它的弊端就非常明显了。想象一下你需要在一个报表里同时向五个不同的外围系统请求数据来拼凑一个完整的视图。如果用同步RFC你的程序会串行地发起五个调用调用A等待返回调用B等待返回…… 总耗时几乎是五个调用耗时的简单相加。如果每个调用平均耗时2秒用户就要盯着进度条至少10秒。更糟糕的是如果其中一个调用因为网络波动或对方系统繁忙卡住了5秒整个程序就会卡住5秒用户体验极差。这就是“并发执行”要解决的问题。我们希望这五个调用能够同时发出去然后同时等待它们返回最后谁先回来就先处理谁的数据。这样总耗时理论上接近于最慢的那个单个调用的耗时而不是所有调用耗时的总和。在ABAP的世界里实现这种“同时发起、分别等待”的核心技术就是异步RFC。异步RFCAsynchronous Remote Function Call是相对于同步RFC而言的。它的核心思想是“发射后不管”——主调程序发起调用后不等待被调函数执行完毕就立刻继续执行后续代码。被调函数在远程系统上进入队列开始执行主调程序可以随后在某个时间点再去“收割”结果。这为实现并发提供了可能我可以连续发起多个异步RFC调用它们就像一群被同时放出的信鸽飞向各自的目的地而我可以在它们都飞出后再统一检查哪些已经飞回来了。然而ABAP的异步RFC并发并不是像Java或Go语言中那种“多线程”并发。ABAP工作进程本质上是单线程的一个对话任务在一个时间点上只能执行一段代码。异步RFC的并发是一种“基于队列和回调的伪并发”或“逻辑并发”。主程序快速、连续地发起多个异步调用请求这些请求被SAP系统的RFC调度器放入队列然后由可用的后台工作进程去实际执行。从主程序的角度看它“瞬间”发起了所有请求实现了并发启动的效果。这对于提高批量数据处理、数据采集、接口调用的效率至关重要尤其是在需要整合多个异构系统数据的复杂业务场景下比如日终对账、大数据量报表生成、实时数据看板等。2. 异步RFC的运作机制与核心参数解析要玩转异步RFC不能只停留在“调用然后忘记”的概念上必须深入理解它在SAP内核层面是如何被调度和管理的。这关系到我们如何设计稳健的并发程序以及如何排查那些令人头疼的“调用丢失”或“结果错乱”问题。2.1 异步RFC的生命周期从发起到回调一个异步RFC的完整生命周期远比同步调用复杂。它大致分为三个阶段发起阶段使用CALL FUNCTION func_name STARTING NEW TASK taskname语法。这里的关键是STARTING NEW TASK它告诉SAP内核“请为这个RFC调用创建一个新的后台任务单元不要阻塞我当前的对话任务。” 调用语句执行后主程序立即得到控制权继续向下执行而被调用的函数及其参数会被打包成一个“执行单元”提交给SAP的RFC调度器。排队与执行阶段RFC调度器收到这个执行单元后会检查当前系统可用的后台工作进程资源。如果有空闲进程则立即分配并执行如果没有则将其放入一个内部的异步RFC队列中等待。这个队列是全局的遵循先进先出FIFO原则。一旦有工作进程空闲队列中的下一个任务就会被取出执行。这里有一个至关重要的点异步RFC的执行完全依赖于可用的后台工作进程数量。如果系统配置的后台进程数很少或者同时有大量异步任务被发起那么任务就会在队列中积压导致实际执行被严重延迟。回调与结果处理阶段这是异步RFC编程模型中最具特色也最容易出错的部分。当被调函数在远端执行完毕后它无法像同步调用那样直接“返回”给主调程序因为主调程序可能早就执行到别的地方去了。因此SAP设计了一套回调机制。你需要在被调函数中使用CALL FUNCTION ‘RFC_CALLBACK’或者更常见的在主调程序中定义一个专门的回调子程序并通过PERFORMING form ON END OF TASK附加项来注册它。当任务结束时系统会自动调用这个子程序并将任务名和结果传递给它。2.2 核心参数DESTINATION,TASKNAME, 与资源控制理解语法中的几个关键参数是写出正确并发程序的基础DESTINATION指定远程目标系统。对于异步RFC这个目标必须是配置在SM59中的RFC连接。如果是调用同一系统内的函数模块本地异步调用可以省略此参数或使用空字符串‘NONE’。但请注意即使是本地调用它也会使用后台工作进程而不是当前对话进程。TASKNAME任务名。这是一个必须手动管理的关键标识符。你需要为每个并发任务分配一个唯一的名称。通常我们会使用一个前缀加上索引或GUID来生成例如TASK_1,TASK_2。这个任务名有两个核心作用在回调子程序中识别是哪个任务完成了。回调子程序会接收到这个任务名作为参数。用于后续的等待和状态检查。你可以使用WAIT UNTIL语句等待一个或多个指定任务名的任务完成。资源控制参数这些参数隐藏在后台但极大地影响着并发行为和系统稳定性。RFC_SYSTEM在发起大量异步调用时你需要关注目标系统的负载。SAP有一个隐式的限制防止单个客户端对同一个目标系统发起过多的未完成异步请求以避免压垮对方。这个限制通常在系统层面配置。MAX_TASKS与QNAME在CALL FUNCTION ... STARTING NEW TASK中可以通过MAX_TASKS限制针对某个目标或队列的最大并发任务数。QNAME则可以指定一个自定义队列名实现更精细的任务分组和优先级控制。但在基础并发场景中我们通常不直接设置而是依赖系统默认行为。2.3 与同步RFC及事务性RFC的对比为了更清晰地定位异步RFC我们把它放在RFC家族的坐标系里看看特性同步RFC (sRFC)异步RFC (aRFC)事务性RFC (tRFC/qRFC)执行模式调用后等待直到函数执行完毕并返回。调用后立即返回函数在后台执行。调用后立即返回函数被确保执行一次Exactly-Once但可能在稍后的时间点。连接性需要实时、稳定的网络连接。发起时需要连接执行和回调时也需要。发起时需要连接执行时如果目标系统不可用任务会留在队列中直到连接恢复。执行保证即时执行。尽力执行。如果资源不足或系统故障任务可能丢失。保证执行。任务被持久化到数据库确保最终被执行一次。适用场景需要即时结果的交互式操作、简单的数据读取。可并行化、不要求即时结果、需要提升整体响应速度的后台处理。关键业务数据同步、跨系统事务保证、必须成功的后台作业。编程复杂度低。线性思维。中高。需要处理回调、任务管理和错误处理。中。需要配置队列和监控事务SMQ1/SMQ2。对系统资源影响占用一个对话工作进程直到返回。占用后台工作进程主对话进程被释放。占用后台工作进程且任务信息持久化占用数据库资源。从对比可以看出异步RFC是在执行效率和可靠性之间的一种折中。它比同步RFC高效但不如事务性RFC可靠。因此它最适合那些“希望尽快完成但偶尔丢失一两个任务也可以接受或可通过其他机制弥补”的场景比如非关键的数据预加载、缓存刷新、并行计算等。3. 实战构建一个稳健的异步RFC并发处理框架理解了原理我们来看如何从零开始构建一个用于并发查询多个工厂库存的实战程序。这个场景非常典型你需要从SAP的多个工厂可能对应不同的逻辑系统或客户端快速拉取库存数据汇总到一个ALV报表中。3.1 程序骨架与全局数据定义首先我们定义程序需要的数据结构。由于并发任务的结果是乱序返回的我们必须有一个中央容器来收集结果并用任务名作为键来关联。REPORT zrfc_async_concurrent_demo. TYPES: BEGIN OF ty_factory_data, werks TYPE werks_d, 工厂 matnr TYPE matnr, 物料号 labst TYPE labst, 非限制库存 END OF ty_factory_data, tt_factory_data TYPE TABLE OF ty_factory_data WITH KEY werks matnr. TYPES: BEGIN OF ty_task_context, task_name TYPE string, 任务名 werks TYPE werks_d, 该任务对应的工厂 rfc_dest TYPE rfcdest, 该任务对应的RFC目标 可以添加更多上下文信息如物料范围等 END OF ty_task_context. DATA: gt_factories TYPE TABLE OF werks_d, 需要查询的工厂列表 gt_results TYPE TABLE OF ty_factory_data, 最终结果表 gt_task_context TYPE HASHED TABLE OF ty_task_context WITH UNIQUE KEY task_name. 任务上下文表 DATA: gv_max_concurrent TYPE i VALUE 5, 最大并发数防止一次性发起过多任务 gv_pending_tasks TYPE i. 当前未完成的任务数这里的关键是gt_task_context哈希表。每个异步任务在发起时我们会将它的任务名和对应的业务上下文这里是工厂代码存入此表。当回调子程序被触发时我们就能通过传入的任务名快速找到这个任务是替哪个工厂执行的从而将结果正确归位。3.2 主控循环发起并发任务主程序的逻辑是循环工厂列表但并非简单地为每个工厂发起一个异步调用。我们需要控制并发度避免瞬间发起成百上千个任务压垮队列。START-OF-SELECTION. 1. 获取需要处理的工厂列表示例 SELECT werks FROM t001w INTO TABLE gt_factories UP TO 20 ROWS. 2. 初始化 CLEAR: gt_results, gt_task_context, gv_pending_tasks. 3. 主控循环发起任务 LOOP AT gt_factories ASSIGNING FIELD-SYMBOL(fs_werks). 检查当前未完成的任务数是否达到上限 WHILE gv_pending_tasks gv_max_concurrent. 如果达到上限则等待至少一个任务完成再继续发起新任务 WAIT UNTIL gv_pending_tasks gv_max_concurrent UP TO 5 SECONDS. IF sy-subrc 0. 等待超时可以记录日志或采取其他措施 MESSAGE i001(00) WITH 等待空闲任务超时 DISPLAY LIKE W. ENDIF. ENDWHILE. 生成唯一任务名 DATA(lv_task_name) |TASK_{ sy-tabix }|. 准备任务上下文并存储 DATA(ls_context) VALUE ty_task_context( task_name lv_task_name werks fs_werks rfc_dest |DEST_{ fs_werks }| 假设RFC目标根据工厂配置 ). INSERT ls_context INTO TABLE gt_task_context. 发起异步RFC调用 CALL FUNCTION Z_GET_FACTORY_STOCK 假设的远程函数 STARTING NEW TASK lv_task_name DESTINATION ls_context-rfc_dest PERFORMING handle_task_completion ON END OF TASK EXPORTING iv_werks fs_werks EXCEPTIONS system_failure 1 MESSAGE lv_msg communication_failure 2 MESSAGE lv_msg resource_failure 3 OTHERS 4. CASE sy-subrc. WHEN 0. 任务成功加入队列 gv_pending_tasks gv_pending_tasks 1. WHEN 3. 资源失败通常是因为没有可用的后台工作进程或达到最大任务数限制 MESSAGE i002(00) WITH lv_task_name 资源不足任务未启动 DISPLAY LIKE E. DELETE gt_task_context WHERE task_name lv_task_name. 清理上下文 WHEN 1 OR 2 OR 4. 系统、通信或其他错误 MESSAGE i003(00) WITH lv_task_name 调用失败: lv_msg DISPLAY LIKE E. DELETE gt_task_context WHERE task_name lv_task_name. 清理上下文 ENDCASE. ENDLOOP.这段代码有几个要点并发度控制通过gv_max_concurrent和gv_pending_tasks变量我们实现了简单的“令牌桶”控制。只有在有空闲“令牌”即未完成任务数小于上限时才发起新任务。WAIT UNTIL的使用当并发数达到上限时程序使用WAIT UNTIL进行等待。这是一个异步等待它不会阻塞整个工作进程而是释放进程去处理其他任务包括可能完成的异步任务回调。UP TO 5 SECONDS设置了超时避免死等。立即错误处理在CALL FUNCTION ... STARTING NEW TASK后立即检查sy-subrc。这里的错误指的是“任务是否成功提交到队列”。常见错误resource_failure意味着系统当前无法处理更多异步任务这是进行流控和降级的重要信号。3.3 回调子程序处理乱序返回的结果回调子程序handle_task_completion是异步RFC编程的灵魂。它会在每个任务结束时被系统自动调用。FORM handle_task_completion USING p_taskname TYPE clike. DATA: lt_stock_data TYPE tt_factory_data, lv_werks TYPE werks_d. 1. 根据任务名从上下文中找到对应的业务信息 READ TABLE gt_task_context ASSIGNING FIELD-SYMBOL(fs_ctx) WITH TABLE KEY task_name p_taskname. IF sy-subrc 0. 找不到上下文可能是任务名管理出错或上下文被误删 MESSAGE w004(00) WITH p_taskname 任务上下文丢失 DISPLAY LIKE E. EXIT. ENDIF. lv_werks fs_ctx-werks. 2. 接收任务返回的数据 RECEIVE RESULTS FROM FUNCTION Z_GET_FACTORY_STOCK IMPORTING et_stock_data lt_stock_data EXCEPTIONS system_failure 1 MESSAGE DATA(lv_msg) communication_failure 2 MESSAGE lv_msg OTHERS 4. 3. 处理接收结果 CASE sy-subrc. WHEN 0. 成功接收到数据 LOOP AT lt_stock_data ASSIGNING FIELD-SYMBOL(fs_data). fs_data-werks lv_werks. 确保工厂代码被正确填充有时函数可能不返回 INSERT fs_data INTO TABLE gt_results. ENDLOOP. MESSAGE s005(00) WITH lv_werks 数据接收成功 DISPLAY LIKE S. WHEN 1 OR 2. 接收时发生系统或通信错误 MESSAGE w006(00) WITH p_taskname lv_werks 结果接收失败: lv_msg DISPLAY LIKE E. WHEN 4. 函数模块本身执行时抛出了异常 MESSAGE w007(00) WITH p_taskname lv_werks 远程函数执行异常 DISPLAY LIKE E. ENDCASE. 4. 清理无论成功与否任务已完成从上下文中移除并减少未完成任务计数 DELETE gt_task_context WHERE task_name p_taskname. gv_pending_tasks gv_pending_tasks - 1. ENDFORM.注意RECEIVE RESULTS FROM FUNCTION语句必须与最初CALL FUNCTION时调用的函数名完全一致。这里是很多初学者犯错的地方他们有时会在回调里写错函数名导致永远接收不到数据。3.4 最终等待与结果展示所有任务发起后主循环结束但可能还有一些任务正在执行或等待回调。我们需要一个最终的等待环节确保所有任务都处理完毕。 4. 等待所有剩余任务完成 WHILE gv_pending_tasks 0. WAIT UNTIL gv_pending_tasks 0 UP TO 30 SECONDS. IF sy-subrc 0. MESSAGE i008(00) WITH 等待最终任务完成超时仍有 gv_pending_tasks 个任务未完成 DISPLAY LIKE W. 可以选择强制清理未完成的任务上下文如果有必要 EXIT. ENDIF. ENDWHILE. 5. 显示结果 IF gt_results IS NOT INITIAL. cl_demo_outputdisplay_data( gt_results ). ELSE. MESSAGE i009(00) WITH 未获取到任何库存数据 DISPLAY LIKE I. ENDIF.4. 进阶话题性能调优、错误处理与常见陷阱掌握了基础框架后要让异步RFC并发程序在生产环境中稳定运行还需要关注以下几个进阶问题。4.1 性能调优找到并发度的“甜蜜点”并发数 (gv_max_concurrent) 不是越大越好。设置过高会导致后台工作进程耗尽所有进程都被占用新的任务包括其他作业或异步请求无法执行。目标系统过载如果所有并发任务都指向同一个目标系统可能会将其压垮导致超时或错误率上升。数据库锁竞争加剧如果异步任务都涉及大量数据库更新高并发可能导致锁等待甚至死锁。如何确定最优并发度基准测试从一个较小的值如3-5开始逐步增加观察程序总耗时和系统负载SM50, SM66。总耗时通常会随着并发数增加而下降但到达某个点后下降曲线会变得平缓甚至回升这个拐点就是“甜蜜点”。考虑系统资源检查系统参数rdisp/wp_no_btc后台工作进程数。你的最大并发数不应超过此值太多并且要为系统其他用途预留进程。考虑目标系统如果调用外部系统必须考虑对方的承受能力。可能需要与对方系统管理员协商或实现更动态的流控例如根据目标系统当前错误率动态调整并发数。4.2 全面的错误处理与重试机制异步RFC的错误处理是分层的调用时错误如前所述在CALL FUNCTION ... STARTING NEW TASK后立即检查。对于resource_failure标准的做法是让程序等待一段时间后重试或者将任务放入一个自定义的待重试列表。回调时错误在RECEIVE RESULTS时检查。这里可能遇到网络问题导致的通信失败或者远程函数本身抛出的业务异常。对于通信失败可以考虑重试对于业务异常则需要根据具体错误码进行业务逻辑上的处理。任务丢失这是异步RFC最棘手的问题。如果SAP应用服务器在任务排队后、执行前发生重启或者任务队列出现异常任务可能会无声无息地消失。异步RFC不保证执行。对于要求绝对可靠的任务应该使用事务性RFC。一个简单的带重试的调用逻辑示例DATA: lv_retry_count TYPE i VALUE 0, lv_max_retries TYPE i VALUE 3. WHILE lv_retry_count lv_max_retries. CALL FUNCTION Z_ASYNC_FUNC STARTING NEW TASK lv_task_name EXPORTING ... EXCEPTIONS resource_failure 1 ... IF sy-subrc 0. EXIT. 调用成功退出循环 ELSEIF sy-subrc 1 AND lv_retry_count lv_max_retries. 资源失败等待后重试 lv_retry_count lv_retry_count 1. WAIT UP TO ( 2 * lv_retry_count ) SECONDS. 指数退避等待 ELSE. 其他错误或重试次数用尽记录错误并放弃 MESSAGE e... 记录详细错误 EXIT. ENDIF. ENDWHILE.4.3 必须绕开的“坑”共享内存的误用异步RFC的回调子程序与主程序共享同一个ABAP内存会话Internal Session。这意味着你可以直接修改主程序中定义的全局变量如我们例子中的gt_results。但这把双刃剑。如果多个回调同时修改同一片内存区域且没有同步机制就会导致数据错乱。在我们的例子中INSERT ... INTO TABLE gt_results操作在极端高并发下也可能出现问题。更安全的方式是让每个回调将结果先存入一个线程安全的临时容器最后再由主程序合并。WAIT语句的误用WAIT UNTIL和WAIT UP TO会释放工作进程。但如果你在循环中不必要地、过于频繁地使用WAIT会导致大量的进程上下文切换反而降低性能。只在必要的同步点使用它。任务名冲突任务名必须在当前内部会话内唯一。如果重复新的调用会失败。使用简单的计数器如sy-tabix在简单场景下可行但如果程序逻辑复杂有分支或递归调用最好使用更可靠的唯一标识符例如cl_system_uuidcreate_uuid_c32_static( )生成的UUID。忘记清理任务上下文回调子程序中一定要记得从gt_task_context等管理结构中删除已完成的任务。否则这个表会不断膨胀影响性能也可能导致后续的逻辑错误如误判任务未完成。在异步RFC中调用有GUI状态的函数绝对禁止。异步RFC在后台工作进程执行没有用户会话和GUI状态。任何尝试弹出对话框、调用CALL SCREEN或执行其他与用户交互的操作都会导致运行时错误RFC_ERROR_SYSTEM_FAILURE。5. 超越基础并行处理框架与监控对于需要大规模并发处理的项目手动管理任务、上下文和错误会变得非常繁琐。此时可以考虑构建一个轻量级的并行处理框架或者利用SAP提供的一些模式。5.1 封装可重用的并行处理器你可以将任务发起、并发控制、上下文管理、回调分发和错误收集封装到一个或多个类中。例如定义一个ZCL_PARALLEL_PROCESSOR类ADD_TASK方法接收一个函数名、参数和业务键内部生成任务名并管理上下文。RUN方法负责控制并发度循环发起任务。WAIT_FOR_COMPLETION方法等待所有任务结束。一个统一的事件处理或回调接口用于处理每个任务的结果。这样业务开发人员只需要关注“要并行做什么”和“怎么处理单个结果”而不必再纠缠于异步RFC的底层细节。5.2 使用SPTA进行并行处理SAP NetWeaver 从某个版本开始提供了SPTA (SAP Parallel Task Administration)框架它本质上是对异步RFC模式的一种高层封装和增强。通过事务SPTA你可以定义并行处理单元系统会自动管理任务的拆分、分发、执行和结果收集。这对于处理可以轻松划分为独立块的大量数据如处理一张大表的每一行特别有用。虽然学习曲线稍陡但对于标准的并行计算场景它可以减少大量的样板代码。5.3 监控与调试异步RFC程序出问题时调试比同步程序困难因为你不能简单地设断点跟踪。SM58这是监控异步RFC和事务性RFC的中央工具。你可以在这里看到所有出错的RFC调用Update Terminated。对于异步RFC如果调用时或回调时发生系统/通信错误通常会被记录在这里。定期检查SM58是生产系统运维的必要步骤。SM50/SM66观察后台工作进程的忙碌状态。如果你的并发程序导致所有后台进程长时间处于忙碌状态会影响系统其他后台作业。在回调子程序中添加日志这是最有效的调试手段。在回调子程序的开始和关键分支使用MESSAGE ... INTO结合APPL_LOG写入应用日志或者使用CL_BALI_LOG等更强大的日志工具。记录下任务名、输入参数、接收结果的状态和返回的数据样本。当结果不符合预期时这些日志是定位问题的唯一线索。模拟与单元测试为你的并发逻辑编写单元测试是很好的实践。你可以通过依赖注入在测试环境中将异步调用替换为同步调用或模拟对象从而验证业务逻辑的正确性而不必依赖于不稳定的RFC连接。异步RFC并发是ABAP开发者从编写顺序执行脚本迈向构建高性能、高响应度应用的关键一步。它要求开发者具备更全面的视角不仅要关心业务逻辑还要关心任务调度、资源管理和错误恢复。虽然引入了一定的复杂度但对于那些受限于I/O等待或远程调用延迟的应用场景它所带来的性能提升是数量级的。理解其原理遵循稳健的编程模式并善用系统工具进行监控你就能让ABAP程序真正“跑”起来。
返回列表