三步跑通ThingsBoard多租户备份:定时任务+恢复演练+行数核对完整方案
【免费下载链接】thingsboardOpen-source IoT Platform - Device management, data collection, processing and visualization.项目地址: https://gitcode.com/GitHub_Trending/th/thingsboard
凌晨三点,一位租户反馈设备历史曲线"断档",排查发现当天全库备份因磁盘满静默失败——手动 pg_dump 一次要十几分钟,还总漏核对。用这套方案做完:ThingsBoard 每天凌晨自动完成整库备份,逐租户核对实体行数,并真实导入临时库做恢复演练,备份是否"能用"当天就有结论。
环境与前置条件
| 项目 | 要求 |
|---|---|
| 运行方式 | docker compose 部署,Postgres 服务来自 docker/docker-compose.postgres.yml |
| 数据库 | 库名thingsboard,账号/密码以 compose 文件中的POSTGRES_DB、POSTGRES_PASSWORD为准 |
| 宿主机 | 已安装bash、cron;备份目录建在仓库外,如/opt/tb-backup |
| 主配置 | application/src/main/resources/thingsboard.yml,注意 EDQS 本地模式下 RocksDB 数据目录(edqs.local.rocksdb_path),它不在 pg_dump 覆盖范围内 |
| 租户模块 | 租户 CRUD 见 dao/src/main/java/org/thingsboard/server/dao/tenant/TenantDao.java |
| 卷持久化 | 数据卷参考 docker/docker-compose.volumes.yml |
数据层:先搞清楚租户数据存在哪
ThingsBoard 的租户隔离是"行级"的:device、asset、customer等业务表都带tenant_id列(见 dao/src/main/resources/sql/schema-entities.sql),时序数据统一落在ts_kv/ts_kv_latest里、按entity_id定位,而不是每个租户一张表。所以备份粒度是"整库 dump",租户维度的核对放在校验阶段做:
-- 实体表结构(schema-entities.sql 节选) CREATE TABLE IF NOT EXISTS device ( id uuid ..., tenant_id uuid NOT NULL, -- 租户隔离列 ... ); -- ts_kv_latest 按 (entity_id, key) 为主键,实体归属于租户提示:如果 EDQS 以 local 模式启用(edqs.mode: local),还要把rocksdb_path指向的目录一并打包,否则设备时序查询层的数据不在备份里。
数据层结论:一次pg_dump覆盖全部租户,下面交给定时任务。
调度层:凌晨两点自动全量备份
备份脚本放在仓库外的/opt/tb-backup/,核心是"dump + 逐租户计数留底":
#!/usr/bin/env bash # /opt/tb-backup/tb_backup.sh set -euo pipefail DB="thingsboard" STAMP=$(date +%Y%m%d-%H%M%S) OUT="/opt/tb-backup/dumps" mkdir -p "$OUT" PG_CTR=$(docker ps --format '{{.Names}}' | grep -i postgres | head -1) docker exec -e PGPASSWORD=postgres "$PG_CTR" \ pg_dump -U postgres -Fc "$DB" > "$OUT/tb_$STAMP.dump" # 留底:每个租户的设备数,恢复后逐行比对 docker exec -e PGPASSWORD=postgres "$PG_CTR" psql -U postgres -d "$DB" -tA \ -c "SELECT 'tenant '||id||' devices='||count(*) FROM device GROUP BY 1" \ > "$OUT/counts_$STAMP.txt" md5sum "$OUT/tb_$STAMP.dump" > "$OUT/tb_$STAMP.md5"用 crontab 接管调度即可:
# crontab -e 0 2 * * * /opt/tb-backup/tb_backup.sh >> /var/log/tb-backup.log 2>&1调度层跑通后,还要回答一个问题:dump 文件完好不等于数据可用,进入校验层。
校验层:恢复不出来等于没备
选"恢复演练 + 行数比对"两招。演练脚本把最新 dump 灌进一个临时库,核对后直接删库,全程不影响生产:
#!/usr/bin/env bash # /opt/tb-backup/verify_restore.sh 用法: verify_restore.sh <dump文件> DUMP=$1 TMP_DB="tb_restore_test" PG_CTR=$(docker ps --format '{{.Names}}' | grep -i postgres | head -1) docker exec -e PGPASSWORD=postgres "$PG_CTR" createdb -U postgres "$TMP_DB" docker exec -i -e PGPASSWORD=postgres "$PG_CTR" \ pg_restore -U postgres -d "$TMP_DB" < "$DUMP" # 与备份时留底的 counts_*.txt 逐租户比对 docker exec -e PGPASSWORD=postgres "$PG_CTR" psql -U postgres -d "$TMP_DB" -tA \ -c "SELECT 'tenant '||id||' devices='||count(*) FROM device GROUP BY 1" \ | diff <(cat /opt/tb-backup/dumps/counts_*.txt | tail -1) - \ && echo "VERIFY_OK $DUMP" || echo "VERIFY_FAIL $DUMP" docker exec -e PGPASSWORD=postgres "$PG_CTR" dropdb -U postgres "$TMP_DB"提示:把verify_restore.sh也加进 crontab(如每日 3 点校验前一天的 dump),并在grep VERIFY_FAIL /var/log/tb-backup.log上接监控告警;监控侧配置可参考 monitoring/src/main/conf/tb-monitoring.conf 的写法。
FAQ:最常踩的三个坑
Q:脚本里找不到 postgres 容器,pg_dump没输出?A:PG_CTR取的是docker ps里名字含 postgres 的容器。换过部署方式(如独立部署的 RDS)时,改成直连:pg_dump -h <host> -U postgres -Fc thingsboard。
Q:mkdir: cannot create directory '/opt/tb-backup':Permission denied?A:cron 以哪个用户跑就以哪个用户建目录。确认crontab -l所属用户,用sudo -u 该用户 mkdir -p建好并授权,再执行脚本。
Q:恢复演练时pg_restore报错退出?A:-Fc自定义格式必须用pg_restore还原,不能用psql -f;报错多为目标库版本高于源库。演练库createdb的报错(已存在)可先dropdb再重试。
进阶方向
数据量涨到几十 GB 后,换成pg_basebackup+ WAL 归档做增量链路,dump 文件再经rclone/aws s3同步到异地,保留策略建议"日备 30 天 + 月备 12 个月"。整篇方案只有两个脚本加两条 cron:备份自动跑、结果自动验,出问题时你拿到的是"哪一行数据丢了",而不是一个无法判断好坏的压缩包。
【免费下载链接】thingsboardOpen-source IoT Platform - Device management, data collection, processing and visualization.项目地址: https://gitcode.com/GitHub_Trending/th/thingsboard
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考