暂无图片
暂无图片
暂无图片
暂无图片
暂无图片

MySQL远程实时备份Binlog日志的Shell脚本

原创 雄霸天下 2025-09-12
170

书接上文,《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进行举报,并提供相关证据,一经查实,墨天轮将立刻删除相关内容。

评论