书接上文,《MySQL备份恢复方案(支持全量 + 增量 + PITR + dry-run + 报告生成)》里面讲了关于PITR按时间点恢复数据的方案。
那怎么少的了binlog日志的实时增量备份呢?
今天给大家分享一个mysql远程实时拉取binlog日志文件进行备份的脚本,弥补之前的文章所没有讲到的内容。
首先,第一步把我们的目标数据库配置信息写入一个配置文件(.my.cnf):
[client]
user=backup_user
password='******'
host=mysql-******-headless.******-data.svc.cluster.local
port=3306
ssl-mode=DISABLED
第二步就是,准备好实时备份脚本(binlogbackup.sh):
#!/bin/bash
# =============================================
#@File : binlogbackup.sh
#@Title : MySQL远程实时binlog备份脚本(支持 GTID 断点续传)
#@Time : 2025/08/28 11:54:00
#@Author : Beauty Bear
#@Version : 1.2.0
#@Desc : 支持 GTID 断点续传,避免重复拉取
# 兼容 proxychains4 和 systemd
# =============================================
# 将 主要用到的程序所在路径 添加到 PATH 里面
export PATH="/usr/local/bin:/usr/bin:/data/mysql/mysql84/bin:$PATH"
# 接收 systemd 传递的参数:IP_PORT
IP_PORT="${1:-$MYSQL_INSTANCE}"
if [ -z "$IP_PORT" ]; then
echo "Usage: $0 "
echo "Example: $0 192.168.4.15_3306"
exit 1
fi
echo "IP_PORT: $IP_PORT"
# 解析 host 和 port
MYSQL_HOST="${IP_PORT%_*}"
MYSQL_PORT="${IP_PORT#*_}"
# =============== 配置 ===============
MYSQL_USER="backup_user"
MYSQL_PASSWORD="******"
# 是否启用 proxychains4 代理
PROXY_ENABLED=true
PROXY_CONFIG="/etc/proxychains4.conf"
# 备份目录
echo "MYSQL_HOST: $MYSQL_HOST,MYSQL_PORT:$MYSQL_PORT"
LOCAL_BACKUP_DIR="/data/mysql_binlog_backup/${MYSQL_HOST}_${MYSQL_PORT}"
echo "LOCAL_BACKUP_DIR:$LOCAL_BACKUP_DIR"
mkdir -p "$LOCAL_BACKUP_DIR"
# 日志
BACKUP_LOG="${LOCAL_BACKUP_DIR}/mysql_binlog_backup.log"
SLEEP_SECONDS=10
# GTID 记录文件
LAST_GTID_FILE="${LOCAL_BACKUP_DIR}/last_gtid_executed"
# 临时配置文件
MY_CNF="${LOCAL_BACKUP_DIR}/.my.cnf"
# =============== 工具函数 ===============
log() {
echo "[$(date '+%Y/%m/%d %H:%M:%S')] $1"
}
exec_cmd() {
if [ "$PROXY_ENABLED" = true ]; then
if ! command -v proxychains4 > /dev/null; then
log "proxychains4 未安装"
return 127
fi
if [ -n "$PROXY_CONFIG" ] && [ -f "$PROXY_CONFIG" ]; then
PROXY_CMD="proxychains4 -q -f $PROXY_CONFIG"
else
PROXY_CMD="proxychains4 -q"
fi
$PROXY_CMD "$@"
else
"$@"
fi
}
# =============== GTID 工具函数 ===============
# 读取上次记录的 GTID
get_last_gtid() {
[ -f "$LAST_GTID_FILE" ] && cat "$LAST_GTID_FILE" | tr -d ' \n'
}
# 保存当前 GTID
save_gtid() {
echo "$1" > "$LAST_GTID_FILE"
}
# 检查主库 GTID 是否已包含指定 GTID(包含则返回 0)
# 使用 MySQL 的 GTID_SUBSET 函数
gtid_contains() {
local SUPERSET="$1"
local SUBSET="$2"
local SQL="SELECT GTID_SUBSET('$SUPERSET', '$SUBSET')"
local RESULT=$(exec_cmd mysql --defaults-file="$MY_CNF" -sNe "$SQL" )
[ "$RESULT" = "1" ] && return 0 || return 1
}
# 获取主库当前 Executed_Gtid_Set
get_master_gtid_set() {
exec_cmd mysql --defaults-file="$MY_CNF" -sNe "SHOW SLAVE STATUS\G" | grep "Executed_Gtid_Set" | cut -d: -f2- | tr '\n' ',' | sed 's/,$//' || \
exec_cmd mysql --defaults-file="$MY_CNF" -sNe "SELECT @@GLOBAL.GTID_EXECUTED"
}
# 从指定 binlog 开始,找出第一个 GTID 大于 last_gtid 的位置
find_starting_binlog() {
local START_BINLOG="$1"
local LAST_GTID=$(get_last_gtid)
if [ -z "$LAST_GTID" ]; then
echo "$START_BINLOG"
return
fi
# 获取主库当前 GTID 集合
MASTER_GTID_SET=$(get_master_gtid_set)
if [ -z "$MASTER_GTID_SET" ]; then
log "无法获取主库 GTID 集合,从 $START_BINLOG 开始"
echo "$START_BINLOG"
return
fi
# 如果主库 GTID 已包含 last_gtid,说明可以继续
if gtid_contains "$MASTER_GTID_SET" "$LAST_GTID"; then
log "主库已包含 GTID: $LAST_GTID,可安全继续"
echo "$START_BINLOG"
return
else
log "主库未包含 GTID: $LAST_GTID,可能数据不一致!从 $START_BINLOG 重新开始"
echo "$START_BINLOG"
return
fi
}
# =============== 主循环 ===============
main() {
log "服务启动,目标: $MYSQL_HOST:$MYSQL_PORT"
[ "$PROXY_ENABLED" = true ] && log "通过 proxychains4 代理连接"
# 恢复上次的 GTID(用于后续 --skip-gtids)
LAST_GTID=$(get_last_gtid)
[ -n "$LAST_GTID" ] && log "上次结束 GTID: $LAST_GTID"
while true; do
# 获取主库当前正在写的 binlog
CURRENT_BINLOG=$(exec_cmd mysql --defaults-file="$MY_CNF" -sNe "SHOW MASTER STATUS" | awk '{print $1}')
echo "CURRENT_BINLOG:$CURRENT_BINLOG"
if [ -z "$CURRENT_BINLOG" ]; then
log "无法获取主库 binlog 信息,${SLEEP_SECONDS}s 后重试"
sleep $SLEEP_SECONDS
continue
fi
# 决定从哪个 binlog 开始(考虑 GTID)
START_BINLOG=$(find_starting_binlog "$CURRENT_BINLOG")
log "开始拉取 binlog: $START_BINLOG"
# 执行 mysqlbinlog(关键:记录 GTID)
# 使用 --base64-output=DECODE-ROWS -v 解码行事件(可选)
exec_cmd mysqlbinlog \
--no-defaults \
--read-from-remote-server \
--stop-never \
--raw \
--host=$MYSQL_HOST \
--port=$MYSQL_PORT \
--user=$MYSQL_USER \
--password="$MYSQL_PASSWORD" \
--ssl-mode=DISABLED \
--to-last-log \
--result-file="$LOCAL_BACKUP_DIR/" \
"$START_BINLOG"
EXIT_CODE=$?
log "mysqlbinlog 退出,返回码: $EXIT_CODE"
# 从日志中提取最后一个 GTID(可改进:使用专用解析)
LAST_PARSED_GTID=$(grep -E "SET @@SESSION.GTID_NEXT" "$BACKUP_LOG" | tail -1 | awk '{print $3}' | tr -d "'")
if [ -n "$LAST_PARSED_GTID" ]; then
save_gtid "$LAST_PARSED_GTID"
log "已保存 GTID 断点: $LAST_PARSED_GTID"
fi
case $EXIT_CODE in
0|2)
break
;;
127)
sleep 10
;;
*)
log "连接中断,${SLEEP_SECONDS}s 后重试..."
sleep $SLEEP_SECONDS
;;
esac
done
rm -f "$MY_CNF"
exit $EXIT_CODE
}
main "$@"有朋友可能有疑问,为啥用了.my.cnf配置文件,脚本里面又要配置一遍数据库账号密码?这是因为 我们用到的mysqlbinlog是一个用于处理 MySQL 数据库的二进制日志的工具,它主要用于查看或导出二进制日志中的数据。mysqlbinlog 本身并不直接使用数据库连接配置文件(如 my.cnf 或 .my.cnf 文件中的设置),而是通过命令行参数直接指定数据库连接信息(如用户名、密码、主机名等)。好了,闲话少叙~!
接下来就是,老规矩,上系统服务了:
vim /etc/systemd/system/mysqlbinlog@.service
[Unit]
Description=MySQL Remote Binlog Replication for %i
Documentation=https://dev.mysql.com/doc/refman/5.7/en/mysqlbinlog.html
After=network.target
[Service]
Type=simple
User=root
Group=root
# 从 %i 解析 IP_PORT
Environment=MYSQL_INSTANCE=%i
# 执行脚本
ExecStart=/usr/local/bin/binlogbackup.sh %i
# 报错自动重启
Restart=on-failure
RestartSec=10
# 超时
TimeoutStopSec=30
# 标准输出到 journal
StandardOutput=journal
StandardError=journal
# 安全加固(可选)
NoNewPrivileges=true
PrivateTmp=true
ProtectSystem=false
ProtectHome=false
# 明确允许写入 /data
ReadWritePaths=/data
# 环境文件(可选:把密码放这里)
# EnvironmentFile=/etc/mysql/backup.env
[Install]
WantedBy=multi-user.target✅ 查看状态与日志及服务启停:
# 查看状态
systemctl status mysqlbinlog@mysql-******-headless.******-data.svc.cluster.local_3306
# 查看实时日志
journalctl -u mysqlbinlog@mysql-******-headless.******-data.svc.cluster.local_3306 -f
# 查看历史日志
journalctl -u mysqlbinlog@mysql-******-headless.******-data.svc.cluster.local_3306 --since "2025-08-27 10:00"
# 重新加载服务配置
systemctl daemon-reload
# 服务启动
systemctl start mysqlbinlog@mysql-******-headless.******-data.svc.cluster.local_3306
# 服务重启
systemctl restart mysqlbinlog@mysql-******-headless.******-data.svc.cluster.local_3306
# 服务停止
systemctl stop mysqlbinlog@mysql-******-headless.******-data.svc.cluster.local_3306
最后修改时间:2025-09-12 14:34:23
「喜欢这篇文章,您的关注和赞赏是给作者最好的鼓励」
关注作者
【版权声明】本文为墨天轮用户原创内容,转载时必须标注文章的来源(墨天轮),文章链接,文章作者等基本信息,否则作者和墨天轮有权追究责任。如果您发现墨天轮中有涉嫌抄袭或者侵权的内容,欢迎发送邮件至:contact@modb.pro进行举报,并提供相关证据,一经查实,墨天轮将立刻删除相关内容。




