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

概述

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

实现机制

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

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 ;

小结

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

相关推荐
BigBigHang1 分钟前
【docker】DM8达梦数据库的docker-compose以及一些启动踩坑
数据库·docker·容器
云边散步19 分钟前
《校园生活平台从 0 到 1 的搭建》第四篇:微信授权登录前端
前端·javascript·后端
架构师沉默22 分钟前
让我们一起用 DDD,构建更美好的软件世界!
java·后端·架构
研究司马懿39 分钟前
【Golang】Go语言函数
开发语言·后端·golang
m0_7202450140 分钟前
QT(四)基本组件
数据库·qt·microsoft
tuokuac1 小时前
创建的springboot工程java文件夹下还是文件夹而不是包
java·spring boot·后端
Databend1 小时前
使用 Databend Cloud 归档 OceanBase 数据数据库
数据库
笃行3501 小时前
搭建专属AI聊天网站:NextChat + 蓝耘MaaS平台完整部署指南
后端
希克厉1 小时前
PostgreSql基础
postgresql
用户1512905452201 小时前
跨域共享(CORS)&CrossOrigin注解
后端