数据窗口的设计思路

  |   0 评论   |   0 浏览

数据窗口定义

  什么是数据窗口?数据窗口是顶层抽象定义,数据窗口提供了筛选数据的功能,数据窗口本质上是一个数据集合,在一个无序的事件流中,到底业务上需要哪些数据,哪些数据应该保留在集合中,哪些应该踢出集合,都是由具体的数据窗口决定。因此我觉得数据窗口是一个大的概念,具体的数据窗口可以分为优先级窗口、时间窗口等。

数据窗口分类

  目前实现的数据窗口主要有两类,基于优先级的数据窗口(Priority-Based DataWindow)、基于时间的数据窗口(Time-Based DataWindow)。
  优先级窗口,定义窗口的大小,其内部就是对象列表,到达指定长度后,就开始根据淘汰策略,淘汰掉多余的对象,淘汰策略不一定是FIFO,也可能是根据对象优先级,具体策略根据需求而定,优先级窗口只负责维护指定长度的对象列表。
  时间窗口,定义一个数据集合,这个集合内部对象都有一个统一的属性——timestamp时间戳,这个是事件的发生时间,每一个事件对象都有这个属性,这个属性决定了哪个事件该进入窗口,哪个事件不该进入窗口,因此基于时间的数据窗口内,对象淘汰策略有且只有一个——基于过期时间(expire-time)的淘汰策略。时间窗口的设计比较复杂,是这次详细介绍的内容。

时间窗口的基本概念

  我们把基于时间的数据窗口(Time-Based DataWindow)简称为时间窗口,我们把交易事件的实际发生时间作为时间窗口的标准时间,所有时间窗口内的数据是否过期都是以交易事件的实际发生时间作为计算依据,时间窗口定义了一个基于事件发生时间的数据集合,这个集合内部定义了一个过期时间,凡是超过了过期时间的事件,都会被时间窗口踢出集合,时间窗口的作用就是维护这么一个具有过期时间的数据集合。

时间窗口的实现

  一个时间窗口,例如5分钟,它是由窗口宽度和时间单位组成,宽度是5,单位是分钟。那么怎么去统计这5分钟内的数据?只是简单的存储这5分钟的数据,然后再拿出来计算吗?万一不是5分钟,而是5天、5个月呢?要计算的量岂不是很大,在一个实时系统中,不允许有太大的延迟,如果每次数据计算的量太大,会对响应这块产生影响。
  我们把一个数据窗口,根据时间单位进行切分累计。为什么要切分时间窗口?因为没有必要缓存(存储)每一条统计数据,假如统计数据是存储缓存集群中的,试想,在一个长度周期为5天(或者更长)的统计定义中,用户每一条统计相关数据都会被存放在缓存中,这不仅增加了网络传输压力,更浪费了缓存宝贵的内存空间,因此要对时间窗口进行切分统计。
  对统计时间窗口内部集合进行切分,通常一个大的时间窗口,内部会被切分成小的时间窗口(SlicedBlock切分块),每个切分块本质上也是一个时间窗口,每个切分块对象都会有一个过期时间expireTime(切分块本身何时过期)、过期时间点expireTimePoint(过期时间点,决定了当前时间窗口内的数据何时过期,跟领跑时间对应,这两个值决定了时间窗口宽度)和领跑时间pacemakerTime(领跑时间本质上是一个时间窗口的最大时间,和窗口的过期时间点对应,两个值决定了时间窗口的宽度。目前以接收到最近交易事件所在的切分块的最大时间点,作为时间窗口滑动的领跑时间,促使时间窗口产生切分块的事件我们可以称为领跑事件(pacemakerEvent),领跑事件所在的切分块的最大时间,为当前时间窗口的领跑时间),切分块是时间窗口内部的最小存储对象,统计数据到来后,会被累计到切分块中,所有切分块的累计值,才是这个统计的实际值。时间窗口在滑动的时候,让时间窗口滑动基于切分块来滑动,而不是整个窗口一次性往前滑动整个窗口,因此不会造成因为时间窗口的滑动导致统计数据误差太大。例如一个5分钟的时间窗口,我们会切分成5个一分钟的切分块,时间窗口是1分钟一分钟的滑动,这样每次滑动仍然会保留4分钟的数据。我们以策略的方式向外提供窗口切分规则(sliceStrategy),策略可以有多种实现。
  注意:切分块在时间窗口内部可以是非连续的,是根据交易事件的发生时间动态切分的。

