第四届全球数据库大赛赛道2:分布式NewSQL性能挑战--复赛方案分享


前言

初赛幸运的获得第10名进入复赛,复赛目标是出分(600秒以内),但是复赛的难度远远大于初赛,分布式集群、8亿数据量,一座座大山横亘面前,出分遥遥无期。解题过程可谓历尽艰辛,在其它参赛队伍都非常卷的刺激下日夜鏖战,拼尽全力,最终复赛成绩256秒,又是第10,如此巧合,压线进入决赛答辩。


一、复赛赛题

提供4台126G的高性能BPS机型,实现分布式的高效的KV存储引擎,支持write、read、init接口,并实现自定义通讯协议
由于初赛已经实现了单机版的KV存储引擎,所以复赛重点考虑以下方面
1)如何建立分布式集群?总共8亿条记录数据如何存放?
2)每台机器50个线程写入,单机共写入记录数据2亿条,超过内存大小,无法像初赛一样把数据写入内存。
3)与初赛不同,复赛增加了读写混合的评测,读数据时也需要加锁
4)读本机写入数据占比较大,读其他机器写入数据占比较小,这是测评的一个重要特性。
5)engine_init、engine_deinit时需要保证集群同步,也就是说init完成时必须保证集群中4台机器都初始化完成,否则读取数据可能出错。
deinit完成时也必须保证集群中4台机器都完成读写,否则可能出现有机器还在读数据,但是资源已经释放了。
6)正确性评测阶段要考虑部分机器崩溃的情况,这也是测评的一个重要特性。网络编程上要考虑(连接正常才发送数据,连接断开不发)
7)复赛userid数据集做了加强,不能像初赛一样只用前8个字节做哈希。而128字节导致索引数据量很大。
8)deinit前会先调用sync强制刷盘,这个时间也会计入性能测评。

二、解题思路

1)方案一:写数据时每台机器把自己的2亿条记录写入自己的持久化内存,并建立三种索引与数据存储位置之间的映射关系。
按PK、UK读数据时,先在本地按索引查询持久化位置,如果查到一条即从持久化内存指定位置读取数据返回。如果本地未查到,
再同时向另外3台机器查询(轮询改为同时,提高效率)。按SK读数据时,除了需要本机查询,还需要向其它三台机器查询(哪怕另外三台机器没有这个SK数据,也需要查询一次)。
所以按SK读数据时大量的网络交互是本方案的性能瓶颈(其中很多是无意义查询交互)。
2)方案二、在方案一的基础上优化,考虑到读本地写入数据占比较大,读其他机器写入数据占比较小,而且写数据时只写入本地,网络空闲。
那么可以充分利用写数据的网络空闲来建立缓存,提高读SK时的效率。
每台机器在写数据时同时把SK索引信息发给另外3台机器缓存,这样每台机器在读SK数据时可以先判断哪些机器上有SK数据,
有的话再通过网络查询,没有的话就不用查询。节省了大量的网络交互。
(读SK数据时需要同步等待返回,所以效率低下。而写SK数据时不需要等待返回,而且可以合包发送。所以效率高)

三、网络模型

在这里插入图片描述

1)4台机器构建一个集群,任意一台机器作为TCP服务端,绑定端口9000。这台机器同时作为TCP客户端,连接另外3台机器。TCP底层使用epoll+socket编写,效率更高
2)Init时创建TCP服务端和客户端,建立连接,为另外3台机器每台建立2个连接。初始化完成后,客户端发送CMD_INIT消息给另3台机器,等待接收另外3台机器的CMD_INIT消息。以实现集群之间的同步。
复赛中初始化过程除了像初赛一样,读取持久化中数据建立索引。同时还需要向另外3台机器发送SK索引。这个时间是不计入性能测评的时间的。
3)由于实际需要跨机读的数据较少,写数据时任意一台机器作为客户端向另外3台机器发送CMD_WRITESK消息(带本机SK索引),另外3台机器收到后直接以哈希数组+单向链表的方式保存在本地。
也就是说每台机器要给另外3台机器的sk数据新建3个哈希表,再通过这3个哈希表就可以判断其它3台机器是否存在SK索引对应的记录数据。
4)跨机读数据时向其它机器发送CMD_READWHEREPK,CMD_READWHEREUK,CMD_READWHERESK三种消息格式,
其它机器收到后按本地索引读取数据返回CMD_READRESPONSE消息。
5)Deinit时客户端发送CMD_DEINIT消息给另外3台机器,等待接收另外3台机器的CMD_DEINIT消息。然后才进行关闭连接、资源释放操作。以实现集群之间的同步。
自定义协议:
enum CMD
{
CMD_INIT, //初始化同步
CMD_READWHEREPK, //按PK索引查询记录
CMD_READWHEREUK, //按UK索引查询记录
CMD_READWHERESK, //按SK索引查询记录
CMD_READRESPONSE, //返回查询结果
CMD_WRITESK, //发送SK索引给其它机器缓存
CMD_DEINIT //退出同步
};
在这里插入图片描述

