postgres备份与恢复

一、脚本说明

1.环境
docker部署的postgres服务端,宿主机安装postgres客户端,使用命令进行备份恢复

2.数据库备份:
使用 pg_dump 命令来备份数据库,并将其压缩为 .sql.gz 或者.tar.gz 格式。
在备份时会包含创建数据库的语句。

3.备份清理:
使用 find 命令清理超过指定天数(RETENTION_DAYS)的旧备份文件。

4.恢复交互式:
列出备份目录中的备份文件,显示时间、大小等信息。
用户输入备份文件编号来选择恢复的文件,进行交互式操作。

5.使用方法:
备份: 脚本会定期创建数据库备份,并保存在 BACKUP_DIR 指定的目录下。
清理: 旧备份文件会被自动删除,保留最近 RETENTION_DAYS 天的备份。
恢复: 如果需要恢复数据库,可以使用 sh pg_bak.sh restore 传参方式,选择要恢复的备份文件,并确认恢复

 

二、docker-compose部署postgres数据库

说明:生产环境中,很多是直接部署在宿主机的,我是为了测试方便使用docker部署,但操作方式一致

1.按照好docker、docker-compose

#略

2.创建目录

mkdir -p /data/aibox-common/postgres

3.执行部署

[root@localhost pg]# cat docker-compose.yaml 
version: '3.8'

services:
  postgres:
    image: postgres:15.6-bullseye
    container_name: postgres
    restart: always
    ports:
      - "5432:5432"
    environment:
      POSTGRES_USER: postgres
      POSTGRES_PASSWORD: 123456
      PGDATA: /var/lib/postgresql/data/pgdata
    volumes:
      - /etc/localtime:/etc/localtime
      - /data/aibox-common/postgres:/var/lib/postgresql/data

4.查看容器

[root@localhost pg]# docker ps |grep postgres
4657eb31724f        postgres:15.6-bullseye                               "docker-entrypoint.s…"   54 minutes ago      Up 54 minutes           0.0.0.0:5432->5432/tcp   postgres

 5.在宿主机下载postgres客户端

(1)postgres客户端必须和部署的服务端大版本一致,否则备份有问题

(2)因为我的postgres服务端版本是15.6,所以我的客户端必须也是15,在yum安装时报错,需要安装一个libzstd

https://rpmfind.net/linux/rpm2html/search.php?query=libzstd(x86-64)

#进入网页进行下载

 

 #下载后rpm  -ivh libzstd-1.4.4-1.el8.x86_64.rpm 安装,

(3)libzstd安装完成后,继续安装postgres15客户端

yum install -y https://download.postgresql.org/pub/repos/yum/reporpms/EL-7-x86_64/pgdg-redhat-repo-latest.noarch.rpm
yum install postgresql15 -y

 

三、创建模拟数据

1.登陆postgres数据库中

[root@localhost pgbak]# docker exec -it postgres psql -U postgres -h 172.16.4.60 -p 5432
Password for user postgres:          #输入docker-compose中设置的密码 123456

2.创建数据库

CREATE DATABASE aibox_cloud;

3.切换到新创建的数据库并查看其中的表

\c aibox_cloud
\dt

4.创建一个测试表

CREATE TABLE test_data (
    id SERIAL PRIMARY KEY,
    name VARCHAR(100),
    value INTEGER
);

5.插入数据

-- 插入字符串和数值类型的数据
INSERT INTO test_data (name, value) VALUES ('test11', 1100);
INSERT INTO test_data (name, value) VALUES ('test12', 1200);
INSERT INTO test_data (name, value) VALUES ('test13', 1300);
INSERT INTO test_data (name, value) VALUES ('test14', 1400);
INSERT INTO test_data (name, value) VALUES ('test15', 1500);

-- 插入日期数据
INSERT INTO test_data (name, value) VALUES ('test16', 1600);
INSERT INTO test_data (name, value) VALUES ('test17', 1700);
INSERT INTO test_data (name, value) VALUES ('test18', 1800);
INSERT INTO test_data (name, value) VALUES ('test19', 1900);
INSERT INTO test_data (name, value) VALUES ('test20', 2000);

-- 插入一些带有特殊字符的数据
INSERT INTO test_data (name, value) VALUES ('test$1', 100);
INSERT INTO test_data (name, value) VALUES ('test@2', 200);
INSERT INTO test_data (name, value) VALUES ('test#3', 300);
INSERT INTO test_data (name, value) VALUES ('test^4', 400);
INSERT INTO test_data (name, value) VALUES ('test&5', 500);

6.查看表内容

SELECT * FROM test_data;

 

