关于分布式分片,你该知道的事儿

关于分布式分片,你该知道的事儿

  • 前言
    • 一、关于分片方式的那些事儿
      • 1.1 按照Hash划分
      • 1.2 按照区间范围划分
      • 1.3 按照数据量划分
      • 1.4 来些例子
        • 1.4.1 Redis的分片划分
        • 1.4.2 Mongo的分片划分
    • 二、关于分区再平衡的那些事儿
      • 2.1 基于固定分片数量
      • 2.2 基于动态分片数量
      • 2.3 基于节点比例进行分片划分
      • 2.4 来些例子
        • 2.4.1 Redis中的再平衡
        • 2.4.2 Mongo中的再平衡
    • 三、关于请求路由的那些事儿
      • 3.1 举些例子
        • 3.1.1 Redis中的路由方式
        • 3.1.2 Mongo中的路由方式
  • 后记
  • 参考

前言

在这里插入图片描述

复制技术讨论的是将同一份数据拷贝到多个节点,每个节点保存相同的数据内容,以冗余来提高可靠性。而数据库分片是和水平分表(将一个表里的行拆分成不同的表)有着一定关系的一种数据拆分方式,它讨论的是将数据进行划分(也有点类似于水平划分),划分的小块叫做逻辑分片(logical shards),然后分配到不同的节点(也做叫物理分片,physical shards)上去,这样每个节点负责处理一部分数据,可以达到提高读写性能的目的。

为了避免混淆,下文中把逻辑分片称作分片,物理分片叫做物理节点。

这篇博客就来讨论讨论分布式中关于分片的那些事儿。 为了避免”总结不举例,就像吹空气“的情况,每块内容会结合例子介绍在实际中分片是如何应用的。


一、关于分片方式的那些事儿

首先要讨论的就是数据是如何分片的,或者说数据是按照什么方式来进行划分的,按照什么方式聚集成为一堆的。

1.1 按照Hash划分

Hash分片主要是根据数据的某些特征进行Hash计算,然后将计算得来的Hash值与集群中节点进行映射(现在大多数系统通过一个虚拟的shard(不同的数据库系统叫法不一样,有的叫做chunk,有的叫做slot等等)层, 数据分配到shard中,然后多个shard分配到节点中, 如下图所示),从而将不同hash值的数据分不到不同的节点上。
在这里插入图片描述
这种方式比较常见。如果选用的hash函数的散列特性比较好,这种方式可以较为均匀的将数据分配到不同的节点中。

但是Hash分片也有两个问题:

  • 数据倾斜。如果整个数据集在某些特征值上有大量的数据,则可能大量的数据会被划分到同一个节点上,造成“数据倾斜”,进而可能引起访问热点。比如说要存储一个群组成员,以群成员所属的groupId作为Hash的key, 因此一个群的所有群成员将被映射到同一个节点中。 当某个群中有了十万,一百万,乃至上千万的群成员,用一个节点来存储和计算估计就顶不住了。
  • 无法进行良好的区间范围查询。key相邻的两个关键字,经过hash之后的值基本不会相邻,因此在存储时很大程度上也不会“紧挨着”。 因此区间查询会发送到多个分片之中。

1.2 按照区间范围划分

在这里插入图片描述

另一种常见的数据划分是按照数据的区间范围进行划分,即将数据按照特征值的值域划分为不同的区间,然后为每个节点分配对应区间范围内的数据。比如说有3个节点N1,N2,N3, 要存储一个图书馆的所有书籍,则可以用书名首字母作为特征值,N1负责[a, g), N2负责[g,t), N3负责[t,z]。这样,每个节点可以处理自己区间范围内的数据,相互之间不会干扰。

对于按照哈希划分来说,整个系统只需要保存哈希函数和节点的个数。 但是按照区间范围划分,需要记录每个节点负责数据范围的对应关系,这部分元信息一般使用专门的服务器来维护。

