乐趣区

关于数据库:基于-TiDB-Flink-实现的滑动窗口实时累计指标算法

作者:李文杰

前言

在不少的领取剖析场景里,大部分累计值指标能够通过 T+n 的形式计算失去。随着行业大环境由增量市场转为存量市场,产品的经营要求更加精细化、更快速反应,这对各项数据指标的实时性要求曾经越来越高。产品如果能实时把握利用的整体运行状况或特色用户的状态,就能够及时安顿正当的市场营销流动,这对改善用户的体验和促成收益的增长有显著的帮忙。

需要指标

有一个场景为了进一步优化营销流动内容,心愿咱们实时提供每个玩家在最近 1 年、2 年、5 年、10 年内的实时生产总金额。

要实时计算每个玩家最近 N 年的实时生产累计总金额,一方面要思考到这个指标随着工夫推动它可能在一直减少,另一方面会有数据过期了而不再属于这个统计周期内,要及时减去,从而保护一个动静的累计值。

这里的每一个用户的“最近 N 年”指标是不断前进的,波及到产品上线以来的全副用户,其累计的用户量、领取数据都在亿级别以上,且明确要求实时统计历史数据。综合剖析下来,解决该问题具备肯定的挑战性。

在通过充沛调研和剖析后,基于实时计算框架 Flink 和分布式数据库 TiDB 的组合应用,咱们提出了一种实时计算滑动窗口内累计指标的算法,在一个数据库里同时反对实时 OLAP 计算和 OLTP 数据服务,无效地解决了这个问题,目前曾经在线上稳固服务了一段时间。上面给大家分享下咱们的思考和实际。

数据特点

首先咱们先从整体上评估下数据的特点,剖析一下数据规模、有哪些关键问题对咱们的计算有影响。

数据详情

  • 根底数据量大,存在乱序、反复等问题

    • 数据源历史数据量较大,亿级别;日增日志数据在百万级别
    • 原始日志数据打印在不同利用机器上,没有集中统一存储,扩散
    • 因为业务有期待逻辑,业务工夫字段存在乱序问题,即先产生的数据的日志打印工夫可能晚于后产生的数据的打印工夫,工夫乱序的数据如果不及时处理可能会呈现漏算的状况
    • 因为业务有重试机制,雷同的日志数据可能反复呈现,数据重算会导致后果谬误
  • 聚合指标要求反对高并发拜访

    • 最终的后果指标要求反对 TP 服务拜访,且满足高并发场景

线上的利用部署在不同的机器上,先后申请的数据的业务工夫和日志打印工夫,可能是乱序的,这会导致咱们须要解决数据排序的问题。且因为业务存在申请重试逻辑,数据也有可能是反复的,须要设计好去重机制。

实现重难点

  • 保障计算的实时性、准确性

    • 须要解决数据乱序问题,使其有序,而后实时监听数据在别离进入统计周期开始边界、完结边界的变动状况,精确在累计值上执行加、减操作
  • 计算的事务性

    • 在对同一个用户的累计指标执行加、减操作时,要严格保障每个操作的原子性和隔离性
    • 此外,还要保障不同用户之间的操作也是事务隔离的
  • 累计指标可重入

    • 数据通过统计窗口边界时,有且仅有一次被计算,须要解决原始数据反复问题
    • 程序重启时数据计算结果应该放弃不变,指标的值不会变多,也不会变少,即保障重入

次要的问题在于对于统计最近一段时间内的值,这个“最近”是实时变动的,即统计区间的开始、完结工夫点也是实时变动的,这个问题可能就比较复杂了,须要严格保障每个操作的原子性和隔离性,而且每笔数据不能反复算也不能漏算,否则就会呈现数据谬误。

可选算法

实时统计

该计划是指,当查问某个用户最近 N 年的累计值的申请发送过去时,间接到数据库统计失去后果,能够了解为是一个用户级的实时 AP 操作。这种办法在良好的表设计、索引设计下,大部分场景在秒级别能够实现查问,在并发高时数据库资源很容易呈现算力瓶颈,导致服务不稳固,业务受影响。

  • 长处

    • 计划简略,实现容易
    • 能获取到精确的指标后果
  • 毛病

    • 由业务方保护计算的办法,拜访和计算是同时进行的,没有做到拆散
    • 数据库要有实时高并发的 AP 能力,对数据库要求过高
    • 计算全副依靠于数据库,IO、CPU 等资源容易呈现瓶颈
    • 高并发时服务不稳固