psql (15.10, server 15.6 (Debian 15.6-1.pgdg110+2))
Type "help" for help.

root=# \c aibox_cloud;
psql (15.10, server 15.6 (Debian 15.6-1.pgdg110+2))
You are now connected to database "aibox_cloud" as user "root".
aibox_cloud=# \dt
         List of relations
 Schema |   Name    | Type  | Owner 
--------+-----------+-------+-------
 public | test_data | table | root
(1 row)

aibox_cloud=# select * from test_data;
 id |  name  | value 
----+--------+-------
  1 | test11 |  1100
  2 | test12 |  1200
  3 | test13 |  1300
  4 | test14 |  1400
  5 | test15 |  1500
  6 | test16 |  1600
  7 | test17 |  1700
  8 | test18 |  1800
  9 | test19 |  1900
 10 | test20 |  2000
 11 | test$1 |   100
 12 | test@2 |   200
 13 | test#3 |   300
 14 | test^4 |   400
 15 | test&5 |   500
(15 rows)

 

四、进行备份

1.备份脚本(一个是gzip压缩解压、一个是tar.gz压缩解压,主要怕有些内网环境没有gzip命令,下边我们以tar.gz为例)

#gzip压缩

[root@localhost pgbak]# cat pg_bak_gzip.sh 
#!/bin/bash

# 配置部分
PG_USER="postgres"                            # 数据库用户名
PG_PASSWORD="123456"                     # 数据库密码
PG_HOST="172.16.4.60"                      # 数据库主机地址
PG_PORT="5432"                            # 数据库端口
DB_NAME="aibox_cloud"                      # 要备份的数据库名
BACKUP_DIR="/data/backup/pgbak/sqlfile"    # 备份存储目录(宿主机目录)
RETENTION_DAYS=7                           # 保留的天数
LOG_FILE="/data/backup/pgbak/backup.log"   # 日志文件路径

# 日志输出函数
log() {
  local level=$1
  local message=$2
  echo "$(date '+%Y-%m-%d %H:%M:%S') [$level] $message" | tee -a "$LOG_FILE"
}

# 创建备份
backup() {
  local timestamp=$(date '+%Y%m%d_%H%M%S')
  
  # 检查备份目录是否存在
  if [ ! -d "$BACKUP_DIR" ]; then
    mkdir -p "$BACKUP_DIR"
    log "info" "创建备份目录:$BACKUP_DIR"
  fi

  # 备份指定的数据库
  local backup_file="${BACKUP_DIR}/backup_${DB_NAME}_${timestamp}.sql.gz"
  log "info" "开始备份数据库:$DB_NAME"
  
  # 执行备份,包含创建数据库语句
  local backup_command="PGPASSWORD=$PG_PASSWORD pg_dump -U $PG_USER -h $PG_HOST -p $PG_PORT --create $DB_NAME -v | gzip > $backup_file"
  bash -c "$backup_command"
  
  if [ $? -eq 0 ]; then
    log "info" "数据库备份成功:$backup_file"
  else
    log "error" "数据库备份失败:$backup_file"
  fi
}

# 清理旧备份
cleanup() {
  find "$BACKUP_DIR" -type f -name "backup_${DB_NAME}_*.sql.gz" -mtime +$RETENTION_DAYS -exec rm -f {} \;
  if [ $? -eq 0 ]; then
    log "info" "清理超过 $RETENTION_DAYS 天的备份文件完成"
  else
    log "error" "清理备份文件失败"
  fi
}

# 恢复指定备份
restore_interactive() {
  # 检查备份目录是否存在
  if [ ! -d "$BACKUP_DIR" ]; then
    log "error" "备份目录不存在:$BACKUP_DIR"
    return 1
  fi

  # 列出备份文件,按时间排序并显示文件大小和时间
  log "info" "列出备份目录中的备份文件:"
  local files=$(ls -lt "$BACKUP_DIR"/backup_${DB_NAME}_*.sql.gz 2>/dev/null)
  if [ -z "$files" ]; then
    log "error" "未找到备份文件,无法恢复"
    return 1
  fi

  # 显示备份文件列表及信息
  printf "%-5s %-40s %-20s %-10s\n" "编号" "文件名" "修改时间" "大小"
  local i=1
  for file in $(ls -t "$BACKUP_DIR"/backup_${DB_NAME}_*.sql.gz); do
    local filename=$(basename "$file")
    local filesize=$(du -h "$file" | cut -f1)
    local filetime=$(stat -c %y "$file" | cut -d. -f1)
    printf "%-5s %-40s %-20s %-10s\n" "$i" "$filename" "$filetime" "$filesize"
    files_array[$i]="$file"
    ((i++))
  done

  # 用户选择恢复文件
  echo -n "请输入要恢复的备份文件编号:"
  read file_index

  local selected_file=${files_array[$file_index]}
  if [ -z "$selected_file" ]; then
    log "error" "无效的编号,恢复取消"
    return 1
  fi

  log "info" "您选择恢复的文件是:$selected_file"

  # 确认恢复操作
  echo -n "确认要恢复此文件吗?(yes/no):"
  read confirm
  if [ "$confirm" != "yes" ]; then
    log "info" "恢复操作已取消"
    return 1
  fi

  # 执行恢复
  log "info" "开始恢复数据库,使用备份文件:$selected_file"
  local restore_command="PGPASSWORD=$PG_PASSWORD gunzip -c $selected_file | psql -U $PG_USER -h $PG_HOST -p $PG_PORT"
  bash -c "$restore_command"
  
  if [ $? -eq 0 ]; then
    log "info" "数据库恢复成功:$DB_NAME"
  else
    log "error" "数据库恢复失败"
  fi
}

