1.缘起:
假设我们的订单报表系统,需要能够实时地统计当天的已成交订单的报表。最直观的解决方案就是,当每次接收到查询报表的请求时,就从存储设备读取当天所有已成交的订单,然后再进行分析计算给出结果。这是可行的,而且得到的结果也是非常实时的。
但是,这种方式无疑也是非常低效的,因为我们报表数据的统计过程可能相当复杂,而且,可能是成千上万的用户在同时查询报表数据。在这种情况下,将给存储服务器和业务服务器都造成巨大的压力。
现在我们假设需求能够放宽一点――报表数据不用非常实时,可以允许最大为1分钟的延迟。对于绝大多数业务系统来说,报表数据延迟在1分钟以内,都是能够接受的,所以这个假设并不过分,下面我们基于这个假设来继续讨论。
在报表数据能够允许最大延迟为1分钟许可条件下,我们就可以做缓存的动作了。因为如果要求是完全实时的,那么缓存报表数据能做到的程度就非常的有限,但是有了这一分钟的延迟允许,缓存就可以完全地发挥出它的优势了。
现在,我们可以每隔1分钟从存储设备读取上一分钟已成交的所有注单,这就是所谓的“增量数据”,然后将其做报表分析计算,然后将计算的结果“累加”到之前的“累积的报表数据”上。这样,当用户每次来请求报表数据时,只要将当前累积的报表数据直接返回就可以了。因为我们是每隔1分钟对增量进行累加一次,所以,用户看到的报表数据的延迟最多不会超过1分钟。
上述这个例子很好地体现了“增量缓存”的核心思想,上面提到的“累积的报表数据”实际上就是一个增量缓存的典型实例。关于增量缓存,我们会在下一节进行详细介绍。本节我们将先介绍增量缓存会用到的一个基础组件――增量自动获取器 ESBasic.ObjectManagement.Increasing.IIncreaseAutoRetriever。
现在,我们将注意力集中到上述例子中关于数据增量获取的这个焦点上:
(1)增量可能需要从多个数据源获取。比如,不同类型的订单可能存放在不同的数据库中。
(2)如何体现以一天为单位进行轮转?如果要查看的是周报表,那么就需要将一周作为一轮(Round)。
(3)如果是一天为一轮,那么当经过每天的00:00:00时,增量获取器该如何完结前一天的增量,并切换到新的一天?
这些都是IIncreaseAutoRetriever要解决的问题,而IIncreaseAutoRetriever很好的解决了这些问题,并以一种非常简单易用的接口提供给使用者。
增量自动获取器的形象示意图如下:
2.适用场合:
在满足以下这些条件时,你可以使用IIncreaseAutoRetriever:
(1)需要定时从数据源获取新的增量数据。
(2)数据源可能不只一个。
(3)需要支持“轮”(Round)的概念。在Round切换时,需要能够准确识别增量断点。
(4)增量数据有某个字段是递增的。
3.设计思想与实现
在正式解析IIncreaseAutoRetriever的源码之前,我们先将其会涉及到的一些重要概念说明一下。
首先,数据源可能是多个,所以我们需要为每个数据源设置一个唯一标志(我们称之为Source Token),这标志的类型可能是一个整数,也可能是一个字符串或枚举类型等等。那么,我们可以将数据源标志的类型抽象为一个泛型参数。
其次,上面我们提到Round,即表示增量完成累积的一个完整周期,比如,可能是一天、一周或一月,这取决于你系统的需求。同样的,我们需要每个Round都有唯一的标志(我们称之为Round ID),以将当前Round同历史的Round区分开来。Round ID的类型也是根据系统的需求来定的,所以,我们也将Round ID的类型抽象为一个泛型参数。
再次,我们将缘起部分例子中的一分钟的时间间隔称为一个增量阶段Phase。所以,一个Round是由N个Phase构成的,而且,N通常是个比较大的值。
最后,由于获取增量数据的时候,我们需要依据数据的某个key来进行判断,哪些数据是在刚过去的一个Phase中新加入进来的,这就要求key是递增的,这对从数据源中提取增量会带来极大的便利。比如,可以使用订单的产生时间作为key,如果订单的编号是递增的,也可以使用订单编号作为key。在数据源是数据库的情况下,最好是使用主键作为key,当然,这个时候主键必须是递增的。
IIncreaseAutoRetriever 增量自动获取器会每隔一段时间就从各个数据源(TSourceToken)获取上一阶段的增量数据(TObject),然后触发事件将得到的增量数据发布出去。
下面,我们来看IIncreaseAutoRetriever接口定义: