近期我无聊看了一眼我司的 Zabbix 的数据库,wdnmd居然历史数据已经超过100G了,内部管家已经形同虚设了(一年前的数据还在)。当年搭建监控系统的同事(也是我的前辈)早就走了,当时根本没考虑到后期的监控量,也没做分区表也没做主从,搞到想升级都难清数据更不敢去操作。

但不行啊,迟早这 600G 的硬盘要快消耗完了,而且这破机械硬盘还是做 RAID5,写性能实在一般,I/O wait 卡着 CPU 的队列了;说到 CPU,哎这 E5606 双路辣鸡 U 都已经带不动了,230 NVPS 就已经 80% user 了,还不换还不换,到时候出事了(例如监控功能突然不工作)领导又要让我出整改方案和扣 KPI。于是跟我的部门领导说了下,领导说可以搞随便搞,服务器去找个机房抢一台好的,那就可以放手开搞了。

旧系统的瓶颈 & 旧版短板

(以下图片源于内部培训 PPT,因为做了特效所以图片看起来很怪)

占用空间过大,难以管理

看这里,光是历史数据已经超过 90G 了,曾经试过根据时间戳删一些历史记录,但光是删 300 天前的数据也需要 1 小时,而且...会锁表,一锁表业务数据就会更加慢或根本不工作了。正因为这个之后也没管过空间了。再加上慢查询的效率是真的低,曾经扩大过 innodb 的缓冲区空间才缓解这问题。

QQ截图20201128110144.png

Zabbix 队列阻塞较多

由于我司的 Zabbix 主要是利用简单检查的 Ping 功能来监视各出口的网络质量情况,但简单检查只能在 Server 或 Proxy 工作,因此是使用 Zabbix Proxy 作为监测点反馈数据,而没有去用 Agent 去搞做。但由于原来的服务器性能拉胯,导致有时候有丢包来了,队列还在卡着还没处理过来,存在一定的延时。

升级后看下面的对比,旧版 Proxy 和新版 Proxy 都是运作在同一个服务器(新版用 docker),一看对比显然不是节点端的服务器有问题,而是服务端存在有瓶颈。

QQ截图20201128110108.png

新版的功能很香

旧版 Zabbix 是 3.2 版本的,毕竟是 2017 年搭建的那时候已经是最新版了,但两年过去已经出到 5.2 了。新功能肯定有升级的必要。例如执行动作可以让 Proxy 执行,而不是用 Agent 或 Server 执行(因此旧版还是让服务端执行 SSH 脚本让节点工作,但是连接期间会有时间消耗,所以有时候想获取路由图由于连接慢了导致故障都过去了才执行动作)。

在 3.4 版本新增的预处理功能也比较有用,毕竟有些需求需要用函数计算的,我一直想做的华为交换机的收发光监控在 SNMP 获取的数值是基于 mw 的,而在交换机是 dbm,单位转换需要用到对数函数,但旧版不支持!升级后可以用 Javascript 计算就行了。

难题点

升级计划我在去年就已经在想了,但是一直想不出好的方案,不过目前都已经迎刃而解了。

  1. 升级需要备份数据但数据库太大,用 mysqldump 备份太慢且内容过大,还会锁表。
  2. 升级失败怎么办,可以回退吗?Proxy 都要一起升级到最新版本吗?
  3. 如何解决历史数据的问题?

开始着手升级

注意:我是不要任何历史数据,完全是另起炉灶,只保留配置数据!

架构参考

在本次新版的 Zabbix,为了解决历史数据的存放问题,我特意使用了 Elasticsearch 存放历史数据,而 MySQL 只存放配置信息。但其实 Zabbix 官方推荐使用 TimescaleDB 来存放历史数据的,但 TSDB 是基于 PSQL 的,可我不会用,至少 elasticsearch 我在 ELK 是用过的。使用第三方的数据库就不需要做 MYSQL 的分区表。

Proxy 就使用共存方案,即物理机使用 Zabbix 3.2 的旧版节点端,而用 Docker 再部署一个新版节点端,两边服务端所使用的端口不同,因此可以一起工作。至于 Docker 的节点容器为什么要用 SQLite3 作为后端存储,主要是懒。

