实现数据冷热分离用什么技术合适?

随着业务的发展,在数据分析(OLAP)应用中,数据库 / 数据仓库存储的数据越来越多,承担的计算任务越来越重,就会出现响应速度越来越慢的情况。仅仅依靠数据库扩容的办法解决这个问题是不可取的,一方面横向或纵向扩容的成本都很高;另一方面,一旦达到数据库的容量极限,再扩容也不会有明显的性能提升了。

数据冷热分离可以很好的解决这个问题,也就是将少量频繁访问的热数据和大量访问量很低的冷数据分开存储计算,以达到降低数据库压力,同时提升查询响应速度的目的。

 

通常,实现冷热分离的方法,是增加一个前置数据库,专门为前端应用存储热数据并提供计算能力。但是,我们无法预测用户要分析计算的是冷数据还是热数据,如果是两者的混合计算,就要做前置库和原数据库之间的跨库计算了。而这两个库往往是不同类型的异构数据库。事实上,数据库管理系统自身提供的跨库计算功能有很多限制,而且性能很差,对异构数据库的支持就更不理想了。

前置数据库的另一个问题是难以实现数据路由。数据路由是指按照一定的规则来判断数据的冷热,由此决定数据是来自于哪个数据源,或者各取一部分做混合计算。在保持对前端应用透明的前提下,用数据库来实现路由规则是非常困难的。

如果将混合计算和数据路由都放在数据库外,用 Java 等高级语言硬编码实现,编程难度高、工作量大,投入产出比很低,也不太可行。

 

开源的集算器 SPL 非常适合用来实现数据冷热分离。具体的做法是采用集算器在前端应用和数据库之间搭建数据计算引擎,如下图:

..

SPL 提供开放的计算能力,数据可以从本地存储读取,也可以来自于各种异构数据库,再使用 SPL 语法进行统一处理,很容易实现冷热数据的跨库混合计算。

并且,SPL 语言可以用简洁的代码实现复杂的数据路由规则。比如依据日期远近划分冷热数据的场景,SPL 中有解析函数能将前端应用提交的 SQL 语句进行拆分,在 WHERE 子句中找到日期段参数,识别出该查询涉及近期热数据还是远期冷数据,或两者都有,再按照冷热数据的不同来源读入数据,计算结果。

 

SPL 支持压缩列存,且封装了大量高性能算法,可以做多线程并行计算,能充分发挥硬件的计算能力,达到并超过专业数据库的性能。对于少量较热的数据,SPL 还支持全内存计算,使响应速度达到极致。同时,SPL 是轻量级计算引擎,热数据量不大时,可以单机部署,甚至可以直接嵌入前端应用中。系统投入成本相对于数据库要低很多。

 

SPL 提供通用的 JDBC 接口和 HTTP 访问方案,可以由各种前端应用访问。特别地,对于 BI 类应用,SPL 还支持常见的 SQL 语法,这样前端无需改造即可以无缝连接。访问冷数据时,SPL 还可以将原 SQL 语句翻译成各种数据库能接受的语法,以适应不同类型的数据库。

 

SPL 解析 SQL 语句实现冷热数据路由处理(依据日期远近划分冷热数据)的样例代码:

A B C
1 =sql="select   id,... from T where tdate=date('2020-07-18') and ..."
2 =sql.sqlparse@w().split(" ") =A2.select@1(like(~,"tdate=date(‘????-??-??’)"))
3 =int(mid(right(B2,14),3,4))
4 if A3>=2020 =connect() =replace(sql,"from   T","from  T.ctx")
5 return   B4.cursor@x(C4)
6 else =connect("orcl") =sql.sqltranslate("ORACLE")
7 return   B6.cursor@x(C6)

A1:前端提交的 SQL 语句,作为参数传入。

A2-A3:拆分 SQL,得到 where 条件中的日期参数,计算年份。

A4-B5:如果年份是 2020 年之后(热数据),则用本地缓存的热数据计算 SQL 结果,并返回。

A6-B7:如果年份是 2020 年之前(冷数据),则连接 oracle 数据库,将 SQL 翻译成 oracle 的 SQL 语句,提交、执行 SQL 并返回结果。

 

SPL 实现冷热数据混合计算的代码样例:

A
1 =cold=orcl.cursor("select   customerid,sum(amt) totalamt from T where  tdate>= to_date('2019-11-11','yyyy-MM-dd')   and tdate<to_date('2020-01-01','yyyy-MM-dd') and … group by customerid")
2 =hot=local.cursor("select   customerid,sum(amt) totalamt from T where    tdate>= date('2020-01-01')  group by customerid")
3 =[cold,hot].conjx()
4 =A3.groups(customerid;sum(totalamt):totalamt)

A1、A2:分别从数据(仓)库和本地数据取出冷、热数据按照客户编号的分组结果。

A3、A4:冷、热数据游标纵向连接后计算最终的分组汇总结果。