Vitess自2011年以来一直为YouTube的所有数据库流量提供服务,目前已被许多企业采用,以满足其生产需求。
Vitess是一个用于部署、扩展和管理大型mysql实例集群的数据库解决方案。它的架构是在公共或私有云架构中高效运行,就像在专用硬件上一样。它结合并扩展了许多重要的mysql特性和nosql数据库的可伸缩性。
Vitess可以解决以下问题
1. 实现MYSQL的分片,应用只需很少的更改或无需更改
2. 部署和管理大型数据库集群实例
3. 裸设备到云的迁移
Vitess相对MySQL改进
MySQL | Vitess |
---|
Vitess整体架构
Topology Topology服务是一个包含服务器信息,分片方案和主从信息的元数据信息存储服务。Topology服务是基于一致性存储方案来实现数据一致性,例如:zookeeper和etcd。用户可以通过使用vtctl(命令行)和vtctld(web)访问Topology。
vtgate vtgate是一个轻量代理服务器,它将查询路由到正确的vttablet并将合并的结果返回给客户端。vtgate接收到请求时会对sql进行解析,根据Topology服务中的元数据判断路由到哪个vttablet; vtgate是直接接受应用程序发起的查询的服务。因为客户端只需要能够找到一个vtgate实例即可正常访问,所以客户端的实现可以非常简单。
vttablet vttablet是一个位于MySQL数据库实例前面的代理服务器,并且要求与对应的MYSQL实例部署在同一个Pod上。在JED中对于每个MySQL实例对应的都有一个Tablet实现。
vtctl vtctl是用于管理弹性数据库集群的命令行工具。它允许人或应用程序轻松地与弹性数据库实现交互。通过Vtctl可以标识主从数据库, 创建表, 启动故障转移, 执行分片(重新分片)等操作。
vtctld vtctld是一个HTTP服务器,允许您浏览存储在锁服务器中的信息。
vtworker vtworker负责处理长时间运行的进程。如: 1. 在分片分割和连接过程中重新划分不同的作业检查数据完整性 2. 垂直分割不同作业检查垂直分割和连接期间的数据完整性
vtctlclient 通过客户端进行基本功能的校验。 使用方法:vtctlclient -server 127.0.0.1:15999 command param
Vitess的使用方式
Vitess借助Kubernetes能更好的实现逻辑库及节点管理。
Kubernetes 是 Google 开源的 Docker 容器集群管理系统,Vitess 是 Kubernetes 用户的逻辑存储引擎的一个可选项。 Kubernetes 对一个计算集群中的节点进行调度,对这些节点之上的负载进行管理,并对包含有同一应用的容器进行分组以易于管理和发现。使用 Kubernetes,你可以很容易去创建和管理一个 Vitess 集群,可谓开箱即用。
基本概念:
keyspace | keyspace 是逻辑上的数据库,在非shard场景下,一个keyspace对应一个MYSQL DataBase。从Keyspace中读取数据和从一个MYSQL DataBase中读取数据很像。但是根据读取数据时不同的一致性要求,可以从一个master database或者从一个replica读取数据。当一个keySpace被sharding成多个Shard时,一个keyspace会对应多个MYSQL database。在这种情况下一个查询会被路由到一个或者多个shard上,这取决于请求的数据所在的位置。 |
---|
从上面可以看出一个KeySpace可以跨越多个Mysql实例,而且每个Shard中的数据都是KeySpace中的一个数据子集,一个KeySpace由分散在各个Shard中的数据子集组成。每个Shard在物理硬件上由多个MySQL实例和Tablet实例组成,一个shard具体包括:一个Master Mysql实例、一个Replica MySQL实例、一个ReadOnly MySQL实例和三个Tablet实例。
基本概念1Topology
Topology服务是一个包含:服务器信息、 Sharding路由信息和MySQL主从信息的元数据信息服务。 Topology服务基于一致性存储方案来实现数据一致性, 例如:zookeeper和etcd。
2KeySpace
KeySpace是逻辑上的数据库。
在非Shard场景下,一个KeySpace 直接定位到一个指定的数据库。对KeySpace的读写操作和直接对相应数据库进行读写操作类似。
在Shard场景下,一个KeySpace会对应多个MySQL Database,这时对KeySpace的读写操作会由vtgate根据路由配置信息路由到一个或者多个MySQL Database。
3Shard
一个Shard就是在一个KeySpace中的一个水平分区或者分片。每个 Shard 都会包含一个master实例和多个slave实例。而且不同的Shard之间不会存在数据重叠的现象。
4Resharding
Vitess支持动态Resharding,也就是说Vitess的Resharding过程可以把一个或者多个Shard分割成更多更小的Shard,也可以将相邻的Shard合并成一个Shard。
在动态Resharding过程中,Source Shard中的数据被拷贝到Destination Shard中,并且Destination Shard可以通过过滤复制一直保持和Source Shard数据一致(忽略复制延迟),还可以通过工具来验证数据是否一致。一旦Resharding完成,会切换到新的Shard、删除旧的Shard。
5vttablet
vttablet可以视作一个MySQL实例前的代理服务器,一般和MySQL实例部署到同一台机器上,Vitess集群对MySQL所有的操作都是通过vttablet来实现的,在Vitess中对于每个MySQL实例对应的都有一个vttablet实现,如图1所示。
6Tablet
mysqld进程和对应的vttablet的组合,我们称之为Tablet。每个Tabblet都会有一个类型或者说状态,我们称之为Tablet Type,Tablet Type决定了Tablet对外提供何种服务。注意区分Tablet和vttablet是两个不同的概念。
7Tablet Type
8Key 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上。
下面是几个例子:
图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的逻辑库的如下信息:
图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。
图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
图5 Tablet架构
现在通过lvtctl.sh ListAllTablets test命令就可以看到新添加的Table有master了,如图6所示。
图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所示。
图7 Resharding
需要注意的是:
Vitess不支持多于两个Shard同时覆蓋到任意一部分或者一段KeyRange,比如在同一个KeySpace里有[0x00]-[0x80], [0x00]-[0x60] 和[0x40]-[0x80]三个Shard,[0x40]-[0x60]这一区间有被三个Shard覆蓋,这时Vitess不确定将数据拷贝到哪个Shard。Vitess就会停止克隆数据并提示错误信息。
图8 Destination Shard重叠
Vitess会检查left 和right所覆蓋的区间是否一致,比如left 覆蓋的整个KeyRange。但是right没有覆蓋整个KeyRange。Vitess会停止克隆数据并提示错误信息。
图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的快照数据。
图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所示。
图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数据不一致,就会打印出相关信息,如果数据一致,就会输出以下信息:
图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
6Resharding过程对业务的影响
以上就是Vitess做Resharding的简要过程,从中可以看出,对线上业务产生影响的只是步骤五,在切换到新的Shard过程中需要先停止master服务,Destination Shard没有复制延迟后停止过滤复制更新路由。线上没有很长的复制延迟的话整个切换过程几秒钟就可以完成。
总结
除了上文中提到的水平拆分,Vitess还支持垂直拆分。垂直拆分的意思就是根据业务的相关性将表放到多个KeySpace。比如一个很复杂的订单系统中涉及到了用户信息、商品信息,所有的数据都存放到了order_keyspace。如果做垂直拆分,可以将用户信息放到user_keyspace,商品信息放到product_keyspace。垂直拆分实际是做了业务拆分。同样整个垂直拆分过程对读操作没有任何影响,仅有几秒钟的时间不能写入数据。垂直拆分数据迁移思路和水平拆分类似,不再做详细介绍。
有了Vitess的背景知识,小A是不是也可以使用类似的思路做数据迁移呢。比如将原来的数据库先挂两个从库,没有复制延迟后,停止原数据库读写服务,将两个从库摘下并删除冗余数据(如果冗余数据对业务无影响也可以后续慢慢删),开始使用新添加的数据库提供服务。虽然没有Vitess的方法高大上,但是也不失为一种选择。
http://vitess.io
https://github.com/youtube/vitess
https://groups.google.com/forum/#!forum/vitess
Vitess Resharding介绍