按照区间范围进行划分最大的一个好处是有利于区间范围查询。 比如说上面的例子,如果要查询【a,c]开头的书名则只需要将请求发送到N1即可。

但是如果系统写入的顺序也是按照key的递增顺序进行写入,会存在针对单个节点的热点写入问题。一些常见的递增key有时间戳、自增序列、计数器等。

Hash划分和范围划分刚好是两个相对互补的分片方式,前者可以达到较为均匀的数据均衡(当然具体还是要根据上层的应用选择的key够不够理想),但是无法进行较好的范围查询;后者可以进行良好的范围查询,但是有可能数据分散的不够合理。

有些情况为了避免某一种分片的缺点,我们把分片键进行改造。比如说为了缓解hash分片的数据倾斜的问题可以考虑在key后加一个随机数,应用层进行打散;为了缓解区间范围分片的问题,则可以在关键字前面加上另外一个属性(比如说传感器以时间为key进行范围分片会有热点写入的问题,则可以在key前加一个传感器的名称,也可以将数据打散)。

1.3 按照数据量划分

还有一种方式是按照数据量进行划分,把整个数据集看作是一个顺序增长的文件,然后将这个数据集划分为固定大小的分片块,每个节点负责一个或多个分片块。

但是这种方式由于是以追加的方式添加数据,因此可能会造成热点写入。对于读取来说,如果写入的顺序是按照某个区间范围,则查询时性能较好。但是对于一般情况,则必须把查询路由到所有的节点。

1.4 来些例子

1.4.1 Redis的分片划分

redis分片集群将整个数据集(具体来说是一个数据库)事先划分了16384(0-16383, 关于为什么要设置成16384个槽位,作者也是有考虑的,具体请参见【12】)个槽(类似于上文中说的shard), 然后将数据库中的所有key分配到这些槽中。具体采用的方式是Hash分片,通过计算key的hash值(使用的hash函数是CRC16(key))得到的结果 与16384做取余运算, 最终计算出key所属的slot, 如下所示。

slot = CRC16(key) % 16384

在redis中使用下述命令可以确定key所属的slot位置。

CLUSTER KEYSLOT key

如下图所示,为“test_key1”,“test_key2”,"test_key3"这三个key所属的slot位置。
在这里插入图片描述

以上只完成了将数据key分配到了“虚拟的”slot, 最后一步过程是将slot分配给不同的节点。一般来说,这一步默认是按照平均分配的原则。如果redis采用3分片节点,则每个分片分到的slot是5041(16384/3)个槽。如下图所示为腾讯云上申请的3节点集群。

在这里插入图片描述
如果采用3分片节点集群,上述示例3个key, “test_key1”,“test_key2”,"test_key3"将分别由3个分片节点进行管理。
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
当然也可以根据具体的情况,手动分配每个节点所管理的slot数量。

1.4.2 Mongo的分片划分

mongo 支持两种分片划分方式。
一种是Hash划分。如下图所示,mongo将数据的key进行hash然后映射到不同的chunk中。一个chunk是一段与key有一定联系的范围值,在Hash分片中它是一段hash(key)后的hash值,即一段相邻的hash值将会存放在同一个chunk中(有点类似于上文说到的Redis中的槽位,但是每个槽位是按照hash(key)值取余的方式得出来的)。
在这里插入图片描述

另一种是范围划分。mongo分片集群支持按照shard key的范围进行分片,相邻范围内的key 将会被分配到同一个chunk, 如下图所示。
在这里插入图片描述

不明白?

上述两种方式中的chunk都是一段key space空间,只不过hash分片划分是以hash(shardKey) 之后的值作为标准,而范围分片是直接以shardKey作为标准。

二、关于分区再平衡的那些事儿

这主要是讨论数据的迁移问题。 当我们用多个节点联合起来组成一个完成的数据库系统时,一个不可避免的问题就是节点的失效退出,和扩容新增。由于节点的数量变量,因此这个时候会涉及到数据的迁移。 这个过程无法避免,但是理所应当我们希望迁移的数据越少越好或者迁移后数据分布的越平衡越好。