QQ截图20201128113203.png

配置选型

难得有机会能随便挑公司的各大数据中心的服务器设备(当然高货色都是被预留了不能用),就要用好一点的设备。因此考虑到这点就选了这服务器。因为考虑到性能就选线程比较多的,内存尽量挑高频的。

  • CPU:双路 E5-2620 v3(6C12T,找了可以用的设备就这台好看点了)
  • 内存:16x2 DDR4 2400T (没说型号,估计是三星条了)
  • 硬盘:2x 120G SSD(系统盘 + 数据盘)+ 4 个 SAS 500G 硬盘(RAID10,数据盘)

部署过程

底层我加上了 BBR 加速,但对于独服来说没有什么帮助,搞不搞都无所谓的。

LNMP 安装

这个忽略了,网上一堆教程,我是用宝塔一键搭的。但在扩展上,Nginx 我加了 Brotli,PHP 我加了 OPcache,都是用以前搭这博客的搭建经验。

另外为了用上 HTTP2,自签了一份证书(没用域名),也是为了更好的利用带宽资源和性能。

数据库

MySQL 我是使用 MySQL 8.0,但为了安全升级,我是特意先备份数据库再丢到虚拟机升级,都无问题后再导入到物理机中。

备份数据库

由于 mysqldump 是基于单线程导出效率很慢,而且会锁表,虽然我们可以 Innodb 可以不锁表,但不知道为什么如何我要排除某些表并不锁表就会导致无法排除表。

因此我也用了 xtrabackup 来备份毕竟效率很高也不锁表。但是!我使用的时候好像无法跳过一些我不想备份的数据表(例如历史数据和趋势的那些表),因此只能备份全表并筛选掉一些不想备份的数据表。具体需要备份的表请看文章最下面,一定要备份好 information_schema,否则还原的时候会出错。

还需要创建一个用户专门来备份该数据库,当然用 root 也行,关于这个请百度找 xtrabackup 的相关教程吧。

备份命令

innobackupex --defaults-file=/etc/my.cnf --user=bkpuser --password=redhat --tables-file=/root/backup.txt  --no-lock /root/backup/

备份好会显示 complate 的,这个只能慢慢等了,反正不锁表不会影响业务,如果是 MyISAM 的话就没办法了,因为会锁表。

还原数据库并升级

将服务端的数据的文件导出来后,放到虚拟机找个目录存放,应用事务再导回数据库的存放位置即可。

innobackupex --apply-log /root/2020-11-18_10-26-49/
innobackupex --copy-back /root/2020-11-18_10-26-49/

还原好后,再导出成普通的 SQL 文件,用 phpmyadmin 或 mysqldump 什么的都行,导出文件为 origin.sql,导出之后直接清理所有表,后面会说。要注意的是因为我们没有备份历史数据等表,因此直接还原升级会报错说没有找到这个表结构,因此下面的操作需要做好这几点,个人建议再装一个 phpmyadmin,下面的操作我全程是管理页面操作,而没有用 SQL 语句来删数据。

装好 Mysql 5.7,再部署好 zabbix-server-mysql 和 zabbix-server-frontend(直接用 yum 安装就行了),要注意装旧版的意义是补全数据库表结构和验证数据还原是否能正常工作。

导入源码包的数据库架构,将 /usr/share/doc/zabbix-server-mysql-*/create.sql.gz 导入进来,导入之后将大部分表都删掉,只保留以下表:

zabbix.alerts 
zabbix.auditlog 
zabbix.events 
zabbix.history 
zabbix.history_log 
zabbix.str 
zabbix.str_sync 
zabbix.sync 
zabbix.text 
zabbix.uint 
zabbix.uint_sync 
zabbix.node_cksum 
zabbix.proxy_dhistory 
zabbix.proxy_history 
zabbix.service_alarms 
zabbix.services_times 
zabbix.trends 
zabbix.trends_uint

