Vitess Resharding介绍

Posted 京东商城技术架构

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Vitess Resharding介绍相关的知识,希望对你有一定的参考价值。

Vitess是一个通过Sharding方式对mysql进行水平扩展的数据库集群系统。


通过封装路由信息,Vitess做到了对业务透明,业务逻辑中可以无感知的通过一条SQL语句对一个或者多个分片进行读写操作。 此外通过Vitess,根据业务需要可以拆分或合并Shard,整个Resharding过程对线上业务影响只有几秒钟。


这篇文章主要介绍Vitess在基于区间(Range-based)的Sharding方式下是如何做水平拆分的,Vitess是如何做到一键Resharding并且几乎不影响线上业务的。在介绍Resharding之前,简要介绍一下Vitess的整体架构和Resharding过程中涉及的几个基本概念。


整体架构

图1 Vitess架构图


Vitess中业务应用节点Application通过驱动连接到vtgate发送请求,vtgate对sql进行解析、路由然后发送到对应的vttablet、MySQL,然后再将MySQL、vttablet返回结果合并处理返回给Application。


可以简单地认为Topology管理路由信息,vtgate到Topology请求到路由信息后,就可以对外部应用提供服务。vttablet是MySQL实例前的一个代理服务器,用来管理MySQL。管理员、DBA可以通过Vtctl管理整个Vitess集群。


基本概念
1
Topology

Topology服务是一个包含:服务器信息、 Sharding路由信息和MySQL主从信息的元数据信息服务。 Topology服务基于一致性存储方案来实现数据一致性, 例如:zookeeper和etcd。

2
KeySpace

KeySpace是逻辑上的数据库。


在非Shard场景下,一个KeySpace 直接定位到一个指定的数据库。对KeySpace的读写操作和直接对相应数据库进行读写操作类似。


在Shard场景下,一个KeySpace会对应多个MySQL Database,这时对KeySpace的读写操作会由vtgate根据路由配置信息路由到一个或者多个MySQL Database。

3
Shard

 一个Shard就是在一个KeySpace中的一个水平分区或者分片。每个 Shard 都会包含一个master实例和多个slave实例。而且不同的Shard之间不会存在数据重叠的现象。

4
Resharding

Vitess支持动态Resharding,也就是说Vitess的Resharding过程可以把一个或者多个Shard分割成更多更小的Shard,也可以将相邻的Shard合并成一个Shard。


在动态Resharding过程中,Source Shard中的数据被拷贝到Destination Shard中,并且Destination Shard可以通过过滤复制一直保持和Source Shard数据一致(忽略复制延迟),还可以通过工具来验证数据是否一致。一旦Resharding完成,会切换到新的Shard、删除旧的Shard。

5
vttablet

vttablet可以视作一个MySQL实例前的代理服务器,一般和MySQL实例部署到同一台机器上,Vitess集群对MySQL所有的操作都是通过vttablet来实现的,在Vitess中对于每个MySQL实例对应的都有一个vttablet实现,如图1所示。

6
Tablet

mysqld进程和对应的vttablet的组合,我们称之为Tablet。每个Tabblet都会有一个类型或者说状态,我们称之为Tablet Type,Tablet Type决定了Tablet对外提供何种服务。注意区分Tablet和vttablet是两个不同的概念。

7
Tablet Type
  • master:mysqld为master,相应Tablet对外提供读写服务

  • replica:mysqld为slave,在master挂掉的时候可能会转换成master

  • rdonly::mysqld为slave,通常对外提供大量数据处理服务,比如备份数据、Resharding等

  • backup:mysqld为slave并且已经停止主从复制数据,可以通过其数据快照备份数据

  • resotre:一个Tablet启动的时候没有数据,在从最新的备份恢复备份数据的过程中其类型为restore

  • drained:vitess进行数据处理的时候独占某个Tablet,会将其标记为drained类型,比如在Resharding的时候就会将一个rdonly类型的Tablet转变为drained

8
Key Ranges and Partitions

Vitess使用 Key Ranges来决定应该由哪个Shard来处理特定的读写操作。


Key Range是连续的Sharding Key序列,一个Key Range有起始值和终止值,如果一个key大于等于某个Key Range的起始值且小于终止值,那么这个key就落到了这个KeyRange。


一个空的终止值代表最大值,一个空的起始值代表最小值。


Vitess在路由计算之前先将Sharding Key转换成字节数组,[0x80]是Sharding key的一个中间值。比如一个KeySpace如果有两个Shard,如果Sharding key对应的字节数组小于0x80的可以落到一个Shard上,大于或等于0x80则落到另一个Shard上。


