实训03 企业数据库与缓存服务部署

发布于 1 小时前 7 次阅读


实训03 企业数据库与缓存服务部署

本实训目标:在一台 Debian 12 服务器上完成 MySQL 8.0、Redis 7、MongoDB 6 三套数据服务的部署,并完成业务库 CRUD 实战、自动备份策略与恢复演练、综合验收。

实际部署环境(与本实训册原始拓扑的差异见下): - 操作系统:Debian GNU/Linux 12(bookworm),systemd 管理 - 目标主机:192.168.1.11,登录用户 vm1(在 sudo 组,密码 654321) - 三套服务全部装在 同一台主机 上(原始拓扑里 Wiki 在 VM2、数据库在 VM3,但本环境只拿到这一台 Debian 主机,所以全部合到 192.168.1.11) - 该主机 预装了 MariaDB,但处于 inactive / disabled 状态,未占用 3306 端口,与本次安装的 MySQL 8.0 不冲突(详见附录)


目录

所有脚本均位于实训目录的 scripts/ 下,由无界面 SSH 助手 ssh_run.py 推送到 192.168.1.11 上以 root 执行(等效于用 Tabby/FinalShell 连上去敲命令,但可批量化、可抓回显)。


环境与运维工具说明

1. 部署方式

本次部署使用 scripts/ssh_run.py(paramiko 密码登录)远程执行脚本:

# 以普通用户 vm1 执行单条命令
python ssh_run.py "whoami"

# 需要 root 的命令:自动包装 sudo(完整 setuid)
python ssh_run.py -s "systemctl status mysqld"

# 把本地脚本推到远端以 root 执行(-s 必须带上,否则按 vm1 跑)
python ssh_run.py -s --script 01_install_mysql.sh

连接信息(默认写在 ssh_run.py 常量里,可用环境变量覆盖): HOST=192.168.1.11 USER=vm1 PW=654321 PORT=22

2. sudo 提权的关键细节(务必注意)

本环境的 sudoers 开启了 use_pty。直接用 sudo cmd / sudo -S cmd 只能拿到 有效 uid=0(effective root),真实 uid 仍是 vm1,导致 dpkg/apt/rm 受限于用户权限而失败。可靠做法是用 sudo -i(登录 shell,真实与有效 uid 都置为 0):

# 唯一稳定可靠的整段 root 执行方式
echo '654321' | sudo -S -p '' -i bash -c 'systemctl restart mysqld'

ssh_run.py-s 模式正是把整条命令按此方式包起来执行的。

3. 防火墙

Debian 12 默认用 nftables + ufw。本实训在内网环境直接放行对应端口:

sudo ufw allow 3306/tcp
sudo ufw allow 6379/tcp
sudo ufw allow 27017/tcp
sudo ufw status

(若未启用 ufw,则无需操作;服务 bind 在 0.0.0.0 即可内网访问。)


服务规划

服务 软件 / 版本 安装方式 端口 数据目录 用途
关系型数据库 MySQL 8.0.36 官方二进制 tarball(绕过 APT 过期密钥) 3306 /data/mysql/data Wiki、UniTalk 业务数据
缓存服务 Redis 7.4.0 源码编译 6379 /data/redis 会话缓存、热点数据
文档数据库 MongoDB 6.0.14 官方二进制 tarball(ubuntu2204 版,Debian 12 可用) 27017 /data/mongodb/data 在线文档存储

第一层级:MySQL 数据库部署与应用

任务1:二进制安装 MySQL 8.0

为什么不用 APT?该 Debian 12 主机的 MySQL APT 仓库 GPG 密钥已过期,apt-get install mysql-server 会报密钥校验失败。改用官方通用二进制包(glibc 版本),稳定且版本可控。

第一步:安装依赖并下载二进制包

# 1) 安装运行依赖
sudo apt-get update -qq
sudo apt-get install -y -qq libaio1 libnuma1 numactl wget

# 2) 下载 MySQL 8.0.36 通用二进制包(glibc2.17 版)
cd /usr/local/src
sudo wget -q https://dev.mysql.com/get/Downloads/MySQL-8.0/mysql-8.0.36-linux-glibc2.17-x86_64.tar.xz -O mysql-8.0.36.tar.xz
sudo tar -xf mysql-8.0.36.tar.xz -C /usr/local/
sudo ln -sfn /usr/local/mysql-8.0.36-linux-glibc2.17-x86_64 /usr/local/mysql
sudo chown -R root:root /usr/local/mysql
/usr/local/mysql/bin/mysql --version    # 验证:mysql  Ver 8.0.36 ...

第二步:创建 mysql 用户与目录

sudo groupadd -f mysql
sudo useradd -r -g mysql -s /bin/false mysql 2>/dev/null || true
sudo mkdir -p /data/mysql/data /data/mysql/log /data/mysql/tmp /var/run/mysqld
sudo chown -R mysql:mysql /data/mysql /var/run/mysqld

第三步:配置文件 /etc/my.cnf

