Apache Kudu在网易的实践
编辑整理:张德通@数数科技
出品:DataFunTalk
本文主要介绍Apache Kudu及在网易实时数据采集、维表数据关联、实时数仓ETL、ABtest等场景的实践应用。主要内容包括:
系统概述:认识kudu,理解Kudu的系统设计与定位
生产实践:分享网易内部的典型使用场景
遇到的问题:实际使用过程中遇到的问题和问题的排障过程
功能展望:对Kudu功能特性的展望
Kudu定位与架构
Kudu是一个存储引擎,可以接入Impala、Presto、Spark等Olap计算引擎进行数据分析,容易融入Hadoop社区。Kudu整合了随机读写和大数据分析能力,具有低延迟的随机读写能力和高吞吐量的批量查询能力。
与HBase、Casandra不同,Kudu要求声明Schema。Schema可以为上层计算引擎提供更多元数据,进行计算优化。Kudu的每个字段有主键、列名和列类型。拿到列类型信息后能够对不同列进行编码和压缩,优化存储空间,减少磁盘开销。Kudu支持bitshuffle、运行长度编码、字典编码等列编码方式,这些编码会根据列的类型不同做不同设计。比如对于重复值多、重复值变化不大的数据的压缩率很好。
Kudu使用列式存储给Kudu带来了如下特性:
1. 存储上可以节约空间
2. 可以对查询做更多优化,如将过滤条件下推到kudu执行,节约计算资源
3. 支持向量化操作
Kudu的Schema和列存
Kudu数据存储在Table中,Tablet是Kudu的读写单元,Table内的数据会划分到各个Tablet进行管理。
创建Table时,需要指定Table的分区方式。Kudu 提供了两种类型的分区方式range partitioning ( 范围分区 ) 、 hash partitioning ( 哈希分区 ),这两种分区方式可以组合使用。分区的目的是把Table内的数据预先定义好分散到指定的片数量内,方便Kudu集群均匀写入数据和查询数据。范围分区支持查询时快速定位数据,哈希分区可以在写入时避免数据热点,可以适应各个场景下的数据。
Kudu有管理节点(Master)和数据节点(Tablet Server)。管理节点管理元数据,管理表到分片映射关系、分片在数据节点内的位置的映射关系,Kudu客户端最终会直接链接数据节点。
Kudu作为分布式系统,为了保障数据可用性和高可用,支持多副本。Kudu 使用 Raft 协议来实现分布式环境下副本之间的数据一致性。Raft算法数据不依赖其他存储和文件系统,优势在于可以保证服务高可用、服务可用性、一致性的均衡。
Kudu的update设计
Olap中对update的设计会影响到Olap性能。update操作可能引发数据多版本问题和update引发的数据merge问题。
Tablet是Kudu数据读写单元,Tablet下更细分的数据存储单元是 RowSet。RowSet有两种, 分别是MemRowSet 和 DiskRowSet,不同RowSet维护了不同组件范围内的数据。内存中的 MemRowSet 在到达一定大小后会刷盘成为DiskRowSet。
Kudu把更新操作当作一条新操作,而不是写一条新日志。更新操作是Undo/Redo记录,这些内存中的更新操作会被整合为DeltaMemstore持久化。Base数据、Undo数据、Redu数据写在同一个RowSet中。这样的存储设计优点是可以在更新时候快速找到数据,缺点是查询时需要确认查询的主键在哪个RowSet位置中。
Kudu也使用了LSM的结构。Kudu的comopaction有多种:MinorDeltaCompaction、MajorDeltaCompaction、MergingCompaction。
Kudu的update是一个多版本操作,目的是写入和读取时互相不干扰、不需要读时额外加锁。
小结
Kudu Update设计特点:
· ** 更新已经flush的数据和写入新数据走不通的处理逻辑,原始数据和更新位于同一个Rowset,不用跨Rowset进行merge**
· **通过base数据的RowID和更新时间戳作为REDO/UNDO数据的key**,读取更新高效
· Key大小固定,存储和比较效率高
· 不需要查询出主键数据也能获取更新数据
· 在大多数使用场景下能够实现更高效的读取
· 如果返回的结果不要求顺序,直接从RowSet中读出数据,不用merge
· 如果更新较少,REDO会快速merge到base数据,这时在读取最新数据时,可以不进行apply REDO的操作
生产实践
实时数据采集场景
实时数据分析中,一些用户行为数据有更新的需求。没有引入Kudu前,用户行为数据会首先通过流式计算引擎写入HBase,但HBase不能支撑聚合分析。为了支撑分析和查询需求,还需要把HBase上的数据通过Spark读取后写入其他OLAP引擎。使用Kudu后,用户行为数据会通过流式计算引擎写入Kudu,由Kudu完成数据更新操作。Kudu可以支持单点查询,也可以配合计算引擎做数据分析。
维表数据关联应用
有些场景中,日志的事件表还需要和MySQL内维度表做关联后进行查询。使用Kudu,可以利用NDC同步工具,将MySQL中数据实时同步导入Kudu,使Kudu内数据表和MySQL中的表保持数据一致。这时Kudu配合计算引擎就可以直接对外提供结果数据,如产生报表和做在线分析等。省去了MySQL中维度表和数据合并的一步,大大提升了效率。
实时数仓ETL
Kudu作为分布式数据存储引擎,可以和Hadoop生态更好结合,因此在生产中我们采用了使用Kudu替换Oracle的做法,提升了扩展性。
ABTEST
在我们的ABTest业务中有两种日志,行为日志和用户分流日志。
架构升级前,我们采用了比较传统的模式,将用户行为日志和用户分流日志分别写入HDFS作为存储的ODS层,通过Spark做清洗、转换后导入HDFS作为存储的DWD层,再通过Spark进行一步清洗、按照时间或其他纬度做简单聚合后写入DWS层。
这个架构的问题是数据产出时间比较长,数据延迟在天级别。业务方需要更及时地拿到ABTest结果。
架构升级后,使用Kafka作为ODS、DWD层存储。Flink在ODS层数据的基础上继续做一层整理和过滤,写入DWD形成明细表数据;DWD层在Flink中做简单聚合后写入DWS层,Kudu在DWS层作为数据存储。
Flink开窗口实时修正实验数据,这一操作在Kudu完成;超出了Flink时间窗口的数据更新则由离线补数据的操作在Kudu中完成修正。
架构升级后,数据延迟大大降低,能够让ABTest业务方更实时地拿到结果。
我们遇到的问题
问题1: 节点负载不均衡
一些大表场景下会有负载不均衡问题。Kudu不会把range下的哈希分片当作一张表,而是把整个表的分片当成了平等的表进行处理。而在真实使用场景中,range基本是时间字段;需要让range的hash分片更均匀地分布在各节点上,防止数据倾斜。下图是数据倾斜的情况展示:
我们的解决方案是实现了一套优化版本的负载均衡算法,这个算法能够把range表当作单独的表做负载均衡,解决了数据倾斜。下图是优化后效果:
问题2: 表结构设计复杂
问题3: 没有二级索引,只能通过控制主键顺序和分区键来优化某几种查询模式
问题4: 创建表时需要根据业务场景专门设计表结构
问题2-4,对业务方要求比较高,经常需要专人介入引导业务方导入数据。为了解决问题,我们内部设计了二级索引来解决上述问题。二级索引可以满足查询性能的要求,同时减少用户设计表时候的复杂度:
通过支持二级索引来优化包含非主键列过滤的查询
支持二级索引能够降低业务设计表结构的复杂度
社区对二级索引的支持进度KUDU-2038:Add b-tree or inverted index on value field
Kudu功能展望
BloomFilter
BloomFilter成本较低、效率较高。Join场景下,小表动态生成BloomFilter下推到存储层,防止大表在Join层做数据过滤。最近的Kudu中已经支持了BloomFilter作为过滤条件。
灵活分区哈希
Kudu每个range的hash bucket数量是固定的。考虑到时间和业务增长,在项目实施前期阶段要给Kudu哈希桶数量设置略大,但是数据量较小的场景下过大的分片个数对资源是一种浪费,社区也不推荐hash bucket设置得比较大。期望后续Kudu可以更灵活地适配hash bucket数。
> KUDU-2671:Change hash number for range partitioning
多行事务
Kudu暂时不能支持多行事务。目前更新主键需要业务自己实现逻辑检测。
> KUDU-2612:Implement multi-row transactions
Flexible Schema
一些业务场景下业务没有唯一主键,但只希望利用Kudu的大批量写入、聚合分析查询的特性。接入业务时Kudu对Schema的要求比较高,一些业务场景无法支持。
> KUDU-1879:Support table without a primary key