下面是几个例子:


Vitess Resharding介绍

图2 KeyRange


我们用图2的线段来代表KeyRange,线段的最小值[0x00]、最大值[0xFF]。从[0x00]到[0xFF]整条线段代表了整个KeyRange。


Start=[0x00], End=[0xFF]: 整个Key Range,也可以用空值表示,比如Start=[], End=[]

Start=[0x00], End=[0x80]:小于0x80的 Key Range,整个Range的前1/2

Start=[0x80], End=[0xFF]:大于0x80的 Key Range,整个Range的后1/2

Start=[0x40], End=[0x80]: 第二个1/4 Key Range.,整个Range的1/4


有了上面的几个基本概念,就可以介绍Resharding了。


Resharding介绍

假设一个场景,程序员小A在构建项目的时候使用了MySQL数据库,MySQL丰富的特性很好的满足了当时的业务需要,不料业务发展过快,不到一年,单表数据量几千万,查询开始变慢,数据库服务器的磁盘也快满了。数据库都是热数据,不能做结转,现在怎么办,使用昂贵的Oracle,还是做分库分表?使用Oracle成本太高,那就分库分表吧。


分库就涉及到了历史数据迁移,迁移历史数据过程中还在不断有数据更新和写入,如何在迁移大量数据的过程不对服务中的数据库产生过大压力、如何保证数据一致性、如何做新旧数据库的切换、又如何保证对线上业务影响最小,想到这里程序员小A炸了。


下面让我们看看Vitess是怎么做的,或许能给小A一些启发。


1
初始化Tablet

初始化一个Tablet,可以简单的理解为启动一个mysqld进程和一个vttablet,将两者绑定并指定一个类型,比如master、replica或者rdonly。


使用Vitess自带脚本vttablet-up.sh,直接执行无需添加任何参数(相关参数在脚本中封装了)就可以初始化一个没有做分片的Shard,该Shard包含了五个Tablet。


使用./lvtctl.sh InitShardMaster -force test_keyspace/0 test-0000000100命令可以指定test_keyspace的名称为test-0000000100的Tablet作为master。


最初业务没有做分片,也就是KeyRange为Start=[0x00], End=[0xFF],所有数据落到一个Shard上。通过Vtctl提供的命令工具./lvtctl.sh ListAllTablets test可以查看名称为test_keyspace的逻辑库的如下信息:


Vitess Resharding介绍

图3 Tablet


第三列中的0代表整个KeyRange,也就是没有做Sharding,Start=[0x00], End=[0xFF]。


第四列中的master、replica、rdonly就是Tablet Type信息。可以看到五个Ttablet一个master,两个replica,两个rdonly。


后来业务发展过快,单机数据库实例难以满足业务需求,需要做Sharding,将数据分到两个Tablet上,key基本服从均匀分布,所以将Key Range分别设置为:


Shard0:Start=[0x00], End=[0x80]

Shard1: Start=[0x80], End=[0xFF]


使用Vitess提供脚本./sharded-vttablet-up.sh添加两个Shard,每个Shard五个Tablet。


Vitess Resharding介绍

图4 新添加Tablet


可以看出,两个Shard已经按照上面的KeyRange添加成功。每个Shard有五个Tablet,三个replica、两个rdonly,但是没有master。


现在需要初始化主从信息,将test-0000000200、test-0000000300置为master。这样Vitess可以通过过滤复制将数据复制到master,而master到replica、rdonly的复制过程通过主从复制完成,如图5所示。


通过以下命令可以完成初始化master的工作:


./lvtctl.sh InitShardMaster -force test_keyspace/-80 test-0000000200

./lvtctl.sh InitShardMaster -force test_keyspace/80- test-0000000300


Vitess Resharding介绍

图5 Tablet架构


现在通过lvtctl.sh ListAllTablets test命令就可以看到新添加的Table有master了,如图6所示。


Vitess Resharding介绍

图6 初始化Tablet主从


2
拷贝表结构

初始化Tablet后,下一步将Source Shard的表结构信息复制到Destination Shard上。通过以下命令完成相应操作:


./lvtctl.sh CopySchemaShard test_keyspace/0 test_keyspace/-80

./lvtctl.sh CopySchemaShard test_keyspace/0 test_keyspace/80-


3
迁移数据

./sharded-vtworker.sh SplitClone test_keyspace/0

3.1  初始化Source Shard、Destination Shard信息

迁移数据,通过以./shard-vtworker.sh SplitClone test_keyspace/0命令就可以将Source Shard的数据迁移到Destination Shard。该命令虽然只是指定了Source Shard的信息,但是Vitess会自动去寻找所有的Shard并且根据KeyRange将所有Shard分成Range没有重合两份,两份分别标记为left、right,然后通过查询Shard是否在服务中(Serving/NotServing)来区分Source Shard和Destination Shard,一般而言,处于Serving状态的是Source Shard,而处于NotServing状态的是Destination Shard,如图7所示。


Vitess Resharding介绍

图7 Resharding


需要注意的是:

Vitess不支持多于两个Shard同时覆盖到任意一部分或者一段KeyRange,比如在同一个KeySpace里有[0x00]-[0x80], [0x00]-[0x60] [0x40]-[0x80]三个Shard[0x40]-[0x60]这一区间有被三个Shard覆盖,这时Vitess不确定将数据拷贝到哪个ShardVitess就会停止克隆数据并提示错误信息。


Vitess Resharding介绍

图8 Destination Shard重叠


Vitess会检查left 和right所覆盖的区间是否一致,比如left 覆盖的整个KeyRange。但是right没有覆盖整个KeyRange。Vitess会停止克隆数据并提示错误信息。


Vitess Resharding介绍

图9 left、right覆盖区间不一致


3.2  合理性检查-SanityCheck

正式克隆数据之前,vitess会就以下列表中的条件进行检查如果任意一个条件不满足就返回错误并停止克隆数据。


a、Destination Shard的master已经存在

b、Destination Shard的Tablet只有master、replica、rdonly三种类型

c、Destination Shard的Tablet都为未上线(NotServing)状态

d、Source Shard为线上(Serving)状态


3.3  克隆数据

a、选择Source Shard的一个类型为rdonly的Tablet,我们称之为Source Tablet,修改成drained类型,在每一个Destination Shard选择一个类型为master的Tablet,我们称之为Destination Tablet。


b、拷贝数据

Vitess给每个Destination Tablet创建一个insertChannel,每个insertChannel都有插入缓冲和流量控制器Throttler(可根据网络流量、机器内存、cpu使用率、复制延迟等多个参数进行流量控制)。


insertChannel类似一个队列,有一生产者去Source Tablet批量读取数据,将读取到的数据插入到队列,有消费者从队列取出数据,然后根据Sharding Key插入相应的Destination Shard。如果Destination Shard有脏数据,克隆数据过程中Vitess会将脏数据删除或者修正;反之,在克隆数据之前将数据插入到Destination Shard是不可以的,数据会被删除!


克隆数据是并行完成的,并行的goroutine最高为Source Shard个数,通过信号量控制。


克隆数据过程中Source Tablet的数据库可能会有更新,所以克隆到Destination Tablet的数据可能和Source Tablet不一致。


c、拷贝数据完成后将Source Tablet(rdonly类型的Tablet)的MySQL从主库上摘下,停止主从复制,并将类型标记为drained,如图10所示。这时候就得到了一份Source Shard的快照数据


Vitess Resharding介绍

图10 克隆数据


d、重复b步骤,因为现在Source Tablet中是一份快照数据,不再进行更新,所以执行完成后就Source Tablet和Destination Tablet的数据是一致的。因为之前执行b步骤,Destination Shard中已有数据,所以d步骤执行速度比b步骤快很多。这样做的目的也就是为了避免Source Tablet停止服务时间过长。因为Source Tablet还需要对外提供复杂查询、数据分析等服务。b步骤是可选项目,通过参数控制,跳过b步骤,Vitess仍然能正确的克隆数据,但是Source Tablet会停止服务时间过长。


e、已有数据克隆完成后将Source Tablet的类型从drained修改成rdonly,将Source Tablet的MySQL实例重新挂到主库下开启主从复制。


f、在每个Destination Tablet上开启过滤复制,过滤复制就是在Destination Tablets上开启一个BinlogPlayer,BinlogPlayer去Source Tablet读取binlog,根据binlog和Sharding Key决定是否执行binlog,开始过滤复制后,Destination Tablets就能追齐因d步骤落下的数据,并保持Destination Tablet和Source Tablet数据的一致(忽略复制延迟),如图11所示。


Vitess Resharding介绍

图11 开启过滤复制


