您当前的位置:首页 > 电脑百科 > 程序开发 > 架构

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

时间:2022-05-10 11:19:43  来源:  作者:Java架构学习指南

业务背景

跑批通常指代的是我们应用程序针对某一批数据进行特定的处理

金融业务中一般跑批的场景有分户日结、账务计提、欠款批扣、不良资产处理等等

具体举一个例子

客户在我司进行借款,并约定每月 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 等,框架无谓好坏,适合自己业务的才是最佳的

可针对自己业务进行技术选型,我们常使用的技术为 xxl-job,针对于我们上文中所说到的不同的跑批任务设置的时间不合理,我们即可利用 xxljob 的子任务特性进行嵌套的任务处理,在保费代扣任务完成后紧接着进行本息代扣任务

防止OOM,切记分片处理

这一点其实没有什么好展开的,在对跑批任务进行开发的时候,一定要记住分片处理

一次性加载所有数据到内存里,无疑是自掘坟墓

那么,如何优雅分片呢?

这时候小张同学举手了:分片我会呀,比如像这种扣款的都是以时间维度来的,我直接 select * from t_repay_plan where repay_time <= "2022-04-10" limit 0,1000

那么,现在我们找个数据来看下这种深度分片的性能如何

我在数据库中插入了大概两百万条数据,把制造数据的过程也分享给你们

// 1、创建表
CREATE TABLE `t_repay_plan` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `repay_time` datetime DEFAULT NULL COMMENT '还款时间',
  `str1` int(11) DEFAULT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=3099998 DEFAULT CHARSET=utf8mb4
  
// 2、创建存储过程 
delimiter $$
create procedure insert_repayPlan()
begin
declare n int default 1;
while n< 3000000
do 
insert into t_repay_plan(repay_time,str1) values(concat( CONCAT(FLOOR(2015 + (RAND() * 1)),'-',LPAD(FLOOR(10 + (RAND() * 2)),2,0),'-',LPAD(FLOOR(1 + (RAND() * 25)),2,0))),n);
set n = n+1;
end while;
end
  
  // 3、执行存储过程
  call insert_repayPlan();
架构师自诉:如何做到百万数据半小时跑批结束

 

随着逐渐的数据偏移,数据耗时逐渐增加。因为这种深度分页是将数据全部查询出来,并且抛弃掉,效果自然不是那么尽如人意

其实我们分片还有一种方法,那就是利用到我们的 id 来进行分页处理(当然是你的 id 是需要保证业务增长,并且结合具体的业务场景来进行分析)

我们同样来试一下怎么利用 id ,进行分片的耗时情况

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

 

我们可以看到效果很明显,利用 id 进行分片,效果是优于我们的这个还款时间字段的

当然关于跑批过程中 「覆盖索引的使用、尽量不去 select * 等、批量进行插入」 等 sql 常见点不和大家一一展开说明了

针对所需数据进行 map 的构造

我们再写一个简单的反例

// 调用数据库查询需跑批数据
List<BizApplyDo> bizApplyDoList = this.listGetBizApply(businessDate);
// for 循环处理数据
for(BizApplyDo ba : bizApplyDoList) {
  // 查询账户数据
  BizAccountDo bizAccountDo = this.getBizAccount(ba.getbizApplyId());
  // 账户处理逻辑.. 省略
  
  // 查询扣款人数据
  CustDo custDo = this.getCust(ba.getUserId);
  // 扣款人处理逻辑.. 省略
}

我们可以这样进行改造(伪代码、忽略判空处理)

// 调用数据库查询需跑批数据
List<BizApplyDo> bizApplyDoList = this.listGetBizApply(businessDate);
// 构建业务申请编号集合
List<String> bizApplyIdList  = bizApplyDoList.parallelStream().map(BizApplyDo::getbizApplyId()).collect(Collectors.toList());
// 批量进行账户查询
List<BizAccountDo> bizAccountDoList = this.listGetBizAccount(bizApplyIdList);
// 构建账户 Map
Map<String, BizAccountDo> accountMap = bizAccountDoList.parallelStream().collect(Collectors.toMap(BizAccountDo::getBizApplyId(), Function.identity()))
// 扣款人数据同样处理
  
for(BizApplyDo ba : bizApplyDoList) {
  account = accountMap.get(ba.getbizApplyId())
 // 账户处理逻辑.. 省略
    
    
}

尽可能减少 for 循环的嵌套,减少数据库频繁连接和销毁

事务控制长点心

