前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >数据库如何做到平滑扩容

数据库如何做到平滑扩容

作者头像
程序员小王
发布2019-05-05 16:52:03
3.7K0
发布2019-05-05 16:52:03
举报
文章被收录于专栏:架构说架构说

摘要: 数据库一般有2种,传统的关系数据 例如 mysql 和内存数据例如redis。

分库分表的扩容是一件头疼的问题,如果采用对db层做一致性hash,

或是中间价的支持,它的成本过于高昂了,

如果不如此,只能停机维护来处理,对高可用性会产生影响

那是否有方案,既可以快速扩展,又不降低可用性?

这一篇,我们聊聊分库分表的扩展方案,供大家一起探讨。

一、水平分库扩展问题

为了增加db的并发能力,常见的方案就是对数据进行sharding,也就是常说的分库分表,这个需要在初期对数据规划有一个预期,从而预先分配出足够的库来处理。

比如目前规划了3个数据库,基于uid进行取余分片,那么每个库上的划分规则如下:

如上我们可以看到,数据可以均衡的分配到3个数据库里面。

但是,如果后续业务发展的速度很快,用户量数据大量上升,当前容量不足以支撑,应该怎么办?

需要对数据库进行水平扩容,再增加新库来分解。 新库加入之后,原先sharding到3个库的数据,就可以sharding到四个库里面了

不过此时由于分片规则进行了变化(uid%3 变为uid%4),

大部分的数据,无法命中在原有的数据库上了,需要重新分配,大量数据需要迁移。

比如之前uid1通过uid1%3 分配在A库上,新加入库D之后, 算法改为uid1%4 了,此时有可能就分配在B库上面了。

如果你有看到之前《一致性哈希的原理与实践》,就会发现新增一个节点,大概会有90%的数据需要迁移,这个对DB同学的压力还是蛮大的,那么如何应对?

一般有以下几种方式。

二、停服迁移

停服迁移是最常见的一种方案了,一般如下流程:

  1. 预估停服时间,发布停服公告
  2. 停服,通过事先做好的数据迁移工具,按照新的分片规则,进行迁移
  3. 修改分片规则
  4. 启动服务

我们看到这种方式比较安全,停服之后没有数据写入,能够保证迁移工作的正常进行,没有一致性的问题。唯一的问题,就是停服了和时间压力了。

  1. 停服,伤害用户体验,同时也降低了服务器的可用性
  2. 必须在制定时间内完成迁移,如果失败,需要择日再次进行。同时增加了开发人员的压力,容易发生大的事故
  3. 数据量的巨大的时候,迁移需要大量时间

那有没有其他方式来改进一下,我们看下以下两种方案。

三、升级从库

线上数据库,我们为了保持其高可用,一般都会每台主库配一台从库,读写在主库,然后主从同步到从库。如下,A,B是主库,A0和B0是从库。

此时,当需要扩容的时候,我们把A0和B0升级为新的主库节点,如此由2个分库变为4个分库。同时在上层的分片配置,做好映射,规则如下:

uid%4=0和uid%4=2的分别指向A和A0,也就是之前指向uid%2=0的数据,分裂为uid%4=0和uid%4=2 uid%4=1和uid%4=3的指向B和B0,也就是之前指向uid%2=1的数据,分裂为uid%4=1和uid%4=3

因为A和A0库的数据相同,B和B0数据相同,所以此时无需做数据迁移即可(扩容期间不允许业务发生,业务阻塞)

只需要变更一下分片配置即可,通过配置中心更新,无需重启。

由于之前uid%2的数据分配在2个库里面,此时分散到4个库中, 由于老数据还存在(uid%4=0,还有一半uid%4=2的数据), 所以需要对冗余数据做一次清理。

而这个清理,不会影响线上数据的一致性,可是随时随地进行。

处理完成以后,为保证高可用,以及下一步扩容需求。可以为现有的主库再次分配一个从库。

总结一下此方案步骤如下:

  1. 修改分片配置,做好新库和老库的映射。(手工处理)
  2. 同步配置,从库升级为主库(手工处理)
  3. 解除主从关系 (手工处理)
  4. 冗余数据清理(手工处理)
  5. 为新的数据节点搭建新的从库(手工处理)

四、双写迁移

双写的方案,更多的是针对线上数据库迁移来用的,当然了,对于分库的扩展来说也是要迁移数据的,因此,也可以来协助分库扩容的问题。

原理和上述相同,做分裂扩容,只是数据的同步方式不同了。

1.增加新库写链接

双写的核心原理,就是对需要扩容的数据库上,增加新库,并对现有的分片上增加写链接,同时写两份数据。

因为新库的数据为空,所以数据的CRUD对其没有影响,在上层的逻辑层,还是以老库的数据为主。

2.新老库数据迁移

通过工具,把老库的数据迁移到新库里面,此时可以选择同步分裂后的数据(1/2)来同步,也可以全同步,一般建议全同步,最终做数据校检的时候好处理。

3.数据校检

按照理想环境情况下,数据迁移之后,因为是双写操作,所以两边的数据是一致的,特别是insert和update,一致性情况很高。但真实环境中会有网络延迟等情况,对于delete情况并不是很理想,比如:

A库删除数据a的时候,数据a正在迁移,还没有写入到C库中,此时C库的删除操作已经执行了,C库会多出一条数据。

此时就需要做好数据校检了,数据校检可以多做几遍,直到数据几乎一致,尽量以旧库的数据为准。(手工处理)

4.分片配置修改

数据同步完毕,就可以把新库的分片映射重新处理了,还是按照老库分裂的方式来进行,

u之前uid%2=0,变为uid%4=0和uid%4=2的 uid%2=1,变为uid%4=1和uid%4=3的。