四、持久化模型

在这里插入图片描述

持久化模型基本同初赛,每台机器总数据量远远大于初赛,为320*200000000=60G。
由于单机总数据量大于测评机器的内存,所以数据不能缓存在内存中。
pmem.db文件大小为90G,其中前60G用于存储数据,后30G用来作为纯内存保存其它机器的UK索引(由于系统内存只有32G,不够保存UK索引)
初赛是分为13块,复赛为了使用移位指令来实现取余,改为16块。

五、索引KV模型

typedef struct kv2
{
int64_t key;
int64_t pos;
int32_t next;
} Kv2;

typedef struct kv1
{
int64_t key;
int32_t next;
} Kv1;

typedef struct kv3
{
char key[128];
int64_t pos;
int64_t next;
} Kv3;
索引KV模型同初赛,还是使用链式哈希表,但是做了一些优化。
在这里插入图片描述

初赛中next为链表指针,指向下一个节点。哈希冲突时动态分配(由于复赛数据量太大,动态分配内存时会浪费大量的内存,导致内存不够)
复赛中next为数组位置,表示下一个节点在索引数组中的位置。
同时复赛中增加了3个哈希表,用来保存另外三台机器的SK索引。otherSK[3]
1)本机的PK、SK索引和其它3台机器的SK索引保存在32G内存中,本机的UK索引(128字节)由于太大,保存在持久化内存的后30G中(只作普通内存使用,无需持久化)。
2)索引哈希函数使用除留余数法,哈希数p=134217728(2的27次方,使用移位来取余),INT类型取绝对值后对p取余,字符串类型std::hash函数哈希后对p取余。
init时给每类索引分别创建一个链表头指针数组,大小为p,初始值全部设为NULL。
3)链表设计有别于普通单向链表,普通链表用指针指向下一个节点,我们这里用数组位置表示下一个节点。
为了提高内存分配的效率,在init时统一为各类索引创建数组(大小2亿)并分配内存,next设为-1表示没有下一节点。
4)单向链表插入的时候没有像初赛一样从尾部插入,而是使用从头部插入的方法。这样省去了对链表的遍历,减少了锁的范围。由于复赛数据量大,锁的优化对成绩的影响也很大。
5)以PK为例,init时为它分配一个大小为2亿的Kv数组a,以及一个大小为p的Kv*数组b。使用除留余数法得到N = id mod p,然后从数组a中取第一个空闲位置M(顺序选取)保存id和pos。
再判断b[N]是否为空,如果为空则把b[N]指向a[M]。如果不为空,说明发生哈希冲突,则把a[M].next设为b[N]在数组a中的位置,再把b[N]指向a[M]。

六、细节优化

除了初赛的优化点外,复赛还进行了以下优化:
1)为了提高网络传输效率,给其它机器发送SK索引时采用合包方法发送。每台机把自己SK索引发给其它3台机器时,由于速度很快,每个包又很小,发送效率很低。
所以把100个包合成一个包发送,提高发送效率。
2)给其它机器发送SK索引时,由于数据包多,所以每台机器使用2个连接来发送,提高并发效率。
每个连接跨多线程发送数据时需要锁,否则会出现乱序等问题,分为2个连接可以提高发送和接收的并发效率。
3)在初始化时提前为各类索引创建数组并分配内存
4)UK索引保存在持久化内存,不落盘。
5)取绝对值和取余操作使用移位指令来实现,提高代码运行效率。
6)由于存在读写混合,这里使用读写锁,提高锁效率。


总结

总结起来,在整个解题过程中学习到了不少新知识,收获良多,也取得了参赛之初不敢想的成绩。
比赛结束后看到其它队伍分享的技术实现方案,发现自己的方案还是有很多不足之处,也学到不少新的思路与方法(比如页高缓等,让我眼界大开)。

下面是我的微信公众号,欢迎大家关注
在这里插入图片描述

  • 23
    点赞
  • 23
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值