OA 审批任务中心性能优化 —— 复杂业务系统重构实践

背景介绍

每一天,约占公司员工总数 10% 的同学都会使用 OA 审批任务中心进行各类审批操作,上游有十余个各类审批系统,承载了公司大量的审批业务需求。审批任务中心的原有架构,是依赖公司企业应用部门的业务流程管理平台审批流数据库实现的。审批流业务的复杂逻辑,带来了大量的历史问题,同时存在欠缺优化的情况,导致数据规模带来的各项问题凸显,严重影响同学们的工作效率。在进行优化前,后端 API 接口的 PCT99 高达 3822ms,进行优化后,降低到了 68ms,降低了 98.22%。本文主要希望提供一种对复杂业务系统的特定接口进行性能优化的方法,为大家提供参考。

原有情况简介

审批任务中心的主体部分是审批任务列表,是对员工在公司内各个具有审批功能的系统中存在的审批任务的汇总。早期审批任务中心的实现,是基于公司企业应用部门的业务流程管理平台流程引擎的业务架构,将外部单据转换为自有单据的数据模型,根据任务指派人信息,查询出用户名下的审批任务。流程引擎作为业务复杂度极高的系统,数据模型主要是以方便流程流转为核心设计的,并不适合作为任务列表的数据模型进行使用。由于 to B 产品的复杂逻辑,具有大量产品功能上的特殊逻辑,如对于审批定义类型,同时具有白名单、黑名单、聚合展示等不同逻辑,原后端服务代码库代码量达 4w 行。由于以上原因,列出任务列表的逻辑,是由多个复杂 SQL 组成的,甚至有进行 4 个表 JOIN 查询的情况,导致无法通过索引进行彻底的优化。

业务梳理

经过对业务的梳理,可以认为任务中心的后端服务,主要需提供 3 类、6 个 API 接口:

  • 计数接口

    • 状态聚合计数
    • 流程定义聚合计数
  • 列表接口

    • 时间顺序列表
    • 流程定义聚合列表
  • 任务实例操作

    • 任务游标接口
    • 任务状态标记接口

其中每种又具有包括流程定义的黑白名单、聚合展示、根据流程定义进行的代理等,请求较为复杂,原有数据模型难以使用简单的查询对需求进行覆盖。

设计方案

针对原有数据模型难以维护、查询过于复杂的问题,根据上文 API 的查询需求,重新设计了数据模型,建立了基于业务需求的索引表,来确保每一查询都能够避免扫描行为。对于一个「无扫描请求」无法完成的复杂逻辑,采用了查询后归并的方案。

范式存储与索引表

在对历史较长的业务系统进行重构时,即使初始的存储数据模型设计符合范式,常出现原有业务抽象无法 Cover 新的需求、人员轮换交接抽象更改等情况,导致脱离存储范式,数据模型出现大量冗余、语义重复。很多系统最初数据量不大,也并未重视性能问题。最终导致系统整体架构积重难返,复杂的查询几乎无法设计恰当的索引。

即使对于完备的范式存储来说,也存在业务需求复杂时,无法控制查询规模的情况。例如,对于具有多对多关系的实体 A 和 B,如果业务需求需要很多类似于「同时根据 A 的字段 a 和 B 的字段 b 进行查询」,索引设计就成为几乎无法完成的任务。

如以审批流程为例,完备的范式存储,由 4 张表组成:

  • 流程定义
  • 流程实例,与流程定义形成多对一的关系
  • 任务实例,与流程实例形成多对一的关系
  • 任务指派人 Ref,与任务实例形成多对一的关系

当实现「使用流程定义和指派人来过滤任务实例」需求时,会对 4 个表有查询需求,整体复杂程度难以控制。类似的需求还有很多,再考虑到分页机制的设计,性能优化难度极大。

对于这种情况,引入索引表能够显著降低索引设计的难度。所谓索引表,即专门以业务需求的查询角度设计的表。在本例中,索引表根据可能进行查询/过滤的字段来进行设计,并根据每一查询方式单独设计索引。由于索引表在业务过程中,只作为查询通路,而没有存储信息的实际功能,当业务需求有变更时,可以方便的进行重新设计,并经过恰当的数据清洗,即可实现新的功能。

通过将数据模型与复杂查询的概念分离,可以妥善的解决「符合范式的存储」和「方便查询的存储」之间的矛盾,并提高了业务抽象模型对于查询复杂需求的适应能力。