需要注意的是,克隆工作完成后过滤复制仍然是开启状态的,也就是说,忽略复制延迟的情况下,Source Shard和Destination Shard的数据是一致的。现在Source Shard的任何更新操作都可以在相应的Destination Shard上读取到。


克隆数据完成后,我们可以再插入一些数据,然后通过以下命令来查看新插入的数据在新的Shard是否能查询到、路由是否正确。


./lvtctl.sh ExecuteFetchAsDba test-0000000100 "SELECT * FROM messages"

./lvtctl.sh ExecuteFetchAsDba test-0000000200 "SELECT * FROM messages"

./lvtctl.sh ExecuteFetchAsDba test-0000000300 "SELECT * FROM messages"



4
数据一致性校验

Vitess做数据一致性校验原理和克隆数据类似,获取一个rdonly类型的Source Tablet,标记为drained类型。然后在Source Tablet的MySQL从master摘下的情况下,通过Vitess自身的过滤复制保证Source Tablet和Destination Tablet没有复制延迟。这时候再对表里的数据逐行进行比较,比较完成后再将Source Tablet的MySQL挂到主库,类型还原成rdonly类型。


执行以下命令,可以进行数据完整性校验:

./sharded-vtworker.sh SplitDiff test_keyspace/-80

./sharded-vtworker.sh SplitDiff test_keyspace/80-


如果Source Shard和Destination Shard数据不一致,就会打印出相关信息,如果数据一致,就会输出以下信息:


Vitess Resharding介绍

图12 校验数据

5
切换到新的Shard

若数据一致性校验通过,则可以切换到新的shard上,可以通过如下命令进行切换:


./lvtctl.sh MigrateServedTypes test_keyspace/0 rdonly

./lvtctl.sh MigrateServedTypes test_keyspace/0 replica

./lvtctl.sh MigrateServedTypes test_keyspace/0 master


以上三条命令分别是切换rdonly、replica、master类型的Tablet到新Shard。


迁移过程中首先需要迁移rdonly和replica模式的Tablet,最后迁移master。因为如果首先迁移了master,replica和rdonly还是原来的Shard提供服务,这时Source Shard数据已经不更新了,相当于replica和rdonly在使用一份历史快照数据提供服务。


下面介绍迁移master的主要步骤:


a、停止master的读写服务并得到master的binlog位置pos

b、等待所有的Destination Shard追binlog到 pos位置,保证无复制延迟

c、停止所有Destination Shard的过滤复制

d、修改DestinationShard的servedType为服务状态、更新路由信息并广播

e、最后确认服务正常,停止Source Shard的所有Tablet。

迁移rdonly和replica的过程类似不做介绍。


图13 切换到新的Shard

6
Resharding过程对业务的影响

以上就是Vitess做Resharding的简要过程,从中可以看出,对线上业务产生影响的只是步骤五,在切换到新的Shard过程中需要先停止master服务,Destination Shard没有复制延迟后停止过滤复制更新路由。线上没有很长的复制延迟的话整个切换过程几秒钟就可以完成。


总结


除了上文中提到的水平拆分,Vitess还支持垂直拆分。垂直拆分的意思就是根据业务的相关性将表放到多个KeySpace。比如一个很复杂的订单系统中涉及到了用户信息、商品信息,所有的数据都存放到了order_keyspace。如果做垂直拆分,可以将用户信息放到user_keyspace,商品信息放到product_keyspace。垂直拆分实际是做了业务拆分。同样整个垂直拆分过程对读操作没有任何影响,仅有几秒钟的时间不能写入数据。垂直拆分数据迁移思路和水平拆分类似,不再做详细介绍。


有了Vitess的背景知识,小A是不是也可以使用类似的思路做数据迁移呢。比如将原来的数据库先挂两个从库,没有复制延迟后,停止原数据库读写服务,将两个从库摘下并删除冗余数据(如果冗余数据对业务无影响也可以后续慢慢删),开始使用新添加的数据库提供服务。虽然没有Vitess的方法高大上,但是也不失为一种选择。


参考:

vitess.io

https://github.com/youtube/vitess

https://groups.google.com/forum/#!forum/vitess


以上内容为IPD原创,如需转载,请注明出处~

以上是关于Vitess Resharding介绍的主要内容,如果未能解决你的问题,请参考以下文章

云原生-vitess数据库

手把手部署原生vitess集群(非k8s部署)

vitess元数据跨机房灾备解决方案

开源中间件Vitess助力MySQL实现分库分表

Vitess告诉你两阶段提交到底长啥样

对比 Vitess,ShardingSphere 有哪些不同