使用直接取余的方式将shard映射到node中一般是不太行的,因为这样当扩充或者减少节点时会有大量的数据迁移。 一般常用的主要有以下几种分片的方式:

2.1 基于固定分片数量

基于固定的分片数量是事先创建远大于节点数的分片数shardNum ( shardNum >> NodeNom ) ,然后为每个节点分配一批分片。当增加节点时,从每个节点平均允一些分片到新的节点中(由于shard是逻辑概念,因此迁移的主要是shard中具体的数据集),直到整个集群达到平衡状态(或者相对平衡状态)。如下图所示。
在这里插入图片描述

2.2 基于动态分片数量

还有一种分片方式是不定数量的动态分片。对于一些按照区间范围分片的分布式系统来说,数据库无法知道一个shard区间范围内有多少数据,因此无法更均衡的以事先分配的方式直接将某个shard指派给具体的节点。
因此,这种数据库往往采用动态分片的方式,当某个shard的数据达到设定的阈值之后,将shard进行拆分,一个shard平均拆分为两个shard(一般以数据量来衡量)。然后将拆分的shard分到不同的节点中去,以此来达到负载均衡的目的。 如下图所示。

在这里插入图片描述

2.3 基于节点比例进行分片划分

还有一种方式是根据节点来进行分片划分,也就是每个节点的分片数量是固定的(注意上面固定分片是整个系统的分片数量是固定的)。如果每个节点的分片数量是N,则多一个节点,整个系统的分片数量要增加N。当新增节点时,以某种方式选择(比如说采用随机或者轮询)一个原有节点,将其中一半的数据分配给新节点。如下图所示。
在这里插入图片描述

2.4 来些例子

2.4.1 Redis中的再平衡

对于Redis来说,其分区再平衡采用的是基于固定分片数量的方式,如上所述,redis集群架构事先规划了16384个slot(相当于上文中说的shard)。当新增一个节点A时,会平均从其他所有的slot中迁移数据(以slot为单位进行迁移)到新节点A.

如下图所示是Redis从3个节点扩充到5个节点的Slot示意图。
在这里插入图片描述
扩充后:
在这里插入图片描述在这里插入图片描述
可以看到,新扩充的节点从以前的3个节点中都允走了部分slot(因此新的节点所管理的slot范围变得不连续了)。

2.4.2 Mongo中的再平衡

mongo集群是采用上述的基于动态分片数量的再平衡方式。 如上所述,mongo是采用chunk,也就是一定范围的shard key空间来组织数据的。程序开始的时候,mongo初始化一个chunk(如果是采用的hash分片方式,则默认每个节点会初始化2个chunk, 具体初始化的chunk数量可以根据numInitialChunks 这个参数进行配置),覆盖整个range空间。

当chunk达到设定的阈值(一般是64M,最新版本的mongo应该是128M)之后,会平均分裂为两个chunk(按照数据量大小进行划分)。

同时, mongo内部会有一个balancer 来异步迁移各个节点之间的数据。当发现节点之间unbalanced 的时候,就会在节点之间进行迁移,具体的做法是数据多的节点迁移chunk到数据少的节点中。 如下图所示。
在这里插入图片描述
这里要注意两个问题:
(1)在5.0版本之前,balance的条件是以节点上chunk的数量来计算的(针对所有启用分片的集合,如果 「拥有最多数量 chunk 的 节点」 与 「拥有最少数量 chunk 的 节点」 的差值超过某个阈值,就会触发 chunk 迁移;)。而在5.0之后的版本中,balance的条件变成了以节点上collection中的数据量进行计算。如下图所示。
在这里插入图片描述

在这里插入图片描述

(2)迁移的单位是chunk. 即每次迁移最少一个chunk的量。