总的来说,实时统计这个算法实现起来绝对简略,但服务容易因算力问题影响,实时性不能保障,尤其是高并发场景容易呈现问题,线上实时数据服务慎用该策略。

全量缓存 + 实时增量

该计划提前将全副用户的最近 N 年的累计值算好,并缓存起来,业务方能够实时读取这个缓存,也能反对高并发实时响应。而后计算侧依据实时变动的状况,更新每个用户指标值。如果是在统计周期内用户有新增数据,则在缓存值根底上累加,如果在统计周期内有用户的数据过期了,则在缓存值的根底上减去。总之,总是保护好用户的实时累计值。

  • 长处

    • 反对实时高并发读取
    • 业务拜访和计算拆散,拜访提早低
  • 毛病

    • 实时保护缓存,要引入额定的机制保障数据更新的事务性
    • 容易呈现读写抵触问题
    • 数据没有落地,故障或宕机时数据失落危险高
    • 计算简单,且不可重入

实时全量缓存计划,解决了实时全量统计的实时性和高并发拜访的问题,然而也带来了数据操作的事务性、安全性等问题,有肯定的可取之处,但毛病也很显著。

全量长久化 + 实时增量

思考到业务侧是 OLTP 的拜访个性,要求反对低提早高并发,提供点查的形式才是最高效的。

该计划在数据初始化时先提前算好全副用户的累计值,并存储到关系型数据库,再基于数据库的基量数据进行实时的增量更新操作。如果是在统计周期内用户有新增数据,则在基量值上累加,如果在统计周期内有用户的数据过期了,则在基量值上减去,始终基于实时的变动量来保护最新的累计值。

  • 长处

    • 反对实时高并发读取
    • 业务拜访和计算拆散,拜访提早低
    • 数据存储在数据库,保留有最新的数据状态,能保障数据安全和事务性,进而能保障计算是可重入的
  • 毛病

    • 计算简单,程序保护老本较高
    • 数据库要求高,必须能存储大量数据且反对高并发拜访,且能应答将来的业务增长量

综合思考之后,咱们选用了全量长久化 + 实时增量的计划。

目前业界畛域内解决实时数据的技术工具,选用 Flink 应该是毫无疑义的。数据库方面选型,咱们须要思考上面的场景:

  • 首先要求数据库具备灵便的扩展性,必须能存储数以亿计的历史数据,且能应答还在一直增长的数据规模
  • 其次要反对良好的事务个性,这一方面反对最好的就是关系型数据库,要能保证数据操作时的事务隔离
  • 同时在高并发场景下保障读、写互不影响,反对业务高并发拜访

满足这些刻薄要求的数据库其实不多,分布式数据库 TiDB 就是其中一个十分优良的选项,它能很好地满足下面的场景需要。

数据模型

咱们计算用户最近 N 年的累计值,这里有两个要害因素,一个是统计工夫周期,一个是用户。

上面咱们以统计工夫周期为剖析切入点,引入工夫窗口来解决咱们的统计问题。

工夫窗口定义

一段固定长度的工夫区间,即咱们需要里说的“最近 N 年”,咱们能够称其为一个工夫窗口。如果一个工夫窗口反对随着工夫变动,那这个窗口就是动态变化的,依据动态变化的状况会有许多细分的窗口类型,用以解决不同场景的问题。上面次要介绍和咱们业务相关度较高的滑动窗口和会话窗口。

滑动窗口

滑动窗口是固定长度的工夫窗口,随着工夫变动以肯定的频率后退,它们之间容许有重叠。滑动窗口的滑动间隔(window slide)能够管制生成新窗口的频率。如果 slide 小于窗口大小,不同的滑动窗口会有局部重叠。这种状况下,一个数据点可能被多个窗口蕴含在内。

