前言
工作中常遇到数据统计的需求,比如统计一段时间内资源浏览PV、UV等,之前的做法是,为每个统计指标创建一个定时任务,统计一定时间范围内的数据。
在刚开始统计指标不多时,这样做是可行的。但是,随着业务发展,需要统计的指标增多,这种做法逐渐暴露出以下两个缺点:
1、每个指标都要创建一个统计任务,任务逐渐膨胀,占用了大量的资源;
2、有的指标要在不同的时间周期上进行统计,比如每天统计、每周统计、每月统计等等,每增加一个周期,要么修改原有的统计代码,增加时间判断,要么增加一个新的定时任务。
为了解决上述两个问题,本文提出一种解决方案:把所有任务组织成一个“任务链”,只使用一个定时任务依次调度“任务链”上的统计任务,这样既减少了所需的定时任务,也方便了后续扩展。
分析与设计
在数据统计的需求中,最核心就是 “统计指标” 这一概念,不同的 “指标” 对应了不同的统计对象和统计方法。即使如此,不同的”指标“之间也有着一些共性,比如,每个指标都有能表明自己含义的名称,每个指标都有要统计的对象等。
我们整理出所有指标都包含的共性,如下所示:
- 名称:一个指标的名称应该唯一地表明了,该指标统计的含义,比如资源的浏览 PV,一个项目中不应该存在两个含义不同的 “资源浏览PV” 指标;
- 统计的对象:统计的对象由 指标 的含义确定,比如 ”资源浏览PV“ 统计对象就是资源,或者指定类型的资源;
- 统计周期:统计周期决定了多久计算一次指标,比如每天统计、每周统计、每月统计、每年统计;
- 统计方法:计算指标数值的方法,与”名称“类似,一个指标的统计行为,应该是唯一、确定的,不能说这次的统计行为与上次的统计行为不一样,否则指标的含义就发生了变化。
前面列出的“共性”中,很显然,“名称、统计行为“ 一旦确定就基本不会改变,”统计周期、统计的对象“是可能发生改变的。我们把“不变、唯一、固定”的部分(名称、方法),作为“指标”的“内部封装“的参数和方法,把”非唯一、变化“的部分(统计周期、统计的对象),作为外部的独立存在以供”指标“引用。
前面提到了“周期”,并把”周期“作为独立的对象,那么我们就需要确定该对象的含义和功能。
从前面的介绍可以知道,“周期”的作用是:在指定的时间点,执行数据统计功能。之前的方法是,在不同的时间点创建定时任务,来定时统计指标。现在我们要求只使用一个定时任务,那么当该定时任务执行到任务时,“周期对象“的功能是:判断当前时间是否执行该统计任务。因此,此时的“周期”对象更像是一个过滤器,如果当前时间点不适合执行统计,通过它过滤掉该任务。
有了“指标”之后,我们就需要考虑,当“指标”逐渐增多时,该怎么管理越来越多的“指标”?这也是本文解决方案的主要目标。
在前面,我们把“周期”对象作为“指标”的内部依赖,当执行该指标时,由”指标”调用“周期”对象来决定自身是否能够执行,不再是人为创建的“定时任务“来决定。因此,我们此时需要的只是一个“触发指标的时机“,而不再是“执行指标的时机”。
我们可以把“指标”组织成一条“任务链”,创建一个定时任务,依次遍历“任务链”上的每个指标,判断是否需要执行,如下图所示:
这样,我们只需要一个定时任务就可以执行所有统计指标。
实现
仿照“分层架构“,把数据统计分为三层:
- Job:创建定时任务;
- IndexChain:管理统计指标,类似于 service 层,每个统计指标提供自身的统计方法;
- Dao:与 DB 交互;
如下图所示:
Index、Chain
从前面的介绍,我们得知,“指标”之间存在共性,比如都持有“周期对象”,都对外提供一个“统计”方法。
因此,我们把“指标”作为一个接口,对外提供一致的方法;使用一个“抽象类”实现该接口,持有“周期对象”,并提供模板方法;用具体的实现类来表示不同的“指标”,具体的实现类称为 “strategy”。
与之对应,”任务链”依赖的是“指标”的接口,而不是它的实现,所以“任务链”持有 index 接口数组。
UML如下图所示:
TimeFilter
同理,“周期”对象也有很多具体的实现类,比如每天执行一次、每周执行一次、每月执行一次等等。因此,我们使用 timeFilter 接口表示 ”周期“ 对象,对于 IndexStrategy 类而言,它依赖的是“周期”对象的接口,而不是具体实现。
TimeFilter 的UML如下图所示:
代码实现
上述代码实现在 Github 仓库 github.com/ShiMengjie/… 的 demo-data-statistic 模块中。
总结
通过前面的介绍,我们实现了“只使用一个定时任务执行多个统计“的目的,但是该方法还存在以下问题:
1、因为所有任务都在一个“任务链”上,所以前面的任务会阻塞后面的任务,导致后面任务延时;可以通过多线程并发执行,避免任务之间的阻塞。
2、定时任务的执行周期要求是任务链上最小任务的周期,如果任务链上的任务周期之间相差过大,会导致频繁的“空转”;可以根据周期粒度创建任务链,比如按照分钟级、小时级、天级、周级等粒度,把相同周期粒度的任务放在同一个任务链上。
以上内容仅仅是自己平时工作中的一些想法,仅供参考。