一旦我们使用了@Trancation进行管理事务,那么就要求组内开发人员在开发过程中需要瞪大眼睛去注意事务的控制范围

因为 @Trancation 是在第一个 sql 方法执行的时候就开启了事务,在方法未结束之前都不会进行提交,有些同学接手改造这个方法的时候,没有注意到这个方法是被@Trancation覆盖,那么在这个方法里加入一些RPC的远程调用、消息发送、文件写入、缓存更新等操作

1、这些操作自身是无法回滚的,这就会导致数据的不一致。可能RPC调用成功了,但是本地事务回滚了,可是PRC调用无法回滚了。

2、在事务中有远程调用,就会拉长整个事务。那么久会导致本事务的数据库连接一直被占用,那么如果类似操作过多,就会导致数据库连接池耗尽或者单个链接超时

我曾经就见过一个方法,经过多人之手后,从而因为大事务导致数据库连接被强行销毁的悲剧

所以「我们可以有选择性的去使用编程性事务去处理」我们的业务逻辑,让接手的同学可以明确看到什么时候开启了事务,什么时候提交了事务,也尽可能将我们的事务粒度的范围缩小

下游接口 hold 住么

分享此条优化之前,先大致介绍一下我们的业务背景

保费代扣的跑批任务中,我们是会借助流程编排这个框架,去异步发起我们的代扣,你可以理解为一笔代扣申请就是一个异步线程,代扣的数据全部在流程编排中进行传递

在我们进行优化完毕的时候,准备在UAT环境进行优化测试的时候,发现仅20w条保费数据,处理时间就非常的不尽入人意

监控系统环境,发现系统频频在进行 GC,我的第一反应,不会是发生内存泄露了吧,在准备 dump 文件的时候

我意外的发现,大部分申请都是卡在了对外扣费的这个节点,经过日志观察,发现下游接口给的响应时间过久,甚至部分出现了超时情况

那么这个GC就合理了,由于我们的代扣申请生成的速度非常快,并且是异步的线程调度,线程还未死亡,一直在尝试对外请求扣费,就导致所有的数据都堆在内存里,就导致了频繁GC

在和下游接口方进行核实之后,的确针对于该接口没有进行限流处理(太坑了)

优化的思路也很简单了,在业务可接受的情况,我们采取的是去发送 mq 请求后,就挂起流程编排(该线程会死亡),然后让消费者进行处理调用成功后唤醒流程进行后续处理即可,当然使用固定的线程池直接调外也是可以的,目的都是防止过多的线程处于 RUNNING,从而导致内存一直的堆积

还有一种对外调用的万金油处理方式,就是在业务可接受的情况下,采取一种 fast success 方式,举个例子,在进行保费扣费的时候,我们调用支付公司的接口之前,直接将我们的扣费状态更改为扣费中,然后直接挂起我们的业务,然后用定时任务去查证我们的扣费结果,收到扣费结果后,在继续我们扣费后的操作

机器利用方面给我打满

针对于生产上面的机器我们通常不会是单机部署,那么如何可以尽可能去压榨我们服务器的资源呢

那就是利用xxl-job的 「分片广播」 和 「动态分片」 功能

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

 

执行器集群部署时,任务路由策略选择”分片广播”情况下,一次任务调度将会广播触发对应集群中所有执行器执行一次任务,同时系统自动传递分片参数;可根据分片参数开发分片任务;

“分片广播” 以执行器为维度进行分片,支持动态扩容执行器集群从而动态增加分片数量,协同进行业务处理;在进行大数据量业务操作时可显著提升任务处理能力和速度。

“分片广播” 和普通任务开发流程一致,不同之处在于可以获取分片参数,获取分片参数进行分片业务处理。

// 可参考Sample示例执行器中的示例任务"ShardingJobHandler"了解试用 
int shardIndex = XxlJobHelper.getShardIndex();
int shardTotal = XxlJobHelper.getShardTotal();

具体举个例子,比如我们做分户日结的时候,可以根据商户的编号对机器进行取模处理,然后每台机器只执行某些特定商户的数据

那么这边留一个问题给大家:如果发生数据倾斜,你会如何处理,即某个商户的数据量特别大, 导致这台机器执行的任务非常的重,要是你,你会如何处理这种场景?

总结

今天针对于大数据量的跑批,在项目中实践思考就到此结束了。

文章介绍了我们常见跑批任务中可能出现的风险和比较常用通用的一些优化思路进行了分享