如上图所示,比方咱们设置了窗口的大小为 10 分钟,每 5 分钟滑动一次,则会在每 5 分钟后失去一个新的窗口,且新窗口会蕴含一部分在之前的窗口里呈现过的数据。

在滑动工夫窗口中,咱们通常要抉择窗口大小和滑动步长。窗口大小指的是每个子时间段的长度,而滑动步长则指的是相邻子时间段之间的工夫距离。依据具体的场景,咱们能够调整窗口大小和滑动步长,使得滑动工夫窗口更好地适应不同的数据流解决需要。

这个数据模型,很合乎咱们的统计最近 N 年的实时累计值的场景。“最近 1 年”、“最近 2 年”、“最近 5 年”、“最近 10 年”就是咱们的窗口大小,滑动步长是实时,这里为了剖析不便,咱们每 1 分钟滑动一次,即每分钟都会产生一个最近 N 年的滑动窗口。

会话窗口

与滑动窗口不同,会话窗口会为沉闷数据创立窗口,会话窗口不会互相重叠,没有固定的开始或完结工夫。咱们能够设置固定的会话距离(session gap)来定义多长时间算作不沉闷。当超出了不沉闷的时间段,以后的窗口就会敞开,并且将接下来的数据散发到新的会话窗口。

在咱们的场景,相当于对每个用户保护一个永远不敞开的会话窗口,不便实时监听“最近”的状况,但会话窗口的开始工夫不好追随工夫变动而动静设置。同时思考到咱们要剖析的数据量在百万级以上,要实时保护这么多的会话窗口,资源耗费会比拟多,难度会比拟大。所以,会话窗口不适合咱们的计算场景。

综合思考后,咱们抉择了滑动窗口模型来发展咱们的计算。这种解决技术罕用于实时数据分析和流媒体解决中。它能够帮忙咱们对数据流中的信息进行实时监听并剖析,可能疾速响应数据流的变动。

业务实现

解决流程

整个解决数据流,过程大抵如下:

  • 1. 数据实时采集

    • 线上利用在不同机器上部署,实时产生日志数据,通过 Filebeat 采集并汇总数据流写入到 Kafka 中
  • 2. 借助 TiDB 关系型数据库的个性解决数据乱序、反复问题,生成根底数据

    • 设计正当的业务惟一键,给每一行数据设置一个准确到微秒的入库工夫(create_time timestamp(6),CT),在咱们的业务场景,能得全副入库数据按 CT 字段严格有序
    • 同时,利用 TiDB 的惟一键个性对反复的数据去重
    • Flink 生产 Kafka,将通过 ETL 后根底数据实时写入到 TiDB 中生成根底数据表,供后续计算、数据校验、监控应用
  • 3. 数据指标的长久化和可重入计算

    • 对 TiDB 的后果指标表设置用户维度的主键,同时设置每个用户在滑动窗口左、右边界已生产的数据的 CT 水位线,保障计算可重入要求,即通过窗口边界的数据只会计算一次
    • Flink 双 Source 读取按 CT 切片的开始边界、完结边界的数据,用双 Sink 别离负责指标的加、减。TiDB 集群当时设置为乐观锁事务模式,Flink 作业在 Sink 时执行串行的 INSERT ON DUPLICATE KEY UPDATE 语句实现累计值的加、减操作,能够保障操作事务的原子性、隔离性。通过调优上线后,该形式在咱们的计算场景里也有不错的性能,能满足业务需要。
  • 4. 计算和对外拜访同时服务

    • 利用 TiDB 写操作不阻塞读的个性,在计算的同时数据也在实时对外服务,不影响线上服务可用性
    • 用户是咱们表的主键,而产品拜访时是对用户的点查,所以咱们的计划具备十分高的并发拜访性能,远超过业务峰值。

上面详细描述具体的计算过程。

滑动窗口计算

窗口建模

基于滑动窗口模型,联合咱们的数据个性,定义了一个滑动的统计工夫窗口,如下图。

最近 N 年的统计周期长度,由统计区间的开始工夫 T1(左边界)和 T2(右边界)独特决定,工夫长度 N = T2 – T1 始终保持固定,即左右边界的距离是固定不变的。