从Redis何Mongo的再平衡方式可以看出,Redis在迁移的时候强调整个集群数据分布的平均(确切的说是slot分布的平均),而Mongo更注重每次迁移较少的数据量(当然,也会达到一定程度的数据均衡)

三、关于请求路由的那些事儿

请求路由解决的问题是我们把数据集划分成了多个分片,当请求到来时,我们怎么知道该请求哪个分片呢?更确切的说,客户发起一个"get test_key” 请求, 这个请求会发往那个节点,哪台机器呢?
在这里插入图片描述
一般来说,常见的路由的方式有以下三种:

  1. 数据和节点的关系是确定的(如果是data-shard-node的方式, 那就是shard和node的关系是确定的),可以通过某种方式计算出来。这样,客户端可以直接根据请求的key计算出目标节点,然后直接将请求发往目标节点。

    一般来说,数据和节点的关系如果是确定的,那么如果增减节点,则映射关系就不存在了。但是有一种算法依旧可以计算出映射关系,那就是一致性hash算法。在实际中,也有一些数据库系统采用这种方式来进行路由原则(如Dynamo和Cassandra)。

  2. 数据和节点的关系保存在集群的各个节点中。这种情况集群中的每个节点都维护了数据和节点(或者shard和节点)的映射关系。 对于这种方式,客户端可以随意请求集群中的节点A,如果请求的节点正好是目标节点B,则目标节点B直接处理请求并返回结果。如果客户端请求的节点A‘不是目标节点,则A’会将请求转发给目标节点(因为集群中的节点知道对应key的目标节点在哪儿),得到结果后再将结果转发给客户端。 还有一种方式是请求的节点A’ 将目标节点的地址信息返回给客户端,有客户端再去请求 对应的目标节点。

    这种方式依赖节点之间通过协议来维护同一份数据和节点的映射关系,增加了数据库的复杂度,但是没有外部协调节点的依赖。

  3. 数据和节点的映射关系保存在集群单独的节点。 这种方式集群中一般有专门的节点来负责维护数据和节点的映射关系,然后还有一个路由节点来订阅这份路由映射表(有些集群可能路由节点自己维护了这份映射关系),所有的请求也是先发送给这个路由节点,然后由路由节点再将请求转发给后端的数据节点。

    有不少数据库集群或者Mq等系统使用开源的外部协调节点(如ZooKeeper)来维护数据和节点的映射关系。所有的数据节点(存储具体数据,负责处理具体请求的节点)的变化情况会向协调节点通知,而客户端或者路由节点则向协调节点订阅映射关系,这种方式虽然简单,但是会带来外部系统的依赖性。

具体如下图所示。
在这里插入图片描述

3.1 举些例子

3.1.1 Redis中的路由方式

Redis通过gossip协议来维护整个集群中数据和节点的映射关系(准确来说是slot和节点的映射关系,因为数据key到slot是采用CRC16 hash算出来的)。

当客户端向集群发送请求时,接收命令的节点会计算出请求的key属于哪个槽,然后根据自身存储的映射关系表判断出请求的slot是否归属自己管理,如果是归自己管理则直接执行请求的命令;否则节点会向客户端返回一个MOVED错误,而且MOVED得回包中会携带正确的节点信息;

客户端收到MOVED错误之后会根据返回的节点信息,重新发送请求到目标节点。如下图所示。

在这里插入图片描述

3.1.2 Mongo中的路由方式

对于Mongo集群,它的路由请求的方式采用的是上述说的通过一个路由节点(mongos)完成的。 而数据和节点的映射关系保存在(config server上)。客户端所有的请求都会发往Mongos节点,然后mongos再将请求转发给后端的节点(注意mongo集群中叫做shard, 指的是开头说的物理shard节点)。

在这里插入图片描述
在mongo集群中把请求主要分为两种,一种是Targeted Operations, 第二种是Broadcast Operations。 一般来说,可以将前者理解为请求中携带了shard key, 这样mongos就可以根据映射关系直接把请求转发到目标节点;

