定义
分区:在Mongo
/ES
和Solr
被称为分片(shard
),在HBase
中称为区域(Region
),Bigtable
中是表块(tablet
),Cassandra
和Riak
中是虚节点(vnode
),Counchbase
中叫做虚桶(vBucket
).
分区主要是为了可扩展性,不同的分区可以放在非共享集群的不同节点上,每个节点可以独立执行对自己的查询.
分区通常与复制结合,使得每个分区副本存储在多个节点上.即每条记录虽然只属于一个分区,但是可以存储在不同的节点上(复制)获得容错能力.
偏斜(skew
):分区不平衡,导致一些分区比其他分区有更多数据或者查询.偏斜会使数据分区效率下降,高负载分区称为热点(hot spot
).
如何分区
- 根据键的范围分区
为每个分区指定一块连续的键范围.键的范围不一定均匀分布,分区边界需要依据数据调整.Bigtable
和HBase
使用了这种策略.
优点:范围扫描简单
缺点: 特定访问模式会导致热点,所以键值选取需要特殊考虑. - 根据键的散列值分区
根据合适的键散列函数,为每个分区分配一个散列范围.分区边界可以是均匀间隔的,也可以是伪随机选择的(一致性哈希).Cassandra
和Mongo
的散列分区模式使用这种策略.
优点:减少热点问题.
缺点:不支持范围查询.但是组合索引可以解决部分问题,例如社交媒体网站上,基于(user_id, update_timestamp
)建立索引,不同用户在不同分区上,同一个用户更新时间存储在单个分区上.这样可以有效检索特定用户在某个时间段内的按时间戳排序的所有更新(使用键的一部分来标识分区,而使用另一部分作为排序顺序).
虽然如此,哈希也不能完全消除热点问题.极端情况下,对于同一个键的大量读写,都会被路由到同一个分区.可以采用键值加上随机数,从而存储在不同分区中.但是读时,需要手动合并.并且需要判断是哪些键值才有必要进行这项操作.
如何对二级索引分区
根据主键可以确定分区,并且将请求路由到该分区.而次级索引在分区中的问题是不能整齐地映射到分区.
- 根据文档的二级索引分区
每个分区独立维护自己的二级索引,仅覆盖该分区中的文档,因此也被称为本地索引.因此二级索引会被分散到各个分区上,利用其进行查询时,需要将查询发送到所有分区上再进行合并.Mongo
/Riak
/Cassandra
/ES
/SolrCloud
和VoltDB
都使用这种方式.最好采用能在单个分区上提供二级索引查询的分区方案. - 根据词条的二级索引分区
以待查找的关键字本身作为索引,建立覆盖所有分区数据的全局索引,对关键词进行分区.Riak
的搜索功能和Oracle
采用此种方式.
优点: 对关键词自身进行分区利于范围查询;对关键字哈希后进行分区可以提供负载均衡.无需查询所有分区,只需要向包含关键词的分区发出请求.
缺点: 写入比较慢.因为单个文档可能会包含多个关键词,从而影响多个分区.同时也需要跨分区的分布式事务支持.
再平衡
当现有的节点不能满足实际情况时,诸如查询吞吐量增加,数据集变大,机器故障,需要将数据和请求从一个节点移动到另一个节点.
平衡策略
当使用hash mod N
,其中N
是节点数,节点发生变化,大量数据都要重新移动,并不是一种好的方案.如何只移动必需数据?
- 固定数量的分区
为每个节点分配更多的分区,分区总数量是固定的.当有新节点加入时,从其他节点获取一些分区,当节点删除时,将这些分区还给其他节点.只有分区在节点之间的移动,分区数量不会改变,键所对应的分区也不会改变.
某种程度上,可以通过为更强大的节点(高性能)分配更多的分区,强制它们承受更多负载.Riak
/ES
/Couchbase
/Voldemort
使用这种方案.
缺点: 分区数量的设置需要充分的考量. - 动态分区
当分区超过配置大小时,会被分为两个分区.如果分区缩小,则与相邻分区合并.HBase
和Mongo 2.4
都使用这种方法.
优点: 分区数量适应总数据量. - 根据节点比例分区
固定数量分区中的每个分区大小与数据集大小成正比;动态分区时,分区数量与数据集大小成正比.这两种情况下,分区数量都与节点数量无关.Cassandra
和Ketama
采用与节点数量相关的分区方式: 每个节点具有固定数量的分区.每个分区大小与数据集大小成比例增长,节点数量保持不变.当节点增加时,分区将再次变小(当一个新节点加入集群时,它随机选择固定数量的现有分区进行拆分,然后占有这些拆分分区中每个分区的一半,同时将每个分区的另一半留在原地).
优点: 每个分区的大小趋于稳定.
请求路由
发起请求时如何决定应该将请求发送至哪个节点呢?
- 允许客户端联系任何节点,由节点之间转发请求并回复
- 由路由层进行转发
- 客户端知道分区和节点的分配,直接发送请求到对应节点
因此,许多分布式数据系统都依赖于一个独立的协调服务,比如ZooKeeper
来跟踪集群元数据.Espresso
/HBase
/Solr
和Kafka
都使用ZK
跟踪分区分配.Mongo
依赖于自己的配置服务器,Cassandra
和Riak
在节点之间采用Gossip
协议传播集群状态变化.