简述
qRFC NoSend 的重新实现和重新设计,基于更高效的数据模型
LDQ 允许应用程序记录可以被接收应用程序读取的数据( pull原理)您应该确保在读取数据时它只能被调用一次,并且按照它的创建顺序( 按顺序恰好一次)。LDG 的访问遵循 FIFO 原则(先进先出 )。
如果您使用移动客户端能够在需要时从相关服务器调用数据,可以使用LDQ。
LDQ 使用队列组织不同的接收者。管理和监控基于这些队列。您可以为一个客户端创建一个或多个队列,但不同的客户端不能使用同一个队列。
LDQ 为开发人员提供了一个 API,用于允许调用者记录数据,然后由被调用的应用程序或通用解决方案(例如, 移动基础设施)的框架层读取。
API 只能在本地使用。要记录的数据以字符串( 字符或二进制)的形式传输。
由于还可以为多个客户端定义记录的数据,LDQ 确保数据只存储一次,并且在队列中对这些数据进行正确的引用。
队列彼此独立。这意味着可以基于一个队列,按照记录的顺序,只读取一次数据。各种队列之间没有隐含的数据依赖关系。
准备
删除程序 RS_LDQ_DAEMON 设计用于定期删除已处理的单元。该程序在后台异步运行。您可以使 用程序 RSLDQREORG 将此删除程序安排为后台作业。
监控
事务 SLDQMON 调用 LDQ (本地数据队列)监视器。
特点
基于后台远程函数调用 (bgRFC) 的 NoSend 场景。bgRFC NoSend场景使用链表作为队列。
基于后台远程函数调用 (bgRFC) 的NoSend场景。迄今为止,qRFC NoSend方案一直用于 MMW。
在规范阶段,构建了 LDQ 和两种替代设计理念的原型,并比较了各种数据量的读取和写入。事实证明,LDQ 选项在性能和可维护性方面都优于其他选项(参见 bgRFC NoSend MMW 性能数据)。
开发
IF_LDQ_READER 接口中的 CONFIRM_QUEUE_UNITS 方法外,LDQ类的所有方法都需要一个 Explicit Commit才能将它们的工作持久化到数据库中。此方法执行 隐式提交。
CL_LDQ_APPLICATION 类是 LDQ 读取器和 LDQ 写入器对象的工厂类。
方法 | 可见度/级别 | 描述 |
GET_WRITER | 公共静态 | 该方法创建一个 LDQ writer 对象或获取一个现有的。该方法需要本地数据队列的应用程序名称并返回接口引用。 |
GET_READER | 公共静态 | 该方法创建一个 LDQ 读取器对象或获取现有的读取器对象。该方法需要本地数据队列的应用程序名称并返回接口引用。 |
写入器类CL_LDQ_WRITER 用于初始化 LDQ 写入用例的队列表。实现接口 IF_LDQ_WRITER。
方法 | 可见度/级别 | 描述 |
SET_QUEUE_NAMES | 公共实例 | 该方法创建一个 LDQ 单元写入器对象。该方法需要一个 LDQ 队列名称表并返回一个接口引用。 |
单元写入器类CL_LDQ_UNIT_WRITER,将 LDQ 队列持久保存在数据库中。该类实现接口 IF_LDQ_UNIT_WRITER。
方法 | 可见度/级别 | 描述 |
GET_NEXT_CONTENT | 公共实例 | 创建下一个单元的内容。该方法返回一个接口引用。 |
CLASS_CONSTRUCTOR | 公共静态 | 类构造函数。 |
ON_COMMIT | 公共静态 | 将创建的 LDQ 单元持久保存在数据库中。 |
ON_ROLLBACK | 公共静态 | 回滚创建的 LDQ 单元。 |
AFTER_COMMIT | 公共静态 | 删除内部表。 |
单元内容编写器类 CL_LDQ_UNIT_CONTENT_WRITER,将有效负载写入 LDQ 单元。该类实现接口 IF_LDQ_UNIT_CONTENT_WRITER。
方法 | 可见度/级别 | 描述 |
SET_CDATA | 公共实例 | 该方法写入类似字符的数据。该方法需要一个字符串。如果单位无效,该方法将引发异常。 |
SET_XDATA | 公共实例 | 该方法写入二进制数据。该方法需要一个字节串。 |
GET_UNIT_ID | 公共实例 | 该方法读取当前的单元 ID。 |
SET_DESCRIPTION | 公共实例 | 该方法将附加描述写入单元。该方法需要一个短字符串。 |
读取器类 CL_LDQ_READER,用于初始化 LDQ 读取用例的队列表。该类实现接口 IF_LDQ_READER。
此类中的 CONFIRM_QUEUE_UNITS 方法是唯一执行 隐式提交的 LDQ 方法。此类和其他 LDQ 类的所有其他方法都需要 显式提交才能将其工作持久保存在数据库中。
方法 | 可见度/级别 | 描述 |
SET_QUEUE_NAME | 公共实例 | 该方法创建一个 LDQ 单元读取器对象。该方法需要一个 LDQ 队列名称并返回一个接口引用。当队列被另一个读取器使用时,该方法会引发异常。 |
SET_QUEUE_NAME_BY_OFFSET | 公共实例 | 为给定队列创建一个 LDQ 单元读取器对象,该队列具有给定的队列读取计数器偏移量。如果偏移量大于当前读取计数器(= 尝试跳过读取某些单元),则会引发异常。如果偏移量小于当前顶部计数器(= 尝试读取已确认的单元),则会引发异常。 |
CONFIRM_QUEUE_UNITS | 公共实例 | 标记删除已读取到给定序列号的单元。如果偏移计数器大于当前读取计数器(= 尝试确认从未读取过的单元),则会引发异常。如果偏移计数器小于当前的顶部计数器(= 尝试确认已确认的单位),则会引发异常。 注意:此方法执行 隐式提交。 |
单元读取器类 CL_LDQ_UNIT_READER,为特定 LDQ 队列提供句柄,以便读取一个或多个 LDQ 单元。该类实现接口 IF_LDQ_UNIT_READER。
方法 | 可见度/级别 | 描述 |
GET_NEXT_STATES | 公共实例 | 读取下一个单元的状态。该方法需要读取的单元数并返回单元状态表。 |
GET_NEXT_CONTENTS | 公共实例 | 读取下一个单元的内容。该方法需要读取的单元数并返回单元内容表。 |
GET_TOTAL_NUMBER_OF_UNITS | 公共实例 | 获取队列中的条目总数。 |
CONFIRM | 公共实例 | 标记删除已读取的单元。 |
CHECK_READ_SEQUENCE | 私有静态 | 验证读取单元的顺序。 |
ON_ROLLBACK | 公共静态 | 回滚称为 LDQ 单元。 |
AFTER_COMMIT | 公共静态 | 删除内部表。 |
ON_COMMIT | 公共静态 | 删除数据库中读取的 LDQ 单元。LDQ 类的所有方法都通过 Explicit Commit将其工作保存在数据库中,但 IF_LDQ_READER 接口中的 CONFIRM_QUEUE_UNITS 方法除外,该方法执行 Implicit Commit。 |
单元状态读取器类 CL_LDQ_UNIT_STATE_READER,CL_LDQ_UNIT_STATE_READER类读取一个或多个LDQ单元的状态数据,即单元大小、单元格式和单元id。该类实现接口 IF_LDQ_UNIT_STATE_READER。
方法 | 可见度/级别 | 描述 |
GET_FORMAT | 公共实例 | 该方法返回 LDQ 单元数据的格式。 |
GET_UNIT_ID | 公共实例 | 该方法返回 LDQ 单元的 Unit-ID。 |
GET_SEQUENCE_NUMBER | 公共实例 | 该方法返回一个 LDQ 单元的序列号。 |
GET_SUCCESSOR_COUNT | 公共实例 | 该方法返回队列中跟随单元的数量。 |
GET_SIZE | 公共实例 | 该方法返回 LDQ 单元数据的大小。 |
GET_DESCRIPTION | 公共实例 | 该方法返回 LDQ 单元的附加描述。 |
CHECK_COMMIT_ROLLBACK | 私有实例 | 该方法检查LDQ单元是否有效。 |
单元内容读取器类 CL_LDQ_UNIT_CONTENT_READER,CL_LDQ_UNIT_CONTENT_READER 类读取指定 LDQ 队列中一个或多个 LDQ 单元的有效负载。
方法 | 可见度/级别 | 描述 |
GET_CDATA | 公共实例 | 该方法读取类似字符的数据。该方法返回一个字符串。 |
GET_XDATA | 公共实例 | 该方法读取二进制数据。该方法返回一个字节字符串。 |
GET_FORMAT | 公共实例 | 该方法返回 LDQ 单元数据的格式。 |
GET_UNIT_ID | 公共实例 | 该方法返回 LDQ 单元的 Unit-ID。 |
GET_SEQUENCE_NUMBER | 公共实例 | 该方法返回一个 LDQ 单元的序列号。 |
GET_SUCCESSOR_COUNT | 公共实例 | 该方法返回队列中跟随单元的数量。 |
GET_DESCRIPTION | 公共实例 | 该方法返回 LDQ 单元的附加描述。 |
GET_SIZE | 公共实例 | 该方法返回 LDQ 单元数据的大小。 |
示例
LDQ写入数据示例
*a) Input parameters:
PARAMETERS: ap_name TYPE ldq_application_name DEFAULT 'Perf_TEST',
qp_name TYPE ldq_queue_name DEFAULT 'LDQ_TEST_',
n_queues TYPE i DEFAULT 5,
n_unit TYPE i DEFAULT 100,
u_size TYPE i DEFAULT 2,
b_data AS CHECKBOX.
*b) Variable D eclarations:
DATA: l_ldq_write TYPE REF TO if_ldq_writer,
l_ldq_unit_writer TYPE REF TO if_ldq_unit_writer,
l_ldq_unit_content_writer TYPE REF TO if_ldq_unit_content_writer.
DATA: l_queue_name_tab TYPE ldq_queue_name_tab,
l_queue_name TYPE ldq_queue_name.
DATA: l_cdata TYPE string.
DATA: l_cdata2 TYPE string.
DATA: l_seq_nr TYPE ldq_unit_id.
DATA: l_queue_nr(4) TYPE n.
DATA: l_size TYPE i.
DATA: l_xdata TYPE xstring.
DATA: l_times TYPE i.
DATA: l_rt_ubegin TYPE i,
l_rt_uend TYPE i,
l_rt_qbegin TYPE i,
l_rt_qend TYPE i.
*c) Creation of queue names:
DO n_queues TIMES.
l_queue_nr = sy-index.
CONCATENATE qp_name l_queue_nr INTO l_queue_name.
CONDENSE l_queue_name NO-GAPS.
APPEND l_queue_name TO l_queue_name_tab.
WRITE: / 'Qname: ', l_queue_name.
ENDDO.
ULINE.
*d) Creation of binary payload:
IF b_data IS NOT INITIAL.
l_cdata2 = '01234'.
DO 100 TIMES.
CONCATENATE l_cdata2 l_cdata INTO l_cdata.
ENDDO.
l_xdata = l_cdata.
l_size = xstrlen( l_xdata ).
l_times = u_size * 1000 / l_size.
CLEAR l_cdata2.
DO l_times TIMES.
CONCATENATE l_cdata2 l_cdata INTO l_cdata2.
ENDDO.
l_xdata = l_cdata2.
l_size = xstrlen( l_xdata ).
ELSE.
IF cl_abap_char_utilities=>charsize = 1.
l_cdata2 = '0123456789'.
ELSE.
l_cdata2 = '01234'.
ENDIF.
DO 100 TIMES.
CONCATENATE l_cdata2 l_cdata INTO l_cdata.
ENDDO.
l_size = u_size - 1.
DO l_size TIMES.
CONCATENATE l_cdata l_cdata INTO l_cdata.
ENDDO.
ENDIF.
*e) Creation of LDQ application object:
l_ldq_write = cl_ldq_application=>get_writer( ap_name ).
*f) Initialisation of the target queues for the units in this application:
l_ldq_unit_writer = l_ldq_write->set_queue_names( l_queue_name_tab ).
GET RUN TIME FIELD l_rt_ubegin. "aktuelle Zeit holen
DO n_unit TIMES.
*g) Request a unit content, i. e. status and payload of a unit, for inserting into specified queues:
l_ldq_unit_content_writer = l_ldq_unit_writer->get_next_content( ).
*h) Ask for sequence number of this content:
* l_seq_nr = l_ldq_unit_content_writer->get_sequence_number( ).
*i) Insert the relevant data into the content:
IF b_data IS INITIAL.
* character payload
l_ldq_unit_content_writer->set_cdata( l_cdata ).
ELSE.
* binary payload
l_ldq_unit_content_writer->set_xdata( l_xdata ).
ENDIF.
ENDDO.
*j) Persist unit contents into database:
COMMIT WORK.
监控查看数据处理状态
LDQ读取示例
*a) Input parameters:
PARAMETERS: ap_name TYPE ldq_application_name DEFAULT 'Perf_TEST',
qp_name TYPE ldq_queue_name DEFAULT 'LDQ_TEST_',
n_queues TYPE i DEFAULT 5,
n_units TYPE i DEFAULT 10,
n_times TYPE i DEFAULT 1.
*b) Declarations:
DATA: l_ldq_reader TYPE REF TO if_ldq_reader,
l_ldq_unit_reader TYPE REF TO if_ldq_unit_reader.
DATA: l_queue_name_tab TYPE ldq_queue_name_tab,
l_queue_name TYPE ldq_queue_name.
DATA: l_xdata TYPE xstring,
l_cdata TYPE string.
DATA: l_size TYPE i.
DATA: l_content_tab TYPE ldq_unit_content_reader_tab.
DATA: l_state_tab TYPE ldq_unit_state_reader_tab.
DATA: l_del TYPE i.
DATA: l_seq_nr TYPE ldq_unit_id.
DATA: l_queue_nr(4) TYPE n.
DATA: l_lines TYPE i.
DATA: l_last_lines TYPE i.
DATA: l_unit TYPE i.
DATA: l_state_wa TYPE REF TO if_ldq_unit_state_reader,
l_content_wa TYPE REF TO if_ldq_unit_content_reader.
DATA: l_format TYPE ldq_data_format.
DATA: l_next_state_nr TYPE i.
DATA: l_number TYPE i.
TYPES: BEGIN OF lt_my_unit_reader,
unit_reader TYPE REF TO if_ldq_unit_reader,
queue_name TYPE ldq_queue_name,
END OF lt_my_unit_reader.
DATA: l_my_unit_reader_tab TYPE TABLE OF lt_my_unit_reader.
DATA: l_my_unit_reader TYPE lt_my_unit_reader.
FIELD-SYMBOLS: <l_content_wa> TYPE REF TO if_ldq_unit_content_reader.
FIELD-SYMBOLS: <l_state_wa> TYPE REF TO if_ldq_unit_state_reader.
*c) Creation of queue names:
CLEAR: l_queue_name_tab[], l_content_tab[].
DO n_queues TIMES.
l_queue_nr = sy-index.
CONCATENATE qp_name l_queue_nr INTO l_queue_name.
CONDENSE l_queue_name NO-GAPS.
APPEND l_queue_name TO l_queue_name_tab.
ENDDO.
LOOP AT l_queue_name_tab INTO l_queue_name.
* d) creation of ldq application object:
l_ldq_reader = cl_ldq_application=>get_reader( ap_name ).
* Creation of LDQ queu objects for each queue name
l_my_unit_reader-unit_reader = l_ldq_reader->set_queue_name( l_queue_name ).
l_my_unit_reader-queue_name = l_queue_name.
APPEND l_my_unit_reader TO l_my_unit_reader_tab.
ENDLOOP.
*
* Read the block units over all queues n_times
DO n_times TIMES.
LOOP AT l_my_unit_reader_tab INTO l_my_unit_reader.
* e) GET n_units in one block:
l_content_tab = l_my_unit_reader-unit_reader->get_next_contents( n_units ).
l_unit = lines( l_content_tab ).
* f) retrieve contained information out of content object:
CLEAR l_number.
WRITE: / l_unit, 'Qname:', l_my_unit_reader-queue_name.
LOOP AT l_content_tab ASSIGNING <l_content_wa>.
l_format = <l_content_wa>->get_format( ).
IF l_format = 'C'.
l_cdata = <l_content_wa>->get_cdata( ).
ELSE.
l_xdata = <l_content_wa>->get_xdata( ).
ENDIF.
l_seq_nr = <l_content_wa>->get_sequence_number( ).
l_size = <l_content_wa>->get_size( ).
l_number = l_number + 1.
WRITE: /10 'number:', l_number, 'sequence number =', l_seq_nr, 'size in kb:', l_size.
ENDLOOP.
* g) confirm the READ unit in the database:
l_my_unit_reader-unit_reader->confirm( ).
* h) persist the READ status of ldq DATA into the database:
COMMIT WORK.
ENDLOOP.
ENDDO.
读取效果,读取完成后,在SLDQMON中可查看已被移除,如果需要读取完成后保留,需要使用方法l_ldq_reader->confirm_queue_units。
https://blog.csdn.net/xiefireworks/article/details/126326006