在这里插入图片描述
后者指的是请求中没有带shard key 这种的操作,mongos只要把请求转发到所有的节点,等待所有节点回复之后,汇集起来然后返回给客户端。

在这里插入图片描述
一般来说,target operation要比broadcast operation要快的多,因此客户端在调用接口时应尽量把分片键给带上,使用target operation来进行请求。

后记

数据库分片或者扩大点说分片技术,本质上是通过增加机器来水平扩展系统性能的方式。这不仅存在于数据库上、很多mq甚至应用层都需要使用分片的思想来提高系统的性能。 但是“遇事不决加机器”这种,也不一定是唯一的方式(很多时候也不一定是最优的),毕竟这增加了系统的复杂度,很多时候通过垂直扩展或者优化系统性能的也能很好的解决问题。总的来说还是马克思爷爷教我们的: “实事求是,因地制宜,合适的才是最好的”。


参考

  • 【1】《DDIA》
  • 【2】《高性能Mysql》
  • 【3】《Redis的设计与实现》
  • 【4】分布式相关技术 && 《DDIA读书笔记》
  • 【5】 为什么Redis集群有16384个槽
  • 【6】Understanding Database Sharding
  • 【7】MongoDB · 引擎特性 · sharding chunk 分裂与迁移详解
  • 【8】Sharded Cluster Balancer
  • 【9】redis集群新增结点slot迁移原理

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/739871.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

计算机毕业设计 | SSM 在线毕业论文管理 线上考试成绩教务管理系统(附源码)

