撬动分布式技术时代, 金融客户持续在国产数据库行业领先, 他们凭什么?
为帮助开发者更好地了解和学习分布式数据库技术, 2020 年 3 月, 腾讯云数据库, 云加社区联合腾讯 TEG 数据库工作组特推出为期 3 个月的国产数据库专题线上技术沙龙《你想了解的国产数据库秘密, 都在这!》, 邀请数十位鹅厂资深数据库专家每周二和周四晚上在线深入解读 TDSQL,TBase,CynosDB 三款鹅厂自研数据库的核心架构, 技术实现原理和最佳实践等. 三月为 TDSQL 专题月, 本文将带来直播回顾第一篇《腾讯自研分布式数据库 TDSQL 核心架构及特性拆解》.
视频内容
大家好, 我今天分享的主题是基于计费海量场景自研演进的分布式数据库 TDSQL 的核心架构解读.
今天这个分享分为五个部分:
第一章, 产品简介以及适用场景
第二章, TDSQL 架构分析及模块介绍
第三章, 数据一致性保障
第四章, 分布式 TDSQL 实践
第五章, 数据同步与备份
一, TDSQL 是什么: 腾讯如何打造一款金融级分布式数据库
我们先初步了解 TDSQL 产品, 以及它的适用场景. 第一章包括四个方面: 使用场景, 发展历程, 核心特性, 以及兼容性.
首先, TDSQL 是腾讯推出的一款兼容 MySQL 的自主可控, 高一致性分布式数据库产品. 这里我们强调一点, 高度兼容 MySQL--TDSQL 完全兼容 MySQL 协议, 并且做到完全自主可控, 数据强一致性. 第二是 TDSQL 具备分布式的特性, 具备一个弹性扩展, 高可用的架构. 在互联网行业, 海量的用户流量场景很常见, 如果数据库不具备可伸缩性, 可扩展性, 是很难应对如: 电商的大型促销, 春节抢红包等突增流量的场景, 这些其实都是对数据库应对海量用户流量的考验.
目前 TDSQL 已经服务超过 500 + 的金融政企, 行业覆盖银行, 保险, 证券, 政务, 互联网金融等各个领域.
我们再看一下 TDSQL 的前世今生. TDSQL 最早可以追溯到 2002 年, 那个时候其实还不叫 TDSQL, 它是腾讯计费平台部的一个数据库服务, 当时使用了开源的 MySQL.2002 年 - 2007 年随着公司业务的发展, 腾讯所面临的用户量的压力也越来越大. 这个时候我们提出了 7*24 小时不宕机的高可用设计方案, 来保证数据库能提供 7*24 小时不间断连续高可用服务. 那个时候, 腾讯的增值业务日渐成规模, 业务对数据也越来越敏感, 对数据可用性的要求越来越高, 甚至平时还要防备一些像运营商的光纤被挖断等各种各样的异常场景.
在 2007 年 - 2012 年, 这可能是互联网时代从互联网到移动互联网的发展的快速 5 年. 当然, 公司的业务也是突飞猛进. 我们开始把这个高可用的数据库产品化. 到 2012 年, TDSQL 的雏形就已经出来了, 作为一款内部产品, 开始在公司内部提供金融级的数据强一致性, 可靠性服务.
从 2012 年起, TDSQL 已经在腾讯内部做得已经比较成熟, 已经是一个知名的产品了, 但是它一直没有对外做商业化. 2014 年恰逢一个很好的机会 -- 微众银行的成立. 微众银行做数据库选型的时候关注到了 TDSQL, 经过反复测试验证, 发现当时的 TDSQL 已经完全具备了微众银行对数据可用性和一致性的要求. 借此机会, TDSQL 成功在微众银行投产, 成为微众银行唯一的数据库, 覆盖了银行的核心业务.
所以说 2014 年, TDSQL 完成了商业化, 也实现了私有化部署. 2014 年以后, TDSQL 推广到了很多银行, 金融机构, 这过程中是借鉴了 2014 年 TDSQL 在微众银行成功实施的宝贵的经验.
因为在 2014 年微众银行的部署中, 我们也踩了很多坑, 也认识到在私有化部署环境的各种各样的挑战, 并一一攻克了这些挑战. 当 2014 年在私有化部署完成之后, 再到 2015 年 TDSQL 上公有云, 我们继续通过公有云服务打磨自己的产品.
所以从 2012 年作为一个内部产品到 2014 年的私有化部署, 再到 2015 年公有云上的部署, TDSQL 已经逐步从一个内部产品逐渐走向行业, 成为一个正式对外的商用数据库. 从 2015 年到 2019 年, TDSQL 已经推广到许多银行和金融政企. 但是很重要的一点是, 虽然服务了很多银行, 金融客户, 但是在银行领域有一块比较难动的蛋糕叫银行的传统核心系统. 传统核心系统数据库长期以来一直是被国外的商用数据库所垄断, 比如说 ORACLE,DB2 啊, 像 TDSQL 这类分布式数据库是很难介入的.
2018 年, 我们关注到张家港银行有更换核心系统的需求, 就此建立联系并成功达成合作, 最终, 2019 年, 我们将腾讯这套分布式数据库系统成功应用到了张家港银行的传统核心系统. 张家港行也是作为全国第一家传统核心系统上分布式数据库的银行, 分布式数据库不再是只局限于银行的互联网核心, 互联网银行等外围系统的尝试, 而是真真正正切入到银行系统的心脏 - 传统核心, 这也是国产数据库领域一个具有里程碑意义的事件.
所以在未来, 我们也将继续 "走出去" 深入到更复杂, 更新核心的业务系统, 打磨我们的产品.
这是 TDSQL 的发展历程.
二, TDSQL 核心特性: 极具挑战的 "四高" 服务与安全可运维
我们再看 TDSQL 的核心特性.
首先作为适应于金融场景的数据库, 数据强一致性是立命之本, 因为数据不能丢, 不能错. 在金融场景, 你没有办法去估量 -- 假如错一条数据, 到底这条数据是 1 分钱还是 1 个亿, 所以数据强一致是我们最根本的一个特性. 不允许丢, 不允许错, 这是对数据库起码的要求.
第二是金融级高可用. TDSQL 确保 99.999% 以上的可用性, 并支持跨 IDC, 多机房, 同城多活的部署方式. 我们最先切入金融场景是因为金融场景的挑战是最大的. 中国金融行业受监管要求最为苛刻, 同时也对数据和业务的可用性, 可靠性, 一致性更是有极高的要求. 我们要求 99.999% 的可用性, 也就是说这个数据库全年故障的时间不能超过 5 分钟.
第三是高性能, 低成本. 互联网时代的企业, 都是海量业务, 海量机器, 性能稍微提高一个 10%, 可能就节约成百上千台机器的成本, 这个经济效益还是比较大的. 所以高性能, 低成本也是 TDSQL 的一个关键特性.
第四点是线性水平扩展. 因为无论是互联网还是其他企业, 随着数字化的发展, 比如说出现突增流量, 搞个活动等, 现在单机的承载量越来越容易凸显出瓶颈. 所以我们提出这种水平线性扩展的能力, 要求它可进行水平伸缩. 可能一台机器的负载, 硬盘, 机器资源容纳不了, 但可以把它拆到多台机器, 不需要考虑太多, 它可以自动地提高自身吞吐量和负载量.
第五点是企业级安全. 金融数据是敏感的, 一些敏感的金融数据需要在当前数据基础上再做一层更高级别的企业安全防护, 比如数据库防火墙, 以及透明加密, 等等.
第六点是便捷的运维, 私有化部署中, 很多情况下其实他们的网络环境和部署环境跟我们是隔离的, 如果银行客户有问题, 那其实我们第一时间是切入不了去帮助解决的, 所以就需要一套完善的配套设施, 简单容易上手, 可以自动帮用户去定位问题, 解决问题, 同时也尽量减少运维的复杂度.
三, TDSQL 核心架构
接下来我们了解 TDSQL 的架构以及模块划分. 通过这一章节的了解, 我们更能切入 TDSQL 的技术细节, 它为什么要这样设计, 这样设计有什么好处, 如何通过这样的架构和设计实现高可用, 线性扩展等能力.
1.TDSQL 系统总览
1.1 资源池
这张图从下往上看, 首先最底层是资源池, 属于 IaaS 层服务, 可以是物理机, 也可以是虚拟机, 只要是给 TDSQL 添加机器就好, TDSQL 是在一个机器的资源池上实现了数据库实例的管理. 当然, 这里推荐的还是物理机, 如果增加一层虚拟机服务, 无疑在稳定性和性能方面都会引入一些隐患.
1.2 存储节点
从资源池再往上是存储节点. 存储节点要强调的是 TDSQL 的两种存储形态, 一种是 Noshard 数据库, 一种是分布式数据库(也叫 Shard 版 TDSQL). 简单来说, Noshard 就是一个单机版的 TDSQL, 在 MySQL 的基础上做了一系列的改造和改良, 让它支持 TDSQL 的一系列特性, 包括高可用, 数据强一致, 7*24 小时自动故障切换等. 第二种是分布式数据库, 具备水平伸缩能力. 所以 TDSQL 对外其实呈现了两种形态, 呈现一种非分布式形态, 一种是分布式的形态. 至于这两种形态的区别, 或者说什么场景更适合于哪种数据库, 后面我们有专门的章节去分析.
1.3 计算节点
再看计算节点. 计算节点就是 TDSQL 的计算引擎, 做到了计算层和存储层相分离. 计算层主要是做一些 SQL 方面的处理, 比如词法解, 语法解析, SQL 改写等. 如果是分布式数据库形态, 还要做分布式事务相关的协调, 所以我们看到计算层不存储数据, 只运行 SQL 方面的实时计算, 所以它更偏 CPU 密集型. 此外, TDSQL 计算节点还具备 OLAP 的能力, 对一些复杂的计算可以进行算法上的优化 -- 什么时候该下推到存储引擎层, 什么时候需要在计算层做汇总等, 这是计算节点需要做的事情.
1.4 赤兔运营管理平台
再往上, 是赤兔运营管理平台, 如果说把下面这一套东西比作一个黑盒, 我们希望有一个用户界面操纵这个黑盒, 这个界面就是赤兔运营管理平台. 通过这个平台, DBA 可以操纵 TDSQL 后台黑盒, 所以相当于是一套 web 管理系统. 让所有 DBA 的操作都可以在用户界面上完成, 而不需要登陆到后台, 不需要关心计算节点是哪个, 存储节点是哪个, 或者怎么样管理它, 要加一些节点或者减一些节点, 或者把这个节点从哪里要迁到哪里...... 这些都可以通过界面化完成. DBA 操作界面不容易出错, 但如果登陆到后台很容易一个误操作, 不小心把机器重启了, 就可能会造成一定的影响.
1.5 "扁鹊" 智能 DBA 平台
有了赤兔之外, 为什么还有一个 "扁鹊" 智能 DBA 平台呢? 可能正常情况下, 我们机器是好的, 但是, 机器如果发生了故障, 或者说哪天磁盘有坏块了, 或者是 IO 性能越来越差......SSD 其实有一个衰老的过程, 到了后期的话, 吞吐量和 IOPS 可能会有一定下降, 导致数据库的响应速度变慢. 这种情况如果 DBA 要排查, 得先去看到是哪一个实例, 涉及到哪一台机器, 这个机器有什么问题, 检测机器的健康状态...... 这些都是机械性的工作, 有了扁鹊智能管理平台, 当出现故障的时候就可以自动分析故障的原因, 举个例子, 可以找出是因为什么导致 SQL 变慢了, 或者又是因为什么原因发生了主备切换, 突然 IO 异常了或者其他什么原因导致机器故障.
此外, 扁鹊智能 DBA 平台还有一个智能诊断系统, 可以定期由 DBA 发起对实例进行的诊断. 比如有些数据库实例, CPU 常年跑得很高, 其实是一些比较差的 SQL 导致的. 这个时候扁鹊智能 DBA 系统, 可以很方便地到用户实例上做巡检, 得到一个健康状况图, 并对它进行打分, 发现这个实例比如他的 CPU 超用了, 需要扩容, 但是没有扩容, 就会减分; 然后其他表的索引没有建好, 要减分...... 以此生成一个诊断报告. 所以, 有了扁鹊, 再加上赤兔运营管理平台, DBA 的工作其实是非常轻松的, 可能每天只需要点几下按纽, 然后就解决了一系列的麻烦, 包括高可用, 性能分析, 锁分析等, 完全把 DBA 从繁杂的工作中解放出来.
此外, 我们看到这里其实还有几个小的模块. 调度系统, 调度系统主要是负责整体的资源调度, 比如说数据库实例的增加删除, 过期作废, 还有一些容量的调度, 即扩容, 缩容, 还有一些多租户的管理. 也就是说这是整个管理台的调度器.
另外还有一个备份系统, 这个是冷备中心, 后面有一个专门的章节去讲, 这里就不再赘述. 此外, 我们还提供了一些服务模块作为辅助, 比如审计, 还有数据库之间的迁移服务 -- 我们 TDSQL 怎么能够帮助异地数据库迁进来, 或者从 TDSQL 再迁出. 此外, 还包括数据校验, 数据订阅, SQL 防火墙, 注入检测等安全方面的模块, 以及一个辅助模块 -- 帮助我们的 DBA 也好, 用户也好, 完成一些个性化的丰富的需求.
以上是 TDSQL 系统总览.
2.TDSQL 架构模块及其特性
我们再看一下核心架构. 核心架构其实是上一个图的缩览, 我们把核心的模块挑选出来.
首先用户的请求通过负载均衡发往 SQL 引擎. 然后, SQL 引擎作为计算接入层, 根据这个 SQL 的要求从后端的存储节点去取数据. 当然, 无论是 SQL 引擎还是后端的数据库实例都存在一个元数据来管理调度. 举个例子, 计算引擎需要拿到一个路由, 路由告诉 SQL 引擎, 这个 SQL 该发往哪一个后端的数据节点, 到底是该发往主节点还是发往备节点. 所以我们引入了 ZK(Zookeeper)来储存类似于路由这类元数据信息. 当然 ZK 只是静态的存储元数据, 维护和管理这些元数据信息, 还需要有一套调度以及接口组件, 这里是 OSS,Manager/Schedule. 所以我们这张图可以看到是 TDSQL 整体来说就分为三部分: 管理节点, 计算节点和存储节点. 当然这里还有一个辅助模块, 帮助完成一些个性化需求的, 比如备份, 消息队列, 数据迁移工具等. 另外, 这里的负载均衡其实不是必需的, 用户可以选用自身的硬件负载, 也可以用 LVS 软负载, 这个负载均衡根据实际的用户场景可自定义.
了解了整体架构以后, 我们继续再看一下每个节点的特性是什么, 对机器的依赖程度如何, 要求机器有哪些特性, 等等.
2.1 管理模块: 轻松通过 Web 界面管理整个数据库后台
首先, 我们要看的是管理模块. 作为一个集群只搭建一套的管理模块, 一般可以复用一组机器. 同时, 管理模块对机器的要求相对来说比较低, 比如资源紧张的时候, 我们用虚拟机就可以代替. 在我们内部, 一套管理模块承载最大的管理单集群近上万实例.
管理模块包含前文说的几个关键模块: Zookeeper(ZK),Scheduler,Manager,OSS 和监控采集程序, 赤兔管理控制台. 那么它们是怎么联合工作的呢? 首先, DBA 用户在赤兔管理台 -- 这一套 Web 前台发起一个操作 -- 点了一个按纽, 这个按纽可能是对实例进行扩容, 这个按纽会把这个 https 的请求转移到 OSS 模块, 这个 OSS 模块有点像 Web 服务器, 它能接收 Web 请求, 但是它可以把这个转发到 ZK. 所以, OSS 模块就是一个前端到后台的桥梁, 有了 OSS 模块, 整个后台的工作模块都可以跟前台, 跟 Web 界面绑定在一起.
好, 捕捉到这个请求之后, 在 ZK 上创建一个任务节点, 这个任务节点被调度模块捕获, 捕获之后就处理任务. 处理完任务, 再把它的处理结果返回到 ZK 上. ZK 上的任务被 OSS 捕获, 最后也是 https 的请求, 去查询这个任务, 最后得到一个结果, 返回给前端.
这是一个纯异步的过程, 但是有了这套管理模块, 让我们可以轻松的通过 Web 界面去管理整个 TDSQL 的后台. 当然, 这整个过程都有一个监控采集模块去采集, 对整个流程的审计及状态进行获取.
2.2 DB 模块: 数据库无损升级
DB 模块, 即数据节点, 数据存取服务属于 IO 密集型的服务, 因此, 数据节点也是我们的存储节点, 它对 IO 的要求比较高, 一般建议配置 SSD 硬盘, 最好是 PCI-E 的 SSD. 因为对数据库服务来说, CPU 再高, 如果 IO 跟不上, 仍然是小马拉大车. 比如只有 1 千的 IOPS,CPU 根本就跑不起来, 用不起来. 所以这里一般建议至少 IPS 要达到 1 万以上.
我们再看一下 SET 的概念. SET 就是数据库实例, 一个 SET 包含数据库的 -- 比如我们默认要求的是一主两备, 一个 Master 节点和两个 Slave 节点. 当然在 DB 节点上有一个 Agent 的模块. MySQL 在执行中, 我们要监控它的行为, 以及进行操作. 如果把这些东西做到 MySQL 里面为什么不可以呢? 这其实存在一个问题, 如果对数据节点进行升级, 可能就要涉及到重启, 一旦重启就影响用户的业务, 影响服务. 这个时候我们就考虑, 在它上面加一个模块 Agent, 它来完成对所有集群对 MySQL 的操作, 并且上报 MySQL 的状态. 有了它之后, 对 TDSQL 数据节点的大部分升级, 都会转变为对 Agent 的升级, 而升级 Agent, 对业务没有任何影响, 这就实现了无损升级. 相比于 Agent 我们对数据节点 MySQL 不会频繁升级, 一般情况下一年, 半年都不会动它. 这是我们 DB 模块, 也是存储节点.
2.3 SQL 引擎模块: 分布式复杂 SQL 处理
接下来再看另外一个比较重要的模块: SQL 引擎模块. SQL 引擎处于计算层的位置, 本身属于 CPU 密集型, 所以我们在选机型上尽量要求 CPU 高一些. 其次是内存, 作为计算接入层, 它要管理链接, 如果是大量的短链接或者长链接, 非常占内存, 所以它对 CPU 和内存的要求比较高. 此外, 它本身不存储数据, 也没有主备之分, 所以对硬盘没有太大要求.
我们看一下 SQL 引擎的特性. SQL 引擎首先还是从 ZK 上拉取到元数据, 作为 SQL 引擎, 包括权限校验, 读写分离, 以及统计信息, 协议模拟等相关的操作.
可能有些人会问, 其实这个 SQL 引擎岂不是一种中间件? 其实并不是这样, SQL 引擎如果是一个中间件, 它都可以脱离 MySQL. 但是我们这个 SQL 引擎, 需要做词法, 语法分析, 以及作为查询引擎等工作. 而且在分布式的场景下, SQL 引擎复杂的功能性就会凸显, 比如要处理分布式事物, 还要维护全局自增字段, 保证多个数据, 多个存储节点共享一个保证全局自增的序列; 如果是分布式的话, 要限制一些语法, 包括词法和语法的解析; 还有在一些复杂计算上, 它还要做一些 SQL 下推, 以及最后数据的聚合. 所以 SQL 引擎还是一个相对来说比较复杂的模块, 作为计算层, 并不是一个简单的中间件那么简单. 这就是一个 SQL 引擎.
三, TDSQL 金融级特性之: 数据强一致性保障
前面我们了解了 TDSQL 的整体架构和核心特性. 接下来我们要重点聊一聊它最重要的特性 -- 作为金融场景下不可或缺的数据强一致性的保障. 我们将从四个方面来聊一聊数据一致性的保障:
1. 主备数据复制方式
2. 数据复制比较: TDSQL 主备数据复制方案 VS MySQL 原生方案
3. 核心功能: 容灾切换, 数据强一致, 0 丢失 0 出错
4. 数据强一致性
TDSQL 主备数据复制: 高性能强同步
首先在讲数据一致性之前, 我们先了解一下 MySQL 原生的数据复制的方式.
首先第一种是异步复制: 主机在不等从机应答直接返回客户端成功. 这个在金融场景是不能接受的, 这样的话相当于数据是没有多副本保障.
第二种是半同步: 主机在一定条件下等备机应答, 如果等不到备机应答, 它还是会返回业务成功, 也就是说它最终还会退化成一个异步的方式, 这同样也是金融场景所不能接受的.
除此之外, 原生半同步其实是有一个性能方面的缺陷, 即在跨 IDC 网络抖动的场景下, 请求毛刺现象很严重. 所以原生的异步复制和半同步复制都存在一些问题, 并不能完全适应金融场景.
TDSQL 引入了基于 raft 协议的强同步复制, 主机接收到业务请求后, 等待其中一个备机应答成功后才返回客户端成功. 比如这张图, 我们一主两备下一条业务请求到达了主机之后必须等其中一个备机应答成功, 才能返回客户端成功, 否则这个请求是不会应答的. 所以说, 强同步是 TDSQL 最基础的一个特性, 是 TDSQL 保证数据不会丢, 不会错的关键.
讲到这里的话, 可能有些同学会问, 你们这个强同步其实也不复杂, 不就是在半同步的基础上把这个超时时间改成无限大同时应答的备机设置为 1. 并不是这样的, TDSQL 强同步这里的关键不是在解决备机应答的问题, 而是要解决这种增加了等待备机的机制之后, 如何能保证高性能, 高可靠性. 换句话说, 如果在原生半同步的基础上不改造性能, 仅把超时时间改成无限大的时候, 其实跑出来的性能和异步比甚至连异步的一半都达不到. 这个在我们看来也是无法接受的. 相当于为了数据的一致性牺牲了很大一部分性能.
TDSQL 强同步复制的性能是在原生半同步的基础上做了大量的优化和改进, 使得性能基本接近于异步.
所以这里强同步强调的是, 实现强同步的同时还具备高性能特性, 所以确切地说是一个高性能的强同步.
那么我们如何实现高性能的强同步? 我们继续往下看. 这里 TDSQL 对 MySQL 主备复制的机制做了改造. 首先, 我们先引入了一个线程池的模型.
原生的 MySQL 是 -- 一个互联请求是一个线程, 这样对操作系统的资源消耗还是很大的: 5 千个连接, 5 千个线程. 那 1 万个连接, 1 万个线程, 操作系统能扛得住吗? 肯定扛不住. 而且原生的数据复制的方式, 其实串行化比较高, 比如说一个用户请求发过来后, 等备机应答; 这个过程中用户线程在这里是完全不能做事的, 只有等备机应答之后, 它才能够返回前端. 也就是说大量的线程都处于一个等待的状态, 这是半同步效率低的根本原因.
引入了线程池模型之后, 我们还需要考虑怎么调度线程池. 我们希望 MySQL 维护一小部分工作的线程, 比如说有 1 万个用户连接, 真正干活的可能就那么 100 个, 50 个 MySQL 的工作线程. 如果是非工作的线程, 他在等 IO 应答时可以先去睡眠, 并不让它去影响我们的吞吐量. 所以 TDSQL 对原生的复制做了改造, 除了引入线程池模型, 还增加了几组自定义的线程. 这个线程是做什么呢? 当一笔用户请求过来之后完成了写操作以及刷新了 binlog, 第二步他应该要等备机应答. 这个时候被我们新引入的线程组所接管, 把这个用户对话保留下来, 释放了工作线程, 工作线程该干什么继续干什么, 处理其他的用户连接请求. 真正备机给了应答之后, 再由另外一组线程将它唤醒, 回吐到客户端, 告诉客户端这个应答成功了.
所以经过这样一个异步化的过程, 相当于把之前串行的流程异步化了, 这样就达到了接近于异步复制的性能, 这就是 TDSQL 在强同步的改造的核心. 所以我们这里强调不单单是一个实现强同步的过程, 更是一个接近异步性能的强同步.
再看一下改造之后的性能对比: 异步 TPS 大概是 6 万左右, 平均时耗小于等于 10 毫秒. 再看半同步, 明显有三分之二的性能损耗, 并且这个时耗波动还是比较大的, 比如说 IDC 网络抖动一下. 强同步一主两备模式下, 首先性能已经接近于异步的性能, 此外时耗并没有额外增加.
因为一主两备, 除非两个机房网络同时抖动, 否则的话强同步的时耗不会有明显波动. 所以我们看到基于 TDSQL 的强同步实现了数据的多副本又保证了性能.
有了这个多副本保障, 又怎么如何实现故障自动切换, 数据不丢不错呢? 这其实还是需要一套切换选举的流程. 这就引出了 TDSQL 的容灾切换功能.
2. 自动容灾切换: 数据强一致, 0 丢失 0 出错
自动容灾切换在有了强同步特性的基础, 就变得非常容易实现了. 我们先看一下这个结构图:
SQL 引擎将请求发给主节点, 主节点被两个备机所同步, 每个节点上都有对应的 Agent 上报当前节点的状态. 这时, 主节点发生了故障被 Agent 觉察, 上报到 zk 被 Scheduler 捕获, 然后 Scheduler 首先会把这个主节点进行降级, 把它变成 Slave. 也就是说此时其实整个集群里面全是 Slave, 没有主节点. 这个时候另外两个存活的备机上报自己最新的 binlog 点, 因为有了强同步的保障, 另外两个备机其中之一一定有最新的 binlog, 那么两个备机分别上报自己最新的点后, Schedule 就可以清楚的知道哪个节点的数据是最新的, 并将这个最新的节点提升成主节点. 比如说发现 Slave1 的数据是最新的, 这个时候 Schedule 修改路由, 把主节点设置为 Slave1, 完成了主备的切换. 有了前述这个切换机制, 保证了整个切换无需人为干预, 并且切换前与切换后的数据是完全一致的. 这里做一个总结, 正是由于强同步的保证, 所以当主机发生故障的时候, 我们一定是可以从一个备节点上找到最新数据, 把它提成主节点. 这就是 TDSQL 容灾切换的整个过程, 容灾切换需要建立在强同步的基础上.
3. 极端场景下的数据一致性保障
聊完了容灾切换, 我们再聊一聊故障节点的后续处理事宜. 故障处理可能有几种情况: 一种是故障以后, 它还能活过来. 比如说像机房可能突然掉电了, 掉完电之后它马上又恢复. 或者机器因为硬件原因不小心发生了重启, 重启完之后节点是可以被拉起, 被拉起之后, 我们希望它能够迅速的再加入到集群中, 这时数据其实是没有丢没有错的, 我们不希望把节点故障之后又重新建一份数据, 把之前的数据全抹掉. 而是从它最后一次同步的数据为断点, 继续续传后面的数据. 带着上述问题, 我们看一下节点的故障后恢复过程.
首先我们考虑一种场景, 比如说 A 节点作为主节点, B,C 是从, 正常去同步 A 节点的数据, A+1,A+2, 接下来该同步 A+3. 当 A+3 还没有同步到从节点的时候发生了故障, 这个时候根据 B,C 节点的数据情况, C 的数据是最新的, 因此 C 被选成了主节点, 进而 C 继续同步数据到 B. 过了一阵, A 节点拉起了, 可以重新加入集群. 重新加入集群之后, 发现它有一笔请求 A+3 还没有来得及被 B,C 节点应答, 但已经写入到日志. 这个时候其实 A 节点的数据是有问题的, 我们需要把这个没有被备机确认的 A+3 的回滚掉, 防止它将来再同步给其他的节点. 所以这里强调的是一个数据的回退, 相当于每一个 Slave 新加入节点的时候, 我们都会对它的数据进行检验, 将多写的数据回滚. 当然刚刚的假设是运气比较好, A 节点还能重启. 有些时候 A 节点可能就挂了, 这个机器就再也起不来了, 我们需要把这个节点换掉, 即换一个新机器, 比如: 加一个 D 节点. 我们希望它快速重建数据并且对当前线上业务尽可能无影响. 如何让它快速去重建呢? 当然是基于物理拷贝, 而且它是从备机上去拉数据, 这样它既不会对主节点产生影响, 又能够快速把数据重建好.
四, 分布式 TDSQL 的实践
第四部分, 我们开始讲分布式 TDSQL 的实践. 前三章我们在聊 TDSQL 的高可用, 强一致. 这些作为金融场景是一个必备的特性, 还没有涉及到分布式, 当涉及到分布式时就开启了 TDSQL 的另外一种形态. 接下来我们就聊一聊分布式 TDSQL 跟单节点的 TDSQL 有什么不同, 以及这种分布式架构下又是如何实现一系列的保障, 同时如何做到对业务透明, 对业务无感知.
1. 分表
分表, 当在单机模式下, 用户看到的一张逻辑表, 其实也是一张物理表, 存储在一个物理节点 (物理机) 上. 在分布式形态下, 用户看到的逻辑表的实际物理存储可能是被打散分布到不同的物理节点上. 所以 TDSQL 分表的目标, 希望做到对业务完全透明, 比如业务只看到一个完整的逻辑表, 他并不知道这些表其实已经被 TDSQL 均匀拆分到各个物理节点上. 比如: 之前可能数据都在一台机器上, 现在这些数据平均分布在了 5 台机器上, 但用户却丝毫没有觉察, 这是 TDSQL 要实现的一个目标 -- 在用户看来是完全的一张逻辑表, 实际上它是在后台打散了的.
这个表在后台如何去打散, 如何去分布呢? 我们希望对用户做到透明, 做到屏蔽, 让他不关心数据分布的细节. 怎么将这个数据分布和打散呢? 这就引出了一个概念: shardkey-- 是 TDSQL 的分片关键字, 也就是说 TDSQL 会根据 shardkey 字段将这个数据去分散.
我们认为, shardkey 是一个很自然的字段, 自然地通过一个字段去将数据打散. 举个例子, 腾讯内部我们喜欢用 QQ 号作为一个 shardkey, 通过 QQ 号自动把数据打散, 或者微信号; 而一些银行类的客户, 更喜欢用一些客户号, 身份证号以及银行卡号, 作为 shardkey. 也就是说通过一个字段自然而然把这个数据分散开来. 我们认为引入 shardkey 后并不会增加额外的工作, 因为首先用户是最了解自己得数据的, 知道自己的数据按照什么字段均匀分布最佳, 同时给用户自主选择分片关键字的权利, 有助于从全局角度实现分布式数据库的全局性能最佳.
所以这里可能有些人会想, 是不是主键是最好的或者尽可能地分散? 没错, 确实是这样的, 作为 TDSQL 的分片关键字越分散越好, 要求是主键或者是唯一索引的一部分. 确定了这个分片关键字后, TDSQL 就可以根据这个分片关键字将数据均匀分散开来. 比如这张图, 我们按照一个字段做了分片之后, 将 1 万条数据均匀分布在了四个节点上.
既然我们了解了 shardkey 是一个分片关键字, 那怎么去使用呢? 这里我们就聊聊如何去使用.
举个例子, 我们创建了 TB1 这个表, 这里有若干个字段, 比如说 ID, 从这个名字上来看就应该知道它是一个不唯一的, 或者可以说是一个比较分散的值. 我们看到这里, 以 "ID" 作为分配关键字, 这样六条数据就均匀分散到了两个分片上. 当然, 数据均匀分散之后, 我们要求 SQL 在发往这边的都需要带上 shardkey, 也就是说发到这里之后可以根据对应的 shardkey 发往对应的分片. 如果不带这个 shardkey 的话, 它不知道发给哪个分片, 就发给了所有分片. 所以强调通过这样的改善, 我们要求尽可能 SQL 要带上 shardkey. 带上 shardkey 的话, 就实现了 SQL 的路由分发到对应的分片.
讲完数据分片, 我们再看一下数据的拆分.
2. 水平拆分
对于分布式来说, 可能最初我们所有的数据都在一个节点上. 当一个节点出现了性能瓶颈, 需要将数据拆分, 这时对我们 TDSQL 来说非常简单, 在界面上的一个按纽: 即一键扩容, 它就可以将这个数据自动拆分. 拆分的过程也比较容易理解, 其实就是一个数据的拷贝和搬迁过程, 因为数据本身是可以按照一半一半这样的划分的. 比如最先是这么一份数据, 我们需要拆成两份, 需要把它的下半部分数据拷到另外一个节点上. 这个原理也比较简单, 就是一个数据的拷贝, 这里强调的是在拷贝的过程中, 其实业务是不受任何影响的. 最终业务只会最终有一个秒级冻结.
为什么叫秒级冻结? 因为, 最后一步, 数据分布到两个节点上涉及到一个路由信息变更, 比如原来的路由信息要发到这个分片, 现在改了之后需要按照划分, 上半部分要发给一个分片, 下半部分发给另一个分片. 我们在改路由的这个过程中, 希望这个数据是没有写入相对静止的.
当然改路由也是毫秒级别完成, 所以数据拆分时, 真正最后对业务的影响只有不到 1s, 并且只有在最后改路由的冻结阶段才会触发.
讲完数据拆分, 我们开始切入分布式里面最难解决的这个问题, 分布式事务.
3. 健壮, 可靠的分布式事务
单节点的事务是很好解决的, 但是在分布式场景下想解决分布式事务还是存在一定的困难性, 它需要考虑各种各样复杂的场景.
其实分布式事务实现不难, 但首要是保证它的健壮性和可靠性, 能应对各种各样的复杂场景. 比如说涉及到分布式事务的时候, 有主备切换, 节点宕机...... 在各种容灾的测试环境下, 如何保证数据总帐是平的, 不会多一分钱也不会少一分钱, 这是分布式事务需要考虑的.
TDSQL 分布式事务基于拆的标准两阶段提交实现, 这也是业内比较通用的方法. 我们看到 SQL 引擎作为分布式事务的发起者, 联合各个资源节点共同完成分布式事务的处理.
分布式事务也是根据 shardkey 来判断, 具体来说, 对于 SQL 引擎读发起一个事务, 比如第一条 SQL 是改用户 ID 为 A 的用户信息表. 第二条 SQL 是插入一个用户 ID 为 A 的流水表, 这两张表都以用户 ID 作为 shardkey. 我们发现这两条 SQL 都是发往一个分片, 虽然是一个开启的事务, 但是发现它并没有走分布式事务, 它实际还是限制在单个分片里面走了一个单节点的事务. 当然如果涉及到转帐: 比如从 A 帐户转到 B 帐户, 正好 A 帐户在第一个分片, B 帐户是第二个分片, 这样就涉及到一个分布式事务, 需要 SQL 引擎完成整个分布式事务处理.
分布式事务是一个去中心化的设计, 无论是 SQL 引擎还是后端的数据节点, 其实都是具备高可用的同时支持线性扩展的设计. 分布式事务比较复杂, 单独讲的话可能能讲一门课, 这里面涉及的内容非常多, 比如两级段提交过程中有哪些异常场景, 失败怎么处理, 超时怎么处理, 怎么样保证事务最终的一致性等等. 这里不再深入, 希望有机会能单独给大家分享这块内容.
所以, 这里只对分布式事务做一个总结, 我们不再去探讨它的细节:
首先是基于两阶段提交, 我们在 MySQL 原生 XA 事务的基础上做了大量的优化和 BUG 修复. 比如说原生的 XA 在主备切换时会发生数据不一致和丢失, TDSQL 在这个基础上做了大量的修复, 让 XA 事务能够保证数据一致性.
第二个是强劲的性能. 起初我们引进原生分布式事务的时候, 分布式事务的性能还达不到单节点的一半. 当然经过一系列的优化调优, 最后我们的性能损耗是 25%, 也就是说它能达到单节点 75% 的性能.
第三个是对业务透明, 因为对业务来说其实根本无需关心到底是分布式还是非分布式, 仅需要按照正常业务开启一个事务使用即可.
第四个是完备的异常容错. 分布式事务是否健壮也需要考虑容错性的能力.
第五个是全局的锁检测. 对于分布式环境下锁检测也是不可或缺的. TDSQL 提供全局视角的分布式死锁检测, 可清晰查看多个分布式事务之间的锁等待关系.
第六点是完全去中心化. 无论是 SQL 引擎还是数据节点, 都是支持高可用并且能够线性扩展.
以上是 TDSQL 分布式事务的总结. 如果说用户要求保持跟 MySQL 的高度兼容性, 那可能 Noshard 版 TDSQL 更适合. 但是如果对于用户来说, 单节点已经达到资源的瓶颈, 没有办法在单节点下做数据重分布或者扩容, 那必须选择 Shard 模式. 但是在 Shard 模式下, 我们对 SQL 有一定的约束和限制, 后面会有专门的一门课去讲分布式 TDSQL 对 SQL 是如何约束的.
我们看到无论是 Noshard 还是 Shard, 都具备高可用, 数据强一致, 自动容灾的能力.
同时 TDSQL 也支持 Noshard 到 Shard 的迁移, 可能早期我们规划的 Noshard 还可以承载业务压力, 但是随着业务的突增已经撑不住了, 这个时候需要全部迁到 Shard, 那么 TDSQL 也有完善的工具帮助用户快速进行数据迁移.
五, TDSQL 数据同步和备份
接下来是 TDSQL 另外一个辅助特性: 数据同步和备份.
1.TDSQL 数据同步组件
数据同步的重点分为三个场景:
第一个场景是一个数据汇总. 比如, 多个数据库实例的数据同步到一个数据库实例, 如保险行业用户多喜欢将全国多个区域数据库实例的数据同步到全国总库进行统计分析.
第二个就是跨城的容灾. 跨城容灾, 一般一个城市的分布式数据库的数据需要同步到另外一个城市异构的分布式数据库中做灾备. 有的时候我们要做异构数据库的跨城容灾, 比如说主城是一个十六节点的数据库, 它非常庞大. 但是备城, 可能我们基于成本考虑, 选用的设备数量, 机房都要差一些. 比如灾备实例只有两个物理分片. 一个是两分片数据库实例, 而另外一个时十六分片的数据库实例. 从十六分片同步到; 两分片, 这是一个异构的数据库的同步, 这时候我们就需要利用数据同步这个组件.
第三个是迁移. 异构数据库的迁移, 将数据从 TDSQL 同步到 MySQL,Oracle,PostgreSQL 等数据库. 当然, 从 TDSQL 到 TDSQL 是一种同步方式, 更有一种是 TDSQL 到其他异构数据库. 举个例子, 张家港农商行, 它的核心系统需要从传统的国外商用数据库替换为 TDSQL, 可能还是需要做一定的风险的防范. 最终我们给了一套用 Oracle 作为 TDSQL 灾备示例的方案, 通过数据同步组件, 将 TDSQL 的数据准实时同步到 Oracle. 假如在极端情况下需要将业务切到 Oracle, 我们也是有这个能力的.
当然数据迁移也体现了 TDSQL 开放的思想, 既然允许用户将数据迁移到 TDSQL, 如果有一天用户可能觉得 TDSQL 不是很好, 觉得有更好的产品可以替代它, TDSQL 支持用户把数据迁走.
2.TDSQL 数据备份
最后我们聊一聊 TDSQL 的备份.
TDSQL 支持在线的实时热备, 同时这个备份是基于备机上做的备份, 备份支持镜像和 binlog 的备份. 镜像又支持物理镜像和逻辑镜像(也叫物理备份和逻辑备份).
物理备份的好处是速度快, 直接操纵物理文件, 缺点是只能备份整个数据库实例, 无法选择指定库表. 逻辑备份的好处是通过 SQL 的方式备份, 它可以选择单个库表备份, 但是如果对整个实例备份效率不及物理备份. 比如说有 1T 的数据, 只有 100 兆是我的关键的数据, 如果为了节省存储空间就没有必要用物理备份, 就用逻辑备份, 只备份我们关心的库表.
有了物理备份和逻辑备份之后, 基于数据的镜像, 再结合 binlog 轻松实现数据的定点恢复. 对于 binlog 的备份 TDSQL 的 Agent 模块完成准实时的异步备份. 比如说我们每天的 0 点备份镜像, 同时各个时间段的 binlog 准实时备份. 当需要恢复到一个早上 6 点的数据, 利用 0 点的数据镜像再结合 0 点到 6 点的 binlog, 即可完成 6 点的数据恢复.
备份是在备机上做不会影响主机. 整个备份过程也是有监控有告警, 实现整个备份过程的追踪.
因为架构这一块内容确实是比较多, 本次也作为所有 TDSQL 系列分享的一个前瞻, 后面更多的系列分享会根据这门课的部分章节详细展开. 这次分享主要是想帮助大家了解 TDSQL 的整体架构和模块划分, 以及它的关键特性是如何设计, 是基于什么样的考虑. 听完本次分享再听后面的专题, 会更容易去理解.
来源: https://www.qcloud.com/developer/article/1600722