上面数据特点 数据集中在一个表中

四、reids 3.0 以前

redis3.0 key/value,

3.0以下版本采用Key的一致性hash算法来区分key存储在哪个Redis实例上

采用这种方式也存在两个问题

  • 扩容问题:

因为使用了一致性哈稀进行分片,那么不同的key分布到不同的Redis-Server上,当我们需要扩容时,需要增加机器到分片列表中,这时候会使得同样的key算出来落到跟原来不同的机器上,这样如果要取某一个值,会出现取不到的情况,之前的缓存相当于全部失效。对于扩容问题,Redis的作者提出了一种名为Pre-Sharding的方式。即事先部署足够多的Redis服务。

  • 单点故障问题:

当集群中的某一台服务挂掉之后,客户端在根据一致性hash无法从这台服务器取数据。对于单点故障问题,我们可以使用Redis的HA高可用来实现。利用Redis-Sentinal来通知主从服务的切换。

Redis的作者提出了一种叫做presharding的方案来解决动态扩容和数据分区的问题,实际就是在同一台机器上部署多个Redis实例的方式,当容量不够时将多个实例拆分到不同的机器上, 这样实际就达到了扩容的效果

拆分过程如下:

  1. 在新机器上启动好对应端口的Redis实例。
  2. 配置新端口为待迁移端口的从库。
  3. 待复制完成,与主库完成同步后,切换所有客户端配置到新的从库的端口。
  4. 配置从库为新的主库。
  5. 移除老的端口实例。
  6. 重复上述过程迁移好所有的端口到指定服务器上。

依然有缺点,手工处理和迁移过程失败的风险

五、reids 3.0

分片: Redis Cluster在设计中没有使用一致性哈希(Consistency Hashing),

而是使用数据分片引入哈希槽(hash slot)来

旁白:

Redis Cluster是自己做的crc16的简单hash算法,没有用一致性hash。Redis的作者认为它的crc16(key) mod 16384的效果已经不错了,虽然没有一致性hash灵活,但实现很简单 使用单节点时的redis时只有一个表,所有的key都放在这个表里; 改用Redis Cluster以后会自动为你生成16384个分区表,

当前集群有3个节点,槽默认是平均分的: 节点 A (6381)包含 0 到 5499号哈希槽. 节点 B (6382)包含5500 到 10999 号哈希槽. 节点 C (6383)包含11000 到 16383号哈希槽.

数据迁移

数据迁移可以理解为slot(槽)和key的迁移,这个功能很重要,极大地方便了集群做线性扩展,以及实现平滑的扩容或缩容。 使用哈希槽的好处就在于可以方便的添加或移除节点。 当需要增加节点时,只需要把其他节点的某些哈希槽挪到新节点就可以了; 当需要移除节点时,只需要把移除节点上的哈希槽挪到其他节点就行了;

我们以后新增或移除节点的时候不用先停掉所有的 redis 服务

需要维护迁移过程中的状态,依赖程序的健壮性。

Q1 你感觉方案四和方案5差不都,都需要迁移数据,区别在哪里?

2P架构

P2P模式,无中央结点,结点之间通过一个称之为Gossip的协议进行通信。

  • 为什么是16384?

很显然,我们需要维护节点和槽之间的映射关系,每个节点需要知道自己有哪些槽,并且需要在结点之间传递这个消息。

为了节省存储空间,每个节点用一个Bitmap来存放其对应的槽:

2k = 2*1024*8 = 16384,也就是说,每个结点用2k的内存空间,总共16384个比特位,就可以存储该结点对应了哪些槽。然后这2k的信息,通过Gossip协议,在结点之间传递。

  • 客户端存储路由信息

对于客户端来说,维护了一个路由表:每个槽在哪台机器上。这样存储(key, value)时,根据key计算出槽,再根据槽找到机器。

  • 无损扩容

虽然Hash环可以减少扩容时失效的key的数量,但毕竟有丢失。

而在redis-cluster中,当新增机器之后,槽会在机器之间重新分配,

同时被影响的数据会自动迁移,从而做到无损扩容。

旁白:槽会在机器之间重新分配,依然有不命中的现象,需要二次查询

  • 主从复制

redis-cluster也引入了master-slave机制,从而提供了fail-over机制,

这很大程度上解决了“缓存雪崩“的问题。

旁白:升级从库 这个方案有这个缺点

总结如下:

1、节点自动发现

2、slave->master 选举,集群容错

3、Hot resharding:在线分片

4、基于配置(nodes-port.conf)的集群管理

5、客户端与redis节点直连、不需要中间proxy层.

6、所有的redis节点彼此互联(PING-PONG机制),内部使用二进制协议优化传输速度和带宽.

引用:

1 数据库秒级平滑扩容架构方案

2 http://antirez.com/news/110

本文参与 腾讯云自媒体分享计划,分享自微信公众号。
原始发表:2019-02-27,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 Offer多多 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 一、水平分库扩展问题
  • 二、停服迁移
  • 三、升级从库
  • 四、双写迁移
    • 1.增加新库写链接
      • 2.新老库数据迁移
        • 3.数据校检
          • 4.分片配置修改
          • 四、reids 3.0 以前
          • 五、reids 3.0
          相关产品与服务
          云数据库 Redis
          腾讯云数据库 Redis(TencentDB for Redis)是腾讯云打造的兼容 Redis 协议的缓存和存储服务。丰富的数据结构能帮助您完成不同类型的业务场景开发。支持主从热备,提供自动容灾切换、数据备份、故障迁移、实例监控、在线扩容、数据回档等全套的数据库服务。
          领券
          问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档