作者简介:杨晓军 现就职于携程的数据库团队,主要负责携程数据库的研发与管理,专注于提升数据库的稳定性。
自分布式关系型数据库OceanBase开源以来,携程已经在线上环境中进行了广泛的应用,取代了原先以MySQL为主力的业务数据库系统。在转型过程中,确保与MySQL的高度兼容性成为了我们选型的关键考量。为此,我们自主研发了一套数据库发布系统,旨在通过该系统,DBA、业务专家及运维团队能够迅速识别并理解MySQL与OceanBase在DDL语句执行上的差异,从而加速对OceanBase数据库的掌握与适应,最终提升整体应用系统的稳定性。
本文从以下三个方面介绍携程自研数据库发布系统的实践。
- 数据库发布系统研发的四个步骤
- MySQL 与 OceanBase 的兼容性与差异性
- MySQL 数据同步 OceanBase 过程中 DDL 遇到的问题
一、数据库发布系统研发的四个步骤
MySQL 作为全球最受开发者欢迎的数据库,被广泛应用于各类场景中。其中,MySQL 5.7 版本正是携程在大规模使用的数据库。但在 MySQL 系统中,并不是所有的 DDL 操作都是线上实时进行的,导致了主从复制延迟的问题,即使是 MySQL 8.0 版本也不能避免这个问题的发生。随着表中数据规模越来越大,DDL 的危险系数就越来越高,我们自研的数据库发布系统通过第三方工具 gh-ost 不断监听从节点的复制延迟,解决了长时间执行 DDL 时导致的延迟问题,确保了 MySQL 的高可用。
数据库发布系统的研发共分为四个步骤:设计系统、生成 SQL 语句和发布计划、发布测试环境、发布生产环境。
第一步,设计系统。
设计系统的分工可能在每个公司略有差异,就携程而言,由于系统中的库、表太多,DBA 不参与表结构评审,表结构的设计及变更都由研发人员进行。如下图,研发人员可以通过界面或通过导入 SQL 来完成表结构的变更操作。
第二步,生成 SQL 语句和计划发布。
所有的表结构变更提交后就进入第二步:生成 SQL 语句和发布计划。生成 SQL 语句比较好理解,基于设计系统中的修改生成代码,比如,在界面上添加一个字段 testindex,并在新加的字段上创建索引,就会生成如下 SQL 语句,包括添加的 testindex 在哪个前置字段的后面、是否为空及默认值信息等,最重要的是找到对应数据库所在的 MySQL 实例:
alter table `testyxj_part` add `testindex` varchar(10) not null default ‘’ comment ‘test’ after `datachange_lasttime`; alter table `testyxj_part` add key test(testindex);
前面说到的系统设计对应的是开发环境,而此处的 SQL 语句其实是对比开发环境和测试环境的表结构差异生成的。在携程,数据库部署环境分为开发环境、测试环境和生产环境,我们会根据配置表来生成数据库最终在哪个环境以及要去哪些集群上执行。从下图的数据库配置信息中可以看到,在每个环境中数据库的类型是不固定的,比如,在设计系统中是 MySQL,在测试环境也可以是 OceanBase。除拉数据库和 OceanBase 的集群关系外,我们还会将数据库和租户的对应关系的元数据放置在这个配置表中。
第三步:发布测试环境。
因为我们会定期检查表数据量,可以保证单表大小基本不会超过 10GB,所以测试环境的发布直接使用了原生的 MySQL DDL 命令。发布测试环境时,没有对同一个发布单下的同一张表做 DDL 合并,比如,同一张表添加2个字段,发布系统会按照字段顺序进行两次拷贝整表的过程。因此,测试环境可以兼容 OceanBase 的表结构变更,只需在连接数据库的时候带上租户即可。
第四步:发布生产环境。
生产环境的表容量大小不一,大则上百 GB,小则几 MB,为了保证 MySQL 大表做表结构变更时主从复制延迟低,我们使用了开源工具 gh-ost。在发布生产环境时,将 DDL 任务放到一个 gh-ost 队列中,进入队列后会进行待发布数据库的检测,比如,待发布数据库所在的集群主从复制是否正常、剩余磁盘空间是否可以支撑一次全量拷贝并在发布完成后磁盘剩余空间是否仍在90%以下,等等。同一个数据库的同一个表会将 DDL 操作合并发布。例如,一个发布要加2个字段,而在 gh-ost 处理时实际只会做一次全量拷贝的动作。其他的 DDL 可以使用 in-place 方法的 DDL 操作判断,如下图,将 varchar10 改为 varchar20,没有跨越 255 字节这个边界,此外,创建表删除、表索引等没有压力的 DDL 都使用原生 DDL 加速发布。
二、MySQL 与 OceanBase 在系统中的兼容性与差异性
1、MySQL 与 OceanBase 在系统中的兼容性。
携程开始使用 OceanBase 数据库后,需要和原来的 MySQL 系统兼容。有两种兼容方案,一是在 OceanBase 数据库上线后,进行表结构变更并发布,开发环境、测试环境、生产环境都采用 OceanBase;二是在迁移过程中进行兼容部署,比如,测试环境是 OceanBase,生产环境使用 MySQL。如下图,在设计系统加入 OceanBase,初期,我们把开发环境的 OceanBase 放在 MySQL 实例上,因为大部分研发人员要做的表结构变更如添加字段、索引都是常规动作,而 hash 分区、key 分区都还刚起步,这部分对于设计表结构的研发人员可能比较陌生,所以暂时将OceanBase数据库部署在 MySQL 实例上用于兼容设计系统。
将 OceanBase DB 和租户、集群的关系加入发布的配置表,这样才能正常生成发布计划,并且代码根据对应的租户连接到数据库之后执行 DDL,如下图。
得益于 OceanBase LSM Tree 的存储引擎,几乎所有 DDL 操作都是真正的 “Online DDL”,因此,OceanBase 数据库发布时我们直接采用了原生 DDL 进行发布(下图为代码判断逻辑)
2、MySQL 与 OceanBase 在系统中的差异性。
在开发环境中,携程将 MySQL 和 OceanBase 做了分离,在 OceanBase 设计时可能会使用分区、特殊压缩等与 MySQL 语法不兼容的功能,这部分在 MySQL 上无法实现,同时前端也会对这些差异功能提供默认值选项,比如,OceanBase 的索引功能可以选 local 索引和 global 索引,在携程,默认使用 global 索引。
但是,Range 分区类型的 Oceanbase 表发布索引自动转换成 local 类型。我们有一张 MySQL 的表,表上索引如下图所示:
研发人员为了实现 MySQL 业务迁移到 Oceanbase 的同时能自动完成数据清理,可能会将原先 MySQL 的普通表在 Oceanbase 中改成 range 分区表,由于分区键必须包含在主键和唯一索引中,并且唯一索引只能创建为 global 类型,因此表结构迁移到 OceanBase 之后演变成下图所示:
从 MySQL 迁移到 OceanBase 时有数据实时同步任务,某一天我们发现使用这个唯一索引查询数据出现了重复数据,如下图所示:
我们在排查时发现,原来是唯一索引失效了,如下图 show index from 的结果显示为 index error,最终,我们判断可能是 drop partition 这个分区清理的动作超时导致了索引失效。
因此,我们放弃了 range 分区,仍然保持了 MySQL 的结构,也就是对原有的表结构进行迁移。经过这次的“踩坑”,我们在发布系统中增加了一个判断,当 OceanBase 的表是 range 类型的分区表时,不能创建唯一索引,并且在生成的发布 SQL 中我们会自动将 global 的索引转换成 local 索引,以保证索引不会失效引发性能问题或其他问题,效果如下图。
三、MySQL 数据同步 OceanBase 时 DDL 遇到的问题
场景一:添加字段。
- OceanBase 添加不允许为空有默认值的字段,增量同步正常;MySQL 添加相同字段,发布期间写入数据,增量同步 OceanBase 正常。
- OceanBase 添加不允许为空无默认值的字段,增量同步报错;MySQL 添加相同字段,无法添加,非空字段需要设置默认值。
- OceanBase 添加允许有默认值的字段,增量同步正常;MySQL 添加相同字段,增量同步正常。
- OceanBase 添加允许为空无默认值的字段,增量同步正常;MySQL侧 添加相同字段,增量同步正常。
场景二:删除字段。
- MySQL 删除字段,增量同步正常; OceanBase 删除相同字段,增量同步正常。
场景三:清空表。
- MySQL 清空表 DDL 的命令无法同步至 OceanBase。
truncate table hjjtesttable;
- OceanBase 清空表后可以继续同步。
truncate table hjjtesttable;
场景四:修改字段。
- OceanBase 修改 allow null 字段为 not null 并设置默认值,需要先处理 MySQL 历史空数据,更新 MySQL 数据后未同步至 OceanBase。
alter table `hjjtesttable` modify `testyxj8` varchar(10) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci not null default '888' comment 'test'; ERROR 1138 (22004): Invalid use of NULL value update hjjtesttable set testyxj8 ='888' where testyxj8 is null;
- OceanBase 修改 not null 字段为 allow null,并且没有默认值,更新 MySQL 数据后未同步至 OceanBase。
OB:alter table `hjjtesttable` modify `testyxj8` varchar(10) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci null comment 'test'; MYSQL:update hjjtesttable set testyxj8='8888' where id =1; OB:select * from hjjtesttable where id =1 and testyxj8='8888'; Empty set (0.00 sec)
- OceanBase 不允许修改非字符类型和修改列定义,仅支持增加特定字符数据类型(VARCHAR、VARBINARY、CHAR等)的长度。
alter table hjjtesttable modify testyxj7 bigint COMMENT 'test'; ERROR 1235 (0A000): Alter non string type not supported
场景五:创建索引。
- OceanBase 创建索引,增量同步正常;MySQL 创建索引,增量同步正常。
场景六:删除索引。
- MySQL创建索引,增量同步正常;OceanBase 创建索引,增量同步正常。
- 类似上述六个场景中的情况还有很多,因此,在 MySQL 向 OceanBase 迁移的过程中,我们将 MySQL 的发布环境全部冻结了,不允许执行任何 DDL 语句。
以上就是携程通过自研数据库发布系统提高应用稳定性的实践经验,希望我们的经验分享能够帮助到你。另外,在携程与 OceanBase 合作期间,携程的各项测试表明,OceanBase 原生分布式数据库的各方面性能都可以满足携程的业务需求。因此,我们非常感谢 OceanBase 的所有技术支持人员,相信在后续的合作中,OceanBase 能发挥更优越的表现。