#主程序
if [ "$1" == "restore" ]; then
  log "info" "开始执行数据库恢复操作"
  restore_interactive
else
  log "info" "PostgreSQL 备份任务开始"
  backup
  cleanup
  log "info" "PostgreSQL 备份任务完成"
fi

 

#tar.gz压缩

[root@localhost pgbak]# cat pg_bak.sh
#!/bin/bash

# 配置部分
PG_USER="postgres"                            # 数据库用户名
PG_PASSWORD="123456"                     # 数据库密码
PG_HOST="172.16.4.60"                      # 数据库主机地址
PG_PORT="5432"                            # 数据库端口
DB_NAME="aibox_cloud"                      # 要备份的数据库名
BACKUP_DIR="/data/backup/pgbak/sqlfile"    # 备份存储目录(宿主机目录)
RETENTION_DAYS=7                           # 保留的天数
LOG_FILE="/data/backup/pgbak/backup.log"   # 日志文件路径

# 日志输出函数
log() {
  local level=$1
  local message=$2
  echo "$(date '+%Y-%m-%d %H:%M:%S') [$level] $message" | tee -a "$LOG_FILE"
}

# 创建备份
backup() {
  local timestamp=$(date '+%Y%m%d_%H%M%S')
  
  # 检查备份目录是否存在
  if [ ! -d "$BACKUP_DIR" ]; then
    mkdir -p "$BACKUP_DIR"
    log "info" "创建备份目录:$BACKUP_DIR"
  fi

  # 临时备份文件路径
  local backup_temp_dir="${BACKUP_DIR}/temp"

  # 检查临时备份目录是否存在
  if [ ! -d "$backup_temp_dir" ]; then
    mkdir -p "$backup_temp_dir"
    log "info" "创建临时备份目录:$backup_temp_dir"
  fi
  
  #备份文件名称
  local backup_temp_file="${backup_temp_dir}/backup_${DB_NAME}_${timestamp}.sql"

  # 执行 pg_dump 输出到临时文件
  log "info" "开始备份数据库:$DB_NAME"
  PGPASSWORD=$PG_PASSWORD pg_dump -U $PG_USER -h $PG_HOST -p $PG_PORT --create $DB_NAME -v -f "$backup_temp_file"
  
  if [ $? -eq 0 ]; then
    log "info" "数据库备份成功:$backup_temp_file"

    # 压缩备份文件
    local backup_file="${BACKUP_DIR}/backup_${DB_NAME}_${timestamp}.tar.gz"
    log "info" "开始压缩备份文件:$backup_file"
    tar -czf "$backup_file" -C "$backup_temp_dir" "$(basename "$backup_temp_file")"
    
    if [ $? -eq 0 ]; then
      log "info" "备份压缩成功:$backup_file"
    else
      log "error" "备份压缩失败"
    fi

    # 删除临时文件
    rm -f "$backup_temp_file"
    rmdir "$backup_temp_dir"
  else
    log "error" "数据库备份失败"
  fi
}

# 清理旧备份
cleanup() {
  find "$BACKUP_DIR" -type f -name "backup_${DB_NAME}_*.tar.gz" -mtime +$RETENTION_DAYS -exec rm -f {} \;
  if [ $? -eq 0 ]; then
    log "info" "清理超过 $RETENTION_DAYS 天的备份文件完成"
  else
    log "error" "清理备份文件失败"
  fi
}

