支付宝定时任务怎么做?三层分发任务处理框架介绍
阿里妹导读
本文将从单机定时调度开始,循序渐进地带领大家了解五福定制三层分发任务处理框架。
一、背景介绍
技术同学对定时任务肯定不陌生。 定时任务一般用来定时批量进行业务处理。 支付宝卡包券到期提醒、删除过期失效券,五福大促批量给用户发放添福红包等场景,都是通过定时任务触发来完成的。作者有幸参与了2023兔年五福大促的开发,主导完成了福气乐园分会场平分5000万大奖需求。通过学习并运用五福定制三层分发任务处理框架,最终平稳丝滑的完成了平分大奖需求任务。本文将从单机定时调度开始,循序渐进地带领大家了解五福定制三层分发任务处理框架。
二、定时任务分类
1、定时调度
在Spring中可以通过@Scheduled 来启用定时任务。触发的方式有两种,分别是:cron 表达式和 fixedRated类配置参数。常用的案例:// cron表达式@Scheduled(cron="0 0/30 9-17 * * ?") //按cron规则执行,朝九晚五工作时间内每半小时@Scheduled(cron="0 0 12 ? * WED") //按cron规则执行,表示每个星期三中午12点// fixedRated类配置@Scheduled(fixedRate=5000) //上一次开始执行时间点后5秒再次执行;@Scheduled(fixedDelay=3000) //上一次执行完毕时间点后3秒再次执行;@Scheduled(initialDelay=1000, fixedDelay=2000) //第一次延迟1秒执行,然后在上一次执行完毕时间点后2秒再次执行;
2、定时调度+批处理
为了解决复杂耗时场景下定时调度效率不高的问题,可以引入批处理框架。定时调度与批处理框架相结合,可以大幅提高数据处理的效率,提升系统稳定性,保障业务稳定运行。 以Spring Batch批处理框架为例,任务处理流程如下:
1、三层分发
2、五福定制三层分发
五福大促有很多业务场景都是需要通过定时任务来进行处理的,比如生肖卡提醒、AI年画提醒,福气乐园平分5000万大奖。五福大促对稳定性和可用性的要求是非常高的,为了解决三层分发处理框架缺陷带来的效率和稳定性风险,五福在三层分发基础上做了定制化改造,改造的目标主要有两点: 1)最大化利用集群机器资源 。做到真正的负载均衡,同时也能够提升集群的任务处理容量。 2) 平滑的任务处理。 减少任务调用的尖刺,避免对DB和外部系统造成稳定性风险。 下面分别从优化目标的两点来进行阐述。 1、最大化利用集群机器资源 由于三层分发默认只会在同一个Zone的A/B组中开启一组,导致浪费了一半的机器。显而易见,要最大化利用集群机器资源,就需要让A/B组的机器都能够参与到任务处理当中。优化的步骤如下: 1.Antscheduler定时调度平台同时开启A/B分组调度。 2.增加任务配置,配置的目的是让A组的机器只处理奇数位eid、B组的机器只处理偶数位eid。 3.Splitor层根据任务配置,将本Zone全量eid进行分组,A组只处理奇数位eid,B组只处理偶数位eid。 上面三步做完以后,就能让集群所有的机器都能参与到任务处理当中,从而最大化的利用了机器资源。
通过代码可知,推送任务配置时,将A组机器的值推成“ODD”,B组机器的值推成“EVEN”,即可实现A/B组的所有机器同时执行定时任务的效果。 2、平滑的任务处理 默认的三层分发通过Loader层获取待处理的任务,然后交由Executor来执行,无法保证整个集群任务处理的量级。在待处理任务变多,或者集群机器扩缩容变化频繁的情况下,任务处理的峰值量级无法保证。同时由于各个层级之间的调用是TR oneway调用,是感知不到调用结果的,也就更难保证任务的平滑调用。为了达到任务平滑调用的目的,五福场景对Loader捞取任务数和单机任务qps做了优化调整,保证了集群任务处理效率在预期范围之内。优化的步骤如下: 1.新增任务配置。核心配置信息包括期望集群执行任务总的qps、任务调度间隔,集群参与任务机器数。 2.计算单机qps和每次DB捞取任务的数量。 3.单机执行时,根据计算好的qps来进行限流调用。 上面三步做好之后,整个集群就能够按照预期的qps进行平滑的任务处理。 为什么这三步做完了之后就能达到预期的效果呢? 重点看下任务配置的解析代码:/*** 根据配置中心的dataFlag过滤* 1、默认ALL不区分* 2、ODD 表示仅分发奇数表号* 3、EVEN 标识仅分发偶数表号** @param eidList*/public void filteByDataFlag(List<String> eidList) {String dataFlag = SchedulerConfigDrmUtil.getIndexFilterFlag();int strategy = -1;if (StringUtil.equalsIgnoreCase("ODD", dataFlag)) {strategy = 1;} else if (StringUtil.equalsIgnoreCase("EVEN", dataFlag)) {strategy = 0;}if (strategy == -1) {// ALLreturn;}// filterIterator<String> it = eidList.iterator();while (it.hasNext()) {String str = it.next();int index = NumberUtils.toInt(str, -1);if (index % 2 != strategy) {it.remove();}}}
通过代码可知,推送的任务配置最终会生成两个重要的配置信息: 1. 单个Loader捞取的任务数。 集群qps和调度间隔确定了一个调度间隔内要处理的任务数,结合eid分片数量(五福是千库千表)确定每个Loader要捞取的任务数。 2. 单机Executor执行任务时的qps。 预期集群qps和集群机器数确定了单机执行任务时的qps,单机上通过Guava的RateLimit来达到限流的效果。如果请求超过了限制的qps,请求将会被阻塞。 下面以福气乐园平分5000万大奖的任务配置作为样例来计算:/*** 计算定时任务的相关配置** 主要计算:* scheduleSingleLimit 单机限流值* scheduleLoaderCount loader捞取条数** @param scheduleConfig*/public static SchedulerConfig calculateScheduleConfig(SchedulerConfig scheduleConfig) {final int qpsLimit = scheduleConfig.getScheduleWholeLimit();final int machineCounts = scheduleConfig.getScheduleMachineCounts();MtLogger.info(LOGGER,"【计算定时任务配置】-开始 任务名称:{0},机器数量:{1},任务吞吐量:{2}.", scheduleConfig.getScheduleType(),machineCounts, qpsLimit);//定时任务的调度频率是scheduleRate 秒执行一次 所以scheduleRate秒中内集群的整体吞吐量=qps限制*scheduleRatefinal int scheduleRate = scheduleConfig.getScheduleRatePerSec();long totalLoaderCounts = qpsLimit * TimeUnit.SECONDS.toSeconds(scheduleRate);//定时任务捞取的表数量为1000 所以到每个表的限制=totalLoaderCounts/1000long loaderCountPerTask = totalLoaderCounts / 1000;if (loaderCountPerTask < 1) {loaderCountPerTask = 1;}scheduleConfig.setScheduleLoaderCount((int) loaderCountPerTask);//整体限流通过单机限流实现 整体限流=单机限流*machineCountsfinal double singleQps = ((double) qpsLimit / machineCounts);//创建的限流需要1秒的预热scheduleConfig.setScheduleSingleLimit(RateLimiter.create(singleQps, 1, TimeUnit.SECONDS));MtLogger.info(LOGGER,"【计算定时任务配置】-结束 任务名称:{0},捞取条数:{1},单机限流:{2}.", scheduleConfig.getScheduleType(),scheduleConfig.getScheduleLoaderCount(),scheduleConfig.getScheduleSingleLimit().getRate());return scheduleConfig;}
三、结语
从单机到集群,再到五福定制集群定时任务,本文逐步做了一个框架设计上的原理介绍。每种定时任务都有自己的优点和缺陷,也都有自己的应用场景。在工作中,要结合当前的业务情况,选择合适的定时任务进行业务处理,避免设计上的失误导致业务受损。以五福定制三层分发任务处理框架为例,虽然日常业务中,因为机器数量不固定,依旧无法做到任务的平滑调用,但我们可以借鉴最大化利用集群机器资源这一点,同时开启A/B组的定时任务,从而实现任务调度真正的负载均衡,提高系统整体的稳定性。