1. 键值数据的分区
1.1. 基于关键字区间分区
即以一段连续的关键字或关键字区间分区。
优点:区间上的分区也利于排序和范围查询。
缺点:容易在热点下的负载不均衡。(需要通过额外信息作为分区的依据,解决这种问题)
1.2. 基于关键字散列分区
根据关键字的散列值进行分区(为每个分区划分散列值范围)。分区边界可为均匀间隔(固定分配),也可为伪随机选择(一致性散列)
一致性散列:详细说明在这,很容易理解。在散列空间(可以用散列环看),用(节点自身的)散列值确定分区边界(即一段弧)。
优点:缓解某些热点问题
缺点:区间查询需要将请求发送给所有节点
1.3. 负载倾斜与热点
对于某个关键字的热点,大多数系统无法自动消除这种高度倾斜的负载,需要应用层控制。
一种简单的方法:对热点关键字附带一个随机数,从而将其分配到不同分区,但是读操作时需要额外将所有不同分区的相关数据合并,带来不必要的开销
2. 分区与二级索引
二级索引的索引值不能唯一标识一条记录,因此主要的挑战是:不能规整地映射到分区。
一般有两种方法:基于文档的分区,基于词条的分区
2.1. 基于文档分区的二级索引
每个分区独立,各自维护自己的二级索引,只负责自己分区内的文档(行或者条目)。即每次写时,只需处理包含目标ID的分区的二级索引,因此叫做本地索引(不是全局索引)。
缺点:查询时,必须查询所有分区,然后合并,带来巨大延迟(读跨分区)
2.2. 基于词条的二级索引
构建的是全局索引,但是索引也会根据对应策略分区
如对
Car
的Color
建立二级索引,前0~499在分区1,后500~999在分区2。Color:red
索引可以在分区1上,Color:black
可在分区2上,但是它们是全局的,记录了2个分区的索引项(如Color:red = [191, 688]
)
优点:查询更加高效,不需并行查询然后再合并
缺点:写入慢(写跨分区),因为二级索引可能较多且复杂,且二级索引所在的分区可能和条目本身所在分区一样(可用异步更新减少性能影响)
3. 分区再平衡
分区再配合(动态平衡):即数据和请求从一个节点转移到另一个节点的负载迁移
目标:负载分布更均匀,平衡过程中可用,避免不必要的迁移
3.1. 动态再平衡策略
3.1.1. 为何不取模(对节点个数)
采用$hash(key) \% N$($N$为节点个数)时,当节点个数变化时,会导致很多关键字需要迁移。
3.1.2. 固定数量分区
首先创建远超实际节点数的分区数,分区数始终不变,然后为每个节点分配多个分区。
当节点加入或离开时,只需均匀拿走或添加几个分区,直到再次平衡为止。再次期间,旧分区依旧可用。
分区数量不应太大或太小,分区数应和数据总量成正比
3.1.3. 动态分区
当一个分区数据量过大时,自动拆分为多个分区;若一个分区数据量过小,自动和邻近分区合并
优点:分区数量可根据数据量自动配置,且不仅适用于关键字区间分区,也适用于基于散列的分区
3.1.4. 按节点比例分区
即分区数和节点数成正比,每个节点拥有固定的分区数。这种方法可维持分区大小的稳定。
节点加入时,根据算法/随机,从集群中选择固定数量的分区,取走它们一部分的数据量,另一部分保留在原节点。
前提:要求使用基于散列的分区
3.2. 自动与手动再平衡操作
自动:方便,操作少,但可能存在风险,并影响性能和可用性
手动:平衡响应慢,但风险少
4. 请求路由
一类典型的服务发现的问题。
一般有三种方式:
-
客户端可请求任意节点,节点可返回结果或转发到合适的节点(每个节点都要分区与节点的关系)
-
客户端请求到路由层,路由层转发请求到合适节点(路由层需要知道分区与节点的关系)
-
客户端感知分区与节点的关系,直接请求对应的节点
可由外部独立的协调服务实现,如利用ZooKeeper。节点向ZooKeeper集群注册自己(插入树形节点),路由层/感知客户端订阅ZooKeeper/从ZooKeeper获取节点信息获取最新状态。
一个小demo在这里,利用ZooKeeper简单实现了软负载平衡。
4.1. 并行查询执行
对于大规模并行处理(MPP),主要用于数据分析的关系数据库,查询很复杂。
MPP查询优化器将复杂的查询分解成许多执行阶段和分区,以便于在集群不同节点上执行,尤其是全表扫描的查询。