从 MySQL 到 TiDB

Posted on Jul 11, 2020

MySQL 窘境

扩展

数据以前所未有的速度增长,例如从 GB 到 TB,甚至到 PB;负载增加,例如单位时间请求数、I/O 次数、活跃用户数激增;这可能引起单机 MySQL server 性能下降,甚至不可用。我们想尽办法扩展 MySQL,通常,要么采购更强大的机器作为数据库服务器,这是一种纵向扩展(scale up);要么对 MySQL 的库或表进行分片(MySQL Sharding),即将数据集分离得到多个子数据集,任意两个子数据集可能存储在同一台机器上,也可能存储在不同机器上,这是一种横向扩展(scale out)

纵向扩展需要应对一些问题:

  • 成本增长过快。如果把一台机器的 CPU 核数增加一倍,主存和磁盘各扩容一倍,则最终总成本增加不止一倍。
  • 预见性能瓶颈。一台机器尽管拥有两倍的硬件指标但却不一定能处理两倍的负载。
  • 有限容错能力。显然无法提供异地容错能力。

横向扩展一般不需要高端的硬件或机器,但需要多台一般的机器和应对分布式系统的许多挑战:

  • 故障与部分失效。
  • 不可靠的网络。
  • 不可靠的时钟。

因为关心系统应对负载增加的能力(可扩展性),所以关心系统容错能力(可用性与一致性)

达成以上目标至少需要分区,对于 Elasticsearch 和 SolrCloud 以及 MongoDB 来说是 shard;对于 Cassandra 和 HBase 分别是 vnode 和 region。

为什么分区

  • 增强可扩展性。海量数据分布在更多磁盘上,查询负载分布到更多处理器上。
  • 分区容错。数据分区数据复制通常结合使用,即每个分区在多个结点上存有副本(replica)

data-partition

副本的优势:

  • 容错。容忍结点失效,支持故障转移。
  • 横向扩展。采用多结点来处理更多的请求。
  • 就近访问。将副本部署到距离用户更近的地方。

Partitioning

项目开始初期,什么情况下适合对 MySQL 分库分表?阿里巴巴 Java 开发手册在“MySQL 数据库“章节中有一则推荐,仅供参考:

【推荐】单表行数超过 500 万行或者单表容量超过 2 GB,才推荐进行分库分表。 说明:如果预计三年后的数据量根本达不到这个级别,请不要在创建表时就分库分表。

我们通常使用垂直分区(vertical partitioning)和水平分区(horizontal partitioning),如下图所示:

DB_image_1_cropped

VP1 和 VP2 表现得像两张可通过 ID 关联起来的表,HP1 和 HP2 的 scheme 和列(columns)相同,但行(rows)不同。行的增长速度通常快于列的增长速度,我们更关注水平分区,数据库分片是数据库或搜索引擎的水平分区;但新问题随之而来:假设 HP1、HP2、HP3、HP4…… 包含了多张表的数据,且由多个 MySQL server 维护,如果客户端要查找满足给定条件的一行或多行记录,那么它应该向哪个或哪些 MySQL server 发起请求?如何合并多个结点返回的结果集?如何执行跨分区 JOIN、排序、分页、分组等操作?如何保证分布式事务?

在以前,上面问题的解决方案常常是数据库中间件,数据库中间件的设计模式至少有两种,ProxySmart Client。Proxy 是应用程序与数据库集群的中间层,它对客户端制造了单一数据库实例的假象,客户端发送到 Proxy 的请求将由 Proxy 分发给下层的数据库服务器;Smart Client 是与应用程序集成的库或框架,客户端向数据库集群发起的请求将由客户端路由。两者的图像可参考代理分发客户端路由

目前,开源且较活跃的数据库中间件有 ShardingSphereMyCATvitess 等等,一直以来勉强可用,但是前两者的缺点也不容忽视:

  • 侵入性。要求用户指定 shard key 和其它分片配置;如果原业务逻辑包含 JOIN、subquery 等复杂 SQL,改动工作量可能难以估计。
  • 不支持透明分片。维护分片或集群的代价随着结点的增多而非线性增长。
  • 暂不支持弹性伸缩。据说都还在开发中。
  • 复杂查询优化能力较弱。不能生成最优的执行计划(plan),许多优化工作推卸给应用程序。