删除完毕后,再导入我们刚刚导出的 origin.sql,按理来说应该不会报错,如果有报错的话需要根据提示内容解决。这时候应该差不多好了,直接配置好 3.2 前端的内容和服务端的配置文件,看看能否正常运作,不能运作就根据提示看看哪里缺了表再补回来。

运作成功后,关掉 zabbix-server,将现在数据库的文件备份出来一个 SQL 叫 successorigin.sql。卸载 5.7 部署 8.0,关于这里如果不升级的话可以跳过,部署好后,重新创建一个新数据库导入刚刚的 SQL 文件,正常来说标准的 SQL 语句导入进去是不会报错的。

卸载 3.2,按照官方教程部署 5.0,具体操作也不说了。部署完毕好配置好 conf 文件,开启服务查看日志,正常来说系统会识别到这个数据库是旧版 Zabbix,就会自动升级到最新版本的表结构。我当初遇到了不少了报错,其实就是跟着错误日志补充一下外键,重启下服务就能升级成功了(在新版的 create.sql ,什么地方报错了我就用 CTRL+F 找到这个字段,重新执行相关的 SQL 语句)。

总的来说,就是补全旧版缺失的表结构来升级新版。

配置历史数据存放到 Elasticsearch

部署 elasticsearch 数据库我也不说了,这里不会像某些文章为了做个功能要把搭建全程都写下来。不过为了后期的业务量,需要调整以下两个参数比较好

Elasticsearch

编辑 /etc/elasticsearch/jvm.options ,调整 JVM 池的内存大小

-Xms8g
-Xmx8g

编辑 /etc/elasticsearch/elasticsearch.yml,配置好缓存回收机制(否则 Zabbix 图形会断层)

indices.fielddata.cache.size:  40%
indices.breaker.fielddata.limit: 60%

如果有下 Zabbix 源码包的话里面就有 database/elasticsearch/elasticsearch.map,按照里面的内容 PUT 过去即可,关于分片和副本要根据实际需求配置,详情 REST 看附录二,类型一定不要错,否则事后看最新数据和图表会出错!也可以参考官方文档:ELASTICSEARCH SETUP

如果要清理历史数据,elasticsearch 有定时清理多少天前索引的自动化插件,这个先不说了。

Zabbix

Zabbix-server.conf 的配置,一定要开 DateIndex,否则不会按照时间索引读数据,这样跟 Mysql 存放历史没啥区别。

HistoryStorageURL=http://127.0.0.1:9200/
HistoryStorageTypes=uint,dbl,str,log,text
HistoryStorageDateIndex=1

前端的配置,编辑 /conf/zabbix.conf.php

global $DB,$HISTORY; # 这个要放在最前面
# 注释有每个索引放在哪个地址,我这边统一放在同一个地址
$HISTORY['url'] = 'http://ESIP:9200';
# 这个是将所有历史类型都可以在 es 中读取
$HISTORY['types'] = ['uint', 'text','log','str','dbl'];

Zabbix 配置

考虑到业务量,我会稍微在 Zabbix 开高一点线程数,调高系统的缓存池等等。

StartPollers=100
StartPreprocessors=20
CacheSize=4G
HistoryCacheSize=768M
ValueCacheSize=512M

部署 Zabbix Proxy 的 Docker

以下是我实验多次的命令,可以做一个脚本来执行。为了实现共存,我是将监听端口调成 20051,否则会跟旧版 Proxy 的端口冲突。使用 SQLite 是轻便快速而已,而且每个节点都要配 MySQL 实在是太累了。

#部署 docker 的一键脚本
curl -sSL https://get.daocloud.io/docker -k | sh
# 升级xfs工具避免无法启动
yum update xfsprogs -y
# 开启docker
systemctl restart docker
systemctl enable docker
# 下载 zabbix-proxy-sqlite3 的容器
docker pull zabbix/zabbix-proxy-sqlite3:centos-5.0-latest
# 部署容器,并指定相关参数
docker run -d -e "ZBX_SERVER_HOST=Zabbix的IP" -e  "ZBX_PROXYOFFLINEBUFFER=48" -e  "ZBX_HOSTNAME=主机名" -e  "ZBX_CONFIGFREQUENCY=300" -e "ZBX_SERVER_PORT=20051" -e "ZBX_LISTENPORT=20051" -v /etc/localtime:/etc/localtime --net host --name zabbixproxy docker.io/zabbix/zabbix-proxy-sqlite3:centos-5.0-latest
# 开启容器
docker start zabbixproxy
# 查看zbx-proxy日志情况,确认有无异常
docker logs zabbixproxy

