背景:当前项目中,数据部分整体采用Hive/SparkSQL+ES+HBase的架构,Hive/SparkSQL用于离线分析,ES+HBase用于实时查询。当业务规模不是那么庞大的时候,这套架构多少显得有点重。于是寻求一种轻量级解决方案,既满足离线分析的需求,又满足明细查询的需求。首先想到的是支持流批一体的MPP数据库,于是将目光锁定在了StarRocks、ClickHouse和TiDB这三款开源的MPP数据库上。
其中StarRocks和TiDB是国产开源,网上有博文说StarRocks和ClickHouse性能秒杀TiDB,因此首先将精力聚焦在StarRocks上面。且从开始预研最新版本V2.1.3到现在仅过去短短一个月时间,StarRocks官方已发布V2.2.0版本,中间还有V2.1.4、V2.1.5、V2.1.6三个小版本的发布。如此频繁的版本迭代,加上社区的高活跃度,StarRocks的未来还是值得期待的!
废话不多说,下面以流水账的形式记录此次StarRocks V2.1.3的踩坑过程。其中部分问题已在新版本中解决,部分可通过设计层面解决,部分仍需持续观察。
一、数据导入导出
A、Hive数据导入StarRocks
StarRocks支持Hive外表,可以建外表直连查询导入。
1、创建resource
create external resource hive0
properties (
"type" = "hive",
"hive.metastore.uris" = "thrift://tw01:9083"
);
2、创建外表
drop table if exists tmp.base_type;
create table tmp.base_type
(
id bigint,
type_name varchar(100)
)
ENGINE=hive
PROPERTIES (
"database" = "dwd",
"table" = "base_type",
"resource" = "hive0"
);
3、查询外表写入olap表
insert into dwd.base_type select * from tmp.base_type;
踩坑1:在此过程中,多张表数据顺利从Hive导入到StarRocks,但仍有两张表导入记录数为0。
而Hive表无论通过HiveServer2还是通过SparkThriftServer访问,都能正常查出数据。
经分析,顺利导入的表相应HDFS文件直接位于相应location目录下,未正常导入的表相应HDFS文件不是直接位于location目录下(location目录到数据文件中还有一层子目录)。
尝试将HDFS数据文件移动至location目录下后,外表正常查询到全部数据,问题得到解决!
(PS. SparkSQL也有类似情况,解决方案:将参数spark.hive.mapred.supports.subdirectories设置为true)
B、StarRocks数据导出到MySQL
StarRocks支持MySQL外表,既能通过外表读MySQL数据,也能通过外表往MySQL写数据。
踩坑2:在写入数据前,需要先清空目标表,因此想当然的对外表执行truncate操作。然而对MySQL外表的truncate操作并不被支持。若有该需求,还需在MySQL侧进行。
二、离线分析
1、对于大部分工作,基本上很难一条语句一步到位。对于复杂点的业务流程,甚至需要拆解成数十步完成,因此需要创建很多的临时表,即有很多的create table my_table_name as select语句。
踩坑3:select语句中出现子查询或者union all时,单独执行select查询没有问题,将查询结果insert into my_table_name也没有问题,但以查询结果create table my_table_name就会报错“unknown error”。期待新版本中解决此问题,或者使用者变通解决:在使用时先建表,再执行insert操作。
2、特定的业务场景,需要将同一分组下不同记录行的某个文本字段进行去重后拼接,且需要根据业务发生的时间进行排序(而非按照需要拼接的字段进行排序),如一个人的两个手机号按开户时间以逗号拼接成一个新字段。在使用SparkSQL时,可以将时间转换成出时间戳拼接在手机号前,再分组使用collect_list、sort_array、concat_ws,最后对时间戳部分内容进行替换,即可实现需求。
regexp_replace(concat_ws(',', sort_array(collect_list(concat_ws(':', cast(unix_timestamp(net_tm) as string), phone_no)))), '\\d+\:','')
踩坑4:在StarRocks V2.1.3中,可用group_concat实现拼接,但无法排序。根据官方论坛中的讨论,V2.2版本将支持去重排序后拼接(详见https://forum.starrocks.com/t/topic/1023),即
array_join(array_sort(array_distinct(array_agg(c1)), ','))
目前V2.2版本已发布,该功能点待验证。
3、对于数据仓库,调度的周期是固定的,每次跑数的时间区间也是固定的。但某些业务场景,不同于传统数据仓库,需要手工干预且设定可变跑数时间区间,如20220101到20220110,或20220101到20220131。而为防止重复写入数据,需要预先清除目标分区。对于Hive&SparkSQL而言,使用insert overwrite table直接覆盖目标分区,无需预先清除目标分区。对于StarRocks而言,就需要先执行truncate table table_name partition(p1, p2, p3),再执行insert语句。
踩坑5:在StarRocks中,对表truncate分区,分区数量可变,但必须全部指定。对于这种可变区间的做法,就需要变通解决:通过额外程序实现,或在跑数前手工指定目标分区进行truncate操作。
PS.根据论坛中官方的回复(https://forum.starrocks.com/t/topic/2138),已在github上提相应需求(https://github.com/StarRocks/starrocks/issues/5387),期待后续版本中实现该功能。
4、对于周末加班和节假日加班,工资需要按2倍、3倍进行发放。
对Hive&SparkSQL而言,设计一张表base_holiday(start_time string, end_time string, multiple int),每一次N天假期,维护一条配置记录即可。计算假期加班工资,只需要关联该表,要求上班日期 between start_time and end_time即可。
踩坑6:以上做法在StarRocks中执行报错,提示不支持非等值关联。非等值关联,意味着笛卡尔积,对很多经验不足的人来说,使用就会带来灾难。
这时候,只能变通解决。改变base_holiday的设计,枚举所有的假期日期。如
start_time
end_time
multiple
2022-04-30 00:00:00
2022-05-04 23:59:59
3
改成
holiday_date
multiple
2022-04-30
3
2022-05-01
3
2022-05-02
3
2022-05-03
3
2022-05-04
3
这时,关联该表,取上班日期等于holiday_date即可。
5、某些业务场景下,需要根据特定字段对数据进行分档分级。在Hive&SparkSQL中,ntile(10)over(order by xxx desc) 即可实现该需求。
踩坑7:StarRocks暂不支持该函数。
一个月前在官方论坛中询问后续是否会提供该函数,今天看到github上已经有将该函数纳入V2.3版本的计划(详见https://github.com/StarRocks/docs.zh-cn/pull/620)。
6、某些业务场景下,需要的功能点无法通过SQL实现,而需要借助UDF,最普遍的是用java实现UDF。如计算两个字符串之间的最小编辑距离等。
踩坑8:StarRocksV2.1暂不支持Java UDF,今天看到官网的公告中V2.2已经支持Java UDF,目前正公测中,我们原有的Hive UDF功能待移植验证。
三、实时查询
功能层面,筛选条件转换成查询语句中的where条件即可,任意一款数据库均能实现,关键在于性能方面能否满足要求,返回查询结果不说毫秒内完成,起码不能超过1秒钟。
StarRocks的前缀索引暂未深入研究,仅采用业务日期分区并对关键过滤字段创建bitmap索引,180个字段日增量800万数据的表,常用字段不到20个,对于80%以上的查询,平均耗时完全可以控制在0.8秒以内。
理论上,正确合理的利用前缀索引、bitmap索引、Bloomfilter索引,以及分区键、分桶键等,查询效率能够大幅提升,对于一般的系统来说,StarRocks完全能够胜任。
踩坑9:曾经使用MySQL、Oracle和DB2等创建索引时就有一个等待过程的心理预期,但在StarRocks创建bitmap索引的过程中,create index语句瞬间执行完成,不免惊讶,这也太快了吧!实际上,这时候查询,索引并不能起到作用,因为StarRocks创建索引是异步操作,create index语句执行完后,索引的创建过程仍在后台执行。创建索引的状态,可以通过以下语句查询。
show alter table column where tablename = 'my_table_name';
版权归原作者 yadianxk 所有, 如有侵权,请联系我们删除。