工作笔记 - 一种业务信息汇报机制的设计和实现

概述

在日常的业务应用开发中,经常会遇到需要在不同的业务系统中,传输业务信息内容和状态的需求。但在很多场景中,并不是简单的数据交换或者同步,而是根据业务的要求,在业务数据集中,选择一定满足条件的数据,进行简单处理和转换后,再传输到另一个采集系统当中。这时就会进一步提出来,采集系统可能需要支持多个同构的下级业务系统,业务数据可能会不定期的产出,不希望进行重复的处理等等,但是可以支持业务数据的更新。

实现机制

笔者先从逻辑和概念上,来总结一下理想中这一需求实现的过程和机制。

1 下级业务系统开展各种应用,形成各种业务信息条目。在经过了一定的业务流程之后,信息条目达到某些业务状态,并且满足了一些上报的条件,可以作为可以作为向上级系统汇报的信息.

2 信息上报程序模块,作为一个独立于业务流程甚至系统的程序(可以外部部署),定期检查业务信息的状态。在查询到需要上报的信息条目时,构建上报的信息,并向上级系统提供的信息接口请求这些信息。这一过程,可以通过HTTP请求、RPC调用或者消息发送来实现。

3 上级系统的信息接口,接收到信息上报请求,按照业务规则接受这些请求,处理后,响应信息上报的处理结果,如成功或者失败信息等等。

4 上报程序根据响应的结果,修改信息条目的上报状态为"已上报"(成功或者失败)。这个状态,将会作为待上报信息查询的过滤条件。

5 上报数据的准备,是基于数据状态,并且可以分批的。一次只处理有限数量的数据,处理完成,更新数据状态;下次查询,就是另一批待汇报状态的数据;这样的设计主要是考虑到性能和处理的可恢复性。

至此,一个完整的业务信息生成-上报检查-信息上报-状态修改的的完整的信息上报生命周期就已经完成。

在逻辑和流程上,这是一个比较简单而清晰的框架,但实际应用在系统中,却可能遇到很多的问题,这里简单的总结一下:

  • 业务流程和信息上报处理需要尽可能的解耦和避免相互干扰
  • 需要支持多种类型的信息上报,但又需要一种统一的处理方式,以便于程序维护和扩展
  • 需要考虑这个处理过程的性能对于应用系统总体的影响
  • 在业务支持上,可能还需要充分考虑的业务信息重新上报(如修改后再上报)和业务信息删除的情况

根据这些问题和要求,笔者在原有业务系统上,提出了相关的改进措施,下面具体讨论。

现状和问题

现有笔者负责运维的系统,虽然在业务需求上,确实是基本满足了。但在实现细节上,有很多问题和不足,急需改进。

现有的实现方式和问题包括:

  • 每个小的业务板块,都有一套信息上报的数据结构,直接在业务数据中,增加了业务上报的状态,并且为一种业务,都使用了不同结构的日志数据表,这个问题的原因可能是在前期规划设计时候,并没有从整体和抽象角度考虑这个需求的共性,而是积累和渐进式的开发和实现。

  • 原有的产生需要汇报数据的机制也不一样。有的直接基于汇报状态的查询,有的基于定期的实体化视图生成机制,有的需要基于日志写入触发器,来更新汇报状态。这种多样性的处理模式,给维护、扩展和问题排查造成了很多不便。

  • 原有的数据汇报,在数据请求的时候,没有分批和限流的机制。笔者就遇到一次需要汇报数据过大,导致查询失败,程序卡住始终无法完成数据汇报的情况。

所以,新的处理流程和方法的设计,需要考虑优化和解决这些问题。为此,笔者提出并实现了以下技术方案。

规划和实现

汇报日志信息表

首先是需要统一抽象和管理各类业务数据汇报的状态和信息。为此,笔者构想并设计了一个汇报日志信息表rp_logs。这个表中包括了以下内容:

  • 唯一的业务信息标识 rpid