如何将原始表(orginal table)的记录分配给多个 MySQL server(实例、进程)?数据库中间件是应用程序级别的实现,MySQL Cluster 则是数据库级别的实现,它声称支持跨结点自动分片(分区),可是它是通过 NDB 存储引擎实现的,不温不火。

MySQL_Cluster_Scalability_v1

主从复制

MySQL 的数据复制模型是主从复制。传统主从复制图像:主结点(主副本、主库)处理读/写请求,若是写请求则通过同步复制或异步复制将数据变更日志(事件或日志记录)发送到所有从结点(从副本、从库),从结点按照日志(日志记录或事件)写副本;一般情况,从结点只读,故障转移时从结点可提升为主结点。传统的同步复制侧重一致性,要求”短暂“的不可用,主结点需要等待从结点的确认;传统的异步复制侧重可用性,要求“短暂”的不一致,从结点滞后于主结点。

如果把所有从结点配置为同步复制模式,那么任何失效或性能下降的从结点会导致系统阻塞。MySQL 支持设置半同步模式,某一个从结点配置为同步模式,其它从结点配置为异步模式;当同步模式的从结点失效时,另一个从结点从异步模式提升为同步模式,这么做的好处之一是保证至少有两个结点(主结点和同步模式的从结点)拥有最新的数据副本。半同步并非高枕无忧,微信后台团队在 MySQL 半同步复制的数据一致性探讨中总结了 MySQL 的半同步复制和 Master 切换都存在一些不足,数据复制存在回滚难题,Master 切换存在多 Master 难题。

主从复制模型不能保证同时满足强一致性和高可用性。如果出现结点失效、网络中断、延迟抖动等情况,多主结点复制方案会更加可靠,但是代价则是系统的高复杂度和弱一致性保证。多主结点复制适用于多数据中心,每个数据中心采用常规的主从复制方案,各个数据中心的主结点负责与其它数据中心的主结点交换 replicated log。

小结

面对透明分片(transparent sharding)弹性伸缩(auto-scaling)自动恢复(auto-failover)异地多活(multi-data center)~等需求,传统的解决方案使我们陷入窘境。

SQL 和 NoSQL

ACID 和 BASE

为什么不使用 NoSQL 数据库代替 MySQL 数据库呢?假设我们有了大刀阔斧迁移数据和重写应用程序的决心(基本不可能),但是许多 NoSQL 数据库都牺牲了一致性,而倾向于可用性,这种一致性模型往往被称为 BASE:

  • 基本可用性(Basically Available)。
  • 软状态(Soft state)。类似中间状态。
  • 最终一致性(Eventual consistency)。

BASE 模凌两可,太长或永远不一致的系统基本不可用。许多处理重要数据的系统(例如,财务、订单、互联网金融系统等)随着快速增长的数据和负载,常规的关系数据库扩展困难,而对于 ACID ,特别是一致性的要求,NoSQL 数据库难以满足。相较于 BASE 的承诺,关系数据库的 ACID 的承诺是五十步笑百步(一致性往往推卸给应用程序):

  • 原子性(Atomicity)。事务中的操作序列,要么全部执行成功(提交),要么全部不执行(回滚)。
  • 一致性(Consistency)。不蕴含矛盾,逻辑自洽……
  • 隔离性(Isolation)。同时运行的事务不应相互干扰,事务提交时,其结果与串行执行完全相同。
  • 持久性(Durability)。无完美或绝对的保证。

数据模型与查询语言

NoSQL 数据库缺乏 JOIN 的能力,这是其文档模型的限制。关系数据库市场占有率一直居高不下,参考 DB-Engines Ranking,原因之一是 SQL(DDL、DML、DQL、DCL) 是声明式语言的代表,它的简单与统一在于指定结果所满足的模式,在不改变语句的前提下,查询优化器或底层引擎的迭代更新就有可能显著提高性能。

SQL 的关系模型理论足够优雅:

  • 关系是笛卡尔积的一个子集。
  • 关系(表)是元组(行)的集合。
  • 关系(表)经过运算以后,如 SELECT、JOIN、WHERE、交、并、差(关系代数),结果还是一个关系(表)。

1920px-Cartesian_Product_qtl1