1, 绪论 研究背景 系统管理也都将通过计算机进行整体智能化操作,对于论文管理系统所牵扯的管理及数据保存都是非常多的,例如管理员;首页、系统用户(管理员、学生、老师)模块管理(指导教师、课…

为什么不要使用elasticsearch

互联网上有很多文章,都在讲为什么要使用elasticsearch,却很少有人讲为什么不要使用elasticsearch。作为深入研究elasticsearch四年,负责公司万亿级别检索的操盘手,借着这篇文章,给大家分享一下,为什么不要使…

Vue3全家桶 - VueRouter - 【2】重定向路由

重定向路由 在路由规则数组中,可采用 redirect 来重定向到另一个地址: 通常是将 / 重定向到 某个页面; 示例展示: router/index.js:import { createRouter, createWebHashHistory, createWebHistory } from vue-route…

混合测试写一写

题目 服务器IP地址规划:client:12.0.0.12/24,网关服务器:ens36:12.0.0.1/24、ens33:192.168.44.1/24,Web1:192.168.44.30/24,Web2:192.168.44.50/24,Nginx&am…

iOS应用内的沙盒目录

iOS系统的沙盒机制规定每个应用都只能访问当前沙盒目录下面的文件,在开发中常常需要数据存储的功能,比如存取文件,归档解档等,因此有必要熟悉沙盒目录及其作用。 Documents目录 开发者可以将应用程序的数据文件保存在这个目录下.…

bzm - Concurrency Thread Group 阶梯式压测

bzm - Concurrency Thread Group 不是JMeter的官方插件,而是一种由Blazemeter提供的高级线程组插件,它提供了更灵活的并发性能测试设置。它可以在不同的时间内并发执行不同数量的线程,模拟不同的负载场景 插件下载地址:Download …

加速 Webpack 构建:提升效率的秘诀

🤍 前端开发工程师、技术日更博主、已过CET6 🍨 阿珊和她的猫_CSDN博客专家、23年度博客之星前端领域TOP1 🕠 牛客高级专题作者、打造专栏《前端面试必备》 、《2024面试高频手撕题》 🍚 蓝桥云课签约作者、上架课程《Vue.js 和 E…

网络通信另个角度的认识(进程间通信),端口号(为什么要有,和pid的关系,分类,如何封装,和进程的定位原理+对应关系),客户端如何拿到服务端的port

目录 另一个角度认识网络通信 端口号 引入 -- 为什么要有端口号 问题 解决 端口号和pid 举例 介绍 分类 知名端口 注册端口 动态端口 客户端如何知道服务端的端口号 封装端口号 定位原理 进程和端口号的对应关系 数据如何被上层进程读到 另一个角度认识网络…

【Java EE初阶十一】多线程进阶二(CAS等)

1. 关于CAS CAS: 全称Compare and swap,字面意思:”比较并交换“,且比较交换的是寄存器和内存; 一个 CAS 涉及到以下操作: 下面通过语法来进一步进项说明: 下面有一个内存M,和两个寄存器A,B; CAS(M,A,B)&am…

吴恩达机器学习-可选实验:梯度下降逻辑回归(Gradient Descent for Logistic Regression)

文章目录 目标数据集Logistic梯度下降梯度下降实现计算梯度,代码描述 另一个数据集 目标 在本实验中,你将: 更新逻辑回归的梯度下降在一个熟悉的数据集上探索梯度下降使用梯度下降给逻辑回归更新参数 import copy, math import numpy as np %matplotl…

怎么判断你的模型是好是坏?模型性能评估指标大全!

模型性能评估指标,大家一定不陌生!很多小伙伴们都说难,但是它真的很重要很重要很重要!它会对我们的模型有很多的指导,也会给我们真正做模型的时候提供一些指导性的思想,不然我们看到别人的东西只能跟着人家…

【linux】冯诺依曼体系与操作系统的理解

本篇文章是进程的预备知识,但也不仅仅是进程的预备知识, 也可以更好地帮助我们理解整个计算机体系。 目录 冯诺依曼体系结构:进一步理解操作系统: 冯诺依曼体系结构: 关于这张图先进行一下必要的解释: 输…

怎样通过IT服务台来增强IT项目管理?

当下,越来越多的企业和组织重视IT项目管理的重要性。而如何通过IT服务台来增强和提升IT项目管理效率,成为了许多企业领导和IT专业人员共同关注的话题。如何充分利用IT服务台,以促进IT项目管理水平的提升和项目成功率的增加变得至关重要。 1…

怎么免费下载无水印视频素材?赶快收藏这六个网站。

今天来教大家怎么下载无水印视频素材,其中一些是免费的,并且可以在商业项目中使用,这些网站都是无水印视频素材,可以放心使用。 蛙学网: 网站的内容非常丰富多彩,包括风景,夜景,食物…

论文阅读:Editing Large Language Models: Problems, Methods, and Opportunities

Editing Large Language Models: Problems, Methods, and Opportunities 论文链接 代码链接 摘要 由于大语言模型(LLM)中可能存在一些过时的、不适当的和错误的信息,所以有必要纠正模型中的相关信息。如何高效地修改模型中的相关信息而不影…

java学习(Arrays类和System类)

目录 目录 一.Arrays类 二.System常见方法 三、Biglnteger和BigDecimal(高精度) 1.Biglnter的常用方法 2.BigDecimal常见方法 3.日期类 1)第一代日期类 2)第二代日期类 3)第三代日期类 一.Arrays类 Arrays包含了一系 列静态方法&am…

11、Linux-安装和配置Redis

目录 第一步,传输文件和解压 第二步,安装gcc编译器 第三步,编译Redis 第四步,安装Redis服务 第五步,配置Redis ①开启后台启动 ②关闭保护模式(关闭之后才可以远程连接Redis) ③设置远程…

12双体系Java学习之局部变量和作用域

局部变量 局部变量的作用域 参数变量

在 Python 中从键盘读取用户输入

文章目录 如何在 Python 中从键盘读取用户输入input 函数使用input读取键盘输入使用input读取特定类型的数据处理错误从用户输入中读取多个值 getpass 模块使用 PyInputPlus 自动执行用户输入评估总结 如何在 Python 中从键盘读取用户输入 原文《How to Read User Input From t…