本文作者 YoKing Ma 从事于一家总部位于宁波,业界领先的新能源上市企业,公司业务涵盖光伏新能源产品的开发、制造及销售。基于 OceanBase 在该公司传统监控数据的存储应用,笔者对该深度应用实践进行了分享。
*本篇内容为 「OceanBase 布道师计划」优质投稿内容,活动仍在进行中,欢迎感兴趣点击《重磅激励!OceanBase布道师计划即日开启》了解详情或投稿。🥳
作为一家产值百亿的企业,监控系统是重要的 IT 管理工具之一,对保障企业业务连续性和预告风险有重要意义。2022 年,该公司选用 Zabbix 企业监控系统,主要负责对公司分布在国内外的服务器、操作系统、中间件、数据库、网络设备等进行指标监控;对集团业务系统设置监控预警,确保集团所有系统异常时准确告警;对 IT 设施的巡检、事件的回溯提供指标数据支撑,便于 IT 管理人员可以快速获取系统中各个组件的历史数据。
一开始,选择该系统是因为其开源,且经过多年发展,不仅架构稳定还具备“监控万物”的能力,适合公司这种以传统架构为主,云原生架构比例很低的企业。与此同时,公司内部相关 IT 人员也有该系统的使用经验,上手门槛较低。
彼时,笔者刚加入公司,对刚上线的企业监控系统做了很多持续且深度的优化和改造,不断提升监控系统的及时性、准确性。但由于该系统底层数据库使用 MySQL 8.0,被 MySQL 的架构限制,因此很快出现了问题。
问题 1:架构高可用瓶颈。无论选择哪种架构,都不尽人意。
主从架构的问题:非读写方式,与单点的性能无差异不推荐。主节点故障后,需要停机切换,还需要校验主从节点的数据差异,所以不推荐。读写分离方式有 2 种改造方式,一是改造 DAL 层代码,这种方式影响后续版本功能的迭代,不推荐;二是引入如 ProxySQL 的中间件,但增加了一层组件,在性能和可靠性上有所降低。
双主架构的问题:单写是我们目前采用的方式,节点上套用一层 keepalived 作为虚拟地址,方便切换。双写的话需要控制写入行的 ID,避免主键冲突和数据冗余。需要改造,不推荐。
MGR 架构的问题:一致性强,但需要配合如 ProxySQL 之类的中间件实现读写分离。在实际测试中,MGR 容易产生雪崩效应,即一个节点掉出后,可能导致整个集群崩溃。所有采用复制方式的架构最大的问题点在于,老架构的写入量很大,binlog 不能保留太久(非常占用磁盘空间),如果复制关系断开太久会导致主从节点之间无法找到同步位点,无法重新同步上。
第二个问题,读写冲突。老系统架构的写入量很大,在业务高峰时段,运维人员和业务人员查询监控数据、history 数据向 trends 数据转换、告警比较等,很容易产生读写冲突(乐观锁)。但 Zabbix 还有一个管家服务,会定期清理过期的监控数据,这容易造成悲观锁,使数据库性能急剧下降。在数据量不断增大的同时,这个冲突会越来越明显。
第三个问题,容量问题。尽管对监控项的数量、保留时间做了大量的改造,但仍有大量的数据需要保存下来。运行 1 年多,Zabbix 的数据库已经超过 1TB,最大单表数据量超 7 亿。数据本身的容量只是问题中的其一,其二是 InnoDB 的 binlog。
基于监控架构痛点,我们需要结合业务情况进行优化,下面是我们对优化的思考。
在众多优化案例里,笔者挑选了一个典型的数据优化案例来分享。
在制作监控模板时,其中 Linux 操作系统监控模板的监控项就多达 100 个,面对公司 2000+生产级别服务器,监控项规模达到了 20w 个,假设每隔 5 分钟对所有的监控项收集一次数据,那么,每小时数据库中将有 20w*(60/5)=240w 笔数据要写入 history 表。
5 分钟采集一次是一种理想情况,因为采集间隔变大后,数据的精度会降低。对于一些流量数据、CPU、内存、I/O 等使用率数据,用户往往要求以较高的精度采集,采集间隔可能是在 1 分钟左右,这样的代价是产生了更多的监控数据。
同时考虑到 history 到 trends 转换,每小时从 hisoty 和 history_unit 表中取出完整 1 小时的监控值进行运算后(min、avg、max),分别 insert 到 trends 和 trends_unit 表中。
这个过程中,查询到的结果集大、运算量大,需要大量的缓存,往往引起 MySQL 的临时表或临时表文件创建过快、磁盘 I/O 过大、占用 SWAP、引发大事务等问题。
随着数据量的增大,以及上文提到的事务悲观锁冲突,导致在清理历史数据时,往往会清理任务失败,进而历史数据越积越多。同时,锁表的问题导致在环境中无法使用 dump 方式进行备份,只能使用物理备份。但在使用物理备份时,由于清理历史数据的过程中使用 delete 方式,造成主要业务表中的空间未得到正常释放,产生碎片,在备份前需要进行碎片整理,使得备份业务推进起来非常艰巨。
大批量的 insert、delete 操作使服务器的 binlog 变得庞大,导致存储压力很大。调小后,如果主从一断,主库上的 binlog 位点很快就会循环覆盖,导致从节点要重新恢复才能加入到集群。
对于上述问题,结合 Zabbix 数据库中的数据表,我们就需要通过优化以下数据表来解决问题。
其中,以 history 开头的表为存储历史数据,trends 开头的是趋势数据。历史和趋势是在 Zabbix 中存储数据的两种方法。
1. History,历史保存着每一个 item 的数据。即从客户端收集过来的原始数据,这部分表的表结构都差不多,唯一的不同是保存的数据类型。如果一个监控项(item)一分钟采集 1 次,那每天就有 24*60*60=86400 笔数据。
如果是数值类型,表中字段如下:itemid bigint(20)、clock int(11)、value bigint(20)、ns int(11),大概一笔数据是 8+4+8+4=24 B,一天有 24*86400≈2 MB; 如果是 history_str 或者 history_text 两个表,其中的数据字段变成了 value varchar(255) COLLATE utf8mb4_bin 和 value text COLLATE utf8mb4_bin ,varchar(255) utf8mb4 最长是 255*4=1020 B,而 text 字段最大可以到 65535 B ,我们考虑极端情况,一个字符型或文本型的 item ,按 1 分钟采集 1 次,分别对应产生的数据量为(8+4+1020+4)*86400≈85 MB 或 (8+4+65535+4)*86400≈5 GB ; 历史数据的大小取决于监控项(item)条目数量、保留时长、监控项(item)值类型、监控项(item)的采集间隔等条件。
2. Trends,是每小时监控的数据聚合后的结果,保存一小时内某个 item 的平均值、最大值和最小值,可以理解为是 history 表的压缩数据,因此减少了对资源的需求。
trends 仅针对数值类型的 history 表,而 history_str 、history_log 和 history_text 是没有趋势表的; 趋势表是由历史表通过 housekeeper(管家服务,类似于一个定时任务)转换而来,转换过程必须消耗数据库的性能。
history 表中存在 2 个时间字段,一个是 clock,另外一个是 ns,接收 item 值时的时间值存放在两个字段内,大于 1 秒的部分存放找 clock 字段单位是秒(s),小于一秒的部分存放在 ns 字段单位是纳秒(ns)。
两个字段相加的值才是接收 item 值时的时间值,一般不用关心小于 1 秒的部分。但是,在 Zabbix 自己做统计时很多查询中都用到了这两个字段,而这些表中,并没有在 ns 上面做索引,导致了全表扫描,所以我们将 history 表进行改造,其余 history 表的改造同样参照下面的方法。
CREATE TABLE `history_old` (
`itemid` bigint(20) unsigned NOT NULL,
`clock` int(11) NOT NULL DEFAULT '0',
`value` double NOT NULL DEFAULT '0',
`ns` int(11) NOT NULL DEFAULT '0',
KEY `history_1` (`itemid`, `clock`) BLOCK_SIZE 16384 LOCAL
) DEFAULT CHARSET = utf8mb4
CREATE TABLE `history` (
`itemid` bigint(20) unsigned NOT NULL,
`clock` int(11) NOT NULL DEFAULT '0',
`value` double NOT NULL DEFAULT '0',
`ns` int(11) NOT NULL DEFAULT '0',
PRIMARY KEY (`itemid`, `clock`, `ns`)
) DEFAULT CHARSET = utf8mb4
针对 history 系列表和 trends 系列表进行分区,定期 create 分区和 drop 分区。在做分区管理时,遇到三个问题。
第一,因为监控数据表的数据量太大,直接在源表上操作风险很大,执行效率也会很低。
所以采用了创建新表,然后将原表中的数据 insert /*+ ENABLE_PARALLEL_DML PARALLEL(2) */ …… select …… 的方式处理 ,但发现部分数据量少的表还能够导数成功,大表、业务繁忙的表根本无法执行。
*这里用到了 OB DML 的并行,具体可以复制 https://www.oceanbase.com/docs/common-oceanbase-database-cn-1000000001050810 进入博客参考详细内容)
第二,如果我们使用 dump 方式去备份还原表,中间停机时间要很久。
第三,使用 DataX 做数据同步的话,什么时候追平数据,如果中途停止,找最后位点的难度很大。
在 Zabbix 中使用频度最高的就是将最新的数据写入数据库,按一定的时间间隔,将历史数据(history)转换趋势数据(trends),删除过期的历史数据,以及 IT 运维人员查询监控数据、报表(grafana)等操作。查询(排序、汇总、计算)、增删(大事务)会极大地消耗 MySQL 数据库的性能,使业务的处理时间变长。
最简单的优化方案就是优化 MySQL 的 innodb buffer pool、query_cache_size、tmp_table_size、innodb_log_buffer_size、 sort_buffer_size、read_buffer_size、join_buffer_size、binlog_cache_size 的大小,尽可能将数据留在内存中,加快数据的处理速度。
但是无论如何优化 buffer,也不能无限制增大,当达到物理内存的上限时,就需要去扩容内存。同时,大量的大事务会过度消耗磁盘 I/O(当脏页达到一定量的时候就会落盘,落盘就会产生 I/O),加剧数据库的压力。底层物理设备的扩容会涉及停机、迁移等中断服务的事件,也是额外增加成本。
可见,在 MySQL 及操作系统的调优和扩容硬件的方法始终会有尽头或者瓶颈,不是最优解,我们需要寻找新的出路。
对于新的数据存储方案,我们希望除了能够解决现有架构瓶颈和业务痛点外,还需要具备三个条件:
必须兼容 MySQL 的语法、函数与表达式、数据类型、表分区、字符集、字符序。 具备 HTAP 能力,能够在基于关系型数据结构在数据引擎层面处理好 TP 和 AP 的关系。 在不依托于其他技术手段的情况下具备高可用、多活能力。
由于 Zabbix Server 支持 MySQL、PostgreSQL,但团队中没有人熟练驾驭 PostgreSQL,同时,在 Zabbix 的 MySQL 数据库上已经开发了比较多的应用、报表,如果贸然将数据库从 MySQL 切换成 PostgreSQL,需要对之前的应用、报表做再开发,因此,暂时不考虑 PostgreSQL,于是将选型的范围放在兼容 MySQL 模式的数据库。
上文提到我们同时在使用 InnoDB 引擎,MySQL 的 TokuDB 引擎作为 InnoDB 引擎的优化升级版本,就成为了我们选型数据存储方案中的备选。与此同时,我们也对目前市面上热度较高的新兴数据库进行了调研,在兼容性的综合考量下,将 TokuDB 引擎、OceanBase 作为优选方案。
综合考虑如下因素,相较于 TokuDB 部分能力不满足预期,OceanBase 支持 HTAP、多活高可用,以及社区活跃,有非常多的参考资料,容易上手。因此,我们最终决定使用 OceanBase。
我们按照官方文档执行部署,过程中遇到了一些小问题,通过查询官方文档、咨询社区技术人员等方式逐一解决。总的来说,OceanBase 的上线流程较为顺利,从 MySQL 迁移到 OceanBase 非常容易。
因为我们用的是 OceanBase 社区版,所以,在迁移前无法使用 OceanBase 迁移评估工具来对迁移过程进行评估,只能依靠经验来判断,在社区的官方人员和社区用户群朋友们的帮助下,我们在迁移前做了如下检查,供大家参考。
字符集检查。因为 OceanBase 没有完整支持 MySQL 的字符集和排序,所以,需要在迁移前做好评估。 OceanBase 中没有 event,原库中有 event 时,需要通过别的方法实现,后文列举了案例。 OceanBase 中对大小写不敏感,有些 MySQL 库可能对大小写敏感,所以要注意原库中 lower_case_table_names 的配置。 如果需要做反向同步,在原库中提前建立好相应的账号、授权,并把 omstxndb 库建立好。 如果主外键约束的话,需要提前关闭主外键约束检查 SET FOREIGN_KEY_CHECKS=0;
相关各个节点的解释可参见官方文档。此外,上文中提到的在 Zabbix 的 history 相关表,trends 相关表需要实现表分区。分区管理需要定时任务,否则管理员定期维护分区的工作量很大。在 MySQL 中,我们可以通过 Event+存储过程的方式来实现自动化。而由于 OceanBase 中没有 event,这似乎又要碰壁了。
俗话说:“当门关上的时候,会给你开一扇窗”,因此,我们可以使用 OceanBase 的 ODC(开发者中心。OceanBase Developer Center)来实现,这是一个开源的企业级数据库协同开发平台。里面集成了“分区计划”的模块,可以将其看作 MySQL 中的 Event+存储过程方案的 Plus 版本。
在写本文时,我们的 Zabbix 监控系统已经在 OceanBase 中运行了半年,相比之前使用 MySQL 时,从资源配置上来讲,在获取相同性能的情况下,可以大幅降低硬件平台的投入。
最初,由于 OceanBase 兼容 MySQL,公司的开发人员和系统管理人员对于数据迁移并没有表示反对,在迁移过程中,OceanBase 的高可靠、HTAP 等能力使笔者在与业务系统运维部门沟通迁移计划时都比较顺利,迁移后带来的性能提升和 80%的空间节约,也让业务部门非常满意。
在性能提升方面,最直观的感受是之前在查询周期较长的历史数据(几周或几个月)时,通常需要好几秒(至少 4 秒)时间才能将数据渲染出来,现在运行在 OceanBase 上之后,基本上点击完后就可以立即加载出统计图;另外一个感受就是,以前 zabbix 有各种各样的性能告警,频度也比较高,自从迁移到 OceanBase 上之后,此类告警明显减少。
总而言之,OceanBase 是一个符合我们预期的数据库平台,目前,我们也在不断探索和实践 OceanBase 的新功能。以下,我将结合公司业务场景与 OceanBase 的功能进行简单总结。
支持并行,对于大量数据的操作,打开并行功能,可以大幅提高处理的效率。 对于资源的热更新,依托于 OCP 平台,可以对于数据库的资源(CPU、内存等)、zone、参数进行热更新,减少停机。 压缩率高,我们在导入 Zabbix 数据库时,在 MySQL 上看到的库的大小是 1.2TB,导入 OceanBase 后,数据库的大小只有 260GB 左右,数据量缩小了 80%,极大地节省了存储空间。 与生俱来的伸缩性和分布式能力,可以支持在线横向(扩展 zone)、纵向(扩展分片)扩容,所有的数据同步都交由后台自动完成,基本不需要人工参与。不论是横向还是纵向都提供了数据的冗余能力。原先我们还尝试过使用 MySQL 的 MGR,虽然保障了一致性,但运维过程非常艰辛,一旦有节点挂掉很有可能把整个集群打穿。 支持慢查询单独队列,如果遇到慢查询,OceanBase 会将慢查询放在一个单独的队列里面慢慢执行,不影响短平快的查询,不阻塞。
此外,OceanBase 丰富的生态工具也为我们带来了更加自动化、更加便捷的运维管理能力。
首先,OCP 为数据库的管理提供了很多实用的功能:
云原生、多租户:可以将数据库在一个平台统一管理,简化数据库的创建、运维浑然一体。使用租户模式,实现租户与租户之间资源隔离。 SQL 诊断:集成 SQL 诊断功能,便捷、快捷的观测 TopSQL、SlowSQL、并行 SQL。可以通过可视化的手段,观测 SQL 的执行情况、快速诊断。
备份:原生支持物理备份和日志备份功能。
其次,OMS 支持 OceanBase-CE、MySQL、PostgreSQL、TiDB、Kafka 和 RocketMQ 等多种类型的数据源与 OceanBase 社区版进行实时数据传输,以及 OceanBase 社区版 MySQL 租户间的数据迁移。
通过自动化、流程化的方案,轻松对数据进行迁移,简化迁移流程。 在一个流程中支持结构同步、数据同步、数据增量同步、数据反向同步,减少运维人员的运维工作。 支持通过匹配规则对部分表进行同步、对表中的数据进行同步。
最后,ODC 作为开发者中心,提供了许多便捷的功能,比如:
SQL 计划:定期执行一段 SQL,类似于 event 功能。 分区计划:原生支持分区创建、删除的功能,非常好用。 数据归档:对一些历史数据通过一定的规则进行归档(从一个表(库)中复制到另外一个表(库))当中。 数据清理:按照规则,定时清理表(库)中的数据。 对所有的执行作业可以查看执行是否成功,方便运维。 所有功能开箱即用,省时省力。
对于 OceanBase 的这些特性,结合我们实际的业务,未来会将更多的库迁移到 OceanBase 上面来。目前正在进行的有生产设备数据采集系统(下称:数采系统)、报表系统的迁移。
数采系统的需求也是类似于 Zabbix,特点是:
数据量大。部分数据都以秒级从生产设备中采集数据,并发量大,数据量大,导致整个库里的数据会很大。 数据时序性。部分表被设计成了横表,横表中存在时序字段,在查询时需要将横表转换为纵表,查询过程复杂关联多,这里我们使用了窗口函数来解决时序内采集结果的对比分析,后面可以升级到 OceanBase 新版,采用列存方式实现。 定期归档、清理:这个功能可以结合 ODC 的数据归档和数据清理功能。在清理过程中结合并行方式,加快处理的能力。 表分区。对大表进行分区管控。
报表系统的特点是:
典型要求 HTAP,借助于 OceanBase 支持慢查询队列,可以有效的保障大事务、大查询最大程度不干涉短平快事务的执行。
当然,一切美好的背后,仍然是有存在不足的地方,执行计划不仅难读,还抖动、恶化,导致 SQL 在执行时候不稳定,而且系统日志理解困难,另外对于中文的支持还是有提升空间。
OceanBase 的功能非常强大是事实,每一个事务都在不断进步。从我们试用到使用 OceanBase 的过程中,有“坑”也有惊喜,遇到问题,先看手册,自己思考。如果自己想不明白可以在社区的问答板块和用户答疑群寻求帮助,社区的老师会及时答复(相较于其他开源社区,OceanBase 的及时性和准确性要好很多)。
OceanBase 还在不断发展,对于 4.3 版本中的列存、物化视图等功能还等着我们探索,道阻且长,行则将至。
10 月 23 号,OceanBase 年度发布会将在北京召开,我们在夜幕降临后为大家准备了一场「开源技术交流会」,届时贝壳、快手、陌陌等用户以及 OceanBase 的技术专家将来到现场,与大家一起“畅吃、畅聊 OB”,欢迎大家通过《全议程公布!想想想想想想见你!》了解全部议程详情和报名参与本次年度发布会,进一步走近 OceanBase,认识 OceanBase!💪