关于线程池和缓存的运用我未在文章中提及,这两点也对我们的高效跑批具有极大的帮助,小伙伴们可以加以利用

当然文章只是引起大家针对于跑批任务的思考,更多的优化还需结合任务具体情况和项目本身环境进行处理



Tags:数据   点击:()  评论:()
声明:本站部分内容及图片来自互联网,转载是出于传递更多信息之目的,内容观点仅代表作者本人,不构成投资建议。投资者据此操作,风险自担。如有任何标注错误或版权侵犯请与我们联系,我们将及时更正、删除。
▌相关推荐
电子数据如何规范取证?市场监管总局发布暂行规定
中新网4月17日电 据国家市场监督管理总局网站消息,为规范市场监督管理行政执法电子数据取证工作,进一步提升执法人员电子数据取证能力,提高行政执法效能,市场监管总局近日发布《...【详细内容】
2024-04-17  Search: 数据  点击:(3)  评论:(0)  加入收藏
“弱智吧”成最佳中文AI语料库,究竟什么算优质数据?
出品|搜狐科技作者|郑松毅谁能想到,被誉为“最大原创段子手孵化地”的弱智吧,最近竟摇身一变成了&mdash;&mdash;最佳中文AI训练语料库?由此受到启发,是否并不是训练AI的优质数据不...【详细内容】
2024-04-16  Search: 数据  点击:(7)  评论:(0)  加入收藏
Kyligence发布企业级AI解决方案!AI数智助理降低数据使用门槛
智东西作者 | 长颈鹿编辑 | 李水青智东西4月12日报道,昨日大数据分析和指标平台供应商Kyligence(硅智信息)召开了数智论坛暨春季发布会,并分享和探讨Data+AI产品及解决方案在金...【详细内容】
2024-04-12  Search: 数据  点击:(10)  评论:(0)  加入收藏
解读3月份CPI和PPI数据
新华社北京4月11日电 题:国内物价总体保持温和上涨&mdash;&mdash;解读3月份CPI和PPI数据新华社记者魏玉坤、吴剑锋国家统计局11日发布的数据显示,3月份,全国居民消费价格指数(CP...【详细内容】
2024-04-11  Search: 数据  点击:(13)  评论:(0)  加入收藏
聊聊Rust里面的数据类型
嘿,朋友们!今天我们来聊聊Rust里面的数据类型。你知道吗?Rust的数据类型可是很重要的哦,它们帮助我们定义变量和函数可以处理什么样的数据。基本数据类型首先,让我们来看看Rust提...【详细内容】
2024-04-11  Search: 数据  点击:(9)  评论:(0)  加入收藏
“霸王条款”“刷单炒信”“大数据杀熟”怎么破解?这部法规都明确了!
预付式消费、直播带货、“一老一小”、“霸王条款”、“刷单炒信”、“大数据杀熟”、自动续费、强制搭售&hellip;&hellip;这些新领域的侵权你遇到过吗?消费者合法权益怎么保...【详细内容】
2024-04-11  Search: 数据  点击:(14)  评论:(0)  加入收藏
统计局解读3月份CPI和PPI数据:CPI季节性回落 PPI环比降幅收窄
2024年3月份CPI季节性回落 PPI环比降幅收窄&mdash;&mdash;国家统计局城市司首席统计师董莉娟解读2024年3月份CPI和PPI数据国家统计局今天发布了2024年3月份全国CPI(居民消费...【详细内容】
2024-04-11  Search: 数据  点击:(14)  评论:(0)  加入收藏
美国3月CPI数据全线高于预期 6月首降希望基本破灭
财联社4月10日讯(编辑 牛占林)当地时间周三,美国劳工部公布的数据显示,因汽油和住房价格上涨,美国基础通胀率连续第三个月超预期,强化了美联储在降息方面的谨慎态度。有不少分析师...【详细内容】
2024-04-10  Search: 数据  点击:(8)  评论:(0)  加入收藏
暴雪公布国服规则:当服务器开启时,将保证账号数据被完整保留
新浪科技讯 4月10日上午消息,暴雪娱乐、微软游戏与网易今日共同宣布,暴雪娱乐旗下的游戏作品,将根据更新后的游戏发行协议自今年夏季开始陆续重返中国大陆市场。消息公布后,暴雪...【详细内容】
2024-04-10  Search: 数据  点击:(10)  评论:(0)  加入收藏
市场监管总局:禁止“大数据杀熟”规范“自动续费”
本报讯 (记者韩昱) 近日,《中华人民共和国消费者权益保护法实施条例》(以下简称《条例》)正式发布。4月9日,国新办举行国务院政策例行吹风会,市场监管总局副局长柳军介绍《条例》有...【详细内容】
2024-04-10  Search: 数据  点击:(7)  评论:(0)  加入收藏
▌简易百科推荐
对于微服务架构监控应该遵守的原则
随着软件交付方式的变革,微服务架构的兴起使得软件开发变得更加快速和灵活。在这种情况下,监控系统成为了微服务控制系统的核心组成部分。随着软件的复杂性不断增加,了解系统的...【详细内容】
2024-04-03  步步运维步步坑    Tags:架构   点击:(7)  评论:(0)  加入收藏
大模型应用的 10 种架构模式
作者 | 曹洪伟在塑造新领域的过程中,我们往往依赖于一些经过实践验证的策略、方法和模式。这种观念对于软件工程领域的专业人士来说,已经司空见惯,设计模式已成为程序员们的重...【详细内容】
2024-03-27    InfoQ  Tags:架构模式   点击:(20)  评论:(0)  加入收藏
哈啰云原生架构落地实践
一、弹性伸缩技术实践1.全网容器化后一线研发的使用问题全网容器化后一线研发会面临一系列使用问题,包括时机、容量、效率和成本问题,弹性伸缩是云原生容器化后的必然技术选择...【详细内容】
2024-03-27  哈啰技术  微信公众号  Tags:架构   点击:(13)  评论:(0)  加入收藏
DDD 与 CQRS 才是黄金组合
在日常工作中,你是否也遇到过下面几种情况: 使用一个已有接口进行业务开发,上线后出现严重的性能问题,被老板当众质疑:“你为什么不使用缓存接口,这个接口全部走数据库,这怎么能扛...【详细内容】
2024-03-27  dbaplus社群    Tags:DDD   点击:(16)  评论:(0)  加入收藏
高并发架构设计(三大利器:缓存、限流和降级)
软件系统有三个追求:高性能、高并发、高可用,俗称三高。本篇讨论高并发,从高并发是什么到高并发应对的策略、缓存、限流、降级等。引言1.高并发背景互联网行业迅速发展,用户量剧...【详细内容】
2024-03-13    阿里云开发者  Tags:高并发   点击:(12)  评论:(0)  加入收藏
如何判断架构设计的优劣?
架构设计的基本准则是非常重要的,它们指导着我们如何构建可靠、可维护、可测试的系统。下面是这些准则的转换表达方式:简单即美(KISS):KISS原则的核心思想是保持简单。在设计系统...【详细内容】
2024-02-20  二进制跳动  微信公众号  Tags:架构设计   点击:(41)  评论:(0)  加入收藏
详解基于SpringBoot的WebSocket应用开发
在现代Web应用中,实时交互和数据推送的需求日益增长。WebSocket协议作为一种全双工通信协议,允许服务端与客户端之间建立持久性的连接,实现实时、双向的数据传输,极大地提升了用...【详细内容】
2024-01-30  ijunfu  今日头条  Tags:SpringBoot   点击:(23)  评论:(0)  加入收藏
PHP+Go 开发仿简书,实战高并发高可用微服务架构
来百度APP畅享高清图片//下栽のke:chaoxingit.com/2105/PHP和Go语言结合,可以开发出高效且稳定的仿简书应用。在实现高并发和高可用微服务架构时,我们可以采用一些关键技术。首...【详细内容】
2024-01-14  547蓝色星球    Tags:架构   点击:(125)  评论:(0)  加入收藏
GraalVM与Spring Boot 3.0:加速应用性能的完美融合
在2023年,SpringBoot3.0的发布标志着Spring框架对GraalVM的全面支持,这一支持是对Spring技术栈的重要补充。GraalVM是一个高性能的多语言虚拟机,它提供了Ahead-of-Time(AOT)编...【详细内容】
2024-01-11    王建立  Tags:Spring Boot   点击:(135)  评论:(0)  加入收藏
Spring Boot虚拟线程的性能还不如Webflux?
早上看到一篇关于Spring Boot虚拟线程和Webflux性能对比的文章,觉得还不错。内容较长,抓重点给大家介绍一下这篇文章的核心内容,方便大家快速阅读。测试场景作者采用了一个尽可...【详细内容】
2024-01-10  互联网架构小马哥    Tags:Spring Boot   点击:(134)  评论:(0)  加入收藏
站内最新
站内热门
站内头条