作者介绍:许超,VIPKID资深DBA工程师。
本文主要分享TiDB4.0版本在VIPKID的一个应用实践。主要涉及两个部分,第一部分是现在TiDB在VIPKID的一些应用场景,第二部分是介绍一下TiDB4.0给我们带来哪些惊喜和收益。
TiDB在VIPKID的应用场景
首先简单介绍一下VIPKID,VIPKID是一家在线少儿英语教育公司,专注于服务4-15岁的青少儿和他们家长们,主要提供北美外教一对一授课服务,目前已经有超过70万的付费用户。
场景一:大数据量及高并发写入
回归主题,TiDB在VIPKID的第一个应用场景是一些大数据量和高并发写入的场景,如下图所示:
举其中一个例子,我们现在有一套教室排障的系统,这套系统会实时收集教室内的一些事件信息,比如进出教室还有教室内服务初始化的信息,这些信息是通过家长端还有教师端统一上报的,业务同学可以根据这些信息快速定位到教室内的故障,并且做一些排障手段,比如说切换线路之类的来保证一些上课质量。
这套系统目前TPS峰值在1万左右,每天的新增数据量大概有万,这张表只保留最近两个月的数据,目前单表有12亿左右,数据量其实相对来说已经比较大了,这种数据量如果现在放在MySQL里维护成本比较高,所以我们把这套系统的数据整个迁移到TiDB。
在迁移过程我们做了一些小的变动,在原有表的基础上把原来的自增ID去掉了,在建表的时候通过指定配置做一下预先打散,避免高写入量的时候出现写热点问题。关于热点定位,其实TiDB本身之前就提供了很多方式了,比如3.0版本其实通information_schema.TIDB_HOT_REGIONS这张表就可以定位,或者直接可以在PD里获取相应的热点Region信息,但是通过这种方式拿到的其实只是一个RegionID,还需要调用具体的TiDBserver的API来获取定位到具体的表或者索引。在4.0里,TiDBDashboard让热点直观地呈现在我们面前,我们可以直接看到热点写入量、读取量等等热点信息。
场景二:核心模块分库分表后的多维度查询
第二个场景是现在我们的很多核心业务在早期就已经做了分库分表,比如约课表。对分库分表比较了解的同学其实知道,一般分库分表的情况下只会以一个业务列作为ShardingKey做拆分,但是约课表这种情况下可能涉及到的维度比较多,既有教室、老师、学生还有课程;另外可能其他端也会访问这部分数据,比如教师端来访问这部分数据的时候他可能会以教师的维度来访问,如果做拆分的时候是以学生的维度拆分的,那么教师的访问请求会广播到所有分片上,这在线上是并不允许的。针对这个问题,我们最开始有两种解决方案,一种是我们把所有的数据汇聚完放在ES,还有一种方式是业务双写,写两套集群,两套集群以各自的维度拆分。无论哪种方案,不论是对RD来说还是对我们DBA来说维护成本都比较高。
所以我们就借助DM,通过DM将上游所有分片的表实时同步到下游,并且合并成一张全局表,所有的跨维度的查询,无论是管理端运营平台,还是业务指标监控的系统都走TiDB这套系统,查这张全局表,如下图所示:
再简单介绍一下DM分库分表的逻辑,其实逻辑相对比较简单,DM其实会拉取线上所有分片的Binlog日志,然后根据黑白名单过滤匹配出需要同步的那部分binlogevent,再去匹配制定的路由规则,通过改写然后应用到下游的全局表里,这里也区分两种情况:一种情况是上游的分库分表包含全局ID,这种情况其实天然的避免了合并之后的主键冲突的问题。还有一种是没有全局ID的情况,这种情况之前官方提供了一个解决方案是通过Columnmapping的方式,其实就相当于对ID列在同步之前做一些预处理,处理之后相当于在底层同步的时候就不会有主键冲突的问题了。但是我们和PingCAP同学沟通了一下,其实他们现在已经不建议用这种方式了。所以我们换了另外一种方式,因为我们的业务对于这种自增ID、主键ID是没有业务依赖的,所以在下游TiDB这块我其实提前建了下图所示的这样一张表,然后把对应ID的主键属性和自增属性去掉,并且将这个ID加入我原先仅有的一个联合的唯一索引里就解决这个问题了:
在DM这块我们简单地做了两件事。第一是DM的延迟监控,其实DM本身支持延迟监控,也是通过写心跳的方式去做的,但在我们线上把DM并不是挂在MySQL集群的主节点下,而是挂在一个只读存库上,所以我们不太允许DM直接去写存库的心跳。我们线上有一套心跳机制来监控延迟,所以在实际DM这块延迟监控实际复用的是线上的那套心跳表,相当于把线上的那套心跳直接同步下来,也是通过路由规则匹配一下,然后改写。这样的话虽然在TiDB里可能涉及到多个集群,但其实每个集群对应单独的一个心跳表,这样就可以做DM的延迟监控。在下图里大家可以看到,大部分情况下分库分表合并的场景同步延迟基本都在毫秒以内:
第二件事就是解决DM高可用问题,由于DM本身并不具有高可用性,可能一个DMworkerdown的时候能够支持自动拉起,但如果出现整个节点down的情况下其实是束手无策的。如下图所示,我们做了一个简单的尝试:
相当于把DM-master、DM-worker的所有的持久化的数据全放在共享存储上,DM-worker是通过容器启动的,如果发生节点宕机的情况下,只需要在备用机上把对应的worker提起来,并且关联到自己指定的DM-worker的共享存储上DM的路径就行。这里有一个问题是IP是固化不了的,所以在这个过程中还需要去变更DM-master的配置文件,之后做一次RollingUpdate。
场景三:数据生命周期管理
TiDB在VIPKID的第三个场景其实就是在数据生命周期管理。如果用过MySQL的都知道,其实大表在MySQL里维护起来成本是比较高的,所以一般很多公司会做逐级的数据归档。我们现在是根据具体业务,按照读写情况将数据划分成多个等级,并把其中温数据、冷数据,其实还有线上读流量的放在TiDB里。
这个场景里引入TiDB的考虑是要把数据的流转、运转起来的一个前提要保证冷热数据的表结构一致,但实际情况是,冷数据大部分情况下比热数据体量大很多,比如线上热数据做了一次表结构变更,比如加一列,同时如果冷数据同样要做这个操作,成本其实是很高的,可能是热表的十几倍或者几十倍都有可能,因此我们想借助TiDB来做这个事情。本身一方面TiDB的DDL有一些特性,加字段、减字段都是秒级的,另一方面就是借助TiDB本身的水平扩展的能力,我们可以每个业务端复用一套TiDB归档集群。下面再简单总结一下我们在使用TiDBDDL的相关经验,如下图所示:
左半部分我列了几个相对比较常见的DDL操作,比如删表、删索引,其实在TiDB里分了两步做,第一步只变更元数据,这时候就已经秒级返回给用户了,剩下实际数据的清理,是放在后台通过GC异步来处理的。而添加索引操作因为涉及到数据的填充和重组,所以这个操作的时间是依赖于数据量,但是并不会阻塞线上业务,我之前看了一下官方的介绍,TiDB中数据填充的过程被拆分为多个单元,每个单元内部并发进行读写操作。线上更常见的是删列和减列,在TiDB里跟MySQL的实现就不一样了,也只是变更元数据就行了,并不需要做底层的数据的rebuild,比如增加一个列,并不需要对原始数据再做一个回填,这里分两种情况,一种是默认值是空的,一种是有一个指定的默认值,其实只需要把这个默认值记录到TiKV里跟表结构记录在一块,当你查询这个数据对列值做decode的时候,如果是空的,直接返回空;如果是有指定的默认值的,直接返回指定默认值,并不需要对历史数据做变更。
场景四:实时数据分析的场景
还有一个场景就是做实时数据分析的场景,下图其实是最早TiDB在VIPKID引入的一个场景:
这个场景主要是BI分析师还有一些BI平台在用,架构上,相当于通过DM把线上的数据全部同步到TiDB里,借助TiDB的水平扩展能力,还有一些计算下推的能力,做一些实时数据分析。
TiDB4.0给我们带来哪些惊喜和收益?
TiFlash列式存储引擎
TiFlash是一个列式存储引擎,相对来说对AP场景更友好一些,并且他本身自己有计算能力,支持一些算子下推、计算加速。另外,TiFlash可以实时复制TiKV中的数据,并且没有打破之前TiKV的运行模式,它把自己作为一个Learner角色加入之前的RaftGroup里可以支持表粒度的同步;还有一个就是智能选择,TiFlash可以自动选择使用TiFlash列存或者TiKV行存,甚至在同一查询内混合使用以提供最佳查询速度,它已经不仅仅是单纯的一个SQL选择哪种存储引擎,粒度可以细化到同一个SQL里具体某个算子,比如一个SQL打过来,一部分可以走索引的其实走行存更快,还有一部分涉及到全表扫描的走列存,通过这种方式整体提高SQL查询速度。最后,TiFlash支持独立部署,可以跟TiKV独立分开,某种程度上做到了硬件资源的隔离。
下面谈谈TiFlash给我们带来的收益。
首先是性能提升,我这里做了一个简单测试,测试环境现在是有五个TiKV节点,一个TiFlash节点,有一个单表2.5亿数据,然后我对这个单表做count,在TiKV里走了索引的情况下跑了40多秒。而在单个TiFlash场景下10秒就跑出来了,这只是单个TiFlash节点,如果我再新添一些TiFlash节点,这个速度应该还可以进一步提高。
TiFlashTiKV第二是成本的下降,我们原来有一套给BI用的集群,现在用新的TiDB4.0的集群替换了,可以看一下下图左边的表格是新的4.0的集群各个组件资源分配比例,左边是新、老集群同样负载情况下的资源分配情况。可以看到,部署新集群让我们整体成本下降了35%。
第三TiFlash上线解决了一些稳定性的问题。之前在TiKV这一层如果跑了一些TP业务,这时候有一个SQL涉及到一个全表扫描,整个TiKV的集群负载就会变大,可能导致TP查询整体响应时间变长。现在我可以加入一个TiFlash节点,把对应集群里的大表自动添加一个TiFlash的副本,在这种情况下如果有一些类似的情况,可以优先会走TiFlash,而保证TiKV尽量不受影响。
再说两点我们在使用TiFlash过程中遇到的一些“限制”。
第一,如果你的SQL中涉及到一些TiFlash还没有实现的函数,其实是无法做到计算下推的,只是利用到了TiFlash本身作为列存的一个加速能力(注:如果有尚未实现的下推需求,可以与PingCAP官方联系技术支持,添加