窗口的右边界 T2 随着工夫变动,一直实时向前滑动,同时也牵引着整个窗口向前滑动。如下图所示,咱们设定固定的后退频率为 Delta t,窗口随该频率一直向前滑动,后退的步调频率最快能够到秒级,然而为了保障读取到的数据稳定性以及应答上游数据可能存在提早的状况,咱们通常设置为 30 秒或 1 分钟。

根底数据处理

读取到线上日志数据写入到 TiDB 中生成根底数据时,咱们借助 TiDB 关系型数据库的个性,解决数据排序、反复的问题。

  • 通过提前设计正当的业务惟一键,Flink Sink 时用 INSERT INGORE 形式写入数据,遇到雷同的数据只会写入一行,达到去重的目标
  • 同时,设置一个准确到微秒的入库工夫字段(create_time timestamp(6),下文简称 CT),在咱们的业务场景里数亿行数据全副入库,每一行数据都能做到按 CT 字段有序递增。

TiDB 不仅解决了海量数据的存储,还保障了优良的读写性能。上游业务能够保障雷同用户在同一时刻不会呈现领取多笔的状况,为了避免极其状况的呈现,Flink 应用串行 Sink 的形式写入根底数据,通过对几十亿行历史日志数据的重放入库验证,每一行数据都有严格的递增入库工夫,能够保障其枯燥递增个性,同时也能达到万级的写入 QPS 性能。这是咱们上面按工夫切片来计算的关键所在。

窗口内累计值计算

1. 计算流设计

为了保障同一个用户在雷同步调下执行操作,咱们起一条 Flink 计算流,流里设置两个 Source 和两个 Sink 别离负责指标累计值的加、减操作,Sink 时借助 TiDB 的乐观事务个性,整个过程能够保障操作的事务性和计算可重入。

  • 这两个 Source 读取数据的工夫点,别离指向统计工夫窗口的左、右边界。指向右边界的指针负责用户累计金额的加操作,指向左边界的指针负责用户累计金额的减操作,它们应用雷同的步调随着工夫推动。
  • 假如有一个用户他每个时刻都有充值行为,那么随着工夫推动,“最近 N 年”这个工夫窗口也在一直推动,窗口的右边界是实时后退的,就会一直有新数据进来,计算累计值则须要一直加;窗口的左边界也在往前走,滑出左边界的数据就过期了、不在这个统计周期内了,所以左边界的指针就需一直减去这些值。

在写入数据的时候,如果是首次计算则须要插入,如果不是首次写入则要求更新多列,于是咱们应用了 INSERT ON DUPLICATE KEY UPDATE 形式执行加、减的操作,同时为了防止锁抵触而影响写效率,设置单线程串行的 Sink 行为。

2. 后果指标表设计

为了保障可重入和 Exactly Once 要求,即通过窗口边界的数据只计算一次。咱们在 TiDB 数据库层面,在后果指标表内,咱们通过对每个用户的指标设置两个水位线字段,别离标识最近一次的曾经执行过的左边界、右边界数据。

  • 以用户为维度,每个用户指标都有 low_water_mark 和 high_water_mark 这两个水位线工夫来做标记这个累计指标的计算状态,它们来自根底数据表的入库工夫。用户指标的 high_water_mark 与 low_water_mark 和 Flink 作业里窗口的左边界和右边界不太一样,作业里的左右边界工夫是和真实世界一样的相对工夫(True Time),而它们是业务上的逻辑工夫,所以它们之间时间跨度,是能够超过窗口的长度的,这样以保障能统计到残缺周期的指标。
  • 作业右边界指针读到的数据是最新的,要执行加操作,当在后果指标表没有该用户时(high_water_mark 为 null)阐明是首次充值能够间接加,且同时设置该 CT 为其 low_water_mark 和 high_water_mark;如果该用户有在表里了则要求其 CT 大于 high_water_mark 才能够累加进去,否则不累加,累计进去的同时更新 high_water_mark 为以后 CT,以保障同一条数据的计算可重入,不会呈现反复加的问题。
  • 左边界指标读到的数据是统计周期内过期的数据,指标是减去,原始数据的有序性保障了通过左边界的数据肯定曾经通过右边界,即肯定曾经实现了加的操作,所以不存在后果指标表没有该用户时的状况,为了防止反复减的问题,要求过期数据的 CT 小于统计周期开始工夫且大于 low_water_mark 才执行减操作,同时更新 low_water_mark 为以后 CT。如果 CT 小于等于 low_water_mark 阐明曾经执行过减操作,不须要反复操作。