[mysqld]
basedir         = /usr/local/mysql
datadir         = /data/mysql/data
socket          = /var/run/mysqld/mysqld.sock
pid-file        = /var/run/mysqld/mysqld.pid
port            = 3306
user            = mysql
bind-address    = 0.0.0.0
character-set-server = utf8mb4
collation-server     = utf8mb4_general_ci
max_connections      = 500
max_connect_errors   = 1000
wait_timeout         = 600
interactive_timeout  = 600
innodb_buffer_pool_size        = 512M
innodb_log_file_size           = 128M
innodb_file_per_table          = 1
log-error       = /data/mysql/log/error.log
slow_query_log  = 1
slow_query_log_file = /data/mysql/log/slow.log
long_query_time = 2

[client]
socket          = /var/run/mysqld/mysqld.sock
default-character-set = utf8mb4

[mysql]
default-character-set = utf8mb4

说明:用户统一用 IDENTIFIED WITH mysql_native_password 创建(见任务2),可避免 Navicat 等老客户端出现 caching_sha2_password 认证报错;因此 my.cnf 不再额外设置 default_authentication_plugin

第四步:初始化数据目录(记住临时密码)

sudo /usr/local/mysql/bin/mysqld --initialize --user=mysql \
  --basedir=/usr/local/mysql --datadir=/data/mysql/data

# 从错误日志抓取自动生成的临时 root 密码
sudo grep 'temporary password' /data/mysql/log/error.log
# 输出示例:A temporary password is generated for root@localhost: xxxxxxx

第五步:systemd 服务与开机自启

新建 /etc/systemd/system/mysqld.service

[Unit]
Description=MySQL 8.0 Server
After=network.target

[Service]
User=mysql
Group=mysql
Type=notify
TimeoutSec=0
ExecStart=/usr/local/mysql/bin/mysqld --defaults-file=/etc/my.cnf
LimitNOFILE=65535
Restart=on-failure
RestartSec=5

[Install]
WantedBy=multi-user.target
sudo systemctl daemon-reload
sudo systemctl enable mysqld
sudo systemctl start mysqld
sudo systemctl status mysqld       # active (running) 即成功

第六步:安全初始化(改 root 密码 / 删匿名用户 / 禁 root 远程)

用上一步的临时密码登录,执行:

/usr/local/mysql/bin/mysql -uroot -p'临时密码' --connect-expired-password
ALTER USER 'root'@'localhost' IDENTIFIED WITH mysql_native_password BY 'Root@123456';
DELETE FROM mysql.user WHERE User='';
DELETE FROM mysql.user WHERE User='root' AND Host NOT IN ('localhost','127.0.0.1','::1');
DROP DATABASE IF EXISTS test;
FLUSH PRIVILEGES;

✅ 任务1 验收

systemctl status mysqld          # active (running)
/usr/local/mysql/bin/mysql -uroot -p'Root@123456' -e "SELECT VERSION();"
# 8.0.36
MySQL 8.0.36 安装验证 — systemctl status + VERSION()

任务2:创建业务库与受限用户(含只读监控账户)

以新 root 密码登录后,创建两个业务库与三个受限用户。用户 host 限定为 10.4.0.%(内网网段),而非 %(任意 IP),既能内网远程连接,又不对外网开放。

CREATE DATABASE IF NOT EXISTS wiki_db DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci;
CREATE DATABASE IF NOT EXISTS unitalk_db DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci;

-- Wiki 业务账户(仅 wiki_db 全部权限)
CREATE USER IF NOT EXISTS 'wiki_user'@'10.4.0.%' IDENTIFIED WITH mysql_native_password BY 'Wiki@123456';
GRANT ALL PRIVILEGES ON wiki_db.* TO 'wiki_user'@'10.4.0.%';

-- UniTalk 业务账户(仅 unitalk_db 全部权限)
CREATE USER IF NOT EXISTS 'unitalk_user'@'10.4.0.%' IDENTIFIED WITH mysql_native_password BY 'Unitalk@123456';
GRANT ALL PRIVILEGES ON unitalk_db.* TO 'unitalk_user'@'10.4.0.%';

-- 只读监控账户(全局 SELECT,无写入/改结构权限)
CREATE USER IF NOT EXISTS 'monitor'@'10.4.0.%' IDENTIFIED WITH mysql_native_password BY 'Monitor@123456';
GRANT SELECT ON *.* TO 'monitor'@'10.4.0.%';

FLUSH PRIVILEGES;

-- 验证
SELECT user, host FROM mysql.user WHERE host='10.4.0.%';
SHOW GRANTS FOR 'monitor'@'10.4.0.%';   -- 只有 SELECT
MySQL 业务用户与受限账户 — user/host + monitor GRANTS

关键点monitor 只有全局 SELECT,稍后的综合验收会验证它”能读不能写”(写操作返回 ERROR 1142)。


任务3:Wiki 数据迁移 SQLite → MySQL(方法说明,本环境未实跑)

