编辑推荐: |
本文介绍了
Greenplum(GP)分布式数据库的哈希分布策略,希望对您的学习有所帮助。
本文来自程序员致知录,由火龙果软件Alice编辑、推荐。 |
|
Greenplum(GP)是分布式数据库,因此,数据的分布是基础。GP提供了多种分布策略:哈希分布、随机分布和复制表。其中,最常用的就是哈希分布。本篇文章我将向大家介绍GP的哈希分布。
首先,我们先回顾一下上篇文章用于调试的那张表:
CREATE table
t1 AS SELECT g c1, g + 1 as c2 FROM generate_series(1,
10) g DISTRIBUTED BY (c1); |
大家可以看到建表语句末尾有DISTRIBUTED BY (c1),这就表示上面这张表是一张哈希分布表,且通过列c1的值散列数据。我们再来看下表里的数据:
因为psql默认连接的是Master,所以这里只能看到整张表的数据。如果我们想观察数据在Segment中的分布情况,那么有没有办法只看某个Segment中存储的数据呢?答案是有的。由于Segment也是一个PostgresQL(PG)实例,psql提供了一个utility模式,可以直接连接Segment(注意不要通过此模式,绕过Master,直接在Segment执行DDL或数据插入语句,这样做可能会引发集群异常)。执行以下命令,直连Segment:
PGOPTIONS='-c
gp_session_role=utility' psql -p 6000 postgres |
连接上Segment后,再次查看Segment上t1表的数据:
由于我们上次搭建的环境只有一台Segment,表t1的数据理所当然全部存储在这台Segment上。所以,下面我们需要将集群扩容,再将t1的数据重分布,然后再观察。
扩容
GP的官方文档中,有对扩容的详细介绍,具体可以查阅《GPDB62Docs》—— Chapter 4
Greenplum Database Administrator Guide —— Managing
a Greenplum System —— Expanding a Greenplum System。
1,初始化新的Segments
cd /home/gpadmin/
#以交互模式创建扩容输入文件
gpexpand
# 是否初始化新的扩容
> y
# 输入需要扩容的主机名,多个主机之间以逗号分隔(若只在当前已存在主机增加segments,输入空行即可)
>
# 输入新加入的primary segments数量
> 1
# 输入新primary segments的目录
> /home/data/primary |
完成后,会在当前目录生成一个input file,gpexpand_inputfile_yyyymmdd_xxxx。
2,执行扩容
# 扩容前需要确保系统安装了rsync命令,没有的话先yum
-y install rsync
gpexpand -i input_file
# 如果执行失败,再次执行前需回滚上次失败的扩容信息
# gpexpand --rollback |
查看扩容状态:
3,执行数据重分布
再次查看扩容状态:
4,删除本次扩容的schema信息:
我们看一下此时后台的进程数:
可以看到新的Segment已启动,至此,扩容已完成。
数据分布
扩容后,我们分别利用psql连接两个Segment,查看数据分布情况。
第一个Segment:
第二个Segment:
从上面两张图片可以看到,原先全部存储于第一个Segment中的数据被分散到了两个Segment中,不过数据比较倾斜(由于t1表的数据量比较少,当前的现象并不能说明什么问题)。
通过阅读官方文档《GPDB62Docs》可知,从6.0版本开始,GP使用了新的哈希算法——Jump
Consistent Hash。这是谷歌提出的一个一致性哈希算法,效率很高。本文暂不对这个算法展开讨论,我们直接从源码中找到这个算法的代码:
/*
* The following jump consistent hash algorithm
is
* just the one from the original paper:
* https://arxiv.org/abs/1406.2294
*/
static inline int32
jump_consistent_hash(uint64 key, int32 num_segments)
{
int64 b = -1;
int64 j = 0;
while (j < num_segments)
{
b = j;
key = key * 2862933555777941757ULL + 1;
j = (b + 1) * ((double)(1LL << 31) / (double)((key
>> 33) + 1));
}
return b;
} |
据此,我们可以验证一下,上面的数据是否按照此算法分布:
直接通过列c1的值去哈希,得到的结果和t1表实际的数据分布是不一致的。为此,我们可以通过调试源码,一探究竟(调试方法大家可以回顾一下上一篇文章)。这次我们直接在jump_consistent_hash处打一个断点,通过psql执行一条sql:
insert into t1
values(1, 3); |
程序中断后查看传入jump_consistent_hash的变量值:
可以发现,传入jump_consistent_hash的入参并不是我们插入的c1的值1,而是变成了2389907270,很明显这是类似哈希值的一个东东。继续看下当前堆栈:
向上溯源找到nodeResult.c:279处的源码,我们看下h->hash是如何产生的:
图中1处,将h->hash置为0。图2根据c1的值重新计算hashkey,并赋给h->hash。继续查看cdbhash的代码:
在cdbhash中可以看到,datum即为c1的真实值1,通过FunctionCallInvoke(&fcinfo)产生了新的hkey,通过调试可以发现,此处调用的函数即hashint4:
Datum hashint4(PG_FUNCTION_ARGS)
{
return hash_uint32(PG_GETARG_INT32(0));
}
Datum hash_uint32(uint32 k)
{
register uint32 a,
b,
c; a = b = c = 0x9e3779b9 + (uint32) sizeof(uint32)
+ 3923095;
a += k; final(a, b, c); /* report the result */
return UInt32GetDatum(c);
} |
看到这里,真相大白。原来是根据列的类型调用相应的hash函数,先对列值计算了一次hash值,然后再对此哈希值进行jump哈希找到对应的segment编号。如此一来,我们在测试代码中添加hash_uint32,继续验证:
可以看到,这次的验证结果与实际的数据分布结果是一致的。至此,GP的哈希分布策略介绍完毕。
|