利用 TiDB 写操作不阻塞读的个性,不论计算工作如许忙碌,只有不影响数据库性能,那线上服务都能够实时读到最新的后果指标,不会影响线上服务可用性,这一点也是 TiDB 十分优良的中央。

3. 典型场景剖析

上面咱们通过不同场景来论述该算法。

1)如上图所示,窗口在前一个统计窗口内容累计总金额值为 100,在通过一次滑动后,有一笔充值金额为 30 的新订单进入了统计周期内,体现在这笔订单的入库工夫小于以后窗口的右边界,那么咱们的计算 FLink 作业就能读取到该值,并在相应用户的累计值上执行加操作,失去实时的最近 N 年累计总充值指标。

2)同理,如下图,如果是有一笔数据随着窗口滑动而过期了,此时这笔订单的入库工夫在最近 N 年之前,咱们的计算 FLink 作业就能读取到该值,并在相应用户的累计值上执行减操作,失去实时的最近 N 年累计总充值指标。

3)更简单的计算场景,如下图,如果随着窗口滑动同时有新数据进入,也有旧数据过期,那么流里设置的两个 Source 和两个 Sink 别离负责指标累计值的加、减操作。因为根底数据源是严格有序的和在 Sink 时设置了串行操作,同时咱们将加、减操作放在了 TiDB 内执行,而 TiDB 具备优良的事务机制保障,所以咱们左、右边界的操作是互相独立的事务,互不影响。如果同时有多条新数据、多条过期数据,根底数据的有序性和 Sink 的事务性也能够保证数据的失常解决。

利用与总结

  • 日志数据通过 Flink ETL 后写入到 TiDB 根底表,借助设置到微秒级别的入库工夫,通过验证,在咱们业务场景的数十亿行数据能能做到枯燥递增,这为咱们前面的计算打下了关键性的根底
  • 计算流首次启动时要解决历史数据,要设置好窗口的左右边界,假如要统计最近 1 年的累计生产金额,则须要手动指定右边界的 Source 终点为 365 天前,左边界的 Source 终点为 730 天前(左右边界独特决定统计窗口的长度)。设置 2 年、3 年、5 年、10 年的场景依此类推。

    • 在跑历史数据时,计算流的串行处理速度能够达到万级 QPS,证实 TiDB 和 Flink 有十分优良的计算能力
    • 历史数据量大,初始化耗时通常较久,一个优化的办法是基于历史日志数据,应用离线统计的形式一次性先算好基量指标,而后 Flink 作业再基于此后果来计算。这能够大大缩短指标首次上线、故障复原、数据重算等场景的工夫,极大进步用户体验
  • 计算策略里设计的每个环节都是可重入的,当遇到网络中断、数据库抖动或 Flink 流失败重启等故障,数据不会丢、也不会重算,能够保证数据的安全性
  • 该算法已正式上线到生产环境,已稳固对外提供数据服务有数月之余。为了保证数据生产的稳定性,在不影响整体服务体验的状况下,咱们设置 Flink 的生产工夫比实时数据略迟一点工夫,这也是一个实时计算的最佳实践经验。

实用场景

该基于 TiDB + Flink 的实时累计指标算法,目标是解决”最近一段时间的实时累计指标“的计算问题。

通过一些调整或优化,它也能够实用于很多的计算场景,如:

  • 有明确工夫范畴的实时指标:

    • 最近一段时间的实时充值总额、订单量、领取率等
    • 最近一段时间的实时 PU/ARPU/ARPPU 等
    • 最近一段时间的实时 AU/DAU//MAU、新增用户数等
  • 实用的统计周期:最近一段时间,即最近 N 时 / 天 / 周 / 月 / 年,指定的统计工夫窗口长度
  • 实用的计算维度:产品、渠道、平台、用户、角色等

如果有任何问题,欢送一起交换摸索!

退出移动版