引言
作为腾讯规模最大的 MySQL 数据库服务,CDB 在腾讯云上也是最受欢迎的关系型数据库产品。CDB 不仅具备备份回档、监控、快速扩容等数据库运维的全套解决方案,而且拥有深度定制的内核版本 Tencent MySQL,简称TXSQL。TXSQL内核版本拥有更高的性能、更强的稳定性,同时提供 Oracle MySQL 企业级版本才拥有的特性,对内支持集团内部业务的发展,对外提供强有力的竟争力,助力腾讯云的快速奔跑。在腾讯云的发展过程中,为其保驾护航,积极的推动了腾讯云的快速发展。
TXSQL-Tencent MySQL 自从2015年5月份正式立项,在近两年的时间里对 MySQL 的读写性能、强同步、大并发量访问和稳定性等方面做了大量工作,读写性能在并发上升到一定程度时具有1倍以上的性能提升,同时在 TXSQL 的版本发展过程中修复了多个影响线上稳定性的 bug(如 redo 死锁,实例异常关闭、字符集引起的主备异常等),在保证了内核稳定的同时,对腾讯云的稳定性提供了有力的保障。 TXSQL 不仅提升了实例性能与稳定性,而且在发展的过程中积极和社区进行交流,在 DBAPlus 等社区发表了多篇具有影响力的文章,并作为演讲嘉宾,多次参与 ACMUG、DTCC、DBAPlus、开源中国和 InfoQ 全球架构师峰会等社区会议,大大提升了腾讯云在业界的影响力,赢得了客户的信任与口碑。
什么是 TXSQL,为什么要做 TXSQL ?
TXSQL 是 Tencent MySQL 的简称,是 TEG 基础架构部CDB (Cloud DataBase) 团队在近十年发展过程中衍生出来的一个对 MySQL 内核源码深度定制、对官方 MySQL 版本进行二次开发的项目。其主要目的是在保证线上稳定性的同时,满足业务对数据库的各种需求。TXSQL 的服务对象是公司内部用户和腾讯云上小至数G大至数百T的外部客户。TXSQL 是支撑这些业务平稳运行的关键基石,在服务用户的同时,以打造腾讯自己的MySQL分支为己任。
MySQL 作为最受欢迎的开源数据库,也是云上使用最多的数据库,不同的业务场景对数据库有不同的功能需求与性能需求,这样就决定了数据库本身的多样化需求,同时 MySQL 在不同使用场景下所衍生出来的各种问题,也影响着线上的稳定性,TXSQL 主要包括以下工作:
数据库性能调优;
对内核进行深度改造,满足应用需要的性能要求,在活动促销时需要保证数据库的平稳;
提供业务需要的各种功能需求;
实现资源管理需要内核支持的功能;
MySQL DBA 在运维过程中需要的诊断相关的功能;
MySQL 在运行过程中存在的问题定位并修复影响系统运行的 Kernel Bug;
保证数据库系统的安全,保证云上用户数据的安全;
作为云上客户的服务者,我们必须有及时响应用户并解决各种问题的能力,也应该满足用户的各种合理要求,对内支持业务的快速发展,满足业务要求;对外为腾讯云提供强有力的竟争力,这就是 TXSQL 存在与发展的目的。
TXSQL 做了哪些事情版本选择
TXSQL 5.6版本,其基准开发版本为 5.6.28, 选择 5.6.28 做为基准版本主要有以下原因:
5.6.28 是当时大版本 5.6 中的最新版本,自 5.6.10 GA (General Availability) 以来,版本已经相当稳定;
5.7 版本刚刚 GA, 但从官方 buglist 上来看有相当多的 serious bugs,因此未被采用,在现在看来选择 5.6.28 也是正确的,因为从 5.7 版本的 release notes 来看,除了 Group replication 是新加入的功能,其它内容基本是 bugfix 相关;虽然 5.7 存在着各种问题,但其新添加的功能 & 卓越的性能都会是未来 2-3 年的主流选择,我们也计划在今年对 5.7 进行支持,并且会对 5.7 新添加的功能 & 性能改进进行详细的源码级别的介绍。
5.6 版本是接受程度最高的 MySQL 版本,也是用户使用最多的数据库版本;
综合以上原因,我们选择了 5.6.28 做为 TXSQL 开发的基准版本。
性能改进
read view 优化
read view 又称读视图,用于存储事务创建时的活跃事务集合。当事务创建时,线程会对 trx_sys 上全局锁,然后遍历当前活跃事务列表,将当前活跃事务的ID存储在数组中的同时,记录最大事务 low_limit_id & 最小事务 high_limit_id & 最小序列化事务 low_limit_no。 当事务执行时,凡是大于low_limit_id 的数据对于事务是不可见的,凡是事务小于 high_limit_id 的数据都是可见的,事务 ID 是 read_view 数组中的某一个时也是不可见的;Purge thread 在执行 Purge 操作时,凡是小于 low_limit_no 的数据,都是可以被 Purge 的,read view 是 MySQL MVCC 实现的基础;
由于read_view 的创建和销毁都需要获取 trx_sys->mutex, 当并发量很大的时候,事务链表会比较长,又由于遍历本身也是一个费时的工作,所以此处便成为了性能瓶颈,详情可以参考 bug#49169,为了解决这个问题 TXSQL 做了以下事情:
在 trx_sys下维护一个全局的事务ID的有序集合,事务的 创建 & 销毁 的同时将事务的 ID 从这个集合中移除;
在 trx_sys下维护一个有序的已分配序列号的事务列表,已记录拥有最小序列号的事务,供 purge 时使用;
减少不必要的内存分配,为每一个 trx_t 缓存一个 read_view,read_view 数组的大小根据创建时的活跃全局事务 ID 集合做必要的调整;
参照 5.7 的实现,在 5.6 中将 ro_trx_list 移除,减少只读视图的长度;
经过上述修改,读写性能都有很大的提升,详情可以参考文章 畅游数据库性能优化过程简析。
redo log group commit
MySQL 有两种很重要的 Log,分别为 redo log & binlog,前者是保证事务原子性操作所产生的日志,后者是主备数据同步所产生的同步日志。 其中 binlog 在 ordered_commit 时进行 group commit, 而 redo log 则是在事务提交的时候分别调用 trx_prepare 使 redo log 落地,导致 log_sys->mutex 竟争较为严重;从 crash recovery 的逻辑来看,只要 redo log 早于 binlog 落地,就不会有数据问题,因此在 ordered_commit 的第一阶段时,TXSQL 会收集各种引擎的最大的 redo log LSN,然后将小于该 LSN 的 redo log 落盘,从而提升写性能。更详细的分析与测试,可以参考 bug#73202。
redo log 双缓冲区
MySQL redo log 是一个顺序写的单缓冲区,log_sys->mutex 锁资源竟争激烈,在事务落盘的过程中对 LSN 相关的读、写都被阻塞,为了解决 log_sys->mutex 的锁竟争问题,引入双缓冲区机制 & w_mutex 锁,在 flush redo log 的过程中释放 log_sys->mutex,继续持有 log_sys->w_mutex,从而阻塞写,不阻塞 LSN 相关的读操作,flush 完成后释放 w_mutex;从而提升并发性,提升性能。
adaptive hash index 锁拆分
MySQL 一次定位 cursor 的过程是从根结点到叶子结点的路径,时间复杂度为:height(index) + [CPU cost time],上述的两个优化过程无法省略定位 cursor 的中间结点,因此需要引入一种可以从 search info 定位到叶子结点的方法,从而省略根结点到叶子结点的路径上所消耗的时间,而这种方法即是 自适应索引(Adaptive hash index, AHI)。查询语句使用 AHI 的时候有以下优点:
可以直接通过从查询条件直接定位到叶子结点,减少一次定位所需要的时间;
在 buffer pool 不足的情况下,可以只针对热点数据页建立缓存,从而避免数据页频繁的 LRU;
AHI 的 btr_search_latch (bug#62018) & index lock 是 MySQL 中两个比较大的锁,详情可以参考 Index lock and adaptive search – next two biggest InnoDB problems,5.7 通过对 AHI 锁拆分 (5.7 commit id: ab17ab91) 以及引入不同的索引锁协议 (WL#6326) 解决了这两个问题。但是AHI 并不总能提升性能,在多表Join & 模糊查询 & 查询条件经常变化的情况下,此时系统监控 AHI 使用的资源大于上述的好处时,不仅不能发挥 AHI 的优点,还会为系统带来额外的 CPU 消耗,此时需要将 AHI 关闭来避免不必要的系统资源浪费,关于 AHI 的适应场景可以参考:mysql_adaptive_hash_index_implementation。
TXSQL 则是和 5.7 的解决思路类似,只是使用的分区算法不同,关于自适应索引的详细说明可以参考:MySQL AHI 实现解析。
当然,以上这些只是我们所做优化的一部分,还有其它一些优化如 GTID 优化、强同步优化、针对业务所做的特殊优化等,这些优化都在线上发挥了不可小觑的作用,以后再以文章的方式一一给大家进行介绍。
功能添加
TXSQL 支持多种工作模式
TXSQL 根据配置的不同,支持不同的工作模式,分别为只读、读写、不可服务,在不同的模式下实例表现形式不同:
只读模式:实例只接受读请求,不接受非管理用户的写请求操作;
读写模式:实例可以接受读写请求,但系统管理的相关权限被禁用;
不可服务模式:此模式下,MySQL 正常运行但不接受除管理账号以外的其它操作;
OSS 在实现跨园区容灾的时候会根据结点的角色设置不同的工作模式,从而避免双写的问题;
alter table … nowait 功能
DDL 在执行过程中需要获取对应的表锁,然后进行操作,如果此时有事务获取表锁,则会造成此语句的阻塞,而后绪操作此表的请求也会被此 DDL 阻塞,因此我们引入了 Oracle 中的 alter table 超时失败的功能,即 alter table …. [nowait | wait for n] 的功能以降低 DDL 对线上的影响,oracle 中 select for update [nowait|wait for n] 则可以通过设置参数 innodb_lock_wait_timeout 来实现;
TXSQL 锁系统的扩展
MySQL 锁系统有两个特征:
MYSQL 中的锁与连接强依赖,在连接断开之后便会释放其占有的锁资源,包括 server 层 & engine 层的所有锁资源;
用户线程获取锁之后,如果没有显示释放锁资源,连接没有断开亦或事物没有提交,则会一直占有锁资源;
TXSQL 对 MySQL的锁系统进行了扩展,实现了一种跨事务的、与连接无关的租约读写锁,用于应用层实现分布式事务,提供接口 cdb_lock, cdb_unlock 用于上锁与解锁请求,从而满足业务需求,详细接口介绍如下:
intcdb_lock(stringkey, inttype, uint64_twait_timeout,uint64_ttimeout);hold_ 多用户对同一个锁对象key加锁,会在加锁超时前等待。直到获取到锁或者超时返回。锁类型目前支持两种,共享锁和排它锁。共享锁是读锁,允许多个客户端对锁key加共享锁。排它锁是写锁,同一时刻只允许一个请求占有排它锁,其他请求需要等待锁释放。排它锁比共享锁有更高优先级,防止过多的共享锁等待情况下,排它锁长时间无法获取。 同一个锁持有者对同一锁key发起多次加锁,会认为重复加锁,相同锁发起者同一时刻只能对同一锁key成功发起一次。 获取的锁,不会因为连接断开而释放。锁持有时间是有指定的,超过持有时间以后锁会自动释放。int cdb_unlock(string key); 主动锁释放。会对指定锁key的持有者释放锁,只有在锁持有情况下才会成功。
基于 semi-sync 的强同步
原生 semi-sync 存在着以下问题:
semi-sync 在时间超过 rpl_semi_sync_master_timeout 会退化为异步;
采用 select 进行监听,当句柄值大于 1024 时则会出现异常,详情可参考 bug#79865;
在 after commit 后等待 ACK 容易出现幻读的问题;
…
为了解决上面的问题并对强同步进行优化及显示状态信息,TXSQL 做了以下事情:
优化半同步,增加ack线程,收发并行化;
修正select时fd超过1024导致异常的bug,改为poll;
优化dump线程判断自身是否为半同步的方法:在线程自身变量上记录;
在半同步基础上实现强同步,一直hold住直到收到ack;
修改同步方式时,唤醒正在等待的用户线程,继续等待或者退出;
增加一些状态,用于展示当前等待的情况(正在等待的binlog位点,已等待时间);
这些只是我们暂时做的,在以后的版本中,我们将会引入 thread pool、更完善的审记、防火墙等内部用户 & 外部云用户都需要的功能,随着时间的积累与版本的推移,TXSQL的功能将会越来越丰富。
稳定性改进与测试
稳定性是数据库服务首先要解决的问题,原生 MySQL 在稳定性方面存在着以下问题:
在压力持续增大的过程时,会存在着内存分配的问题,原生 GCC 在内存分配的过程中会调用 __lll_lock_wait_private,在并发量加大且线程数量变化较大时,内存分配会造成系统性能的抖动,详情可以参考: Impact of memory allocators on MySQL performance ,MySQL performance: Impact of memory allocators (Part 2);
随着并发的加大,由于原生 MySQL 没有流量控制或者 Server 层线程调度的功能,导致线程之间资源争夺比较剧烈,直接导致 MySQL 性能的不稳定,性能曲线先升后降;
innodb buffer pool 刷脏算法在 IO bound 类型时,很容易成为瓶颈;
主备同步时备库性能过低导致的延迟问题等;
MySQL 各个版本在实现的过程中存在的 bug, 如 redo log 死锁, 典型的字符集问题、MySQLD normal shutdown 等;
TXSQL 在服务业务的过程中不断的自我完善和发展,解决在发展过程中遇到的各种问题,如:
解决在线上遇到的各种 内核bug;
引入 jemalloc 解决内存分配的效率问题;
对复制线程进行改进,减缓主备延迟;
引入 thread pool 在 server 层进行线程高度,从而避免过多资源浪费在资源争夺等(内测中…);
实现 OSS 需要的功能,结合 zookeeper 实现强一致功能等;
…
为了保证版本稳定性,TXSQL 对每一个 Patch 都有对应的 testcase 文件和覆盖率测试,对于比较重要的改动,不仅要有理有据,还会邀请 Oracle 官方人员进行必要的 Code Review; 每个小版本发布之前,都会进行回归测试、覆盖率测试、稳定性测试、性能测试以及压力测试,然后才会进行灰度发布;当然,对于每一次的版本发布,都有详细的 release notes 以供参考。
金融业务支持
数据的可靠性是金融业务必然的需求,由于 MySQL 自身存在的问题,很难保证数据的强一致性,这些问题包括:
MySQL 是典型的两段提交,在 Crash 的过程中依赖 binlog 是否存在 决定事务的 commit 或 rollback,对于已经写 binlog 的事务,一定会提交事务,这样在主备切换后容易出现双写的问题;
当 rpl_semi_sync_master_wait_point=after_commit 时,主备切换时存在着幻读问题,当 rpl_semi_sync_master_wait_point=after_sync,如果用户线程在等待备库 ACK 的过程中被 kill, 那么其它用户线程可以看到被 kill 线程的提交,此时主备切换,仍然存在着幻读问题;
当 rpl_semi_sync_master_wait_point=after_sync 时,用户线程在 binlog 切换时容易出现死锁问题 (bug#68250), 详情如下:
T1 (user thread) ......... rotate(holding Lock_log,waitingforxids decrease to be zero) T2 (user thread) ......... after_sync, wait for ACK from slaveT3 (binlog sender thread) ......... requestingforLock logto send binlog
为了解决上述问题并实现主备数据的强一致,TXSQL 结合 CDB 管控平台针对金融业务的需求,实现了以下特性:
为了规避幻读,强同步插件在线程等待 ack 的过程中,禁止用户 kill 线程,同时本地 agent 会通过实例状态变量 rpl_semi_sync_master_tx_cur_wait_time 监控是否出现长时间 hang 问题,触发告警和 HA 操作,减少 hang 住对实例的影响;
本地 agent 在主库 crash 后,会向 OSS 获取故障切换时的 binlog 位点信息,并以这个位点启动实例,TXSQL 将截断多余的 binlog,并回滚相应事务,与新主库建立主从,确保可以快速补充节点;
跨园区高可用的支持。MySQL 是常见的 HA 架构:由本地 agent 负责监听 MySQL 实例,如果实例出现故障,则上报总控节点 scheduler,scheduler 选择拥有最多数据的 slave,将其提升为 master,并更新接入层的 RS 为新 master。在跨园区环境中,如果主库所在园区故障,OSS HA 时可能无法更新该园区的接入层,而该园区的 client 依然能够操作原主库,导致出现双写的问题,为了规避网络分区情下的双写问题,TXSQL实现了租约功能,租约过期的实例将拒绝服务。本地 agent 只有成功向 scheduler 续租后,才会更新实例的租约,确保实例的租约小于scheduler,从而解决双写问题;
对主库 Lock_log 进行拆分,解决死锁的同时提升主库写性能;
除了数据强一制的支持,TXSQL 还实现了一套与连接无关的分布式锁来支持业务部门实现分布式事务,后绪随着对业务的深入,更多更好的金融特性将会在 TXSQL 中一 一实现,比如热点更新等;
版本表现
TXSQL 的首要发展目标是解决线上存在的问题并支持业务的稳健发展,同时在解决问题的过程中推动 TXSQL 自身的发展。
TXSQL 最近的3 个版本,分别为 TXSQL_20160730、TXSQL_20160902 以及 TXSQL_20161130 版本,每个版本包含若干 issue,每个 issue 的紧迫程度决定其优先级,每一次版本发布都会对新版本的实例做严密的监控,以保证新版本的稳定性。下面我们从版本在线上的表现以及与官方数据的性能对比来对 TXSQL 做简单的介绍。
线上业务表现
TXSQL_0730 引入业界先进思想并 Fix bug#49169,成功解决了业务在高并发下的性能下降问题,并支持业务在大压力下的稳定运行,详情可参考之前的文章:
畅游数据库性能优化过程简析;
TXSQL_0902 版本对 redo log 做了必要的优化并对写 redo log 的代码 (log_write_up_to) 进行了重构,使写性能得到了一定的提交,在与竟品的性能测试 & 压力测试 (长时间的进行压力测试且数据不能出现抖动) 的对比过程中脱颖而出,对 B 站在腾讯云上的最终落户起到了一定的积极作用;同时通过租约控制 TXSQL 的在线/离线模式,规避双写,满足跨园区需求;
TXSQL_1130 版本除了必要的优化外,Fix 了影响较为广泛的字符集问题、MySQL normal shutdown 问题,使得线上的稳定性得到提升;
TXSQL 与官方性能测试对比
TXSQL_0228 版本目前处于开发状态,所以我们选取了 TXSQL_20161130 版本与 TXSQL 的基准版本 MySQL 5.6.28、MySQL 5.6 的最新版本 5.6.35 以及 MySQL 5.7 的最新版本 5.7.17 做了性能对比,测试机器为 ts85, 测试详情如下:
测试说明
测试概况:测试结果为 sysbench 端统计出来的数据,其中 select.lua & update_index.lua 为单语句事务, oltp.lua 中包含10 个SQL语句,配置文件为:my.cnf,测试数据为 100个表,每张表 10000条数据;
TXSQL 与各版本 select.lua 性能对比
TXSQL 与各版本 update_index.lua 性能对比
TXSQL 与各版本 oltp.lua 性能对比
从测试的过程与测试结果来看,TXSQL 不仅拥有更好的性能,还拥有更好的稳定性,在新的 ideas 不断实现的基础上,我们对 TXSQL 的表现拥有更多的期待。
关于 TXSQL 5.7
MySQL 5.7 自 GA 以来已经有1年有余,社区 & 用户的呼声也越来越高,TXSQL 会在今年上半年增加对 5.7 的支持,同时会对 5.7 的特性进行详细的分析以飨大家,另外在阅读代码 & 文档的过程中,笔者做了一些整理,希望对大家理解 5.7 有帮助;
5.7 中对 index lock 的锁拆分,详情可参考:worklog#6326;
支持强同步的 Group Replication,原理可参考:xcom protocol, The Database State Machine Approach ;
新的数据格式的支持 JSON Support;
更为方便的优化器控制选项 & 代价计算,详情可参考: cost-model;
对独立表空间加密的支持,暂时不支持 binlog & redo 的加密,详情可参考:InnoDB Tablespace Encryption;
虚拟列 & 函数索引的支持Virtual Columns and Effective Functional Indexes in InnoDB;
Query Rewrite 功能 (调整执行计划出错的利器,但现在有性能问题,详情见 bug#81298);
更为详细的系统运行信息监控 sys schema;
…
其实,我们在 TXSQL 5.6 中做了很多 5.7 的事情,如 redo log 优化、read view 创建优化等,在新的 TXSQL 5.7 中,我们会有更多可以做的事情,比如执行计划缓存、比如写优化、再比如基于于 Group Replication 服务等等,我们相信 TXSQL 5.7 会是一个不俗的版本。
TXSQL 未来的发展发向
TXSQL 的发展只是刚刚起步,相比 percona, facebook 等这样的公司我们做的还远远不够,还有很多需要改进的空间,但路总是一步步走出来的,我们的差距也在讯速的缩小,在未来的发展过程中 TXSQL 仍然会以用户为导向从以下方面不断的进行改进:
功能开发,以业务需求为出发点,实现业务所需要的各种功能,如审记、防火墙等;
性能优化,在保证稳定性的前提下不断提升单机性能,并根据业务特性有针对性的进行优化,如热点更新、多语句提交等;
提高内核的抗风险能力,提升对烂SQL、索引异常、流量异常等异常的防范能力,在不修改应用的前提下,保证系统的稳定运行;
兼容并包,不断加强与开源社区的联系,并引入 TXSQL 需要的功能或性能 patch,并将有价值的 Patch 提交至官方;
以线上稳定为基础,不断提升内核能力的同时,实现运维需要的各种功能,方便运维兄弟;
所以,大家在使用数据库的过程中如果有好的想法或者需求也可以和我们联系,我们会尽最大的努力来满足大家的需求,支持业务的发展。