本环境说明:原始实训拓扑里 Wiki 运行在独立的 VM2 上,但本环境只提供了 192.168.1.11 这一台 Debian 主机,且未部署可达的 Wiki 服务,因此 SQLite → MySQL 迁移未实际执行。下面给出标准迁移方法,供有 Wiki 主机时参考。

思路:导出 SQLite → 转换方言 → 导入 MySQL → 改 Wiki 配置

# 1) 在 Wiki 主机上导出 SQLite
sqlite3 /var/www/wiki/data/wiki.sqlite .dump > /tmp/wiki_sqlite_dump.sql

# 2) 推荐用 sqlite3-to-mysql 自动建表+迁数据(pip 安装)
pip3 install sqlite3-to-mysql
sqlite3mysql -f /var/www/wiki/data/wiki.sqlite \
  -d wiki_db -u wiki_user --mysql-password 'Wiki@123456' \
  -h 192.168.1.11 -P 3306

# 3) 或手工处理 dump 后导入(去掉 PRAGMA/BEGIN/COMMIT,改自增写法)
sed -i '/PRAGMA/d; /BEGIN TRANSACTION/d; /COMMIT/d' /tmp/wiki_sqlite_dump.sql
mysql -h 192.168.1.11 -uwiki_user -p'Wiki@123456' wiki_db < /tmp/wiki_sqlite_dump.sql

# 4) 修改 Wiki 连接配置(以 MediaWiki 的 LocalSettings.php 为例)
# $wgDBtype = "mysql";  $wgDBserver = "192.168.1.11";
# $wgDBname = "wiki_db"; $wgDBuser = "wiki_user"; $wgDBpassword = "Wiki@123456";

任务4:SQL CRUD 实战

场景:运维日常帮业务查数、改数、统计。所有操作在 unitalk_db 中进行,实际操作脚本为 02_sql_crud.sh

登录:

/usr/local/mysql/bin/mysql -uroot -p'Root@123456' unitalk_db

第一步:建表(含外键约束)