如果希望使用索引表的模式,可能需要注意以下几个问题:

  • 维护辅助索引所产生的开销可能很大,必须分析并了解应用程序使用的查询。仅当范式的的数据抽象模型无法覆盖业务查询需求,或会造成严重的性能问题时,才创建索引表。不要创建索引表来支持服务不会执行或只是偶尔执行的查询。
  • 采用索引表的模式,会明显增大存储基础设施的硬件成本。同时,也需要额外的工作量来维护多个数据副本,要求对数据库的操作必须通过数据模型抽象来进行,避免遗漏对索引表的更新。
  • 索引表可能会引入数据一致性问题,如果数据库操作没有进行有效的集中,可能很难维持索引表与原始数据之间的一致性。也许可以围绕最终一致性模型设计应用程序,如插入、更新或删除数据时,应用程序可以向队列发布一条消息,让单独的任务执行该操作,并维护以异步方式引用此数据的索引表。 关于一致性的维护,在下文将会加以详述。
  • 索引表本身可能已分区或分片。

复杂逻辑的拆分 - 归并

当前 OA 审批任务中心的数据日益增长,审批业务需求使得难以对旧数据进行归档操作,导致个别业务人员有 100k 级别的存量历史审批任务;同时整个系统有共计 5000 余个审批流程定义、500 万个审批流程实例,2000 万个审批任务实例。具有根据类别进行过滤的跨人员任务查询、根据流程定义的聚合任务查询、流程定义聚合等功能。大量的复杂特性设计,使得难以在保证无扫表过滤的情况下,通过单一数据库查询实现指定的业务需求。

类似 MapReduce 对于复杂问题的拆解,对于这类复杂业务需求,可以通过拆分查询后归并的模式,降低整体延迟。将复杂的单一请求拆解成多个简单可索引的请求,使得每一请求在可控时间内可以完成。并行进行多个请求后,将多次请求根据需求进行归并,并向前端返回。

通过这种方式,对于单一请求无法在可控时间内返回的复杂查询,可以转换为可控时间内能够返回的查询,同时能够适应更多的复杂业务需求,如归并等与常规流程不同的特性。

写扩散一致性问题的解决

CAP 定理指出,对于一个分布式计算系统来说,不可能同时满足以下三点:

  • 一致性(Consistency) (等同于所有节点访问同一份最新的数据副本)
  • 可用性(Availability)(每次请求都能获取到非错的响应——但是不保证获取的数据为最新数据)
  • 分区容错性(Partition tolerance)(以实际效果而言,分区相当于对通信的时限要求。系统如果不能在时限内达成数据一致性,就意味着发生了分区的情况,必须就当前操作在C和A之间做出选择。)

对于由业务系统、OA 任务中心两套数据源组成的系统来讲,使用 CAP 定理的视角来看,是一个由两个节点组成的分布式系统。对于理想的分布式系统(保留了以上三个特性中的两个),为了方便进行考虑,假设仅存在两个节点(A 和 B),存在三个典型的模式:

  • AP:保证了可用性和分区容错性,对于更新一般采用异步方式同步到另一方。在同步完成前,可能会出现读取到的数据不一致的情况。MySQL 的 Binlog 同步机制即为此类,一般被称为 BASE 模式。BASE是指基本可用(Basically Available)、软状态( Soft State)、最终一致性( Eventual Consistency)。基本可用是指分布式系统在出现故障的时候,允许损失部分可用性,即保证核心可用。软状态是指允许系统存在中间状态,而该中间状态不会影响系统整体可用性。最终一致性是指系统中的所有数据副本经过一定时间后,最终能够达到一致的状态。
  • CP:保证了一致性和分区容错性,目前较多的实现采用强 leader 模式来处理。ZooKeeper 的模式即为此类。
  • CA:保证了一致性和可用性,由于分布式系统必然是分区容错的,CA 一般被认为是节点内状态同步的实现。

根据业务所需的特性,需要对强一致性和可用性做 Tradeoff。在本系统中,为了保证系统的随时可用,和避免与业务方的复杂逻辑耦合造成难以优化,使用了 BASE 模式,构建了一个 AP 系统。为了降低迁移成本,降低公司企业应用部门的业务流程管理平台的业务压力,使用了 Binlog 监听变更、摘取流程 ID、反向拉取的方案,避免了乱序消费场景下状态更新不幂等的问题。

AP 系统一个可能的问题是,同步机制可能产生比较明显的延迟。对于 OA 审批任务中心来讲,当发生同步延迟,就会导致用户难以判断操作是否生效的问题。根据业务需求,最终一致可能不足以保证较高的用户体验,出于 OA 审批任务中心上游服务较多,同步机制的延迟难以控制,因此根据用户操作,增加了「处理中」的中间状态,作为对用户行为的即时响应。

结果和计划

使用这些方法,使得 OA 审批任务中心任务列表侧的稳定性和性能提升了一个数量级,解决了历史上长期存在的大量 Bug。当前 OA 审批任务中心将与飞书审批合并,本次重构也为合并的长期演进提供了坚实的基础。

参考

猜你喜欢

转载自juejin.im/post/7117213168559980552
OA