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 ...
随机推荐
- NOIP2008普及组第3题 传球游戏
NOIP2008普及组第3题 传球游戏 时间限制: 1 Sec 内存限制: 128 MB提交: 29 解决: 16[提交][状态][讨论版][命题人:外部导入] 题目描述 上体育课的时候,小蛮的老 ...
- Java-Maven-Runoob:Maven 构建 & 项目测试
ylbtech-Java-Maven-Runoob:Maven 构建 & 项目测试 1.返回顶部 1. Maven 构建 & 项目测试 在上一章节中我们学会了如何使用 Maven 创建 ...
- Java开发进阶技能(附文章引用链接)
一.玩转源码 1.Java+Selenium3方法篇0-如何在Eclipse上查看Selenium源码 (在github上下载源码)
- [PHP] - Laravel - CSRF token禁用方法与排除验证csrf_token的url设置
laravel5.1排除验证csrf_token的url设置 <?php namespace App\Http\Middleware; use Illuminate\Foundation\Htt ...
- java 最差实践
HashMap size 陷阱: 错误写法: Map map = new HashMap(collection.size()); for (Object o : collection) { map.p ...
- FusionCharts simple demo for (html+js、APS.NET Webform、MVC)
做GIS或其他内部数据统计项目的应该对FusionCharts也不会太陌生,简单易用已无需多说什么了,只是有时候框架不同,实现起来也稍有差异 引用dll调用FusionCharts类的静态方法Rend ...
- MySQL 性能测试经验
一.背景 近期在进行资源调度管理平台的重构工作,其中的Resource/Property数据库设计,在没有更加优化的方案前,打算沿用当前平台的数据库结构:这就需要对当前平台的数据库结构进行剖析研究,并 ...
- Py修行路 python基础 (八)函数(随时更改)
为何要用函数: 1.解决代码重用的问题 2.提高代码的可维护性,统一维护 3.程序的组织结构清晰,可读性强 定义函数 先定义后使用!!! def funcname(arg1,arg2,.....) ...
- powerdesigner Oracle.sql转mysql.sql
将要导入的库的所有表的表结构(不要表数据,只要表结构)导出成一个.sql文件. 在PowerDesinger里找到 File -->> Reverse Engineer --->&g ...
- Nginx Rewrite研究笔记
原文出自:http://blog.cafeneko.info/2010/10/nginx_rewrite_note/ 在新主机的迁移过程中,最大的困难就是WP permalink rewrite的设置 ...