针对不同的业务对象,通过组合业务代码和业务对象的ID,我们可以设计在系统内部唯一的业务信息标识。需要检查和确保,每项业务,在业务范围内为业务记录提供唯一的标识。

  • 汇报状态 status

在逻辑上的汇报状态包括 0-初始状态 0x1000成功和各种可能的失败项目,可以使用编码表示,由于现有的实现方案是同步的,所以在信息同步操作的时候,就可以立刻获得服务端的响应,然后直接修改当前的汇报状态。

  • 失败信息 info

某些业务汇报失败时,其实是有详细的描述信息的,可以在汇报日志信息表中,记录这些失败信息,有利于后续问题发现和分析。一般情况下,操作成功是无需额外信息的。

  • 操作时间 rptime

这一字段主要用于记录业务汇报操作实际发生的时间,便于分析汇报操作的规模和性能,并且有利于跟踪和查找问题。

这个表,其实就是统一数据汇报状态和操作管理的核心:

  • 信息汇报状态,和具体的业务类型和内容无关
  • 可以通过数据标识的设计,将业务数据标识和汇报信息关联起来
  • 这样,就可以保证通用性和可扩展性(如新的业务类型,只需要添加业务类型标识前缀即可)

业务汇报状态标记

业务汇报状态标记,是在各个业务信息表中,针对每一个数据记录,都使用一个状态标记,来表示当前业务信息的汇报状态。其实这个标记并没有绝对的必要性。因为在逻辑上可以通过关联查询汇报日志信息表,来获得其汇报状态,所以这里有两个技术选择:

  • 保留业务数据表中,表示汇报状态的字段

这个方案的优势主要是性能,在数据汇报操作中,无需进行日志表的关联,直接基于状态对数据进行查询。但这个处理虽然比较方便于汇报状态的查询,但却带来了一个问题就是这个状态标记的维护,需要在上报操作后进行维护。现有的方式是在每批次上报后,立刻根据日志的结果,批量更新关联的业务条目汇报状态标记。

  • 业务数据表和汇报日志完全结构,在业务数据中没有汇报状态

这个技术处理的好处是结构更加清晰简单。可能的问题是不能以业务状态进行直接的查询,性能可能会稍差一点。笔者根据实际业务使用场景,认为在小批量查询的状态下,这个影响应当不大,是优先选择的技术方案。

主要流程

以某类业务信息为例,数据汇报操作的具体流程如下:

  • 通过相关条件和汇报状态(如无汇报记录),查询限定数量的可汇报业务信息

  • 构造业务汇报请求,向上级系统提交

  • 根据上级系统的响应,来更新汇报信息日志表

  • 如果是成功的条目,插入(或者更新)成功状态和操作时间

  • 如果是失败的条目,按照失败信息和类型,插入(或更新)失败的状态、信息

  • 根据日志信息表的状态,更新业务信息表中,汇报状态的字段的值(可选)

  • 执行下一轮查询和请求,直到所有需要处理的数据都完成处理

关于业务汇报信息的请求和响应的实现,其实是另外一个技术问题。如可以使用HTTP接口实现,也可以使用消息队列发送的方式,这和本文探讨的主题也是解耦的,没有直接的关系。唯一需要注意的问题是我们希望能够尽量实现类似"实时"响应的效果,可以让系统及时的更新记录的状态,来保证每次请求时,是新的需要处理的数据。

如果相对而言这个过程的异步效应比较强的话,也可以考虑在完全收到对应的请求结果后,再进行下一轮请求;或者,也可以考虑在请求后,就以一种中间的"处理中"作为状态信息插入日志信息表,然后在异步响应之后,再更新为响应的状态。

参考实现(SQL)

下面是一些参考实现的SQL代码,方便读者理解和移植:

sql 复制代码
// 汇报日志表
CREATE TABLE rp_logs (
    rpid varchar(32) primary key,    
    status int2 DEFAULT 0 NULL,
    rptime timestamptz(6) DEFAULT now() NULL,
    info text NULL
);

