为团队会议准备的材料
Spark thrift-server
前言
Spark Thrift Server是相当值得参考的技术。核心的内容包括有:
- catalyst 高性能的优化器
- sql core 分布式查询实现
- hive 实现元数据管理,兼容hive
- thrift server 对hive jdbc的支持
这一套组合下来,就是一个完整的分布式OLAP查询实现。结合之前介绍的Hadoop存储体系,即可搭建完整的OLAP系统。
OLAP系统的研究,有几个值得挖掘的问题:
- 能否进一步提高计算的效率,对更大规模的数据集进行实时查询
- 能否对某些特定的查询进行优化,满足特定场景应用的需求
- 能否从简单的数据信息获取,到更进一步的数据特征处理
- 能否融合智能算法,实现数据更强大的处理能力
目录
- catalyst
- sql-core
- thrift server
- hive
catalyst
用于解析转换SQL语句,并生成逻辑计划。
语法解析、计划生成可以算是基础功能。一些研究的方向是如何加速解析转换的速度,但总的来说,该环节对查询执行影响不大。KtSQL使用Calcite作为SQL的解析器。
sql-core
查询计划执行器。把catalyst生成的逻辑计划,转换成Spark RDD操作。sql-core模块也提供了SQLContext,作为公共接口,SQLContext允许调用SQL语句并执行。
thrift server
thrift server实现了HiveServer和客户端SQL CLI,通过JDBC/ODBC,可以连接到thrift server。假如我们通过UDFs, UDAFs, UDTFs扩展了hive,可以借助thrift server把请求提交到spark进行计算。
Hive扩展,个人认为是拓展OLAP的关键切入点。数据应用也应该采用二次扩展的方式来实现,实现自然且使用简便。
hive
实现了Hive的支持,对SQLContext进行了扩展,提供HiveContext作为接口。HiveContext允许用户使用HiveQL子集访问底层的数据。HiveContext访问数据时,使用的是Hive Metastore和Hive SerDes。HiveContext还允许进行二次拓展,通过Hive UDFs, UDAFs, and UDTFs的形式。
SparkSQL可以使用hive udf,也可以使用自身机制支持的udf。如果是使用hive udf,需要通过hive metadata来注册管理。spark udf则按文档操作吧。
附录
写在2019年即将来临之际
新的一年又即将来到,盘点今年的开源进度,感触良多。
KtSQL最早开始于2016年,当时还记得只有一点MySQL分库分表中间件的开发基础,对分布式事务数据库领域只能算是懵懵懂懂。萌生开发一个分布式事务数据库的原因,是因为在大数据系统应用开发过程中,反复感觉到所谓大数据,无非是把数据库从单机或小规模集群往分布式系统发展的过程。既然如此,为什么不直接做一个分布式数据库呢?
说来容易做来难,项目启动的时候,其实有诸多波折。开源项目和商业项目是不同的,商业项目会围绕战略展开,而开源项目则需要自己去思考项目的意义,这其实会导致想法的纷乱。KtSQL开始后,有相当长的一段时间,阅读了许多分布式数据库方向资料,回头想想,真是一段充实的时光。但这时虽然增长了知识,却陷入了各种想法的引诱,导致很长一段时间项目的立意都不清晰;到了真正开始动手的时候,一开始的想法是底层存储基于MySQL实现,充分利用MySQL生态。但是,在分布式事务时,思路卡住了,Google Percolator要在MySQL上实现,诸多限制,这也算是走了弯路,后来才换成HBase。经历过这些挫折磨难,才真正进入到正轨。
一晃眼两年多过去,大数据领域果然出现了NewSQL的潮流,也开始出现了数据中台的概念。引发这个趋势的,不光是自然而然的信息化复杂度增加,还在于除了结果数据外,过程数据也开始被重视起来,而且出现了过程数据与结果数据的融合协同趋势。
从商业领域角度观察,信息化水平的提升,对于克服规模效应带来的效率不足问题,有着颠覆性的价值。中国的经济规模,互联网技术带来跨地域经济协作,都是这一方向的良好温床,而一带一路带来的全球化效应,更是为进一步发展打下良好基础。大势如此,必然会导致一个个的独立个体加入到各种的经济联系体中,而经济联系体天生有需求往规模化发展。
既然规模化是大趋势,那么信息化的复杂度必然不断提升,必然带来更多的信息化系统。这些信息化系统都需要数据库,而且通过数据库来完成信息化系统之间的协同工作,成本远低于现在所谓的微服务系统。
而且,随着机器学习、深度学习、人工智能领域的科研持续发展,数据的应用已经不仅仅局限于结果的保存,过程数据的监控、分析、处理,把数据应用带到了一个新的高度。能不能通过读取计算用户在多个系统之间的操作行为,判断出用户的当前意图,为下一步的用户响应做出恰如其分的应对?这就是下一步KtSQL的发展方向。
KtSQL将迎着未来的方向持续进步,坚持不懈。也必然会在开源的路上奏出强音!
hfile, parquet, orcfile, carbondata and indexr
目录
- OLTP和OLAP的核心区别
- 问题的解决思路
- Hadoop体系下的各种存储格式的简介
- KtSQL如何做出自己的选择
- 附录
OLTP和OLAP的核心区别
OLTP面向小量数据的操作,包含保存、读取、更新等操作。高并发的小量数据是OLTP的场景。OLAP则面向数据信息的获取,需要大批量读取所需的数据。这两者的区别,以及现有的硬件架构,决定了OLTP使用行存格式,OLAP采用列存格式。
如果是对数据建立索引,在面向OLTP场景时,从大量数据中检索出所需的数据,索引可以起到很大的加速效果。但如果是面向OLAP,大批量数据获取则对索引的依赖性大幅度降低,IO的速度变成了瓶颈,预计算、压缩存储、并行读取、并行计算等技术才是关键。这些区别,导致一种可以
同时满足OLTP和OLAP需求的存储架构难以出现。
问题的解决思路
正是因为鱼与熊掌难以兼得,所以市面上出现了不少妥协方案。如Apache Kudu、PingCap TiKV、Peloton Pax。尝试通过一个折衷方法来满足所有场景。简单点说,既然没有一种架构能够同时满足OLTP和OLAP,那么通过寻找一种两者架构的最优交集,也算是解决方法。
PingCap TiKV和Peloton Pax的解决方案可以理解,毕竟这两者是立足于OLTP的。但是Apache Kudu,一个舍弃了应用场景,尝试去取得两者折衷效果的中立方案,这种解决思路是很怪的。
既然没法解决,就要改变解决的思路。两份拷贝甚至多份拷贝并不是问题,存储的成本一直在下降,适当付出一定的存储成本,满足性能的需求,显然是另外一种思路。Apache Druid和SnappyData就是采用这样思路的解决方案。通过把热点数据放在内存中(可使用行式格式进行保存),把历史数据通过列式格式保存起来,通过查询要求的判断,找到满足查询需求的相应存储。
简单说,通过数据拷贝,形成不同的存储格式,满足不同场景的使用需求,就是解决思路的方向。
Hadoop体系下的各种存储格式的简介
业界中,Hadoop一直被当作可靠存储、数据仓库、商业智能的解决方案来使用,在OLTP场合下的场景看起来只有HBase一支独秀,且远未能成为主流。支撑Hadoop OLTP和OLAP的,是底层的各式存储格式。
因为硬件的原因,顺序写的速度比随机写的速度要快得多,所以LSM的思路被广泛使用。同时,为了提升读速度和满足随机读,采用SSD作为底层存储也是被广泛采取的技术方案。下面的文件格式设计方式,大多采用基于现代硬件的方式进行设计,所以随机读是被应用到文件结构设计中的。
hfile
hfile作为面向HBase专用的底层存储格式,信息的组织方式以keyvalue方式组织,value以行式方式组织。
hfile v1 包含以下文件存储信息(按文件从上到下次序排列):
- Data Block,由DATABLOCKMAGIC和若干个record组成,record以keyvalue形式保存,长度不定
- Meta Block,由METABLOCKMAGIC和Bloom Filter信息组成
- File Info,由MapSize和若干个key/value组成,保存HFile的基本信息,如hfile.LASTKEY, hfile.AVG_KEY_LEN, hfile.AVG_VALUE_LEN, hfile.COMPARATOR
- Data Block Index,由INDEXBLOCKMAGIC和若干个record组成,记录了data block的位置
- Meta Block Index,由INDEXBLOCKMAGIC和若干个record组成,记录了meta block的位置
- Fixed File Trailer,大小固定,可以根据它查找到File Info, Block Index的起始位置
hfile v1在初始化的时候,把meta block、file info、data block index、meta block index、fixed file trailer读取到内存中,方便快速查找,但需要读取的信息多、延时大,占用内存高。
hfile v2 包含以下文件存储信息(按文件从上到下次序排列):
- scanned block section,保存了data block和leaf index block/bloom block
- non scanned block section, 保存了meta block
- load on open section, 保存了root data index, fields for midkey, meta index, file info, bloom filter metadata
- trailer, 保存了trailer fields, version
hfile v2采用分层索引的方式,减少一开始初始化时的加载成本。
parquet
parquet常被用于和orcfile来对比,相比orcfile而言,parquet因为其精妙的嵌套数据格式实现而闻名,同时也带来了相对orcfile略好的压缩性能。不过从功能上看,parquet的性能和orcfile接近,但功能却不如orcfile丰富。
orcfile
orcfile是列式存储,单个orcfile由多个area组成,每个area包含:
- header, file head info
- body, 包含多个stripe,每个strip包含index data, row data, stripe footer. index data包含min, max和column index. stripe footer是数据的编码,文件所在位置等信息
- footer, metadata, file footer, postscript. metadata包含stripe级别的统计信息, file footer含stripe list, column count in stripe, data type, min, max, sum等信息,postscript则是file’s Footer和Metadata sections长度, file version和压缩类型等信息
orcfile通过记录column变化历史表,记录了ACID操作历史。当读取的时候,由读取程序读出最新的数据记录。为了实现实时写入,orcfile包含以下功能:
- additional footers,旧的footers会被作废,新的footer被写入
- side file named “*_flush_length”,用于记录preliminary footer的位置
orcfile的更新性能不佳,建议采取批量更新的方式对数据进行操作。
carbondata
carbondata是华为提供的列式存储方案,成为了apache顶级项目,也算是开源里面的国产骄傲。
carbondata出彩的功能点:
- 支持update & delete,整体思路和orcfile类似
- datamap,相当于二次索引或预计算,可用于预聚合或时间序列的优化,相当于子表
- 引入主键作为segment的划分依据
从功能上来说,carbondata比orcfile要强。如果能融合indexr的实时和离线节点设计,就更进一步了。
indexr
indexr是广州舜飞提供的数据存储方案,文件按以下格式组织:
- IndexR 在HDFS存储的一个文件是一个Segment,一个Segment保存一个表的部分行,包含所有的列。以列式的方式组织。
- File, 包含文件版本和SegmentMeta描述。Segment的元数据包括:行数,列数,每列的MAX和MIN值,每列的name, type,每列的各种索引的偏移量等。
- Column, 包含DataPackNodes, PackRSIndexs, PackExtIndexs, DataPacks
- DataPackNode是Pack元数据信息,包括索引文件的大小和偏移等;
- PackRSIndex是Pack的Rough Set索引,RSIndex通过对Pack中列的max/min记录,可以快速判断某个值是否在某个Pack中。
- PackExtIndex是Pack的内索引,包括equal,in, greater, between, like 5种。支持字典和bitmap两种实现方式。
- DataPack是实际的数据,以列式存储的方法,对不同column独立处理
- outerIndex, outerIndex是Pack级别的倒排索引,主要用于Pack之间的精准过滤。倒排索引按需创建。
indexr采取实时节点和离线节点分开的方式进行数据管理,数据一开始写入内存,并定期写入到文件中。indexr支持子表,当需要聚合结果的时候,通过sql查询的路由处理,则可以更快获得结果。
个人总结
通过上述存储格式的介绍,可以看到,一些出彩的功能点:
- 支持update & delete,ACID
- 支持实时节点和离线节点
- 预计算的支持
- 丰富使用可配置的索引机制
KtSQL如何做出自己的选择
KtSQL主要面向的场景是OLTP,所以优先满足的还是OLTP的需求。采用HBase作为底层的存储,是与这一点不违背的。KtSQL提供了二次索引的能力,对于从大范围数据中获取小量数据的场景,也是满足需求的。对于不可避免需要满足的统计式查询,HBase并不擅长。这可以通过mini cube提前聚合(参考carbondata、indexr)的方式来处理,即通过二次索引机制实现自定义cube,对KtSQL进行扩展。
采用apache kudu作为底层存储
Kudu 底层数据文件并没有存储在 HDFS 这样的分布式文件系统上,而是基于 Raft 算法实现了一套副本同步机制,保障数据不丢失及高可用性。为满足统计的需求,Kudu 的磁盘存储对字段采用列式存储,据说很多地方直接复用了 Parquet 的代码。这样的处理导致难以实现高效的更新操作。Kudu 为了提供实时写入功能,采用了在不可变的基线数据上,叠加后续更新数据的方法。
假如对HBase创建二次索引,以及因为行式存储带来的scan io成本为原来的3倍(如果太多列则远不止3倍),那么kudu对大量scan的情况,还是要远优于HBase的。不过如果更新很频繁,且更新的量很大,Kudu就会引发数据频繁的compact操作。不过对于更新操作,业务上可以绕过去。
总的来说,如果要同时满足OLTP和OLAP,Kudu是更好的选择,后续可根据发展需要增加Kudu的支持。
附录
MySQL协议流程笔记
目录
- MySQL协议支持的思考
- MySQL协议相关技术介绍
- KtSQL的MySQL协议实现
- 附录
MySQL协议支持的思考
随着互联网的发展,MySQL毫无疑问已经成为OLTP的标准,这个从DB Engine Rank的排名就可以轻易看出来。这是一个很有意思的现象,开源技术如果可以伴随新行业的发展而成长,则可以长大到一个无可估量的高度。闲话扯一下,做开源的各位,是不是应该考虑一下和新趋势结合一下,成为新趋势的推动力呢?
数据库的迁移成本是昂贵的,这也是为什么去IOE这么困难的原因。虽然节约成本是每个客户都希望得到的,但是应用对数据库的深度依赖让迁移成本远高于节约的成本,导致数据库无法迁移。如果无法迁移意味着技术落后,这将是难以忍受的结果。
支持MySQL协议正是基于这样的想法而坚定下来的。降低迁移成本,让原有的数据库使用者可以低成本迁移到KtSQL,是兼容MySQL协议的意义。
MySQL协议相关技术介绍
分布式数据库一直以来都存在两个实现方向。一个是在原有的数据库系统基础上,通过替换底层的存储引擎,通过存储引擎的分布式实现完成原有单机系统升级为分布式系统的改造,Amazon的Aurora就是典型例子;另一个是在SQL引擎层,实现对SQL协议的支持,过往许多的MySQL分布式中间件都是采用这种方式。
KtSQL选择的是后一种方式,出于以下几点考虑:
- 为将来可能支持其他的协议做准备,Oracle、Postgres、SQL Server等
- 突破数据库引擎的局限,为SQL引擎的创新做准备,如引入UDF
从技术上来说,MySQL协议是一个TCP协议解包分发处理的逻辑,核心的两块是TCP服务和协议解包,这些都有成熟的类库支持,只需要认真仔细结合MySQL协议实现即可。
KtSQL的MySQL协议实现
KtSQL Tcp服务底层使用Vertx框架,这是出于Vertx的高性能和Kotlin支持而选择的。协议解包则采用手写的方式实现,适当参考了第三方实现的代码。MySQL协议的命令并没有考虑全部支持,这不仅仅因为工作量的原因,还因为底层引擎实现差异,导致某些MySQL命令支持缺乏基础。MySQL协议支持的目标是实现常用的语句,让基于MySQL标准协议开发的应用“无缝”切换过来,如JDBC应用。
虽然底层的SQL解析用Calcite实现,但是部分MySQL语句,Calcite并不支持,需要对SQL协议解析进行增强。对此需求,引入了协议解析库。在协议解析上尝试了jparsec和parboiled两个库,最终选择了jparsec,原因是jparsec的解析器构建速度比parboiled快。用小规模样例测试的方法,得知jparsec和parboiled两者解析速度基本持平,在解析器构建环节,jparsec则因为其实现简单所以速度比parboiled要快不少。考虑到OLTP高并发的需求,速度的提升还是很重要的一个因素。
MySQL命令实现的测试方法是采用MySQL JDBC Driver和服务端交互,测试是否能够支持所需命令。主要的交互流程为:
- 客户端连接,服务器主动发出握手包
- 客户端响应握手包,发出身份认证包,服务器认证通过后,发出确认结果
- 上述过程完成后,双方进入命令包的交互阶段
- MySQL JDBC Driver在交互阶段会先查询服务器参数,并设置服务端参数,完成后才会进入到编码可控制的命令部分
- 双方完成交互后,客户端主动关闭,双方通信结束