Kylin存储和查询的分片问题
本文来自网易云社区
作者:汪胜
相关概念介绍
为了了解Kylin存储和查询的分片问题,需要先介绍两个重要概念:segment和cuboid。相信大数据行业的相关同学都不陌生。Kylin每次提交一个新的build任务都会生成一个新的segment,而用户一般都是每天构建一次。那么,这种情况下,每天都会生成一个新的segment,用来保存昨天的数据。 Kylin的核心思想是预聚合,就是将用户预先定义的维度组合计算出来,然后保存到HBase中。这样查询的时候就可以直接查询预先计算好的结果,速度非常快。这里的维度组合就是cuboid。Kylin在构建过程中,会产生很多的cuboid数据(每一种cuboid都对应着一种维度组合),这些数据最终都会以HFile的形式存储在HBase中。Kylin对于每一个cuboid都会有一个唯一的id(一个cube的所有segment都有着相同的cuboid和cuboid id)。而这个id就是根据用户在定义cube时,维度列的排序来确定的。下面来举一个简单的例子。假设表一共有三列ABC,那么所有的cuboid组合就是:
| cuboid | cuboid_id |
|---|---|
| ABC | 7(111) |
| AB | 6(110) |
| BC | 5(101) |
| AC | 4(100) |
| A | 3(011) |
| B | 2(010) |
| C | 1(001) |
其中,cube的维度列顺序为A,B,C,括号里面的是id对应的二进制,用户可以在构建cube的时候进行排序。最终数据在HBase中存储的时候,rowkey也就是按这个顺序将这些维度值组合起来(rowkey还包含其他一些成员,这里不展开)。一般推荐将用户经常使用或者基数很大的维度放在前面,这样在查询的时候有利用提高扫描效率。
存储分片问题
Kylin在build过程中,每一个cuboid的数据都会被分到若干个分片中(这里的分片就对应HBase中的region)。对于每个segment都会保存cuboidShardNums和totalShards成员。如下所示:
//key表示cuboid的id,value表示该cuboid占用的region数private Map<Long, Short> cuboidShardNums = Maps.newHashMap();//该segment占用的region总数private int totalShards = 0;
请注意,一个region可能会存储多个cuboid数据,因此cuboid和region之间是多对多的关系。 Kylin可以通过下面三个配置项来控制生成build过程中生成的region相关信息:
//单个region的大小kylin.storage.hbase.region-cut-gb//region最小数量kylin.storage.hbase.min-region-count//region最大数量kylin.storage.hbase.max-region-count
通过上面这三个配置项,我们就可以控制每个build过程中生成的region数量和大小,从而进行相应的优化。segment的分片信息也会收到这几个参数的影响。具体如下:
float cut = cubeDesc.getConfig().getKylinHBaseRegionCut();int nRegion = Math.round((float) (totalSizeInM / (cut * 1024L)));
nRegion = Math.max(kylinConfig.getHBaseRegionCountMin(), nRegion);
nRegion = Math.min(kylinConfig.getHBaseRegionCountMax(), nRegion);//省略余下部分代码
其中,cut就是通过kylin.storage.hbase.region-cut-gb来设置的region分割阈值,totalSizeInM是本次build过程中生成的数据大小(所有cuboid数据之和),这样就可以求出每个segment对应的totalShards大小,即nRegion。再通过如下代码便可以求出每个cuboid所占用的分片数:
int mbPerRegion = (int) (totalSizeInM / nRegion);for (long cuboidId : allCuboids) { double estimatedSize = cubeSizeMap.get(cuboidId); double magic = 23; int shardNum = (int) (estimatedSize * magic / mbPerRegion + 1); if (shardNum < 1) {
shardNum = 1;
} //省略余下部分代码}
首先求出每个region的实际大小mbPerRegion,然后根据每个cuboid的数据大小estimatedSize就可以求出每个cuboid所占的region数,即shardNum。这里使用了一个magic,这是为了将cuboid数据尽量分散到多个region中,这样在查询的时候就可以多个region并行扫描,提高查询效率。 搞定cuboidShardNums和totalShards之后,还需要确定每个cuboid存储数据的起始region(再通过region数shardNum便可以确定指定cuboid的所有数据分布的位置)。这里主要就是根据cuboid id和region总数来获取每个cuboid存储起始region id,具体不再展开,有兴趣的同学可以自行查看源(ShardingHash.java)。
short startShard = ShardingHash.getShard(cuboidId, nRegion);
Segment使用cuboidBaseShards成员来保存cuboid id和起始region id的映射,如下所示:
private Map<Long, Short> cuboidBaseShards = Maps.newConcurrentMap();
这样一来,就基本搞定了Kylin build过程中,segment的存储分片问题。
查询分片问题
当新的segment生成之后,我们就可以查询其中的数据了。从上面的分析中我们得知,每一个segment的构建结果其实就是多个cuboid的数据集合。那么,当我们进行查询的时候,Kylin会根据sql中的列来获取到最佳匹配的cuboid(join情况下可能会存在多个匹配的cuboid)。然后根据筛选出来的cuboid id去对应的segment中进行扫描。Kylin对于每一个待扫描的segment都会生成一个CubeSegmentScanner。在对每个segment进行扫描的时候,首先需要根据筛选到的cuboid id去获取相应的region信息(主要是起始region id和region数)。主要处理逻辑如下所示:
//传入的三个参数都可以通过cuboid id去相应的segment中获取private List<Pair<byte[], byte[]>> getEPKeyRanges(short baseShard, short shardNum, int totalShards) { if (shardNum == 0) { return Lists.newArrayList();
} if (shardNum == totalShards) { //该cuboid的数据分布在所有的region中
return Lists.newArrayList(Pair.newPair(getByteArrayForShort((short) 0),
getByteArrayForShort((short) (shardNum - 1))));
} else if (baseShard + shardNum <= totalShards) { //该cuboid的数据分布在id连续的region中
return Lists.newArrayList(Pair.newPair(getByteArrayForShort(baseShard),
getByteArrayForShort((short) (baseShard + shardNum - 1))));
} else { //0,1,2,3,4 存储在 4,0这种情况
return Lists.newArrayList(Pair.newPair(getByteArrayForShort(baseShard),
getByteArrayForShort((short) (totalShards - 1))),
Pair.newPair(getByteArrayForShort((short) 0),
getByteArrayForShort((short) (baseShard + shardNum - totalShards - 1))));
}
}private byte[] getByteArrayForShort(short v) { byte[] split = new byte[Bytes.SIZEOF_SHORT];
BytesUtil.writeUnsigned(v, split, 0, Bytes.SIZEOF_SHORT); return split;
}
这样就可以获取每个segment需要扫描的region,由于Kylin目前的数据都存储在HBase当中,因此扫描的过程都在HBase中进行。对于每个region,kylin都会启动一个线程来向HBase发送扫描请求,然后将所有扫描的结果返回,聚合之后再返回上一层。为了加快扫描效率,Kylin还使用了HBase的coprocessor来对每个region的扫描结果进行预聚合。关于coprocessor的相关知识这里就不再介绍,可参考源码(CubeHBaseEndpointRPC.java和CubeVisitService.java)。
到这里,关于Kylin存储和查询的分片问题就整理的差不多了,本文省略了一些Kylin在使用HBase进行存储时的一些相关细节,后续会陆续补充上来,有感兴趣的同学可以一起交流学习。
网易云免费体验馆,0成本体验20+款云产品!
更多网易研发、产品、运营经验分享请访问网易云社区。
相关文章:
【推荐】 3分钟掌握一个有数小技能:收入贡献分析
【推荐】 【专家坐堂】四种并发编程模型简介
Kylin存储和查询的分片问题的更多相关文章
- Redis 存储、查询
[TOC] 数据存储 假设我们在MySQL数据库中有这样一张表: mysql> desc user_info; Field Type Null Key Default Extra id int( ...
- Elasticsearch集群搭建及使用Java客户端对数据存储和查询
本次博文发两块,前部分是怎样搭建一个Elastic集群,后半部分是基于Java对数据进行写入和聚合统计. 一.Elastic集群搭建 1. 环境准备. 该集群环境基于VMware虚拟机.CentOS ...
- 编写高质量代码改善C#程序的157个建议——建议26:使用匿名类型存储LINQ查询结果
建议26:使用匿名类型存储LINQ查询结果 从.NET3.0开始,C#开始支持一个新特性:匿名类型.匿名类型有var.赋值运算符和一个非空初始值(或以new开头的初始化项)组成.匿名类型有如下基本特性 ...
- ID和Phone高压缩比存储和查询
ID和Phone高压缩比存储和查询的简单例子, 无多线程处理 运行环境JDK8+maven 0. 模块分割 1. 基本思路 源文件BCP每一行都转为一个全局的RowID,可以直接映射到FileName ...
- 分布式文档存储数据库之MongoDB分片集群
前文我们聊到了mongodb的副本集以及配置副本集,回顾请参考https://www.cnblogs.com/qiuhom-1874/p/13953598.html:今天我们来聊下mongodb的分片 ...
- 最新IP数据库 存储优化 查询性能优化 每秒解析上千万
高性能IP数据库格式详解 每秒解析1000多万ip qqzeng-ip-ultimate.dat 3.0版 编码:UTF8 字节序:Little-Endian 返回规范字段(如:亚洲|中国| ...
- 「SQL归纳」树形结构表的存储与查询功能的实现——通过路径方法(非递归)
一.树形结构例子分析: 以360问答页面为例:http://wenda.so.com/c/ 我们通过观察URL,可以明确该页面的数据以树形结构存储,下面三块模块分别为: ①根节点 ②根节点的第一层子节 ...
- .NET+MVC+ORACLE存储分页查询一后端实现
MemberController:public ActionResult UserList() { UserBll userBll = new UserBll(); string keyWords = ...
- sql server 表变量存储临时查询数据
对于使用sql server 编写存储过程或者类似的sql 查询的时候我们使用表变量进行临时数据的存储,可以方便我们进行下来的数据处理 表变量的使用类似如下: declare @userinfo ta ...
随机推荐
- C#之Application.DoEvents()
Application.DoEvents()的最大作用就是时时响应, 可以看做是个线程的一个封装 private void button1_Click(object sender, EventArgs ...
- Nginx反向代理图片总结
配置需求: 内网192.168.80.205的机器上部署了一个Web项目,下文称web, url为http://192.168.80.205:8082. 并且使用nginx访问图片,url格式 ...
- 如何规避javascript多人开发函数重名问题
命名空间 封闭空间 js模块化mvc(数据层.表现层.控制层) seajs(如果了解的呢,可以说) 变量转换成对象的属性 对象化
- python---Redis 学习笔记
缓存 前言: 大家都听过缓存,缓存是干啥的呢?我们可以和json和pickle来说,两个程序之间实现信息交互,可以通过在A程序中把数据改成json ,然后传给B程序,通过文件这个介质.文件这个效率很低 ...
- linux 动态静态库
库从本质上来说是一种可执行代码的二进制格式,可以被载入内存中执行.库分静态库和动态库两种. 1 静态库和动态库的区别1.1. 静态函数库 (1)静态函数库的名字一般是lib[name].a( ...
- Oracle11gr2_ADG管理之跳归档恢复dg实战
模拟故障 关闭备库 SQL> shutdown immediate; Database closed. Database dismounted. ORACLE instance shut dow ...
- 华为部分真机调试无法显示log问题解决
真机测试时,部分华为手机无法获取全部的log信息.或者说无法获取Error以下级别的log信息.比如P7 这是因为部分华为机出厂默认log设置为关闭状态,因此只能获取Error以上级别的log信息.蛋 ...
- 吴恩达-AI-机器学习课后习题解析-第三周
================================================= sigmod.m ====================================== ...
- OSCache-JSP页面缓存(2)
如果在jsp中使用如下标签 <cache:cache key="foobar" scope="session"> some jsp content ...
- sqlserver 把SELECT结果集中一列的所有的值 用逗号隔开放进一个字段内
首先我们有一个表 查询结果如下: 现在我们想要把USER_NAME这一列的内容 放到一个字段里面去: 一行sql语句解决问题: SELECT STUFF(( SELECT ',' +convert(V ...