// 查询和视图
create view vw_report as 
select 'E-' || "EventID" rid,  
jsonb_build_object('n',"EventName",'t',"EventTime") rcontent  from "Events" R
where not exists (select 1 from rp_logs where rpid = 'E-' || "EventID")
order by "CreateTime" desc
limit 50;

//日志和状态更新
insert into rp_logs( rpid, status,info )
values ('E-0002', 200, null)
on conflict(rpid) do update 
set (status,info,rptime) = (excluded.status, excluded.info, now())
returning * ;

// 视图执行计划
Type: Limit; ; Cost: 76186.30 - 76192.13
  Type: Gather Merge; ; Cost: 76186.30 - 115443.40
    Type: Sort; ; Cost: 75186.27 - 75606.86
       Type: Hash Join (Anti); ; Cost: 21.93 - 69597.69
	  Type: Parallel Seq Scan; Rel: Event ; Cost: 0.00 - 64444.66
             Type: Hash; ; Cost: 15.30 - 15.30
		Type: Seq Scan; Rel: rp_logs ; Cost: 0.00 - 15.30

这里有几点考虑和设计:

  • 使用业务类型+业务ID的形式,将汇报日志ID归一化处理,所以这里需要保证业务ID也是唯一的
  • 将业务相关数据,统一封装成为JSON字段,这样可以在查询后统一处理
  • 单次查询需要设置限制
  • 为了方便在代码中编写,将查询简化成为视图使用
  • 这里使用 not exists作为查询条件,也可以使用left join null的方式,笔者简单测试了一下,并没有显著差异
  • 插入汇报日志,可以重复操作,增加或者更新

汇报状态查询

如果同步成功或者失败,需要在业务系统中进行查询。有两种常用的查询方式,一种是基于业务数据,查询业务信息关联的汇报状态;一种是对于汇报异常的信息,查询关联的业务信息。这两种方式都比较好处理,就是根据类型和ID进行关联即可:

js 复制代码
// 业务数据汇报状态
select "RecordID" rid, L.status, L.info, L.rptime  from "Record_D_Event" R
join rp_logs L on rpid = 'R-' || "RecordID" 
where "RecordID" = 1000;

// 异常汇报关联业务数据
select "RecordID" rid, L.status, L.info, L.rptime  from "Record_D_Event" R
join rp_logs L on rpid = 'R-' || "RecordID" 
where L.status = 500 ;

小结

本文针对笔者在工作中,遇到的一个数据汇报机制实现的场景和问题,提出了改进的技术方案,并且总结出类似业务场景中,一种相对通用的技术方案和流程,和原实现相比,新的技术方案具有更好的可扩展性,处理的一致性,更好的可维护性,并避免了可能的性能问题。

相关推荐
野生技术架构师43 分钟前
Spring Boot 定时任务与 xxl-job 灵活切换方案
java·spring boot·后端
.Eyes1 小时前
OceanBase 分区裁剪(Partition Pruning)原理解读
数据库·oceanbase
MrZhangBaby2 小时前
SQL-leetcode— 2356. 每位教师所教授的科目种类的数量
数据库
一水鉴天2 小时前
整体设计 之定稿 “凝聚式中心点”原型 --整除:智能合约和DBMS的在表层挂接 能/所 依据的深层套接 之2
数据库·人工智能·智能合约
翔云1234563 小时前
Python 中 SQLAlchemy 和 MySQLdb 的关系
数据库·python·mysql
寒士obj3 小时前
SpringBoot中的条件注解
java·spring boot·后端
孙霸天3 小时前
Ubuntu20系统上离线安装MongoDB
数据库·mongodb·ubuntu·备份还原
Java 码农3 小时前
nodejs mongodb基础
数据库·mongodb·node.js
G探险者3 小时前
循环中的阻塞风险与异步线程解法
后端
TDengine (老段)3 小时前
TDengine IDMP 运维指南(4. 使用 Docker 部署)
运维·数据库·物联网·docker·时序数据库·tdengine·涛思数据