ABAP使用异步远程RFC实现并行处理

1、使用场景

当开发复杂报表,需要处理大量数据,不管怎么优化计算和查询语句,程序的运行效率还是达不到用户要求,怎么办?

为了解决这个问题,就需要程序实现并行处理。

本文档就是通过异步调用远程RFC的办法,实现对大量数据的计算,以并行的方式,更快的计算出最终结果。

2、代码实现

在实现并行处理时,首先要看系统当前能并行的资源数

"--------------------@斌将军--------------------  
"获取服务器标识
CALL 'C_SAPGPARAM'
   ID  'NAME'   FIELD  'rdisp/myname'
   ID  'VALUE'  FIELD  gv_applserver.

"获取登录/服务器组名称
SELECT SINGLE
  classname
FROM  rzllitab
INTO  gv_classname    "Server Group Name
WHERE  applserver  =  gv_applserver
  AND  grouptype  =  'S' .    "S:服务器组,空:登陆组

    CALL FUNCTION 'SPBT_INITIALIZE'
      EXPORTING
        group_name                     = gv_classname
      IMPORTING
        max_pbt_wps                    = gv_total "可用资源总数
        free_pbt_wps                   = gv_available "空闲资源数
      EXCEPTIONS
        invalid_group_name             = 1
        internal_error                 = 2
        pbt_env_already_initialized    = 3
        currently_no_resources_avail   = 4
        no_pbt_resources_found         = 5
        cant_init_different_pbt_groups = 6
        OTHERS                         = 7.
"--------------------@斌将军--------------------

将逻辑处理封装为远程RFC,比如现在要计算1000行数据,每行数据乘以一百万次循环的累加数,然后展示出结果。远程RFC的计算逻辑如下:

"--------------------@斌将军--------------------
FUNCTION ytest001_001.
*"----------------------------------------------------------------------
*"*"本地接口:
*"  IMPORTING
*"     VALUE(I_INDEX) TYPE  I
*"     VALUE(I_COUNT) TYPE  I
*"  EXPORTING
*"     VALUE(E_INDEX) TYPE  I
*"     VALUE(E_COUNT) TYPE  I
*"----------------------------------------------------------------------

  DO 1000000 TIMES.
    i_count = i_count + 1.
  ENDDO.

  i_count = i_count * i_index.

  e_index = i_index.
  e_count = i_count.

ENDFUNCTION.
"--------------------@斌将军--------------------

在主程序中调用远程RFC,并且添加远程调用的系统报错异常communication_failure和system_failure

"--------------------@斌将军--------------------
CALL FUNCTION 'YTEST001_001' STARTING NEW TASK gv_taskname
    DESTINATION IN GROUP gv_classname
    PERFORMING frm_ytest ON END OF TASK "调用每条线程的处理方法,接收处理结果
    EXPORTING
      i_index               = ps_alv-index
      i_count               = ps_alv-count
    EXCEPTIONS
      communication_failure = 1 MESSAGE lv_message
      system_failure        = 2 MESSAGE lv_message
      resource_failure      = 3.
"--------------------@斌将军--------------------

并在主程序中接收远程RFC的返回消息

"--------------------@斌将军--------------------
"返回消息处理
FORM frm_ytest USING taskname.

  DATA:ls_alv TYPE ty_alv.

  "接收处理数据的返回消息
  RECEIVE RESULTS FROM  FUNCTION 'YTEST001_001'
   IMPORTING
     e_index       = ls_alv-index
     e_count       = ls_alv-count
  EXCEPTIONS
      communication_failure  = 1 MESSAGE lv_message
      system_failure         = 2 MESSAGE lv_message.

  MODIFY gt_alv FROM ls_alv TRANSPORTING count WHERE index = ls_alv-index.

  "已完成进程 + 1
  gv_end_jobs = gv_end_jobs + 1.
ENDFORM.
"--------------------@斌将军--------------------

完整参考代码:

"--------------------@斌将军--------------------
TYPES:BEGIN OF ty_alv,
        index TYPE i,
        count TYPE i,
      END OF ty_alv.

DATA:gt_alv TYPE TABLE OF ty_alv,
     gs_alv TYPE ty_alv.