近年来,流行开源组件提供类 SQL 的趋势越来越明显,例如 Spark SQLKSQLFlink SQLClickHouse SQL

NewSQL 新在哪

NewSQL 是一类关系数据库系统,旨在为 OLTP 提供 NoSQL 数据库系统的可扩展性,同时提供传统关系数据库系统的 ACID 保证。OLTP 与 OLAP 有时区分并不是那么明显,前者侧重 T(事务),后者侧重 A(分析):

OLTP与OLAP

NewSQL 数据库系统新在架构、存储引擎、共识算法。

你好 TiDB

TiDB 项目受到了 Spanner/F1Raft 的启发,TiKV 对应的是 Spanner,TiDB 对应的是 F1,详情请看 TiDB 整体架构TiDB 数据库的存储TiDB 数据库的计算调度概述……

tidb-overview

SQL Layer 无状态,可以通过负载均衡组件(如 LVS、HAProxy、F5)对外提供统一的接入地址。

tiup playground v4.0.0 --db 3 --kv 4 --pd 3 --monitor

如上所示,使用 tiup 启动了 3 个 TiDB 实例和 4 个 TiKV 实例以及 3 个 PD 实例。

Waiting for tikv 127.0.0.1:20160 ready
Waiting for tikv 127.0.0.1:20161 ready
Waiting for tikv 127.0.0.1:20162 ready
Waiting for tikv 127.0.0.1:20163 ready
CLUSTER START SUCCESSFULLY, Enjoy it ^-^
To connect TiDB: mysql --host 127.0.0.1 --port 4000 -u root
To connect TiDB: mysql --host 127.0.0.1 --port 4001 -u root
To connect TiDB: mysql --host 127.0.0.1 --port 4002 -u root
To view the dashboard: http://127.0.0.1:2379/dashboard
To view the monitor: http://127.0.0.1:9090

TiDB 非常友好地兼容了 MySQL 5.7 协议,从 MySQL 迁移到 TiDB 对应用程序无限接近零侵入。这里我们可以通过 MySQL 命令行工具连接某一个 TiDB 实例。

% mysql --host 127.0.0.1 --port 4000 -u root
Welcome to the MySQL monitor.  Commands end with ; or \g.
Your MySQL connection id is 5
Server version: 5.7.25-TiDB-v4.0.0 TiDB Server (Apache License 2.0) Community Edition, MySQL 5.7 compatible

Copyright (c) 2000, 2020, Oracle and/or its affiliates. All rights reserved.

Oracle is a registered trademark of Oracle Corporation and/or its
affiliates. Other names may be trademarks of their respective
owners.

Type 'help;' or '\h' for help. Type '\c' to clear the current input statement.

mysql> show databases;
+--------------------+
| Database           |
+--------------------+
| INFORMATION_SCHEMA |
| METRICS_SCHEMA     |
| PERFORMANCE_SCHEMA |
| mysql              |
| test               |
+--------------------+
5 rows in set (0.00 sec)

导入样本数据集到某一个 TiDB 实例,样本数据集的 database 名称为 employees。

% mysql --host 127.0.0.1 --port 4001 -u root < employees.sql
INFO
CREATING DATABASE STRUCTURE
INFO
storage engine: InnoDB
INFO
LOADING departments
INFO
LOADING employees
INFO
LOADING dept_emp
INFO
LOADING dept_manager
INFO
LOADING titles
INFO
LOADING salaries
data_load_time_diff
NULL

我们会发现,连接任意一个 TiDB 实例都能观察到 employees 库。

mysql> show tables from employees;
+----------------------+
| Tables_in_employees  |
+----------------------+
| current_dept_emp     |
| departments          |
| dept_emp             |
| dept_emp_latest_date |
| dept_manager         |
| employees            |
| salaries             |
| titles               |
+----------------------+
8 rows in set (0.00 sec)

TiKV 数据分区的术语是 Region,使用 Raft 对写入数据在多个 TiKV 结点(实例)之间自动分片和复制日志,每个 Region 的所有副本(默认为三副本)组成一个 Raft Group,每个 TiKV 实例(结点)负责多个 Region。

tikv-overview.png

同城多数据中心部署两地三中心部署则提供了更强大的容错(容灾)能力。

本文首发于 https://h2cone.github.io/

参考资料