# 恢复指定备份
restore_interactive() {
  # 检查备份目录是否存在
  if [ ! -d "$BACKUP_DIR" ]; then
    log "error" "备份目录不存在:$BACKUP_DIR"
    return 1
  fi

  # 列出备份文件,按时间排序并显示文件大小和时间
  log "info" "列出备份目录中的备份文件:"
  local files=$(ls -lt "$BACKUP_DIR"/backup_${DB_NAME}_*.tar.gz 2>/dev/null)
  if [ -z "$files" ]; then
    log "error" "未找到备份文件,无法恢复"
    return 1
  fi

  # 显示备份文件列表及信息
  printf "%-5s %-40s %-20s %-10s\n" "编号" "文件名" "修改时间" "大小"
  local i=1
  for file in $(ls -t "$BACKUP_DIR"/backup_${DB_NAME}_*.tar.gz); do
    local filename=$(basename "$file")
    local filesize=$(du -h "$file" | cut -f1)
    local filetime=$(stat -c %y "$file" | cut -d. -f1)
    printf "%-5s %-40s %-20s %-10s\n" "$i" "$filename" "$filetime" "$filesize"
    files_array[$i]="$file"
    ((i++))
  done

  # 用户选择恢复文件
  echo -n "请输入要恢复的备份文件编号:"
  read file_index

  local selected_file=${files_array[$file_index]}
  if [ -z "$selected_file" ]; then
    log "error" "无效的编号,恢复取消"
    return 1
  fi

  log "info" "您选择恢复的文件是:$selected_file"

  # 确认恢复操作
  echo -n "确认要恢复此文件吗?(yes/no):"
  read confirm
  if [ "$confirm" != "yes" ]; then
    log "info" "恢复操作已取消"
    return 1
  fi

  # 执行恢复
  log "info" "开始恢复数据库,使用备份文件:$selected_file"
  local restore_command="PGPASSWORD=$PG_PASSWORD tar -xzf $selected_file -O | psql -U $PG_USER -h $PG_HOST -p $PG_PORT"
  bash -c "$restore_command"
  
  if [ $? -eq 0 ]; then
    log "info" "数据库恢复成功:$DB_NAME"
  else
    log "error" "数据库恢复失败"
  fi
}

# 主流程
# 判断传入参数并决定是备份还是恢复
if [ "$1" == "restore" ]; then
  log "info" "开始执行数据库恢复操作"
  restore_interactive
else
  log "info" "PostgreSQL 备份任务开始"
  backup
  cleanup
  log "info" "PostgreSQL 备份任务完成"
fi

 

2.执行备份,可以取消pg_dump中的-v 参数不输出备份信息

[root@localhost pgbak]# sh pg_bak.sh
2024-12-12 17:40:38 [info] PostgreSQL 备份任务开始
2024-12-12 17:40:38 [info] 创建临时备份目录:/data/backup/pgbak/sqlfile/temp
2024-12-12 17:40:38 [info] 开始备份数据库:aibox_cloud
pg_dump: last built-in OID is 16383
pg_dump: reading extensions
pg_dump: identifying extension members
pg_dump: reading schemas
pg_dump: reading user-defined tables
pg_dump: reading user-defined functions
pg_dump: reading user-defined types
pg_dump: reading procedural languages
pg_dump: reading user-defined aggregate functions
pg_dump: reading user-defined operators
pg_dump: reading user-defined access methods
pg_dump: reading user-defined operator classes
pg_dump: reading user-defined operator families
pg_dump: reading user-defined text search parsers
pg_dump: reading user-defined text search templates
pg_dump: reading user-defined text search dictionaries
pg_dump: reading user-defined text search configurations
pg_dump: reading user-defined foreign-data wrappers
pg_dump: reading user-defined foreign servers
pg_dump: reading default privileges
pg_dump: reading user-defined collations
pg_dump: reading user-defined conversions
pg_dump: reading type casts
pg_dump: reading transforms
pg_dump: reading table inheritance information
pg_dump: reading event triggers
pg_dump: finding extension tables
pg_dump: finding inheritance relationships
pg_dump: reading column info for interesting tables
pg_dump: finding table default expressions
pg_dump: flagging inherited columns in subtables
pg_dump: reading partitioning data
pg_dump: reading indexes
pg_dump: flagging indexes in partitioned tables
pg_dump: reading extended statistics
pg_dump: reading constraints
pg_dump: reading triggers
pg_dump: reading rewrite rules
pg_dump: reading policies
pg_dump: reading row-level security policies
pg_dump: reading publications
pg_dump: reading publication membership of tables
pg_dump: reading publication membership of schemas
pg_dump: reading subscriptions
pg_dump: reading large objects
pg_dump: reading dependency data
pg_dump: saving encoding = UTF8
pg_dump: saving standard_conforming_strings = on
pg_dump: saving search_path = 
pg_dump: saving database definition
pg_dump: creating DATABASE "aibox_cloud"
pg_dump: connecting to new database "aibox_cloud"
pg_dump: creating TABLE "public.test_data"
pg_dump: creating SEQUENCE "public.test_data_id_seq"
pg_dump: creating SEQUENCE OWNED BY "public.test_data_id_seq"
pg_dump: creating DEFAULT "public.test_data id"
pg_dump: processing data for table "public.test_data"
pg_dump: dumping contents of table "public.test_data"
pg_dump: executing SEQUENCE SET test_data_id_seq
pg_dump: creating CONSTRAINT "public.test_data test_data_pkey"
2024-12-12 17:40:38 [info] 数据库备份成功:/data/backup/pgbak/sqlfile/temp/backup_aibox_cloud_20241212_174038.sql
2024-12-12 17:40:38 [info] 开始压缩备份文件:/data/backup/pgbak/sqlfile/backup_aibox_cloud_20241212_174038.tar.gz
2024-12-12 17:40:38 [info] 备份压缩成功:/data/backup/pgbak/sqlfile/backup_aibox_cloud_20241212_174038.tar.gz
2024-12-12 17:40:38 [info] 清理超过 7 天的备份文件完成
2024-12-12 17:40:38 [info] PostgreSQL 备份任务完成

