架构师自诉:如何做到百万数据半小时跑批结束

业务背景跑批通常指代的是我们应用程序针对某一批数据进行特定的处理
在金融业务中一般跑批的场景有分户日结、账务计提、欠款批扣、不良资产处理等等
具体举一个例子
客户在我司进行借款,并约定每月 10 号码还款,在客户自主授权银行卡签约后
在每月 10 号(通常是凌晨)我们会在客户签约的银行卡上进行扣款
然后可能会有一个客户、两个客户、三个客户、四个客户、好多个客户都需要进行扣款,所以这一“批” 所有数据,我们都要统一地进行扣款处理,即为我们“跑批”的意思
跑批任务是要通过定时地去处理这些数据,不能因为其中一条数据出现异常从而导致整批数据无法继续进行操作,所以它必须是健壮的;并且针对于异常数据我们后续可以进行补偿处理,所以它必须是可靠的;并且通常跑批任务要处理的数据量较大,我们不能让它处理的时间过于久,所以我们必须考虑其性能处理;总结一下,我们跑批处理的应用程序需要做到的要求如下

  • 健壮性
  • 针对于异常数据,不可能导致程序崩溃
  • 可靠性
  • 针对于异常数据,我们后续可以跟踪
  • 大数据量
  • 针对于大数据量,可在规定的时间内进行处理完毕
  • 性能方面
  • 必须在规定的时间内处理完从而避免干扰任何其他应用程序的正常运行
跑批风险一些未接触过跑批业务的同学,可能会犯一些错误·
  • 「查询跑批数据,未进行分片处理」
  • 这种情况具体有两种情况
  • 一种是同学无意识进行分片处理,直接根据查询条件将全量数据查出;
  • 第二种情况呢,不单是在跑批的时候可能出现的情况,在平时的业务开发过程中也可能发现,针对于查询条件未进行判空处理 。比如 select id from t_user_account weher account_id = "12"; 然而在业务处理过程中,account_id 为空,却直接进行查询,数据量一旦上来,就容易导致 OOM 悲剧
  •  
  • 「未对数据进行批量处理」
  • 这种情况也是同学们容易犯的一个错误,通常我们跑批可能会涉及到数据准备的过程,那么有的同学就会直接梭哈,边循环跑批数据边去查找所需的数据,一方面for嵌套的循环处理,时间复杂度通常是随着你的 for 个数上升的,在项目中一个同学在保费代扣的跑批任务中,进行了五次for 循环,这个时间复杂度就是O(n ^ 5)了,并且如果你的方法未进行事务管理的话,数据库的连接释放也是一个非常消耗资源的事情
  • 上一个伪代码可能会比较好理解
// 调用数据库查询需跑批数据List<BizApplyDo> bizApplyDoList = this.listGetBizApply(businessDate);// for 循环处理数据for(BizApplyDo ba : bizApplyDoList) {// 业务处理逻辑.. 省略// 查询账户数据List<BizAccountDo> bizAccountDoList = this.listGetBizAccount(ba.getbizApplyId());for (BizAccountDo bic : bizAccountDoList){// 账户处理逻辑.. 省略}... // 后续还会嵌套 for 循环}
  • 「事务使用的力度不恰当」
我们知道 Spring 中间的事务可分为编程性事务和声明式事务,具体二者的区别我们就不展开说明了
在开发过程中,就有可能同学不管三七二一,爽了就行,直接 @Trancational 覆盖住我们整个方法
一旦方法处理时间过久,这个大事务就给我们的代码埋下了雷
  • 「未考虑下游接口的承受能力」
  • 我们跑批任务除了在我们本系统进行的处理外,还有可能需要调用外部接口
  • 比如代扣时,我们需要调用支付公司侧的接口,那么我们是否有考虑下游接口的承受能力和响应时间(这里有一个坑,下一个 part 我们展开说一下)
  • 「不同的跑批任务时间设置不合理」
  • 在我们的项目中,有一个的业务玩法是,我们必须在保费扣完之后,才可进行本息的代扣
  • 小张同学想当然,我的保费代扣定时任务从凌晨12点开始,一个小时定时任务总该结束了吧,那么我的本息代扣的定时任务从凌晨1点开始吧,可是这样设置真的合适吗?
优化思路定时框架的选择常用的有 Spring 定时框架、Quartz、elastic-job、xxl-job 等,框架无谓好坏,适合自己业务的才是最佳的


推荐阅读