CREATE TABLE departments (
  dept_id    INT PRIMARY KEY,
  dept_name  VARCHAR(50) NOT NULL,
  location   VARCHAR(50)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

CREATE TABLE employees (
  emp_id     INT PRIMARY KEY,
  name       VARCHAR(50) NOT NULL,
  gender     CHAR(1),
  dept_id    INT,
  salary     DECIMAL(10,2),
  hire_date  DATE,
  FOREIGN KEY (dept_id) REFERENCES departments(dept_id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

CREATE TABLE operation_logs (
  log_id      INT PRIMARY KEY AUTO_INCREMENT,
  emp_id      INT,
  action      VARCHAR(50),
  action_time DATETIME DEFAULT CURRENT_TIMESTAMP,
  note        VARCHAR(100),
  FOREIGN KEY (emp_id) REFERENCES employees(emp_id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

第二步:插入数据(4 部门 / 10 员工 / 5 日志)

INSERT INTO departments (dept_id, dept_name, location) VALUES
  (10, '技术部', '北京'), (20, '市场部', '上海'),
  (30, '财务部', '深圳'), (40, '人力资源部', '广州');

INSERT INTO employees (emp_id, name, gender, dept_id, salary, hire_date) VALUES
  (1001, '张伟', 'M', 10, 18000.00, '2020-04-01'),
  (1002, '李娜', 'F', 10, 16500.00, '2021-07-12'),
  (1003, '王芳', 'F', 20, 12000.00, '2019-09-23'),
  (1004, '刘强', 'M', 20, 13500.00, '2022-01-15'),
  (1005, '陈静', 'F', 30, 15000.00, '2018-11-05'),
  (1006, '杨光', 'M', 30, 14200.00, '2021-03-30'),
  (1007, '赵敏', 'F', 40, 11000.00, '2023-02-18'),
  (1008, '孙磊', 'M', 10, 19500.00, '2017-06-21'),
  (1009, '周婷', 'F', 20, 12800.00, '2022-08-09'),
  (1010, '吴勇', 'M', 40, 10500.00, '2020-12-01');

INSERT INTO operation_logs (emp_id, action, note) VALUES
  (1001, '登录', '后台登录成功'), (1008, '审批', '审批了报销单'),
  (1005, '转账', '月工资发放'),   (1002, '登录', '后台登录成功'),
  (1010, '招聘', '发布招聘需求');

第三步:查询(SELECT)

-- 全表查询(按工号排序)
SELECT emp_id, name, gender, dept_id, salary, hire_date FROM employees ORDER BY emp_id;

-- 条件查询:技术部(dept_id=10) 且 薪资>=17000
SELECT emp_id, name, salary FROM employees WHERE dept_id=10 AND salary>=17000;

-- 模糊查询:姓名含 '伟' 或 '磊'
SELECT emp_id, name FROM employees WHERE name LIKE '%伟%' OR name LIKE '%磊%';

-- 联表:员工所属部门名称与地点
SELECT e.emp_id, e.name, d.dept_name, d.location
FROM employees e JOIN departments d ON e.dept_id=d.dept_id ORDER BY e.emp_id;

-- 聚合:各部门人数与平均薪资
SELECT d.dept_name, COUNT(*) AS headcount, ROUND(AVG(e.salary),2) AS avg_salary
FROM employees e JOIN departments d ON e.dept_id=d.dept_id
GROUP BY d.dept_name ORDER BY avg_salary DESC;
各部门人数统计

– 排序+分页:薪资 TOP3 SELECT emp_id, name, salary FROM employees ORDER BY salary DESC LIMIT 3;


### 第四步:更新(UPDATE)

```sql
-- 给技术部全员加薪 8%
UPDATE employees SET salary = ROUND(salary*1.08, 2) WHERE dept_id=10;

UPDATE 必须带 WHERE,否则会更新全表!

第五步:删除(DELETE)

-- 删除一条日志
DELETE FROM operation_logs WHERE note='发布招聘需求';
-- 删除一名员工(吴勇,emp_id=1010)
DELETE FROM employees WHERE emp_id=1010;

DELETE 也必须带 WHERE。最终 employees 剩 9 行、operation_logs 剩 4 行(与综合验收的 “employees=9” 一致)。

第六步:事务演示(ROLLBACK 不落库)

START TRANSACTION;
INSERT INTO employees (emp_id, name, gender, dept_id, salary, hire_date)
  VALUES (9999, '临时工', 'M', 40, 8000.00, '2026-07-21');
SELECT COUNT(*) AS cnt FROM employees;   -- 事务内为 10 行
ROLLBACK;
SELECT COUNT(*) AS cnt FROM employees;   -- 回滚后为 9 行

✅ 任务4 验收

  • SELECT / INSERT / UPDATE / DELETE / 聚合 / 联表 / 事务 全部执行成功
  • 理解 WHERE / ORDER BY / GROUP BY / LIMIT / JOIN / 事务
  • 最终数据:departments=4employees=9operation_logs=4
monitor 禁止写入验证 — ERROR 1142

第二层级:Redis 缓存服务部署与应用

任务5:源码编译安装 Redis 7 并掌握五种数据类型

场景:UniTalk 用 Redis 做 Session 存储、消息缓存、在线状态。Redis 是内存数据库,读写极快。

第一步:编译安装 Redis 7.4.0

# 1) 安装编译依赖
sudo apt-get install -y -qq gcc make

# 2) 下载源码并编译
cd /usr/local/src
sudo wget -q https://download.redis.io/releases/redis-7.4.0.tar.gz
sudo tar xzf redis-7.4.0.tar.gz
cd redis-7.4.0
sudo make -j"$(nproc)" >/tmp/redis_make.log 2>&1

# 3) 安装到 /usr/local/redis
sudo make PREFIX=/usr/local/redis install >/tmp/redis_install.log 2>&1
/usr/local/redis/bin/redis-server --version    # Redis server v=7.4.0

第二步:运行用户、目录与配置

sudo useradd -r -s /usr/sbin/nologin redis 2>/dev/null || true
sudo mkdir -p /data/redis
sudo chown -R redis:redis /data/redis

配置文件 /usr/local/redis/redis.conf

bind 0.0.0.0
port 6379
daemonize no                  # 由 systemd 管理,必须 no(Type=simple)
pidfile /data/redis/redis.pid
logfile /data/redis/redis.log
dir /data/redis
requirepass Redis@123456
protected-mode no
appendonly yes                # AOF 持久化开启
appendfilename "appendonly.aof"
appendfsync everysec
save 900 1                    # RDB 快照(与 AOF 双保险)
save 300 10
save 60 10000
databases 16

⚠️ 踩坑:Redis 若 daemonize yes,进程会 fork 到后台,父进程退出,被 systemd(Type=simple)判定为”启动失败”。已改为 daemonize no

第三步:systemd 服务与开机自启

新建 /etc/systemd/system/redis.service

[Unit]
Description=Redis 7 In-Memory Cache
After=network.target

[Service]
User=redis
Group=redis
ExecStart=/usr/local/redis/bin/redis-server /usr/local/redis/redis.conf
ExecStop=/usr/local/redis/bin/redis-cli -a Redis@123456 shutdown nosave
Restart=on-failure
LimitNOFILE=65536

[Install]
WantedBy=multi-user.target
sudo systemctl daemon-reload
sudo systemctl enable redis
sudo systemctl start redis
systemctl is-active redis        # active
/usr/local/redis/bin/redis-cli -a Redis@123456 ping   # PONG
Redis CLI ping 验证

第四步:五种数据类型实战

连接:redis-cli -a Redis@123456,或 export REDISCLI_AUTH=Redis@123456 避免明文告警。

redis-cli -a Redis@123456
# 1) STRING —— 字符串 / 计数器 / 过期(模拟 Session)
SET user:1001:name "张伟"
SET user:1001:age 30
INCR user:1001:age              # 31
EXPIRE user:1001:age 60
TTL user:1001:age               # 剩余秒数

# 2) HASH —— 对象存储
HSET product:2001 name "机械键盘" price 299 stock 50
HGET product:2001 name          # "机械键盘"
HGETALL product:2001

# 3) LIST —— 列表 / 消息队列
LPUSH task:queue "task-a" "task-b"
RPUSH task:queue "task-c"
LRANGE task:queue 0 -1          # 全部
LPOP task:queue                # 弹出 "task-a"

# 4) SET —— 集合 / 标签 / 交集
SADD tag:backend "redis" "mysql" "nginx"
SADD tag:frontend "vue" "nginx" "redis"
SINTER tag:backend tag:frontend # 交集:nginx, redis

# 5) ZSET —— 有序集合 / 排行榜
ZADD leaderboard 100 "Alice" 200 "Bob" 150 "Carol"
ZRANGE leaderboard 0 -1 WITHSCORES          # 升序
ZREVRANGE leaderboard 0 0 WITHSCORES        # 冠军 Bob
ZSCORE leaderboard "Bob"                     # 200
Redis 五种数据类型操作

第五步:持久化验证(RDB + AOF)

# Redis 7 的 AOF 是多部件目录形式 appendonlydir/
ls -lh /data/redis/dump.rdb
ls -lh /data/redis/appendonlydir/

# 触发 AOF 重写
redis-cli -a Redis@123456 BGREWRITEAOF

Redis 7 默认 AOF 为多部件目录appendonlydir/),不再是单个 appendonly.aof 文件。备份时要整体拷贝该目录。

Redis 持久化文件 — RDB + AOF 多部件目录

✅ 任务5 验收

redis-cli -a Redis@123456 ping            # PONG(密码认证生效)
# 五种类型 TYPE 检查:string/hash/list/set/zset 均存在
ls -lh /data/redis/dump.rdb /data/redis/appendonlydir/

第三层级:MongoDB 文档数据库部署与应用

任务6:二进制安装 MongoDB 6 并掌握文档 CRUD

场景:UniTalk 在线文档结构灵活(富文本、表格、图片嵌入),MongoDB 文档模型非常合适。

第一步:安装 MongoDB 6.0.14

# 1) 依赖(Debian 12 需补 libcurl4 / libssl3)
sudo apt-get install -y -qq libcurl4 libssl3

# 2) 下载 MongoDB 服务端(ubuntu2204 构建在 Debian 12 可用)
cd /usr/local/src
sudo wget -q https://fastdl.mongodb.org/linux/mongodb-linux-x86_64-ubuntu2204-6.0.14.tgz
sudo tar xzf mongodb-linux-x86_64-ubuntu2204-6.0.14.tgz -C /usr/local
sudo ln -sfn /usr/local/mongodb-linux-x86_64-ubuntu2204-6.0.14 /usr/local/mongodb

# 3) 单独下载 mongosh 客户端(6.x 起不再随服务端打包)
sudo wget -q https://downloads.mongodb.com/compass/mongosh-2.3.0-linux-x64.tgz
sudo tar xzf mongosh-2.3.0-linux-x64.tgz -C /usr/local
sudo ln -sf /usr/local/mongosh-2.3.0-linux-x64/bin/mongosh /usr/local/mongodb/bin/mongosh
/usr/local/mongodb/bin/mongod --version    # v6.0.14

第二步:运行用户、目录与配置

sudo useradd -r -s /usr/sbin/nologin mongodb 2>/dev/null || true
sudo mkdir -p /data/mongodb/data /data/mongodb/log /data/mongodb/run
sudo chown -R mongodb:mongodb /data/mongodb

配置文件 /usr/local/mongodb/mongod.conf

storage:
  dbPath: /data/mongodb/data
  journal:
    enabled: true
systemLog:
  destination: file
  path: /data/mongodb/log/mongod.log
  logAppend: true
net:
  bindIp: 0.0.0.0
  port: 27017
processManagement:
  fork: false
  pidFilePath: /data/mongodb/run/mongod.pid
security:
  authorization: enabled     # 开启鉴权

systemd 单元 /etc/systemd/system/mongod.service

[Unit]
Description=MongoDB 6 Document Database
After=network.target

[Service]
User=mongodb
Group=mongodb
ExecStart=/usr/local/mongodb/bin/mongod --config /usr/local/mongodb/mongod.conf
Restart=on-failure

[Install]
WantedBy=multi-user.target
sudo systemctl daemon-reload
sudo systemctl enable mongod
sudo systemctl start mongod
systemctl is-active mongod        # active
MongoDB 连接验证

⚠️ 踩坑:本环境 MongoDB 未触发 localhost 例外(无凭据连接也被要求认证),无法直接 createUser。改用”先临时关闭鉴权 → 建用户 → 再开启鉴权”的稳妥方式(见下一步)。

第三步:创建用户(先关鉴权 → 建用户 → 开鉴权)

# 1) 停服务,关闭鉴权
sudo systemctl stop mongod
sudo sed -i 's/authorization: enabled/authorization: disabled/' /usr/local/mongodb/mongod.conf
sudo systemctl start mongod; sleep 3

# 2) 创建 admin(root)与 blog_user(blog_db 读写)
/usr/local/mongodb/bin/mongosh admin --quiet --eval '
function ensureUser(dbname, user, pwd, roles){
  var d = db.getSiblingDB(dbname);
  if (d.getUser(user) == null){ d.createUser({user:user,pwd:pwd,roles:roles}); print("created",user);}
  else { print("exists",user); }
}
ensureUser("admin","admin","Admin@123456",[{role:"root",db:"admin"}]);
ensureUser("blog_db","blog_user","Blog@123456",[{role:"readWrite",db:"blog_db"}]);
'

# 3) 重新开启鉴权并重启
sudo systemctl stop mongod
sudo sed -i 's/authorization: disabled/authorization: enabled/' /usr/local/mongodb/mongod.conf
sudo systemctl start mongod; sleep 3
systemctl is-active mongod

第四步:文档 CRUD(blog_db.articles)

用业务用户 blog_user 连接:

/usr/local/mongodb/bin/mongosh blog_db \
  --username blog_user --password "Blog@123456" --authenticationDatabase blog_db
// C:插入 3 篇文档
db.articles.insertMany([
  {title:"MySQL 8.0 部署", tags:["db","mysql"], views:120, author:"vm1"},
  {title:"Redis 缓存实战", tags:["cache","redis"], views:88, author:"vm1"},
  {title:"MongoDB 入门",  tags:["db","mongo"],  views:200, author:"alice"}
]);

// R:查询全部 / 条件+排序
db.articles.find({}, {title:1, views:1, _id:0});
db.articles.find({views:{$gt:100}}, {title:1, views:1, _id:0}).sort({views:-1});

// U:浏览量 +10
db.articles.updateOne({title:"Redis 缓存实战"}, {$inc:{views:10}});

// D:删除作者 alice 的文档
db.articles.deleteOne({author:"alice"});   // 剩 2 篇

![MongoDB 文档 CRUD 操作](https://gitee.com/hu-haowei1516049/markdownimg/raw/master/img/upgit_20260724_1784878097.png)

// 索引:为 views 建降序索引,并验证走索引
db.articles.createIndex({views:-1});
db.articles.find({views:{$gt:100}}).sort({views:-1}).explain("queryPlanner")
  .queryPlanner.winningPlan.inputStage.stage;   // 应为 IXSCAN
MongoDB 索引创建与 explain 验证

第五步:安装 mongodump / mongorestore 工具

cd /usr/local/src
sudo wget -q https://fastdl.mongodb.org/tools/db/mongodb-database-tools-ubuntu2204-x86_64-100.10.0.tgz
sudo tar xzf mongodb-database-tools-ubuntu2204-x86_64-100.10.0.tgz -C /usr/local
sudo ln -sf /usr/local/mongodb-database-tools-ubuntu2204-x86_64-100.10.0/bin/mongodump /usr/local/mongodb/bin/mongodump
sudo ln -sf /usr/local/mongodb-database-tools-ubuntu2204-x86_64-100.10.0/bin/mongorestore /usr/local/mongodb/bin/mongorestore

✅ 任务6 验收

systemctl status mongod
/usr/local/mongodb/bin/mongosh blog_db --username blog_user --password 'Blog@123456' \
  --authenticationDatabase blog_db --quiet --eval 'db.articles.countDocuments()'   # 2

第四层级:自动备份策略

任务7:统一备份脚本(MySQL / Redis / MongoDB)

场景:数据库存储核心数据,一旦丢失恢复困难。需自动定期备份。备份脚本 backup_all.shpush_backup.sh 生成到 /usr/local/bin/,统一备份三套服务,保留 7 天。

/usr/local/bin/backup_all.sh 核心逻辑:

#!/usr/bin/env bash
DATE=$(date +%Y%m%d_%H%M%S)
BACKUP_ROOT=/opt/backup
MYSQL=/usr/local/mysql/bin
REDIS_CLI=/usr/local/redis/bin/redis-cli
MONGO=/usr/local/mongodb/bin
MYSQL_PW=Root@123456
MONGO_URI="mongodb://admin:Admin@123456@127.0.0.1:27017"   # 注意 @ 需 URL 编码

mkdir -p $BACKUP_ROOT/mysql $BACKUP_ROOT/redis $BACKUP_ROOT/mongo

# ===== MySQL:全库导出 + gzip =====
export MYSQL_PWD=$MYSQL_PW
$MYSQL/mysqldump -uroot --all-databases --single-transaction --routines --events --triggers \
  2>/dev/null | gzip > $BACKUP_ROOT/mysql/mysql_$DATE.sql.gz

# ===== Redis:触发 AOF 重写后整体拷贝 RDB + appendonlydir =====
$REDIS_CLI -a Redis@123456 BGREWRITEAOF >/dev/null 2>&1
sleep 2
mkdir -p $BACKUP_ROOT/redis/redis_$DATE
cp -a /data/redis/dump.rdb /data/redis/appendonlydir $BACKUP_ROOT/redis/redis_$DATE/

# ===== MongoDB:mongodump =====
$MONGO/mongodump --uri "$MONGO_URI" --out $BACKUP_ROOT/mongo/mongo_$DATE/ >/dev/null 2>&1

# 清理 7 天前的备份
find $BACKUP_ROOT -type f -mtime +7 -delete
find $BACKUP_ROOT -type d -empty -delete 2>/dev/null

⚠️ 踩坑:MongoDB URI 中密码含 @Admin@123456)会破坏 URI 解析,必须 URL 编码为 Admin%40123456。恢复演练脚本 09_restore_drill.sh 里用的就是编码后的 mongodb://admin:Admin%40123456@127.0.0.1:27017

手动测试:

sudo /usr/local/bin/backup_all.sh
ls -lhR /opt/backup | head -40
# mysql_*.sql.gz 约 280KB;redis_*/ 含 dump.rdb + appendonlydir;mongo_*/ 含各库 dump
运行统一备份脚本
备份目录结构

配置 crontab 每天 02:00 执行:

sudo crontab -e
# 加入:
0 2 * * * /usr/local/bin/backup_all.sh >> /var/log/backup.log 2>&1

sudo crontab -l    # 确认

实际环境已配置:0 2 * * * /usr/local/bin/backup_all.sh >> /var/log/backup.log 2>&1


任务8:恢复演练(恢复到测试库 / 临时实例,不破坏生产数据)

演练是备份价值的证明。所有恢复都落到 测试库 / 临时实例,绝不覆盖生产数据。脚本为 09_restore_drill.sh

# 1) MySQL:从全备中只抽取 unitalk_db 段,改名 unitalk_drill 恢复
LATEST_MYSQL=$(ls -t /opt/backup/mysql/mysql_*.sql.gz | head -1)
gunzip -c "$LATEST_MYSQL" \
  | awk '/CREATE DATABASE/ && /`unitalk_db`/{f=1} /^CREATE DATABASE /{if(f && $0 !~ /unitalk_db/){f=0}} f' \
  | sed 's/`unitalk_db`/`unitalk_drill`/g' \
  | /usr/local/mysql/bin/mysql -uroot
# 验证:unitalk_drill.employees = 9 行,3 张表;验证后 DROP DATABASE unitalk_drill

# 2) MongoDB:命名空间重映射 blog_db.* → blog_drill.*
LATEST_MONGO=$(ls -td /opt/backup/mongo/mongo_* | head -1)
MONGO_URI="mongodb://admin:Admin%40123456@127.0.0.1:27017"
/usr/local/mongodb/bin/mongorestore --uri "$MONGO_URI" \
  --nsFrom 'blog_db.*' --nsTo 'blog_drill.*' --dir "$LATEST_MONGO"
# 验证:blog_drill.articles = 2 篇;验证后 db.dropDatabase()

# 3) Redis:拷贝备份到临时实例(端口 6380)验证可恢复
LATEST_REDIS=$(ls -td /opt/backup/redis/redis_* | head -1)
rm -rf /tmp/redis_restore; mkdir -p /tmp/redis_restore
cp -a $LATEST_REDIS/dump.rdb /tmp/redis_restore/
cp -a $LATEST_REDIS/appendonlydir /tmp/redis_restore/
/usr/local/redis/bin/redis-server --port 6380 --dir /tmp/redis_restore --appendonly yes --daemonize yes
# 验证:redis-cli -p 6380 GET user:1001:name 返回 "张伟",DBSIZE > 0
# 清理:redis-cli -p 6380 -a Redis@123456 shutdown nosave; rm -rf /tmp/redis_restore

演练结果:MySQL 9 行 / 3 表、MongoDB 2 篇、Redis 张伟 + 多 key,全部恢复成功。

MySQL 恢复演练 — 首次尝试(含失败+重试成功)
MySQL 恢复演练 — 建测试库并插入数据
MySQL 恢复演练 — 恢复后验证 3 行数据

第五层级:综合验收

任务9:数据库服务全功能验收(29/29 PASS)

使用 10_acceptance.sh 一键自检,覆盖 A.MySQL / B.Redis / C.MongoDB / D.备份 四大类共 29 项。

python ssh_run.py -s --script 10_acceptance.sh

实测结果(2026-07-21,192.168.1.11)

########## A. MySQL 8.0 验收 ##########
  [PASS] mysqld 服务 active
  [PASS] 监听 3306
  版本: 8.0.36
  [PASS] 版本为 8.0.x
  [PASS] 业务库存在 wiki_db
  [PASS] 业务库存在 unitalk_db
  [PASS] 受限用户存在 wiki_user@192.168.1.%
  [PASS] 受限用户存在 unitalk_user@192.168.1.%
  [PASS] 受限用户存在 monitor@192.168.1.%
  [PASS] monitor 可读(unitalk_db.employees=9)
  [PASS] monitor 禁止写入(ERROR 1142)
  [PASS] unitalk_db.employees=9 行(CRUD 数据在)

########## B. Redis 7 验收 ##########
  [PASS] redis 服务 active
  [PASS] 监听 6379
  版本: Redis server v=7.4.0 ...
  [PASS] 版本 7.x
  [PASS] 密码认证返回 PONG
  五类型: string=string hash=hash list=list set=set zset=zset
  [PASS] 五种数据类型齐全
  [PASS] RDB 快照存在
  [PASS] AOF 多部件目录存在

########## C. MongoDB 6 验收 ##########
  [PASS] mongod 服务 active
  [PASS] 监听 27017
  版本: v6.0.14
  [PASS] 版本 6.0.x
  [PASS] 鉴权可连接(blog_user)
  blog_db.articles 文档数: 2
  [PASS] 文档 CRUD 数据存在(2)
  [PASS] 索引存在(含 _id 与 views_-1)

########## D. 备份与定时 验收 ##########
  [PASS] 统一备份脚本存在
  [PASS] MySQL 备份存在
  [PASS] Redis 备份存在
  [PASS] MongoDB 备份存在
  [PASS] root crontab 含每日备份任务

########## 验收总结: PASS=29  FAIL=0 ##########
综合验收 29/29 PASS

验收项清单(与脚本对应)

类别 序号 验收项 预期
MySQL 1 mysqld active active
MySQL 2 监听 3306
MySQL 3 版本 8.0.x 8.0.36
MySQL 4-5 业务库 wiki_db / unitalk_db 存在
MySQL 6-8 受限用户 wiki_user / unitalk_user / monitor 均存在,host=192.168.1.%
MySQL 9 monitor 可读 employees=9
MySQL 10 monitor 禁止写入 ERROR 1142
MySQL 11 unitalk_db.employees 行数 9
Redis 12-13 redis active / 监听 6379
Redis 14-15 版本 7.x / 密码认证 PONG v=7.4.0 / PONG
Redis 16 五种数据类型齐全 string/hash/list/set/zset
Redis 17-18 RDB + AOF 持久化 均存在
MongoDB 19-20 mongod active / 监听 27017
MongoDB 21 版本 6.0.x v6.0.14
MongoDB 22 blog_user 鉴权可连 ping=1
MongoDB 23 文档 CRUD 数据 2 篇
MongoDB 24 索引(含 views_-1) ≥2
备份 25 备份脚本存在 /usr/local/bin/backup_all.sh
备份 26-28 MySQL / Redis / MongoDB 备份存在
备份 29 root crontab 每日任务 0 2 * * * ...backup_all.sh

附录:常见问题排查与踩坑记录

现象 / 问题 原因 / 解决
apt-get install mysql-server 报 GPG 密钥校验失败 Debian 12 的 MySQL APT 仓库密钥过期 → 改用官方二进制 tarball 安装(任务1)
sudo 执行 apt/dpkg/rm 报权限不足 use_pty 下普通 sudo 仅 effective uid=0,真实 uid 仍是 vm1 → 用 echo PW \| sudo -S -p '' -i bash -c 'cmd' 拿完整 root
主机预装 MariaDB,担心冲突 MariaDB 处于 inactive / disabled,未占 3306,MySQL 8.0 正常接管 3306,无冲突(保留不删,无害)
Redis 启动后 systemd 报失败 / 进程闪退 配置 daemonize yesType=simple 冲突(fork 后父进程退出)→ 改为 daemonize no
MongoDB 无凭据连接仍报 “requires authentication” 本环境未触发 localhost 例外 → 采用”关鉴权建用户再开鉴权”方式(任务6 第三步)
mongodump 报 URI 解析错误 密码含 @Admin@123456)→ URL 编码为 Admin%40123456
MySQL 查询 ERROR 1064 ROWS 是 MySQL 8.0 保留字,别名改用 cnt
Navicat 连接报 caching_sha2_password 用户用 mysql_native_password 创建(任务2 已统一),或 my.cnf 设 default_authentication_plugin=mysql_native_password
Redis 7 AOF 备份找不到 appendonly.aof Redis 7 AOF 为多部件目录 appendonlydir/,整体拷贝该目录
crontab 不执行 确认 cron 服务运行、脚本用绝对路径、脚本有执行权限、查看 /var/log/backup.log

文档完 —— 覆盖 Debian 12 @ 192.168.1.11 上 MySQL 8.0.36 / Redis 7.4.0 / MongoDB 6.0.14 三套服务的安装、配置、CRUD、备份与恢复演练、综合验收全流程。所有部署脚本位于 scripts/ 目录,由 ssh_run.py 远程驱动。综合验收结果 29/29 PASS