写在前面

在做正常的需求开发时,当我们提供了一个接口或是调用别人接口时,我们需要考虑接口除了正常的逻辑处理外,还需要考虑接口能接收报文的上限,性能,响应时间等一系列非功能性需求。如果不注意这些问题,就可能在某一天的某个时刻收到一系列系统告警,严重者甚至导致系统不可用,引发线上事故。如涉及明细列表相关的接口中没考虑明细的上限,某一时刻上游下发了一个大明细从而可能就引发了上述的问题。这就是日常所说的大报文,其特点就是单次请求的数据量特别大,超出了系统正常的处理能力,需要耗费较长的时间才能处理完成。面对此类非功能性需求,在日常的开发中如何去避免和解决呢,今天主要带来的是主子模型下,明细行过大的处理。

一、识别大报文

大报文一般来源于批量接口或单个接口中入参为主子模型的接口,如果是消息队列,则是一次发送的消息中消息报文里发送了多条业务消息或是消息的报文结构也为主子模型的消息导致。

批量接口模型
Response> method(List list);

单个接口入参为主子模型
T method(OrderInfo orderInfo);

class OrderInfo{
List orderDetailInfoList;
}

class OrderDetailInfo{
}

对于批量接口或是消息队列里发送了多条业务消息的情况在这不做过多阐述,通常的逻辑是改为分批调用或分批发送消息就可以。但对于主子模型的报文,如果是正常的业务,则不能要求业务不要这么来下发,尤其是 TOB 类业务,因此需要从技术的角度加以解决。

二、解决大报文

这里主要讨论针对单个接口,里面的参数是主子模型的场景,因这类场景在日常的开发中最常见,用得很普遍。





第一点,先从 AppA 侧看可优化的点:

从调用方 AppA 的角度,如果传入的消息报文过大,导致 AppB 处理失败,那么对 AppA 来说也是失败,为了确保调用的成功,从 AppA 的角度看,对其可做如下一些优化。

首先,AppA 在调用 AppB 时,可以和 AppB 约定接口的超时时间。超时后,AppA 需要有重试机制,而对应的 AppB 需要保证接口执行的幂等性。在超时时间的设置上,如果大报文是个极少发生的场景,还可以考虑对超时时间进行动态设置,针对大报文的请求,把相应的超时时间适当加长,确保调用的成功。

其次,为了保证数据报文能快速达到 AppB,AppA 在请求调用 AppB 时,可以对报文进行压缩,确保网络传输能以最快的速度把数据报文发送到 AppB。





第二点,从 AppB 侧看可优化的点:

而从 AppB 侧来看,需要从业务逻辑里分析耗时的逻辑,一般来说这个分析比较靠谱的是压测,搭建一套与线上环境类似的环境,模拟线上场景进行压测,压测出耗时的业务逻辑,针对耗时的业务逻辑进行优化。在这里由于我们的入参是主子模型的,按经验来讲,耗时点一般位于处理明细的逻辑里,所以可以重点关注明细处理相关逻辑。

首先,AppB 在接收到请求后,对请求报文中的参数做基本的业务逻辑校验,校验通过后,为了能快速的响应调用方,可以把整个业务处理逻辑转入异步进行处理,确保调用方不超时。业务逻辑进入异步处理后还可能处理的时间比较耗时,此时可根据业务的时效性再进行处理。

其次,在 AppB 内,如果通过压测最后的耗时的确是在对明细的处理上,那么可以考虑对明细进行分批处理,不同的处理逻辑对于分批大小后得到的处理时长是不一样的,需要经过多轮调整参数的压测,找到适合自己业务逻辑的参数值。在我经历的大明细保存数据库实践中,不分批一次直接保存 1 万条明细到数据库与分批保存数据库及每批次的大小不同最终导致所有明细保存完数据库的时间也是有天壤之别的。



数据条数 保存方案 每次保存条数(条)平均耗时 (毫秒)
10000 条 一次保存 10000 159005
10000 条 分批保存 200 15363
10000 条 分批保存 500 18720
再次,在确定了耗时的业务逻辑后,针对耗时的业务逻辑可以启用多线程处理。在使用多线程时,核心线程数的设置建议与 CPU 的核数相同,确保有较好的性能输出。在本文所述的此类场景中,用到了多线程就需要对批量的明细进行分批,结合上面说的不同的批次大小会有不同的性能表现,所以设置的参数建议还是通过压测获取,确保参数的最优。



还是刚才保存 10000 条明细进数据库的案例,在使用多线程并结合分批保存的方案后,在相同硬件及网络条件,单次保存的耗时降低至 1 秒左右,最终所有明细保存完成的总耗时在 10 秒左右,此时应用的压力有所上升,但数据库的压力几乎没什么变化。


数据条数 保存方案 每次保存条数(条)单次平均耗时 (毫秒) 总耗时 (毫秒)
10000 条 分批 + 多线程保存 100 1103 10184

三、经验沉淀

在进行大报文优化时,没有一个通用可行的方法能解决各类问题,本文阐述的仅是在主子模型下,明细过大的一些可行方法及在实际中的实践。而在实际的开发中,结合具体的业务场景,大报文的处理会更加复杂,需要结合具体业务具体分析。但我们日常积累的分批,多线程,异步,调整超时时间等手段都可在解决大报文中发挥各自应有的作用,可结合具体场景进行多种技术手段的综合运用,最终把大报文场景消灭在上线前,确保生产环境的稳定、可靠。


↙↙↙阅读原文可查看相关链接,并与作者交流