欢迎访问原文地址来阅读最新版本
转载请注明出处:https://kang.fun/data-partition/
个人博客:kang.fun
一、数据分区的方式
1. 根据关键字范围分区
优点
- 分区逻辑带有业务属性或者数据特征,所以便于范围查询。
- 一般场景下,比如根据时间分区,可以根据数据量级自动增加分片以扩容,且不涉及扩容后的数据迁移
缺点
- 更容易造成数据热点问题(同一时间请求相邻数据会造成所在分区热度过高)
2. 根据关键字哈希值分区
哈希方式可以使用MD5,或者一些加密性相对较差但性能较好的哈希算法,如Fowler-Noll-Vo
函数。
而哈希值对于分区的映射,可以由所有分区均匀分摊,或者使用一致性哈希算法。二者区别在于一致性哈希的动态平衡特性会使分区再平衡变得不可控。
优点
- 最大优点在于分区均匀
缺点
-
范围查询困难,通常需要查询所有分片。
比如mongo和es范围查询时需要将请求发送到所有分片后对结果进行汇总,而某些数据库干脆不支持范围查询。
-
扩容往往伴随数据迁移
热点问题
基于hash分区只能让数据分布更均匀,但仍旧避免不了热点key所在分区会承受更多的请求流量的问题。
那么通常解决办法是在热点key的后缀增加随机数,使数据分摊到更多的分区上,这样所有的分区分摊了写请求的压力。缺点是读取该key时需要查询所有随机数组合的key,然后将结果进行合并。
所以对热点key增加多级缓存在很多场景下更为合适。
二、分区数据如何索引
基于文档分区的二级索引(本地索引)
每个分区完全独立,维护自己的二级索引,因此也被称为本地索引。
优点是索引容易维护,但查找某项数据时,不能保证数据全保存在同一个分区内,所以需要访问所有分区的二级索引,并将结果进行汇总。
基于词条的二级索引分区(全局索引)
所有数据统一进行索引,然后将索引分片存储到不同的节点上。
所以查询某一项数据的执行顺序是:先定位到该查询项所对应的索引所在位置,然后通过索引找到数据所在节点。
优点是读取方便,但是写入复杂性大大提高,尤其当一条数据对应多个二级索引时,且这些二级索引并没有存在一起,那么可能需要对多个节点进行更新而造成写放大。而且还需要分布式事务来对多个节点内索引进行更新,这又降低了写入速度。
三、分区数量变化
当集群内分区数量变化时,需要进行分区再平衡
来让数据在集群内分步均匀,再平衡的方式通常分为自动和手动:
-
自动
可以通过取模确定节点和分区的匹配关系,但扩缩容时频繁的数据迁移,导致该方案几乎不被使用。
-
手动
手动指定节点和分区的匹配关系,该方案更可控,比如Redis的哈希槽的映射关系初始化时可以根据分区数量均匀划分,但变更时需要手动维护。
四、请求的分区路由策略
数据落到不同分区后,需要路由策略来使请求被正确分发到对应的分区所在的节点,而这个路由策略可以维护在客户端、节点,或者在二者之间增加路由层。
1. 路由策略存储在节点
每个节点都保存节点和分区的映射关系,请求随机打到某个节点下,然后通过该映射关系找到真正需要访问的分区所在的节点。这种方案提高了复杂性,映射关系需要在各个节点保证低延迟的一致性,但这个节点数量成反比。好处是减少了对ZK的依赖,众所周知ZK是AP应用,强一致性的缺点就是选举时不能对外提供服务。
2. 路由策略存储在客户端
将分区和节点的映射关系下发到客户端上,让客户端能够定向找到对应节点。
3. 路由策略存储在路由层
路由层承接所有的请求,并将请求分发到对应分区节点上。比如通过ZK来维护节点到分区的映射关系。