尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

ABAP后台JOB完全指南:创建、调度与监控实战

ABAP后台JOB完全指南:创建、调度与监控实战 有些事不是实时跑就一定要在前台跑。用过SAP的人都知道一到月底结账或者大批量凭证导入光靠SE38执行一把要么卡死人要么因为用户误关窗口导致程序中断。这时候ABAP后台JOB就是最常规也最靠谱的解法。后台JOB说白了就是把这个任务交给系统在指定时间、指定条件下悄悄处理不占前台会话还能自动排队、自动重跑、日志完整。我在项目里做过不少这种批量处理这中间不光是调一个函数就够了变式、启动条件、周期、监控、权限、事件触发这些坑要一个一个趟。今天把这些东西串起来写清楚给准备用ABAP做后台任务的同学一条可以直接抄的路。1. 后台Job到底在解决什么问题1.1 批量任务的天然归宿后台JOB在SAP里不是一个神秘的东西它本质上就是一段可被系统调度器管理的任务序列。你可以把JOB理解成一个“外卖订单”系统按你定的时间把餐送到中间不需要你盯着厨房商家按步骤出餐送到后你只需要看结果。对应到ABAP一个后台JOB就是一组预先排好的ABAP程序步骤这些步骤可以是一个报表、一个主数据批处理程序、或者一个接口同步程序。我见过很多刚做ABAP的人第一反应是把批量逻辑写进一个程序然后在SE38里手动执行。这样做小数据量没事一旦数据量上到几十万或者业务要求每天凌晨固定跑问题就来了。一来前台上执行长耗时任务会占住一次对话用户被锁在界面里二来系统升级、网络断连、用户误操作都会导致任务中断而且中断后根本没人知道。后台JOB就是把这些批量任务从“前台交互”挪到“系统调度”里由系统自己启停、写日志、管理重试这是SAP成熟项目实施批量处理的通用做法。1.2 为什么不是直接跑程序有人会问既然最终都是跑同一个ABAP程序为什么一定要包一层Job我举个实际例子。我之前维护过一个对账单生成程序单次运行要处理20万条明细前台执行要40分钟用户期间动鼠标误点了按钮程序直接停了。改成后台JOB后我指定了每天凌晨2点跑输出直接到后台输出列表Spool跑完发消息给业务。系统自己监控进程不需要用户时刻盯着出问题也有JOB_LOG可以查。还有一点很关键。后台JOB是SAP后台调度器统一分配的默认情况下一个JOB会占用一个后台工作进程Background Work Process和你的在线会话是分开的。这带来两个好处第一大批量数据处理时对在线用户影响小第二系统可以控制并发Job数量防止多个重型任务互相抢资源。所以JOB不是“把程序换个地方跑”而是把资源控制、优先级、时间触发这些能力全部交给系统。1.3 Job的组成步骤、变式、启动条件后台JOB由三个核心部分组成。第一步是“步骤”。每个Job至少有一个步骤每个步骤通常指定一个ABAP程序也可以指定外部命令、SAP查询、或者另一个Job。多个步骤就按顺序执行。注意步骤里的程序一般要么是报表程序不要在START-OF-SELECTION里面写UI交互要么是纯逻辑的程序这是因为后台模式下没有界面任何交互式语句都会变成空白或报错。第二步是“变式”。变式其实就是程序选择屏幕中的一组参数值。后台执行时系统不会弹选择屏幕所以必须给程序配一个变式把执行参数写死。我见过最典型的报错就是“Variant xxx does not exist”这就是没有预先维护变式。第三步是“启动条件”。启动条件决定这个Job什么时候跑。你可以让它立即执行可以指定日期时间也可以是固定周期循环还可以通过作业事件触发。这块我会在第三章重点讲因为很多人在这里以为只是填一个时间忽略了周期的边界值。把这些组成想清楚后面用ABAP代码创建工作才会有方向。代码不是去创建程序而是去组装一个“符合调度器要求”的Job描述。2. 通过ABAP代码创建后台Job2.1 三个核心函数JOB_OPEN、JOB_SUBMIT、JOB_CLOSE手工创建后台Job肯定都知道SM36但项目里经常需要在一个ABAP程序里去动态创建另一个后台Job比如接口程序根据外部文件到达情况自动生成一批处理任务。这时候就需要用ABAP标准函数组BTCH里的三个核心函数。第一个是JOB_OPEN作用是为新Job打开一个“句柄”相当于在调度器里申请了一个Job编号。它有一个Important参数是JOBNAME可以用你的业务标识拼出可读的Job名。还有一个输出的JOBCOUNT这是系统生成的唯一计数字段后面关闭和提交都要靠它。第二个是JOB_SUBMIT作用是给当前打开的Job添加一个步骤。你需要把JOBNAME、JOBCOUNT、REPORT程序名、VARIANT变式名传进去。如果还要传一些动态参数可以通过S_PARAMETER表或者S_PROCESS记住一点同一个Job可以多次调用JOB_SUBMIT每调一次就增加一个步骤步骤顺序按照调用顺序执行。第三个是JOB_CLOSE作用是“定稿”。你在JOB_CLOSE里指定启动条件比如立即执行、指定日期时间、周期执行然后系统才会真正把Job放到调度队列里。没有执行JOB_CLOSE的Job只是处于“打开”状态不会被调度。这三个函数是成套作业缺一不可。经常有人只调用了JOB_OPEN和JOB_SUBMIT结果发现Job永远不跑就是因为没有CLOSE调度器根本不知道这个Job已经建完了。2.2 完整示例一个报表的夜间作业以最常用的场景为例每天晚上1点运行一个名为Z_NIGHT_REPORT的报表变式是DAILY_001。下面的代码可以直接照抄调整。DATA: lv_jobname TYPE tbtcjob-jobname, lv_jobcount TYPE tbtco-jobcount. lv_jobname Z_NIGHT_REPORT_DAILY. CALL FUNCTION JOB_OPEN CHANGING jobname lv_jobname jobcount lv_jobcount EXCEPTIONS cant_create_job 1 invalid_jobname 2 OTHERS 3. IF sy-subrc 0. MESSAGE e000(zmsg) WITH Job创建失败. ENDIF. CALL FUNCTION JOB_SUBMIT EXPORTING jobcount lv_jobcount jobname lv_jobname report Z_NIGHT_REPORT variant DAILY_001 EXCEPTIONS bad_jobcount 1 bad_jobname 2 bad_reportname 3 bad_variantname 4 OTHERS 5. IF sy-subrc 0. MESSAGE e000(zmsg) WITH 步骤添加失败. ENDIF. CALL FUNCTION JOB_CLOSE EXPORTING jobcount lv_jobcount jobname lv_jobname strtimedef DAT sdlstrtdt sy-datum sdlstrttm 010000 EXCEPTIONS cant_close_job 1 OTHERS 2. IF sy-subrc 0. MESSAGE e000(zmsg) WITH Job关闭失败. ENDIF.重点说下JOB_CLOSE里的几个参数。STRTIMEDEF有三个常用值IMM表示立即执行DAT表示按指定日期时间执行PER表示周期循环执行。上面用的是DAT日期取当天时间是凌晨1点也就是“每天凌晨1点”跑一次。如果只写一次就这样如果想每天都跑就需要用周期后面会讲。JOB_SUBMIT里的REPORT参数也可以是函数组不行这里必须是程序名也就是Report名。如果是要执行某个函数封装好的逻辑那你还是得写一个ABAP程序去调用函数然后把程序名放进JOB_SUBMIT。我一开始也试过直接填函数模块名结果返回BAD_REPORTNAME后来才意识到步骤只能落到一段可执行程序或者系统命令上。2.3 变式与参数校验的技巧动态创建Job的坑有一大半都出在变式上。SM36手工创建时可以在界面上直接用一个已有的变式也可以用程序创建的变式。ABAP代码里JOB_SUBMIT要求变式必须已经存在所以很多实现是在某个作业程序里先判断变式有没有没有就用RSVAREDIT或者直接通过SE38手工建再提交作业。还有一个更精细的场景变式里有个参数是数字比如订单运行天数、批次大小你需要提前校验它是不是一个合法数字。ABAP里没有现成的“IS_NUMBER”函数但判断逻辑并不复杂。我常用这样的实现DATA: lv_input TYPE string. lv_input 12345. IF lv_input CO 0123456789. WRITE: / 是数字. ELSE. WRITE: / 不是数字. ENDIF.这里CO是比较运算符“仅包含”意思是字符串只包含数字字符就返回真。注意空字符串会被判断为真所以判断前最好再检查一下长度。还有一点数字字符串如果想去掉前导空格需要先用CONDENSE。在后台Job的变式校验里这种简单判断比用正则表达式更容易被业务人员理解测试也方便。另一个实用的点是用GET TIME获取当前时间用来给Job的变式写入时间戳。比如我做过一个每天生成增量文件的Job变式里有个“截止时间”参数必须根据上一次运行结束时间动态生成。这时候就需要在生成变式前拿到系统时间DATA: lv_date TYPE sydatum, lv_time TYPE syuzeit. GET TIME. lv_date sy-datum. lv_time sy-uzeit.GET TIME这段语句虽然简单但在后台日志里相当好用。JOB跑完后把开始时间、结束时间写成日志业务人员一看就知道运行了多长时间。关于运行时间统计我放到第三章详细说。3. 调度、监控与运维3.1 启动类型立即、定时、周期、事件后台JOB的启动方式分几类实操中要注意取值。立即启动是最简单的JOB_CLOSE里STRTIMEDEF IMM系统马上把Job丢到后台工作进程队列但不保证立刻执行要等空闲进程。这种适合接口大量到达后顺手触发一个处理不追求精准时间。定时启动就是指定日期和时间对应STRTIMEDEF DAT。这个看起来简单但有一个经典坑如果只传SDLTSTRTTM而不传SDLTSTRTDT系统可能使用默认日期导致它在“今天”和“默认日期”之间摇摆。所以代码里我永远两个一起写日期用SY-DATUM时间写业务要求的HHMMSS。周期启动对应STRTIMEDEF PER。在手工SM36里你可以勾选周期然后指定每小时、每天等系统会自动生成SDLTSTDURH小时数、SDLTSTDURM分钟数、SDLTSTPERD周期值。ABAP代码里比较麻烦的是周期边界比如“每隔2小时从早上4点跑到晚上8点”你不光要写周期还要写结束时间和重复次数。如果只是固定每天/每周代码里用PER加相应的周期参数远比想象中容易出错。事件启动是更高级的方式。SAP里可以定义事件Event ID比如物料主数据批量导入完成后抛一个事件让另一个汇总Job自动开始。手工定义事件在SM64代码里抛事件用函数BP_EVENT_RAISE。示例如下CALL FUNCTION BP_EVENT_RAISE EXPORTING eventid Z_MATERIAL_IMPORT_DONE eventpar lv_batch_number.然后你在SM36或代码里创建Job时把启动条件设置为“After Event”参数填事件ID。这样Job就不依赖死板的时钟而是依赖前一个业务动作的完成状态特别适合批次链式处理。我手上有个销售订单日结的链路就是文件导入Job完成后事件触发汇总Job再触发报表输出Job。稳定性比分点定时高很多。3.2 用SM37和函数监控作业Job创建完不是结束了上线以后最核心的是监控。手工监控的入口是SM37在这里你能看到每个Job的运行状态Scheduled、Released、Ready、Running、Finished、Canceled。后台作业如果失败状态会变成Canceled双击能看到Job Log和抛出的错误消息。代码层面可以用函数BP_JOB_READ或者更直接的授权检查来读状态。在自定义运维程序里我通常用BP_JOB_SELECT读取Job列表再对返回的内部表判断状态如果连续几次都是Canceled就发邮件。关于用ABAP发邮件可以用函数SO_NEW_DOCUMENT_ATT_SEND_API1这个在项目里也是刚需不展开。监控最重要的经验是“不只盯Job还要盯后台进程”。一个Job明明处于Ready状态却不开始执行十有八九是后台进程池不够。事务码SM50可以看到各个工作进程正在跑什么。如果后台进程全部被长事务占住新Job只能排队。这时候你赶紧优化占用进程的程序而不是反复重跑Job。3.3 日志里的时间戳用GET TIME记录运行时长后台Job的执行日志默认是系统自动记录的但系统记录的是Job管理层的信息看不到程序内部每个环节的耗时。为了定位性能瓶颈我习惯在程序里手动埋点。在ABAP程序中最直接得到当前时间的方法就是DATA: lv_start_date TYPE sydatum, lv_start_time TYPE syuzeit, lv_end_date TYPE sydatum, lv_end_time TYPE syuzeit. lv_start_date sy-datum. lv_start_time sy-uzeit. ... 执行核心逻辑 ... lv_end_date sy-datum. lv_end_time sy-uzeit.有了开始和结束的时间还要转换成秒才能算时长。直接用两个TIME类型相减会返回一个时间类型不直观。比较方便的做法是先转成时间戳。SAP里可以调用函数CALL FUNCTION GET_TIME_STAMP获取时间戳或者用简单的时间计算。如果只需要精确到秒我常用这样一个算式DATA: lv_duration TYPE i. lv_duration ( lv_end_time - lv_start_time ) MOD 86400.SY-UZEIT是一个六位数直接相减的例子是开始时间为230000结束时间为010000相减得到-220000这显然不对。这里有一个常见的错误逻辑跨午夜的时候必须考虑日期偏移。所以更好的方案是用ABAP系统自带的时间戳GET TIME STAMP FIELD lv_start_ts. ... 逻辑 ... GET TIME STAMP FIELD lv_end_ts. lv_duration lv_end_ts - lv_start_ts. 精确到秒GET TIME STAMP这个写法比旧式GET TIME更好用因为时间戳跨日期也行。不过要注意时间戳是UTC存储的你如果想显示本地时间还需要用CONVERT TIME STAMP加上时区换算。后台Job常常跑在夜间差8小时时区容易看错。我一般为了省事既保留本地时间也保存UTC时间戳日志里同时输出。热词里提到的“ABAP中GET TIME应用获取运行时间”很多人找半天函数模块其实直接用GET TIME STAMP是最简单的。下面给一个实际记录片段DATA: lv_ts_start TYPE timestampl, lv_ts_end TYPE timestampl. GET TIME STAMP FIELD lv_ts_start. CALL FUNCTION Z_BATCH_PROCESS. GET TIME STAMP FIELD lv_ts_end.用这个方式程序跑完在应用日志里写一行“本次耗时xx秒”业务方那边对账非常方便。4. 常见坑与增强思路4.1 我踩过的几个典型问题后台JOB写多了坑也大多踩过。挑几个最常见的分享。第一个是Job名重复。JOB_OPEN不给报错但JOB_CLOSE的时候会混到旧Job里最后出现同名的两个Job互相干扰。我在开发环境里就遇到过重跑第二次Job结果第一次的Job被莫名其妙的更新状态。现在我的代码习惯是先查一下同名Job有没有未完成实例有的话先等它结束或者加日期后缀。第二个是变式被运维人员修改了。JOB_SUBMIT里指定的是变式名变式里的日期参数如果用了“今天 N天”这类相对值在不同日期执行时会因为基准日期不同而出现意外结果。所以变式里最好不要放“根据当前日期推算的硬编码日期”尽量在程序逻辑里动态算参数或者用变式里面的SY-DATUM相关计算。第三个是权限问题。后台用户一旦被去掉某个事务代码的授权Job跑起来可能卡在权限检查上。特别痛的场景是你开发机里跑得好好的生产机Job频繁报权限错误排查半天发现运行Job的系统用户和你的测试用户不一致。解决办法是先看运行Job的系统用户叫什么再针对这个用户补角色。第四个是并行Job的资源竞争。两个Job同时更新同一张表很容易造成锁等待或者数据错乱。我的做法是在标准作业程序里加一个“作业互斥标志”如果发现同一时间已有同类Job在Running状态新Job直接退出并写日志。4.2 作业里做增强从BASIC到BADI后台JOB本身是调度载体但真正的业务逻辑往往需要增强。以F110批量付款为例SAP标准程序在后台跑大批量付款的时候可以通过BADI来对付款媒介格式做扩展。如果你用ABAP写一个调用F110的辅助Job原理还是先创建JobJob步骤里去调用F110的接口封装程序。F110的业务操作可以拆成“运行付款建议”和“执行付款”两个阶段不少公司把这两步用两个后台Job串起来中间用事件或周期控制。标准BADI增强用于处理供应商银行信息、打印付款格式等。这给我们的启发是后台Job不是“只放一个标准程序”你也可以把增强逻辑封装成独立的ABAP程序然后在Job里依次调度。比如付款前校验供应商主数据发现异常就用ALV表格输出到Spool再由业务人员第二天看输出清单。这样增强和主流程解耦出了事不会影响标准功能。还有一个小技巧很多人做BADI或者屏幕增强后想在后台Job里动态打开维护界面比如SM30维护表后台模式不支持界面。直接调用表维护的生成程序可以但前提是不要依赖界面交互。碰到这种情况我通常写一个批量更新程序作为Job步骤去批量调用BAPI或者直接更新表避免弹窗。4.3 扩展编码转换、文件处理与视图维护后台Job经常跑在接口场景里这就绕不开文件处理。最典型的场景是从外围系统拿一个ANSI编码的文本放到SAP应用服务器上程序再去读取。如果直接用ABAP读CSV经常会出现中文乱码原因就是文件编码和SAP内部代码页不一致。解决办法是读取前先做UTF-8到ANSI的转码或者反过来。ABAP语言里可以用CL_ABAP_CONV_CODEPAGE类代码示意DATA: lv_binary TYPE xstring, lv_text TYPE string. lv_binary ...读取的文件二进制内容.... lv_text cl_abap_codepageconvert_from( source lv_binary codepage UTF-8 ).这里面最容易踩的坑是字节顺序标记BOM有些文件带BOM有些文件不带转换后第一个字符会变成莫名其妙的东西。批量处理时要么统一去掉BOM要么读取前判断。我之前的解决方案是统一用函数模块SCMS_XSTRING_TO_BINARY按照UTF-8转换后再用一个正则表达式去掉开头的xEFBBBF。代码不复杂但如果不考虑线上乱码会让你查很久。视图维护这块SM30是交互式后台Job里可以通过函数VIEW_MAINTENANCE_CALL去更新视图。注意它不是纯后台友好的如果视图配置里带了对话框到了后台也会有问题。更稳妥的做法是直接写ABAP程序用UPDATE/INSERT语句更新透明表或者调用对应的标准更新函数。4.4 常见问题速查表为了方便排查我把后台Job开发中经常遇到的问题整理成表。问题现象可能原因处理办法Job一直处于Released/Ready但不跑后台进程池满SM50查看后台进程结束长任务或扩展进程Job状态Canceled程序运行时异常或权限不足双击Job Log找报错消息JOB_SUBMIT报错BAD_REPORTNAME填了函数名而不是程序名确认填入的是可执行Report名变式不存在变式名拼写错误或未建立SE38维护变式或检查变式名称周期Job漏跑结束时间或重复次数设置不当检查调度周期边界适当放宽中文乱码文件编码与系统代码页不一致转换UTF-8/ANSI统一BOM处理两个Job互相锁表并发更新同一表增加互斥标志或错峰调度跑完没输出程序用了交互式输出改成写数据库表或SpoolJob名冲突重复创建同名Job创建前检查或用日期做后缀运行时间异常长数据量增长或索引失效查看SQL执行计划优化索引白屏排查还有个方向后台JOB里如果用了WRITE语句输出默认送到Spool但Spool可能因为输出设备没有配置而不能生成。我遇到过一次程序明明跑完了状态是Finished但找不到输出列表后来发现是输出设备没有绑定打印机。如果业务要看输出一定要提前配置好输出设备或者在程序里把结果写到应用日志表。再说一个增强思路用SAP标准事件Job来做链式处理。比如数据导入完成后通过BP_EVENT_RAISE抛事件下游Job启动处理完再抛下一个事件。这样比依赖固定时间点可靠得多。我在物料批量导入的对接接口里就是这么实现的导入Job跑完事件触发“一致性检查”Job检查Job通过后事件触发“发布”Job。整个过程没有人工参与出错也全部留在日志里。我个人做后台JOB项目最大的体会是不要在JOB_SUBMIT之前写太多“聪明”的代码先把Job创建-运行-监控这条最短链路跑通再去加变式校验、事件触发、编码转换这些高级功能。很多问题不是出在逻辑上而是出在Job调度参数没有吃透。先把STRTIMEDEF、周期边界、变式、权限这几个基本功练好后台任务这块基本就稳了。最后再分享一个小技巧给Job命名的时候我习惯把业务类型、动作、日期都拼进去比如Z_SD_ORDER_XLSX_20240501这样在SM37里一眼就能看出这个Job是干嘛的省得每次点进去看程序名。生产环境里Job一多命名规范比功能实现还重要。
返回列表