DATA:gv_open_jobs TYPE i, "开启的进程
     gv_jobs      TYPE i, "可用的进程
     gv_end_jobs  TYPE i. "结束的进程

DATA:gv_applserver TYPE rzlli_asvr, "服务器标识 实例名称(用于登录/服务器组分配)
     gv_classname  TYPE rzlli_apcl, "登录/服务器组名称
     gv_taskname   TYPE char10, "进程名称
     gv_init_flag  TYPE char1,
     gv_total      TYPE i, "可用资源总数
     gv_available  TYPE i. "空闲资源数

DATA:lv_count   TYPE i,
     lv_flat    TYPE p DECIMALS 2,
     lv_message TYPE char200.

"准备1000行测试数据
DO 1000 TIMES.
  gs_alv-index = sy-index.
  gs_alv-count = 1.
  APPEND gs_alv TO gt_alv.
ENDDO.

"获取服务器标识
PERFORM frm_get_server.

"获取服务器组对应的可用资源数
PERFORM frm_get_jobs_available.

"处理每行数据,实际应用时,看如何将数据“分批”
CLEAR:lv_count.
LOOP AT gt_alv INTO gs_alv.

  lv_count = lv_count + 1."进程任务计数器
  IF gv_open_jobs - gv_end_jobs = gv_jobs."已开进程 - 已结束进程 = 可用进程
    WAIT UNTIL gv_open_jobs - gv_end_jobs < gv_jobs."等待 已开进程 - 已结束进程 < 可用进程
  ENDIF.

  "拼接进程名称
  gv_taskname = 'Task' && lv_count.
  CONDENSE gv_taskname.

  "逻辑处理
  PERFORM frm_deal_task USING gs_alv.
  CLEAR:gs_alv.
ENDLOOP.

"等待所有的进程执行完毕
WAIT UNTIL gv_end_jobs >= gv_open_jobs.

"展示结果
CALL METHOD cl_demo_output=>display
  EXPORTING
    data = gt_alv.

*&---------------------------------------------------------------------*
*&      Form  FRM_GET_SERVER
*&---------------------------------------------------------------------*
*       text 获取服务器标识
*----------------------------------------------------------------------*
FORM frm_get_server.
  "获取服务器标识
  CALL 'C_SAPGPARAM'
     ID  'NAME'   FIELD  'rdisp/myname'
     ID  'VALUE'  FIELD  gv_applserver.

  "获取登录/服务器组名称
  SELECT SINGLE
    classname
  FROM  rzllitab
  INTO  gv_classname    "Server Group Name
  WHERE  applserver  =  gv_applserver
    AND  grouptype  =  'S' .    "S:服务器组,空:登陆组

ENDFORM.

*&---------------------------------------------------------------------*
*&      Form  FRM_GET_JOBS_AVAILABLE
*&---------------------------------------------------------------------*
*       text 获取服务器组对应的可用资源数
*----------------------------------------------------------------------*
FORM frm_get_jobs_available.

  gv_jobs = 0."可用资源数
  IF gv_init_flag IS INITIAL."第一次获取资源
    "资源查询 - 获取最多jobs 个数
    CALL FUNCTION 'SPBT_INITIALIZE'
      EXPORTING
        group_name                     = gv_classname
      IMPORTING
        max_pbt_wps                    = gv_total "可用资源总数
        free_pbt_wps                   = gv_available "空闲资源数
      EXCEPTIONS
        invalid_group_name             = 1
        internal_error                 = 2
        pbt_env_already_initialized    = 3
        currently_no_resources_avail   = 4
        no_pbt_resources_found         = 5
        cant_init_different_pbt_groups = 6
        OTHERS                         = 7.

    CASE sy-subrc.
      WHEN 0.
        lv_flat = gv_available * 9 / 10.
        gv_jobs = floor( lv_flat ). "拿其中一部分的空闲资源数来执行
        IF gv_jobs = 0 AND gv_available = 1.
          gv_jobs = 1.
        ENDIF.
        gv_init_flag = 'X'.
        "按照jobs来进行数据拆分
      WHEN 1.
*        MESSAGE E836."未定义 PBT 服务器组
      WHEN 2.

      WHEN 3.
*        MESSAGE E833."已为组初始化了 PBT 环境
      WHEN 4.