但其实用 Docker 有一个不好点就在于后期弄脚本动作可能就比较麻烦,毕竟不能直接使用宿主的工具。

运作测试

试运作的一周,产生的数据也是非常非常多,光是历史数据也是一天一个 G 的增长,按照规律来说一年下来估计才 360 G。半年清理一次数据也未必会吃满硬盘。

QQ截图20201128125326.png

在旧版的 CPU 占用率高达 70%,而同样业务量的新版只占用了 3% ,真的是一个天一个地。

旧版 Zabbix 3.2

QQ截图20201128130023.png

新版 Zabbix 5.0

QQ截图20201128125931.png

由于新版的底层优化比较多,总体使用体验比旧版好了不少,至少值班同事都比较喜欢新版的操作和速度,总算对工作有一定的提升吧。

附录

附录一:需要备份的表

内容太长,请直接看文本文件。

backup.txt

附录二: REST API 语句

最初始的几个索引请对着 elasticsearch.map 提交,我这里说的是做模板 (本质上就是对着原来的索引的内容复制就行了) 和时间索引的配置。这里只列举 dbl/uint/text,至于 log/str 根据下面的规则,更改 index_patternsvalue 的内容就行了。

建立模板

PUT _template/text_template

{
   "index_patterns": [
      "text*"
   ],
   "settings": {
      "index": {
         "number_of_replicas": 0,
         "number_of_shards": 1
      }
   },
   "mappings": {
      "properties": {
         "itemid": {
            "type": "long"
         },
         "clock": {
            "format": "epoch_second",
            "type": "date"
         },
         "value": {
            "fields": {
               "analyzed": {
                  "index": true,
                  "type": "text",
                  "analyzer": "standard"
               }
            },
            "index": false,
            "type": "text"
         }
      }
   }
}

PUT _template/uint_template

{
   "index_patterns": [
      "uint*"
   ],
   "settings": {
      "index": {
         "number_of_replicas": 0,
         "number_of_shards": 1
      }
   },
   "mappings": {
      "properties": {
         "itemid": {
            "type": "long"
         },
         "clock": {
            "format": "epoch_second",
            "type": "date"
         },
         "value": {
         "type": "long"
      }
   }
}

PUT _template/dbl_template

{
   "index_patterns": [
      "dbl*"
   ],
   "settings": {
      "index": {
         "number_of_replicas": 0,
         "number_of_shards": 1
      }
   },
   "mappings": {
      "properties": {
         "itemid": {
            "type": "long"
         },
         "clock": {
            "format": "epoch_second",
            "type": "date"
         },
         "value": {
         "type": "double"
      }
   }
}

建立时间模板

PUT _ingest/pipeline/text-pipeline

{
   "description": "daily text index naming",
   "processors": [
      {
         "date_index_name": {
            "field": "clock",
            "date_formats": [
               "UNIX"
            ],
            "index_name_prefix": "text-",
            "date_rounding": "d"
         }
      }
   ]
}

PUT _ingest/pipeline/dbl-pipeline

{
   "description": "daily dbl index naming",
   "processors": [
      {
         "date_index_name": {
            "field": "clock",
            "date_formats": [
               "UNIX"
            ],
            "index_name_prefix": "dbl-",
            "date_rounding": "d"
         }
      }
   ]
}

PUT _ingest/pipeline/uint-pipeline

{
   "description": "daily uint index naming",
   "processors": [
      {
         "date_index_name": {
            "field": "clock",
            "date_formats": [
               "UNIX"
            ],
            "index_name_prefix": "uint-",
            "date_rounding": "d"
         }
      }
   ]
}