3.查看备份数据

[root@localhost pgbak]# ls /data/backup/pgbak/sqlfile
 backup_aibox_cloud_20241212_173159.tar.gz backup_aibox_cloud_20241212_174038.tar.gz

4.可以解压验证备份的有效性

 

五、进行恢复

1.删除aibox_cloud库下的test_data表

aibox_cloud=# DROP TABLE IF EXISTS test_data;
DROP TABLE
aibox_cloud=# \dt
Did not find any relations.

2.数据恢复

[root@localhost pgbak]# sh pg_bak.sh restore
2024-12-12 17:56:34 [info] 开始执行数据库恢复操作
2024-12-12 17:56:34 [info] 列出备份目录中的备份文件:
编号 文件名                                修改时间         大小    
1     backup_aibox_cloud_20241212_175439.tar.gz 2024-12-12 17:54:39  4.0K      
2     backup_aibox_cloud_20241212_175414.tar.gz 2024-12-12 17:54:14  4.0K      
3     backup_aibox_cloud_20241212_174038.tar.gz 2024-12-12 17:40:38  4.0K      
4     backup_aibox_cloud_20241212_173159.tar.gz 2024-12-12 17:31:59  4.0K      
请输入要恢复的备份文件编号:1
2024-12-12 17:56:44 [info] 您选择恢复的文件是:/data/backup/pgbak/sqlfile/backup_aibox_cloud_20241212_175439.tar.gz
确认要恢复此文件吗?(yes/no):yes
2024-12-12 17:56:46 [info] 开始恢复数据库,使用备份文件:/data/backup/pgbak/sqlfile/backup_aibox_cloud_20241212_175439.tar.gz
Password for user root: 
SET
SET
SET
SET
SET
 set_config 
------------
 
(1 row)

SET
SET
SET
SET
ERROR:  database "aibox_cloud" already exists
ALTER DATABASE
You are now connected to database "aibox_cloud" as user "root".
SET
SET
SET
SET
SET
 set_config 
------------
 
(1 row)

SET
SET
SET
SET
SET
SET
CREATE TABLE
ALTER TABLE
CREATE SEQUENCE
ALTER TABLE
ALTER SEQUENCE
ALTER TABLE
COPY 15
 setval 
--------
     15
(1 row)

ALTER TABLE
2024-12-12 17:56:49 [info] 数据库恢复成功:aibox_cloud

3.查看数据库,表和数据已经恢复了

aibox_cloud=# \dt
         List of relations
 Schema |   Name    | Type  | Owner 
--------+-----------+-------+-------
 public | test_data | table | root
(1 row)

aibox_cloud=# select * from test_data;
 id |  name  | value 
----+--------+-------
  1 | test11 |  1100
  2 | test12 |  1200
  3 | test13 |  1300
  4 | test14 |  1400
  5 | test15 |  1500
  6 | test16 |  1600
  7 | test17 |  1700
  8 | test18 |  1800
  9 | test19 |  1900
 10 | test20 |  2000
 11 | test$1 |   100
 12 | test@2 |   200
 13 | test#3 |   300
 14 | test^4 |   400
 15 | test&5 |   500
(15 rows)

4.恢复完成

 

posted @ 2024-12-12 18:49  Leonardo-li  阅读(58)  评论(0编辑  收藏  举报