*        MESSAGE E837."所有服务器正忙:没有可用的资源
      WHEN 5.
      WHEN 6.
    ENDCASE.

    "刷新资源数量
  ELSE.
    "第二次获取资源调用函数
    CALL FUNCTION 'SPBT_GET_CURR_RESOURCE_INFO'
      IMPORTING
        max_pbt_wps                 = gv_total
        free_pbt_wps                = gv_available
      EXCEPTIONS
        internal_error              = 1
        pbt_env_not_initialized_yet = 2
        OTHERS                      = 3.
    CASE  sy-subrc .
      WHEN 0.
        lv_flat = gv_available * 9 / 10.
        gv_jobs = floor( lv_flat ). "拿其中一部分的空闲资源数来执行
        IF gv_jobs = 0 AND gv_available = 1.
          gv_jobs = 1.
        ENDIF.
      WHEN 1.
      WHEN 2.
        CLEAR gv_init_flag.
        PERFORM  frm_get_jobs_available. "获取失败则递归调用
      WHEN 3.
    ENDCASE.
  ENDIF.
ENDFORM. " FRM_GET_JOBS_AVAILABLE

*&---------------------------------------------------------------------*
*&      Form  FRM_DEAL_TASK
*&---------------------------------------------------------------------*
*       text 逻辑处理
*----------------------------------------------------------------------*
FORM frm_deal_task USING ps_alv TYPE ty_alv.

  "调用需要并行执行的函数,此函数必须为远程函数
  CALL FUNCTION 'YTEST001_001' STARTING NEW TASK gv_taskname
    DESTINATION IN GROUP gv_classname
    PERFORMING frm_ytest ON END OF TASK "调用每条进程的处理方法,接收处理结果
    EXPORTING
      i_index               = ps_alv-index
      i_count               = ps_alv-count
    EXCEPTIONS
      communication_failure = 1 MESSAGE lv_message
      system_failure        = 2 MESSAGE lv_message
      resource_failure      = 3.

  IF sy-subrc = 0.
    gv_open_jobs = gv_open_jobs + 1. "开启进程成功,则已开启变量 + 1
  ELSEIF sy-subrc = 3.
    WAIT UP TO '0.1' SECONDS.
    WAIT UNTIL gv_open_jobs - gv_end_jobs < gv_jobs."等待 已开进程 - 已结束进程 < 可用进程
    PERFORM frm_deal_task USING ps_alv."递归调用,重复开启进程
  ELSE.
*    WRITE: sy-index,sy-subrc,lv_message.
  ENDIF.
ENDFORM.

*&---------------------------------------------------------------------*
*&      Form  FRM_YTEST
*&---------------------------------------------------------------------*
*       text 返回消息处理
*----------------------------------------------------------------------*
FORM frm_ytest USING taskname.

  DATA:ls_alv TYPE ty_alv.

  "接收处理数据的返回消息
  RECEIVE RESULTS FROM  FUNCTION 'YTEST001_001'
   IMPORTING
     e_index       = ls_alv-index
     e_count       = ls_alv-count
  EXCEPTIONS
      communication_failure  = 1 MESSAGE lv_message
      system_failure         = 2 MESSAGE lv_message.

  MODIFY gt_alv FROM ls_alv TRANSPORTING count WHERE index = ls_alv-index.

  "已完成进程 + 1
  gv_end_jobs = gv_end_jobs + 1.
ENDFORM.
"--------------------@斌将军--------------------

3、效率对比

常规处理方式的代码:

"--------------------@斌将军--------------------
LOOP AT gt_alv INTO gs_alv.
  CALL FUNCTION 'YTEST001_001'
    EXPORTING
      i_index = gs_alv-index
      i_count = gs_alv-count
    IMPORTING
      e_index = gs_alv-index
      e_count = gs_alv-count.
  MODIFY gt_alv FROM gs_alv TRANSPORTING count WHERE index = gs_alv-index.
ENDLOOP.
"--------------------@斌将军--------------------

耗时对比

并行处理耗时1.7秒,常规处理耗时172秒,效果立竿见影

 

定期更文,欢迎关注

 
 
 
 
posted @ 2023-11-15 17:37  斌将军  阅读(369)  评论(0编辑  收藏  举报