时间窗口的切分策略

  时间窗口的切分目前按窗口定义里面的时间单位来切分,目前时间单位有秒、分、时、日、周、月、年和永久。每个单位的1个时间周期为1个切分块,例如分,那么分的切分块长度是60×1000毫秒(目前内部计算都采用毫秒级),单位“分”的切分块所在的时间都是整点的,例如一个领跑事件发生的时间戳是2017-05-19 14:01:02.111,那么这个事件生成的切分块的时间范围是2017-05-19 14:01:00.000~ 2017-05-19 14:01:59.999,切记,不是2017-05-19 14:02:00.000,切分块的时间窗口不会跨度到下一个周期中。
  为什么要以时间单位作为窗口切分的基本单位?因为这里每个时间单位代表的意义不同,例如24小时和一日的区别,24小时是连续的24个小时,是以“小时” 作为单位,例如2017-5-19 13:00:00.000~2017-5-20 12:59:59.999是24小时,而时间单位“日”,是每天的00:00:00.000至当天的23:59:59.999,只要过了0点,就算第二日,虽然也是24小时,但是跟时间单位为“小时”的意义不一样。因此窗口切分策略必须以统计的时间单位作为切分基础。
  再拿单位“月”举例,“月”和“年”比较特殊,它们没有固定的时间周期长度,例如1月和2月,它们天数不一样,年的话分闰年和平年,润年366天,平年365天,所以它们的实际时间长度是不一样的,所以单位是“月”的时候,每个切分块的时间长度是动态计算的,例如当前领跑事件的时间戳是2017-05-2 14:01:00.000,当前时间窗口会新生成一个5月份的切分块,切分块过期时间点为2017-05-1 00:00:00.000,切分块领跑时间为2017-05-31 23:59:59.999,所有在这个时间范围内的事件都会被累计到该切分块。在领跑事件触发时间窗口生成新的block时候,会设置窗口领跑时间为block的领跑时间,同时会触发当前时间窗口计算新的过期时间点,这个过期时间点的计算也跟block有关,秒、分、时、日、周比较方便计算,因为他们时间单位是固定的,例如 1周是7天、1天是24小时,那么时间窗口过期时间点可以根据“窗口领跑时间-周期×block时间宽度”计算出来。而月、年则需要动态计算,只能根据当前的领跑时间,反推N个月之前的1日0点0分0秒000的时间戳,年跟月一样,反推N年之前的1日0点时间戳。这样计算之后,就能得到一个时间宽度和位置确定的时间窗口。

时间窗口的滑动

  目前主流的时间窗口滑动有两个方向,一个是基于墙上时间wall-clock、一个是基于事件时间戳event-timestamp,墙上时间是固定的,连续变化的时间,就像墙上的钟表一样,每时每刻都在滑动,内部需要一个触发器,定时触发相应的时钟心跳,统计窗口根据此心跳决定当前滑动位置。而基于事件时间戳的时间窗口滑动是由事件决定的,只有交易事件到来后,才能决定当前时间窗口到底滑动到哪个位置。我们目前采用基于事件时间戳的时间窗口滑动。
  我们定义的时间窗口是以事件发生时间为基准的一个数据集合,窗口定义了一个过期时间点(expireTimePoint),该时间表示本集合内的数据何时该被踢出集合(过期)。目前时间窗口的过期时间点是由三个参数决定,单位(秒、分、时、日、月、周、年、永久)、周期、领跑时间,其中单位和周期是由WEB管理界面设置的,属于静态属性(对于当前统计对象来说是静态,虽然可以在管理界面中修改),单位和周期决定了时间窗口的宽度,领跑时间是动态,所以真正决定时间窗口位置的是领跑时间。
  时间窗口滑动一次滑动一个切分块block,也就是滑动一个时间单位,并且会保持(周期×时间单位)的数据。

时间窗口抽象模型

  基于以上介绍的概念,一个时间窗口模型如下图所示:
1682190378645155840.png
  注意,切分块SlicedBlock中包含了数据累计的值,这个跟具体的统计函数实现有关,具体的在统计函数篇章里面去介绍基于数据窗口的统计函数实现。由SlicedBlock独立统计每个切分块内部的统计累计,每个SlicedBlock内部如何统计由具体函数决定,SlicedBlock是时间窗口组成的最小单元,极端情况是每个交易统计值一个SlicedBlock,正常情况下,一段时间内的统计为一个SlicedBlock。新交易统计数据进来过后,会被累计到对应时间范围的SlicedBlock。

基于事件timestamp的时间窗口的不足

  由于种种原因,例如各个服务器节点时间不同步,服务器内部线程执行阻塞,网络IO、磁盘IO等等原因,导致交易事件不能按正常的FIFO原则进入到统计,我们在此忽略这种情况,我们目前提供的架构,并不能保证交易事件的FIFO,也不能保证所有交易事件都能正确进入统计,能进入统计的交易事件,那肯定是在领跑事件决定的时间窗口滑动范围内的事件,有可能部分交易过来后就直接过期,不会进入统计,这里我们认为是正常的。

数据窗口的开源实现

  开源项目junx中的junx-stat https://github.com/junxworks/junx/tree/master/junx-stat提供了数据窗口的实现,可以作为数据窗口的实现参考,并且提供了基于数据窗口的统计函数实现,可以参考代码样例https://github.com/junxworks/junx/tree/master/junx-sample/src/main/java/io/github/junxworks/junx/test/stat。基于数据窗口的统计函数思路在后面篇章中进行介绍。


标题:数据窗口的设计思路
作者:michael
地址:https://blog.junxworks.cn/articles/2018/10/30/1540871892864.html