00:00
接下来我们聊一个细腻度滑动窗口的一个问题,这个也是咱们比较头痛的啊。那什么叫细粒度呢?很简单,就是相比于我的窗口长度而言,我的滑动步长特别显得特别小。举个例子,你开了一天的窗口。滑动窗口,你的长度是一天,然后你的滑动是一分钟。那你可想而知,这个滑动窗口是不是会大量重叠是吧,一分钟滑一次啊,整个长度是一天呢,那我们来顾一下滑动窗口,它是不是就是一个滑动窗口的值啊的样子,对吧。滑动窗口,它基本上他们用的时候都会有一个重叠部分,比如说这里是不是一个重叠部分,对吧,这里也是重叠,这个未来应该也是会重叠在一个窗口里面。每一个都有重叠,那么也就是说重叠的话,根据他们的窗口分配机制,每一条数据来的时候,是会根据他的时间计算它应该属于哪个窗口,它会计算一个开始时间,结束时间,那就比如说我们随便挑一个数据,就这条数据而言,它属于几个窗口,是不是两个?
01:12
也就是说这个数据它会在两个窗口里面都会出现,都会有。而每个窗口它其实是一个不同的对象,那这个时候你可想而知,窗口也有状态,它首先是不是得把这个数据存储在自己的状态,那这个窗口也要把这条数据存在状态里,不管你是做聚合计算还是预聚合都一样,你都得算这条数据有几个窗口重叠它,它就得被计算几次,那你可想而知,如果你这个滑动力度,滑动的步长太小了,那一条数据被重叠的。呃,重叠的窗口数量就更多了。那怎么算呢?直接咱们的窗口的长度除以不长,就是一条数据会属于几个窗口。那我们来好好算一算啊。
02:04
长度除以不长,那如果你的长度是一天。你的步长是一分钟,那应该等于多少啊,一天是不是24小时,每个小时是不是60分钟,那这个算起来应该是1440吧,啊幺二对1440吧,也就是说咱们一条数据。就单纯一条数据就要被属于1440个窗口。你想想这个是不是出现了一个计算膨胀的问题?那其实咱们如果能学习Spark一样,你想想它的滑动窗口是怎么做的?Spark首先是不是一个重叠部分?那我这个叫窗口一。这个叫窗口二,咱们就只看两个窗口就好了,那其实滑动咱们可以这么做呗。
03:00
这一块跟这一块,这一块我叫A,这一块我叫B。窗口一正常计算,那窗口二怎么算啊,这一块我叫C,就是中间这一段啊,重叠部分叫C。其实。它是不是一减去A,然后加上B,就能得到我们窗口二的一个结果啊。对吧,也就是说中间重叠部分咱们是可以复用的呀,你没必要在这边再算一遍。你直直接把它不步长滑的这段距离,这个A这一块把它减掉,再把B的这一块加上,不就是二要的结果嘛,对吧,但是flink前来办个自己计算就不重叠重叠。他并不会去做这么一个优化。所以问题就来了,那我们经常也会碰到这种需求啊,比如说以三分钟的频率实时计算,呃,一个24小时的PV和UV,那你想想这个需求三分钟的频率更新,那是不是不长三分钟,那它的时间窗口范围是不是24小时?
04:10
那按照咱们刚才的算法,1440分钟除以三是不是有480个窗口重叠,一条数据属于480个窗口?那影响的有什么呢?一个是状态。对于每一个元素而言,每一条数据而言。每个数据来都会被这480个窗口啊给计算进去,那这个时候计算由于它自己本身存储的一个状态,对吧。那这个时候它的开销是不是得480个状态的读写对吧,这开销是非常大的了,尤其是咱们用RODB,我们说了loss DB写肯定,呃,他要落落盘嘛。他既然要落盘,他写就比较耗时,那你一条数据要写480遍,触发480次的一个写。这个就是一个巨大的开销了,另外一个是定时器,咱们窗口。
05:04
不是有几个动作嘛,第一个呢,就是。窗窗口触发计算,第二一个是不是得清空窗口结束,得清空它的之前状态对吧,那这两种行为,它其实源码里面都是用定时器来实现的。那你想想480个窗口。你维护的定时器是不是增加的特别多,内存负担是不是有点扛不住,但这个倒还好,最严重的是这个状态写的问题。你即使做的是预聚合也没用啊,他是不是来一条聚合一次,然后要更新一次状态一样的,你减少不了你写的次数啊。所以这个就是细粒度啊,就长度除以不长特别大,这就细力度的滑动问题,那怎么办呢。解决思路啊。解决思路。也很也简单,但是呢,这个东西其实实现起来没那么复杂,但是这官方它就不实现,可以看一下这个一。
06:11
你看。改善什么呢?滑动时间窗口。的一个优化问题,那可以看到现在还是open状态未解决,那我们翻译成中文给大家扫一眼。你看,对于滑动一秒十分钟的窗口而言,数据要复制什么600倍?对吧,就是布长除以好不长度除以布长。你看他解决方案是可以将窗口划分为窗格。来优化一个,这样就避免重复计算,就跟Spark一样呗,就我刚才讲的,减掉划走的部分,加上新增的部分就好了。那么你直接看到最后。
07:03
最后反正就是一直什么没有收到任何更新,一直没解决。对吧,一直未解决,所以呢,咱们只能自己来了啊。自己来,咱们一般解决方案是什么?把它划分成滚动窗口,那以。把每个滚动窗口的结果存起来,那我们要读的时候再去聚合,什么意思呢?看这张图。这边是只有两个窗口重叠对吧,那我们就可以时间分片的方式来用滚动窗口。算什么意思呢?诶,啊,按到了啊。你不是长度这么长吗?不长这么长吗?那我就切分,以不长为时间分片,我划分这样的滚动窗口,这是一个滚动窗口。这是一个滚动窗口。这是一个滚动窗口。这是一个滚动窗口。
08:00
这个就是我说的时间分片的意思,按照不长,因为咱们一般这种业务需求来讲。封口长度一般就是这个。滑动步长的整数倍,那我们就以步长为一个时间分片好。那么接下来。你原先的需求。窗口一的结果咱们可以怎么办呢?我是不是可以把这两个滚动窗口的结果聚合起来,是不是得到窗口一原先需求的结果,那窗口二呢?我是不是可以把这两个结果聚合起来,是不是得到窗口二的结果?窗口三呢,我是不是这两个滚动窗口的结果聚合起来,得到的是原先滑动窗口三的一个结果。那我再把这两个滚动窗口的结果聚合起来,是不是原先滑动窗口四的一个聚合结果,这就是时间分片的意思,你把它拆成一份一份的进行一个聚合方式呢,就是用滚动窗口之后呢,再根据你原先的时间范围将这些结果再聚合一遍就行了。
09:04
就是这么一个思路。那一般常用的就是咱们讲的这个意思了,把它切分成一个一个的滚动窗口,然后呢,把它存起来,存起来之后我在读的时候再按照原先的需求把它聚合起来,对吧?啊,这是我刚才聊到了,从业务视角看,往往窗口长度是可以被布长整除的,也就说是布长的倍数。我们找到最小公约数,它俩的长度跟不长的公约数,其实就不长呗,一般不长小嘛。以此呢,我们来做滚动窗口。那每个。滚动窗口内将其周期内的数据做聚合,也就是滚动窗口。做聚合嘛,存到下游的状态,或者打入一个外部的在线存储,对吧,你可以利用外部存储,也可以自己维护起来都行。那接下来呢,你外部的话比较推荐就快一点的,比如说这种对吧。
10:03
接下来你再去读取,比如说你从radius,你就从radius里面读radius的key,你就设计为那个,比如说窗口聚合时间结结束时间,然后呢,你根据你的需要把它扫描出来去进行聚合就可以了,那这样就得到你原先要用滑动实现的这个指标结果。这样就。这这就是咱们目前最通用的一个解决方案啊。那后面呢,咱们也会给一个代码,我们看一个案例,看一下一个效果。
我来说两句