2024年11月

前言

领导要求做一个小项目,要求独立运行,用以最少的依赖,此时不想集成到主项目中,但是又想用HzyAdmin中如此好用的自动注入,还有操作简单的仓储模式,话不多说,直接开干。

HzyAdmin 是一个前后端分离的权限管理框架,前端采用 Vue 3 框架,后端采用 .NET 8、ORM 采用 EF 8。该框架提供了丰富的功能和插件,支持模块化开发和高效管理。

项目介绍

项目采用最新的 .NET 8 技术栈,结合 HZY.Framework,提供了一个强大且灵活的管理解决方案。平台支持模块化设计,兼容 WinForms 和 WPF,满足不同应用场景的需求。

项目功能

项目功能流程图,具体下图所示:

项目技术

  • 后端技术:.NET 8、EF Core 8
  • 前端:Vue3.x 、Antd Of Vue 4.x 、Vite 、Pinia 2.0.x 、TypeScript
  • 开发需要环境:VS 2022 、 .NET 8.0 、VS Code 、Redis
  • MVC 版本: https://gitee.com/hzy6/hzy-admin-mvc
  • WebApi 任务调度平台: https://gitee.com/hzy6/hzy-quartz

模块化工程结构

微服务案例模块化工程结构

属性依赖注入

项目特点

  • HzyAdmin
  • 前后端分离权限框架,前端采用Vue3框架,后端采用.NET 8,ORM 采用 EF 8
  • IOC、AOP插件
  • HZY.Framework.AutoRegisterIOC,针对微软IOC,对DI自动扫描注入服务,并且提供Aop 拦截功能。
  • EF仓储插件
  • HZY.Framework.EntityFrameworkRepositories,基于 EFCore 实现仓储。优雅的语法,高效便捷。仓储插件。
  • Api Controller 增强插件 HZY.Framework.DynamicApiController ,NET6、NET7 动态 Api Controller
  • hzy-quartz
  • 开箱即用 Quartz,WebApi 任务调度中心、统一化、自动化、可视化、管理企业项目中的定时任务。
  • 前端 UI
  • 采用 hzy-admin-client-ui 基于 vue3 + antdv + typescript 实现

项目效果

项目文档

文档地址:
http://47.98.179.56/docs/guide/hzy-admin/database/dbs.html

数据库脚本位置根目录 doc 文件夹 目前仅提供了 MySql、SqlServer、PgSql 脚本。

其他请使用efcore迁移(迁移教程请看文档)

项目地址

GitHub:
https://github.com/hzy-6/hzy-admin

Gitee:
https://gitee.com/hzy6/HzyAdmin

总结

本文示例仅展示了框架的部分功能。感兴趣的朋友可以通过项目地址获取更多详细信息。希望本文能在通用平台开发方面为大家提供有益的帮助。欢迎在评论区留言交流,分享您的宝贵经验和建议。

最后

如果你觉得这篇文章对你有帮助,不妨点个赞支持一下!你的支持是我继续分享知识的动力。如果有任何疑问或需要进一步的帮助,欢迎随时留言。

也可以加入微信公众号
[DotNet技术匠]
社区,与其他热爱技术的同行一起交流心得,共同成长!
优秀是一种习惯,欢迎大家留言学习!


title: Nuxt.js 应用中的 vite:extendConfig 事件钩子详解
date: 2024/11/12
updated: 2024/11/12
author: cmdragon

excerpt:
vite:extendConfig 钩子允许开发者在 Vite 项目中扩展默认配置。这使得开发者可以根据特定需求自定义 Vite 的构建和开发行为,增强开发体验。

categories:

  • 前端开发

tags:

  • Nuxt
  • Vite
  • 钩子
  • 配置
  • 自定义
  • 构建
  • 开发


image
image

扫描
二维码
关注或者微信搜一搜:
编程智域 前端至全栈交流与成长

目录

  1. 概述
  2. vite:extendConfig
    钩子的详细说明
      1. 钩子的定义与作用
      1. 调用时机
      1. 参数说明
  3. 具体使用示例
      1. 示例:基本用法
      1. 示例:添加全局 CSS
  4. 应用场景
      1. 动态修改 Vite 配置
      1. 根据环境变量调整配置
      1. 扩展插件和构建设置
  5. 注意事项
      1. 配置验证
      1. 效能影响
  6. 总结

1. 概述

vite:extendConfig
钩子允许开发者在 Vite 项目中扩展默认配置。这使得开发者可以根据特定需求自定义 Vite 的构建和开发行为,增强开发体验。

2.
vite:extendConfig
钩子的详细说明

2.1 钩子的定义与作用

vite:extendConfig
钩子用于扩展 Vite 的默认配置。通过这一钩子,开发者可以添加或修改 Vite 的配置项,以满足应用的需求。

2.2 调用时机

vite:extendConfig
钩子通常在 Vite 初始化和构建开始之前被调用,这样配置的修改可以在应用构建和启动过程中生效。

2.3 参数说明

该钩子接收一个
viteInlineConfig
对象和
env
对象作为参数,
viteInlineConfig
包含了当前的 Vite 配置,而
env
提供了运行时的环境变量信息。

3. 具体使用示例

3.1 示例:基本用法

// plugins/viteExtendConfig.js
export default defineNuxtPlugin((nuxtApp) => {
  nuxtApp.hooks('vite:extendConfig', (viteInlineConfig, env) => {
    // 修改根目录
    viteInlineConfig.root = 'src';

    // 添加到环境变量中
    console.log('Current environment:', env.MODE);
  });
});

在这个示例中,我们修改了 Vite 的根目录配置,同时打印了当前的运行环境。

3.2 示例:添加全局 CSS

// plugins/viteAddGlobalCss.js
export default defineNuxtPlugin((nuxtApp) => {
  nuxtApp.hooks('vite:extendConfig', (viteInlineConfig) => {
    viteInlineConfig.css = {
      preprocessorOptions: {
        scss: {
          additionalData: `@import "src/styles/global.scss";`,
        },
      },
    };
  });
});

在这个示例中,我们为 Vite 配置添加了全局的 SCSS 文件,以便在项目中任何地方使用。

4. 应用场景

4.1 动态修改 Vite 配置

可以根据不同的环境动态修改 Vite 配置,例如根据 NODE_ENV 来设置 API 地址。

// plugins/viteDynamicConfig.js
export default defineNuxtPlugin((nuxtApp) => {
  nuxtApp.hooks('vite:extendConfig', (viteInlineConfig) => {
    if (process.env.NODE_ENV === 'production') {
      viteInlineConfig.server = {
        proxy: {
          '/api': 'https://api.example.com',
        },
      };
    }
  });
});

4.2 根据环境变量调整配置

根据环境变量,可以灵活调整 Vite 的构建设置。

// plugins/viteEnvConfig.js
export default defineNuxtPlugin((nuxtApp) => {
  nuxtApp.hooks('vite:extendConfig', (viteInlineConfig, env) => {
    if (env.MODE === 'development') {
      viteInlineConfig.base = '/dev/';
    } else {
      viteInlineConfig.base = '/prod/';
    }
  });
});

4.3 扩展插件和构建设置

添加和配置 Vite 插件。

// plugins/viteAddPlugin.js
import someVitePlugin from 'some-vite-plugin';

export default defineNuxtPlugin((nuxtApp) => {
  nuxtApp.hooks('vite:extendConfig', (viteInlineConfig) => {
    viteInlineConfig.plugins = [
      ...(viteInlineConfig.plugins || []),
      someVitePlugin(),
    ];
  });
});

5. 注意事项

5.1 配置验证

在更改 Vite 配置时,务必确认配置项的有效性,以防止构建失败。

5.2 效能影响

不合理的配置更改可能会影响构建和开发服务器的性能,因此需谨慎添加或修改配置项。

6. 总结

通过使用
vite:extendConfig
钩子,开发者可以灵活扩展 Vite 的默认配置,以满足特定的项目需求。这种自定义能力不仅增强了开发效率,还可以适应不同的环境和构建要求。合理使用这一钩子,将有助于提升开发体验和项目维护性。

余下文章内容请点击跳转至 个人博客页面 或者 扫码关注或者微信搜一搜:
编程智域 前端至全栈交流与成长
,阅读完整的文章:
Nuxt.js 应用中的 vite:extendConfig 事件钩子详解 | cmdragon's Blog

往期文章归档:

0.背景

XtraBackup 优势

  1. 在线热备
    :支持在不停止数据库的情况下进行 InnoDB 和 XtraDB 的热备份,适合高可用环境。
  2. 增量备份
    :支持增量备份,能够显著减少备份时间和存储空间需求。
  3. 流压缩
    :可以在备份过程中进行流压缩,减少传输数据量,提高传输效率。
  4. 主从同步
    :XtraBackup 可以更方便地创建和维护主从同步关系,简化数据库扩展。
  5. 低负载备份
    :在备份过程中对主数据库的负载相对较小,不会显著影响正常业务。

备份工具选择

  • xtrabackup
    :专门用于 InnoDB 和 XtraDB 表的备份。
  • innobackupex
    :一个脚本封装,能够同时处理 InnoDB 和 MyISAM 表,但在处理 MyISAM 时会加锁。

其他备份策略

  • 分库分表
    :对于超大数据量,考虑使用分库分表策略以优化管理和备份效率。

  • LVM 快照
    :利用 LVM 快照来快速获取数据库的瞬时备份,这样可以减少备份时间并降低对原库的压力。

一、备份方式 xtrabackup

1.安装

Software Downloads - Percona

mysql版本:mysql8.0.24

xtrabackup版本:8.0.25

版本对应关系

暂时没用

在线安装

yum install -y https://repo.percona.com/yum/percona-release-latest.noarch.rpm
yum install -y percona-xtrabackup-80
yum list | grep percona-xtrabackup

离线包下载地址

image-20240925110412143

服务器更新yum源

sudo cp /etc/yum.repos.d/CentOS-Base.repo /etc/yum.repos.d/CentOS-Base.repo.bak
sudo wget -O /etc/yum.repos.d/CentOS-Base.repo http://mirrors.aliyun.com/repo/Centos-7.repo
sudo yum clean all
sudo yum makecache

依赖包安装

sudo yum install epel-release
sudo yum install -y zstd
zstd --version

离线安装,需要网络解决依赖

yum -y localinstall percona-xtrabackup-80-8.0.25-17.1.el7.x86_64.rpm

2.备份

2.1 工具说明

Xtrabackup 工具简介

  • xtrabackup
    :用于热备份 InnoDB 和 XtraDB 表的数据工具,不能备份其他类型的表或数据表结构。
  • innobackupex
    :将
    xtrabackup
    封装的 Perl 脚本,提供了备份 MyISAM 表的能力。

常用选项

  • --host
    :指定主机。

  • --user
    :指定用户名。

  • --password
    :指定密码。

  • --port
    :指定端口。

  • --databases
    :指定数据库。

  • --incremental
    :创建增量备份。

  • --incremental-basedir
    :指定包含完整备份的目录。

  • --incremental-dir
    :指定包含增量备份的目录。

  • --apply-log:对备份进行预处理操作。
    

    • 说明
      :在备份完成后,数据尚不能用于恢复,因为备份中可能包含尚未提交的事务或已经提交但尚未同步至数据文件的事务。
      --apply-log
      的主要作用是回滚未提交的事务,并同步已提交的事务,使数据文件处于一致性状态。
  • --redo-only
    :不回滚未提交事务。

  • --copy-back
    :恢复备份目录。

2.2 全量备份
xtrabackup --backup \
    --target-dir=/var/backups/xtrabackup \
    --datadir=/data/dstore/1.0.0.0/mysql/data \
    --parallel=4  \
    --user=root \
    --password=Hbis@123 \
    --socket=/srv/dstore/1.0.0.0/mysql/mysql.sock \
    --host=localhost \
    --compress \
    --compress-threads=4 \
    --compress-chunk-size=65536

gzip 本地压缩备份

使用流式备份,配合管道使用 gzip 命令对备份在本地进行压缩

--stream=xbstream \
	| gzip - > /data/backup/backup1.gz

恢复时需要先使用 gunzip 解压,再使用 xbstream 解压,才能进行 Prepare 阶段。

# gzip 解压
gunzip backup1.gz

# xbstream 解压
xbstream -x --parallel=10 -C /data/backup/backup_full < ./backup1

说明

  • --target-dir
    :备份目标目录需要事先创建,确保该目录存在。
  • --password
    :建议使用环境变量或其他安全方式传递密码以保护敏感信息。
  • --compress=quicklz
    指定使用
    quicklz
    作为压缩算法。
  • --compress-threads=4
    指定使用 4 个线程进行压缩。
  • --compress-chunk-size=65536
    指定压缩线程的工作缓冲区大小。

报错,没有权限
failed to execute query ‘LOCK INSTANCE FOR BACKUP’ : 1227 (42000) Access denied

grant BACKUP_ADMIN on *.* to 'root'@'%';
flush privileges;

LOCK INSTANCE FOR BACKUP 是MySQL 8.0引入的一种新的备份相关SQL语句,主要用于在进行数据库备份时,以一种更为细粒度和高效的方式控制对数据库实例的访问,以保证备份的一致性。这个命令的工作原理及特点如下:

目的:在执行备份操作时,此命令用于获取一个实例级别的锁,该锁允许在备份过程中继续执行DML(数据操作语言,如INSERT、UPDATE、DELETE)操作,同时防止那些可能导致数据快照不一致的DDL(数据定义语言,如CREATE、ALTER、DROP)操作和某些管理操作。这样可以在不影响数据库服务的情况下进行备份,特别适用于需要最小化服务中断的在线备份场景。

权限需求:执行LOCK INSTANCE FOR BACKUP语句需要用户具备BACKUP_ADMIN权限。这是一个专门为了备份相关的高级操作而设计的权限级别。

兼容性:此特性是在MySQL 8.0及以上版本中引入的,早于8.0的MySQL版本并不支持这一语句,因此在使用旧版本时,可能需要依赖其他机制(如FLUSH TABLES WITH READ LOCK)来确保备份的一致性。

解锁:执行备份后,需要使用UNLOCK INSTANCE语句来释放之前由LOCK INSTANCE FOR BACKUP获得的锁,从而恢复正常操作。

与传统备份命令的对比:相比于传统的备份方法,如使用FLUSH TABLES WITH READ LOCK,LOCK INSTANCE FOR BACKUP提供了更小的性能影响,因为它不会完全阻止写操作,只是限制了可能引起数据不一致的活动,更适合于高可用性和高性能要求的生产环境。

2.3 增量备份

xtrabackup 支持增量备份。在做增量备份之前,需要先做一个全量备份。xtrabackup 会基于 innodb page 的 lsn 号来判断是否需要备份一个 page。如果 page lsn 大于上次备份的 lsn 号,就需要备份该 page。
在这里插入图片描述

先进行一次全量备份。

xtrabackup --backup \
    --target-dir=/var/backups/full \
    --extra-lsndir=/var/backups/full \
    --datadir=/data/dstore/1.0.0.0/mysql/data \
    --parallel=4  \
    --user=root \
    --password=Hbis@123 \
    --socket=/srv/dstore/1.0.0.0/mysql/mysql.sock \
    --host=localhost \
    --compress \
    --compress-threads=4 \
    --compress-chunk-size=65536 \
    --stream=xbstream \
     2>/var/backups/full/backup_full.log | gzip -  > /var/backups/full/full.gz 

备份命令加上 了–extra-lsndir 选项,将 xtrabackup_checkpoints 单独输出到文件。增量备份时需要根据 xtrabackup_checkpoints中的 lsn,以下是相关文件。

[root@node83 full]# ll
total 4684
-rw-r--r-- 1 root root   43918 Sep 25 15:40 backup_full.log
-rw-r--r-- 1 root root 4741603 Sep 25 15:40 full.gz
-rw-r--r-- 1 root root     102 Sep 25 15:40 xtrabackup_checkpoints
-rw-r--r-- 1 root root     794 Sep 25 15:40 xtrabackup_info

现在,发起增量备份。

xtrabackup --backup \
    --target-dir=/var/backups/inc1 \
    --extra-lsndir=/var/backups/inc1 \
    --datadir=/data/dstore/1.0.0.0/mysql/data \
    --user=root \
    --password=Hbis@123 \
    --socket=/srv/dstore/1.0.0.0/mysql/mysql.sock \
    --host=localhost \
    --compress \
    --incremental-basedir=/var/backups/full  \
    --stream=xbstream \
    2>/var/backups/full/backup_full.log | gzip -  > /var/backups/inc1/backup_inc1.gz 
  • –incremental-basedir:全量备份或上一次增量备份 xtrabackup_checkpoints 文件所在目录

增量备份也可以在上一次增量备份的基础上进行

xtrabackup --backup \
    --target-dir=/var/backups/inc2 \
    --extra-lsndir=/var/backups/inc2 \
    --datadir=/data/dstore/1.0.0.0/mysql/data \
    --user=root \
    --password=Hbis@123 \
    --socket=/srv/dstore/1.0.0.0/mysql/mysql.sock \
    --host=localhost \
    --compress \
    --incremental-basedir=/var/backups/inc1 \
    --stream=xbstream \
    | gzip -  > /var/backups/inc2/backup_inc2.gz

结构如下

[root@node83 backups]# tree
.
├── full
│   ├── backup_full.log
│   ├── full.gz
│   ├── xtrabackup_checkpoints
│   └── xtrabackup_info
├── inc1
│   ├── backup_inc1.gz
│   ├── xtrabackup_checkpoints
│   └── xtrabackup_info
└── inc2
    ├── backup_inc2.gz
    ├── xtrabackup_checkpoints
    └── xtrabackup_info

3 directories, 10 files

恢复增量备份时,需要先对基础全量备份进行恢复,然后再依次按增量备份的时间进行恢复。
在这里插入图片描述

恢复全量备份

cd /var/backups/full

gunzip full.gz

# 需要先删除这两个文件,否则 xbstream 提取文件时有冲突
rm xtrabackup_checkpoints xtrabackup_info

xbstream -x -v < full

# 由于使用compress压缩,所有还有一层压缩
xtrabackup --decompress --target-dir=/var/backups/full
# 准备阶段
xtrabackup --prepare --apply-log-only --target-dir=. > prepare_full.log 2>&1

恢复增量备份时,切换到全量备份的目录执行

cd /var/backups/inc1
rm xtrabackup_checkpoints xtrabackup_info
gunzip full.gz
xbstream -x -v < full
# 由于使用compress压缩,所有还有一层压缩
xtrabackup --decompress --target-dir=/var/backups/inc1

cd  ../full
xtrabackup \
    --prepare \
    --apply-log-only \
	--incremental-dir=/data/backup/inc1 \
	--target-dir=.

最后

2.4 流式备份

XtraBackup支持流式备份,将备份以指定的tar或xbstream格式发送到STDOUT,而不是直接将文件复制到备份目录。

在 xtrabackup 2.4 版中支持 tar 和 xbstream 流格式,但 tar 格式不支持并行备份。

在 xtrabackup 8.0 中,仅支持 xbstream 流格式,不再支持 tar 格式

xtrabackup --backup \
--datadir=/data/dstore/1.0.0.0/mysql/data \
--user=root \
--password=Hbis@123 \
--socket=/srv/dstore/1.0.0.0/mysql/mysql.sock \
--host=localhost \
--parallel=4  \
--compress \
--compress-threads=4 \
--compress-chunk-size=65536 \
--stream=xbstream | ssh -C root@192.168.2.41 "cat > /var/backups/backup.xbstream.gz" 

登录远程主机解压

xbstream -x --parallel=10 -C /opt/backup  < /opt/backups/backup.xbstream.gz

xbstream 中的 -x 表示解压,–parallel 表示并行度,-C 指定解压的目录,最后一级目录必须存在。

远程备份限速

直接备份到远程服务器,如果担心备份会占用较大的网络带宽,可以使用 pv 命令限速。

--stream=xbstream | pv -q -L10m | ssh -C root@192.168.2.41 "cat > /var/backups/backup.xbstream.gz" 

pv 命令中,-q 是指 quiet 不输出进度信息,-L 是指传输速率 10m 指 10MB。

2.5 备份指定库

注意备份的单个库恢复到别的机器时,需要提前手动创建好数据库和表结构

我们可以备份数据库架构,并使用与上述相同的过程进行恢复。

使用
--databases
选项备份数据库

对于多个数据库,请将数据库指定为列表,例如
--databases=“db1 db2 db3”。
数据库也可以在文本文件中指定,并与选项
--databases-file
一起使用。要从备份中排除数据库,请使用选项
--databases-exclude

使用
--export
选项准备备份。

xtrabackup \
  --defaults-file=/srv/dstore/1.0.0.0/mysql/conf/my.cnf \
  --backup \
  --target-dir=/var/backups/test2 \
  --socket=/srv/dstore/1.0.0.0/mysql/mysql.sock \
  --user=root \
  --password=Hbis@123 \
  --databases="test2" \
  --stream=xbstream | gzip > test2.tar.gz

说明:

  • --defaults-file
    :指定 MySQL 配置文件。
  • --backup
    :表示执行备份操作。
  • --target-dir
    :指定备份文件的目标目录。
  • --socket
    :指定 MySQL socket 文件路径。
  • --user

    --password
    :指定连接数据库的用户和密码。
  • --databases
    :指定要备份的数据库(在这里是
    test2
    )。
  • --stream=xbstream
    :将备份数据以 xbstream 格式输出。
  • | gzip > test2.tar.gz
    :将输出通过管道压缩为
    test2.tar.gz
    文件。

备份完成后scp到远端机器,如/var/backups ,执行导入命令

gunzip test2.tar.gz
xbstream -x --parallel=10  <test2
# 执行准备
xtrabackup --prepare --apply-log-only --export --target-dir=.

现在使用 ALTER TABLE

DISCARD TABLESPACE 删除数据库中所有 InnoDB 表的表空间。

ALTER TABLE person  DISCARD TABLESPACE;

将所有表文件从备份目录 (/var/backups/test2/test/*) 复制到 mysql 数据目录 (/opt/mysql/data)。

注意:
在复制文件之前,请禁用 selinux。复制文件后,如果备份用户不同,请将复制文件的所有权更改为 mysql 用户。

最后,使用 ALTER TABLE

IMPORT TABLESPACE; 恢复表。

ALTER TABLE person IMPORT TABLESPACE; 

这会将表还原到备份时。对于
时间点恢复
,二进制日志可以进一步应用于数据库,但应注意仅应用那些影响正在还原的表的事务。

使用此方法的优点是不需要停止数据库服务器。一个小缺点是每个表都需要单独恢复,尽管它可以在脚本的帮助下克服。

脚本如下:

#!/bin/bash

# 检查输入参数
if [ "$#" -lt 2 ]; then
  echo "用法: \$0 <数据库名> <删除|导入>"
  exit 1
fi

DB_NAME=\$1
ACTION=\$2
MYSQL_USER="root"
MYSQL_PASSWORD="Hbis@123"
MYSQL_SOCKET="/srv/dstore/1.0.0.0/mysql/mysql.sock"

# 获取所有表名
TABLES=$(mysql --user="$MYSQL_USER" --password="$MYSQL_PASSWORD" --socket="$MYSQL_SOCKET" -D "$DB_NAME" -e "SHOW TABLES;" | awk '{ print \$1 }' | grep -v '^Tables_in_')

echo "数据库 '$DB_NAME' 中的表: $TABLES"

# 根据操作参数执行删除或导入
for TABLE in $TABLES; do
  if [ "$ACTION" == "删除" ]; then
    echo "删除表空间: $TABLE"
    mysql --user="$MYSQL_USER" --password="$MYSQL_PASSWORD" --socket="$MYSQL_SOCKET" -D "$DB_NAME" -e "ALTER TABLE $TABLE DISCARD TABLESPACE;"
  elif [ "$ACTION" == "导入" ]; then
    echo "导入表空间: $TABLE"
    mysql --user="$MYSQL_USER" --password="$MYSQL_PASSWORD" --socket="$MYSQL_SOCKET" -D "$DB_NAME" -e "ALTER TABLE $TABLE IMPORT TABLESPACE;"
  else
    echo "无效的操作: $ACTION"
    exit 1
  fi
done

echo "操作完成."

3. 恢复备份

解压缩

压缩算法默认是
qpress
,所以解压缩需要有 qpress 命令

yum -y install qpress

使用 --decompress压缩的备份集在准备备份之前需要解压,解压工具是qpress。解压后的原文件不会被删除,可以使用--remove-original选项清除,--parallel可与--decompress选项一起使用以同时解压缩多个文件。

xtrabackup --defaults-file=/opt/mysql/conf/my.cnf --decompress --target-dir=/var/backups/xtrabackup

准备备份命令

首先要进行 Prepare 阶段,在该阶段 Xtrabackup 会启动一个嵌入的 InnoDB 实例来进行 Crash Recovery。该实例的缓冲池的大小由 --use-memory 参数指定,默认为 100MB。如果有充足的内存,通过设置较大的 memory 可以减少 Prepare 阶段花费的时间。

 --use-memory=2G 
xtrabackup --defaults-file=/opt/mysql/conf/my.cnf \
    --prepare \
    --target-dir=/var/backups/xtrabackup \
    --user=root \
    --password=123456 \
    --socket=/opt/mysql/mysql.sock \
    --host=localhost \
    --apply-log-only

Prepare 阶段完成后,下面进入恢复阶段,可以手动拷贝文件到数据目录,也可以使用 xtrabackup 工具进行拷贝。

恢复备份命令

xtrabackup 	--defaults-file=/opt/mysql/conf/my.cnf \
    --copy-back \
    --parallel=10 \
    --target-dir=/var/backups/xtrabackup \
    --user=root \
    --password=Hbis@123 \
    --socket=/srv/dstore/1.0.0.0/mysql/mysql.sock \
    --host=localhost 

恢复全量备份时,需要加上 apply-log-only 参数。如果不加上 apply-log-only 参数,执行 prepare 的最后阶段,会回滚未提交的事务,但是这些事务可能在下一次增量备份时已经提交了。

  • 恢复备份前需要保证数据目录为空
  • 数据库必须处于停止状态

执行成功后,必须授权

 chown -R mysql:mysql /opt/mysql/data

启动数据库

mysqld_safe --defaults-file=/etc/my.cnf &

二、测试数据完整性

方式一

  • 模拟4个数据库,执行定时数据写入,模拟随机插入。
  • 备份
    • 首先全量备份
    • (记录偏移量)增量备份
  • scp数据,恢复数据到从机
  • 配置主从,停止主节点脚本,对比数据完整性
编写脚本

模拟数据

#!/bin/bash

# MySQL 配置
MYSQL_USER="root"
MYSQL_PASSWORD="Hbis@123"
MYSQL_SOCKET="/srv/dstore/1.0.0.0/mysql/mysql.sock"

# 创建数据库和插入数据的函数
create_databases_and_insert_data() {
    for i in {1..4}; do
        DB_NAME="test_db_$i"
        TABLE_NAME="test_table"
        
        # 创建数据库
        mysql --user="$MYSQL_USER" --password="$MYSQL_PASSWORD" --socket="$MYSQL_SOCKET" -e "CREATE DATABASE IF NOT EXISTS $DB_NAME;"
        
        # 创建表
        mysql --user="$MYSQL_USER" --password="$MYSQL_PASSWORD" --socket="$MYSQL_SOCKET" -D "$DB_NAME" -e "
        CREATE TABLE IF NOT EXISTS $TABLE_NAME (
            id INT AUTO_INCREMENT PRIMARY KEY,
            data_value VARCHAR(255) NOT NULL
        );"

        # 插入约10000条数据
        for ((j=1; j<=10000; j++)); do
            random_string=$(tr -dc 'A-Za-z0-9' < /dev/urandom | head -c 10)
            mysql --user="$MYSQL_USER" --password="$MYSQL_PASSWORD" --socket="$MYSQL_SOCKET" -D "$DB_NAME" -e "
            INSERT INTO $TABLE_NAME (data_value) VALUES ('$random_string');"
        done

        echo "数据库 $DB_NAME 创建完成,已插入 10000 条数据."
    done
}

# 执行创建数据库和插入数据
create_databases_and_insert_data

单个库数据量:474K

模拟定时插入脚本

#!/bin/bash

# MySQL 配置
MYSQL_USER="root"
MYSQL_PASSWORD="Hbis@123"
MYSQL_SOCKET="/srv/dstore/1.0.0.0/mysql/mysql.sock"

# 数据库和表的名称
DB_COUNT=4
TABLE_NAME="test_table"
INSERT_COUNT=10  # 设置要插入的总数

# 模拟定时插入数据
insert_data() {
    for ((n=1; n<=INSERT_COUNT; n++)); do
        for i in $(seq 1 $DB_COUNT); do
            DB_NAME="test_db_$i"
            random_string=$(tr -dc 'A-Za-z0-9' < /dev/urandom | head -c 10)
            mysql --user="$MYSQL_USER" --password="$MYSQL_PASSWORD" --socket="$MYSQL_SOCKET" -D "$DB_NAME" -e "
            INSERT INTO $TABLE_NAME (data_value) VALUES ('$random_string');"
            echo "向 $DB_NAME 插入数据: $random_string"
        done
        sleep 2  # 每次循环后暂停1秒
    done
}

# 执行数据插入
insert_data

模拟数据生成后,开启定时写入脚本,接下来开始执行备份

nohup sh insert_data.sh > insert_data.log 2>&1 & 
备份测试

首先备份全量数据

xtrabackup --backup \
    --target-dir=/var/backups/xtrabackup \
    --datadir=/data/dstore/1.0.0.0/mysql/data \
    --parallel=4  \
    --user=root \
    --password=Hbis@123 \
    --socket=/srv/dstore/1.0.0.0/mysql/mysql.sock \
    --host=localhost \
    --compress \
    --compress-threads=4 \
    --compress-chunk-size=65536

再执行增量备份

查看偏移点

mysql> show master status;
+---------------+----------+--------------+------------------+----------------------------------------------+
| File          | Position | Binlog_Do_DB | Binlog_Ignore_DB | Executed_Gtid_Set                            |
+---------------+----------+--------------+------------------+----------------------------------------------+
| binlog.000029 |    26321 |              |                  | 3949cf93-71b5-11ef-925f-fa163e75258c:1-40306 |
+---------------+----------+--------------+------------------+----------------------------------------------+
1 row in set (0.03 sec)

开始增量备份

xtrabackup --backup \
    --target-dir=/var/backups/inc1 \
    --extra-lsndir=/var/backups/inc1 \
    --datadir=/data/dstore/1.0.0.0/mysql/data \
    --user=root \
    --password=Hbis@123 \
    --socket=/srv/dstore/1.0.0.0/mysql/mysql.sock \
    --host=localhost \
    --compress \
    --incremental-basedir=/var/backups/xtrabackup

执行完后,开始执行恢复操作,需要将xtrabackup目录和inc1 传输到从节点

scp -r /var/backups/   192.168.2.41:`pwd`

执行恢复操作【参考上述3.0恢复备份】

数据迁移完成,启动数据库后执行主从配置

stop slave;

CHANGE MASTER TO 
  MASTER_HOST='192.168.2.83', 
  MASTER_USER='asyncuser', 
  MASTER_PASSWORD='Hbis@123', 
  MASTER_PORT=3306,
  MASTER_LOG_FILE='binlog.000029',
  MASTER_LOG_POS=26321;
  
start slave;
show slave status\G;

执行成功后,检查数据一致性

数据库
test_table1 10033 10033
test_table2 10033 10033
test_table3 10033 10033

可以看到数据是一致的

方式二

  • 首先安装从库,并按照主库创建数据库、表结构信息

  • 记录偏移量

    binlog.000030 |   617532 
    
  • 按照顺序备份 test_table1 、2、3、4

  • 恢复数据

  • 配置主从,查看数据一致性

在进行备份之前,您需要启用 innodb_file_per_table

开启数据循环插入脚本

备份 test_table1 、test_table2、test_table3、test_table4,命令示例:

xtrabackup \
  --defaults-file=/srv/dstore/1.0.0.0/mysql/conf/my.cnf \
  --backup \
  --target-dir=/var/backups/db1 \
  --socket=/srv/dstore/1.0.0.0/mysql/mysql.sock \
  --user=root \
  --password=Hbis@123 \
  --databases="test_db_1"

数据备份完成并传输到从机,执行恢复操作

xtrabackup \
    --prepare \
	--export \
	--apply-log-only  \
	--target-dir=/var/backups/db1

发现报错

[ERROR] [MY-012179] [InnoDB] Could not find any file associated with the tablespace ID: 10

暂时没有排查出来,测试发现当定时写入脚本运行时,导出的数据报错

依次恢复每个数据的表

检查发现数据存在无法同步,不一致

$.参考

https://blog.csdn.net/m0_66011019/article/details/136206192

https://blog.csdn.net/weixin_4156186

MySQL 社区开源备份工具 Xtrabackup 详解-CSDN博客

Percona XtraBackup:备份和恢复单个表或数据库

备份和恢复单个数据库 - MySQL & MariaDB / Percona XtraBackup - Percona社区论坛

前言

Volo.Abp.VirtualFileSystem
是ABP(ASP.NET Boilerplate)框架中的一个重要组件,它提供了一种抽象文件系统的方式,使得应用程序可以轻松地访问和管理文件资源,无论这些资源是来自于物理文件系统、嵌入资源,还是远程存储。

通过
Volo.Abp.VirtualFileSystem
,开发者可以使用统一的接口来处理文件和目录,而不用关心这些文件和目录的实际存储位置。这使得应用程序更加灵活,可以轻松地切换不同的文件存储方式,而不用修改大量的代码。

新建mvc项目

引用以下nuget包

新建BookAppWebModule.cs

using Volo.Abp.Localization.ExceptionHandling;
using Volo.Abp.Localization;
using Volo.Abp.Modularity;
using Volo.Abp.VirtualFileSystem;
using Volo.Abp.Autofac;
using Volo.Abp.AspNetCore.Mvc;
using Volo.Abp;
using Volo.Abp.AspNetCore.Mvc.Localization;
using Microsoft.AspNetCore.Mvc.Razor;
using Microsoft.Extensions.Hosting.Internal;
using BookApp.Localization;
using BookCategory;

namespace BookApp
{
    [DependsOn(
        typeof(AbpAutofacModule),
        typeof(AbpLocalizationModule),
        typeof(AbpVirtualFileSystemModule),
        typeof(AbpAspNetCoreMvcModule),
        typeof(BookCategoryModule)
    )]
    public class BookAppWebModule: AbpModule
    {
        public override void PreConfigureServices(ServiceConfigurationContext context)
        {
            var hostingEnvironment = context.Services.GetHostingEnvironment();
            var configuration = context.Services.GetConfiguration();

            context.Services.PreConfigure<AbpMvcDataAnnotationsLocalizationOptions>(options =>
            {
                options.AddAssemblyResource(
                    typeof(BookStoreResource)
                );
            });
        }
        public override void ConfigureServices(ServiceConfigurationContext context)
        {
            var hostingEnvironment = context.Services.GetHostingEnvironment();

            ConfigureVirtualFileSystem(hostingEnvironment);

            Configure<AbpLocalizationOptions>(options =>
            {
                options.Languages.Add(new LanguageInfo("ar", "ar", "العربية"));
                options.Languages.Add(new LanguageInfo("cs", "cs", "Čeština"));
                options.Languages.Add(new LanguageInfo("en", "en", "English"));
                options.Languages.Add(new LanguageInfo("en-GB", "en-GB", "English (UK)"));
                options.Languages.Add(new LanguageInfo("hu", "hu", "Magyar"));
                options.Languages.Add(new LanguageInfo("fi", "fi", "Finnish"));
                options.Languages.Add(new LanguageInfo("fr", "fr", "Français"));
                options.Languages.Add(new LanguageInfo("hi", "hi", "Hindi"));
                options.Languages.Add(new LanguageInfo("it", "it", "Italiano"));
                options.Languages.Add(new LanguageInfo("pt-BR", "pt-BR", "Português"));
                options.Languages.Add(new LanguageInfo("ru", "ru", "Русский"));
                options.Languages.Add(new LanguageInfo("sk", "sk", "Slovak"));
                options.Languages.Add(new LanguageInfo("tr", "tr", "Türkçe"));
                options.Languages.Add(new LanguageInfo("zh-Hans", "zh-Hans", "简体中文"));
                options.Languages.Add(new LanguageInfo("zh-Hant", "zh-Hant", "繁體中文"));
                options.Languages.Add(new LanguageInfo("de-DE", "de-DE", "Deutsch"));
                options.Languages.Add(new LanguageInfo("es", "es", "Español"));

                options.Resources
                    .Add<BookStoreResource>("en")
                    .AddVirtualJson("/Localization/BookStore");

                options.DefaultResourceType = typeof(BookStoreResource);
            });
        }

        public override void OnApplicationInitialization(ApplicationInitializationContext context)
        {
            var app = context.GetApplicationBuilder();
            var env = context.GetEnvironment();

            app.UseAbpRequestLocalization();

            if (env.IsDevelopment())
            {
                app.UseDeveloperExceptionPage();
            }

            app.UseHttpsRedirection();
            app.UseStaticFiles();

            app.UseRouting();
        }

        private void ConfigureVirtualFileSystem(IWebHostEnvironment hostingEnvironment)
        {
            Configure<AbpVirtualFileSystemOptions>(options =>
            {
                options.FileSets.AddEmbedded<BookAppWebModule>();

                if (hostingEnvironment.IsDevelopment())
                {
                    options.FileSets.ReplaceEmbeddedByPhysical<BookAppWebModule>(hostingEnvironment.ContentRootPath);
                    options.FileSets.ReplaceEmbeddedByPhysical<BookCategoryModule>(Path.Combine(hostingEnvironment.ContentRootPath, string.Format("..{0}BookCategory", Path.DirectorySeparatorChar)));
                }
            });
        }
    }
}

修改Program.cs

using BookApp;
using Microsoft.Extensions.DependencyInjection;

var builder = WebApplication.CreateBuilder(args);

builder.Host
    .AddAppSettingsSecretsJson()
    .UseAutofac();

await builder.AddApplicationAsync<BookAppWebModule>();

var app = builder.Build();

await app.InitializeApplicationAsync();

app.MapControllerRoute(
    name: "default",
    pattern: "{controller=Home}/{action=Index}/{id?}");

await app.RunAsync();

新建资源文件

新建BookCategory类库项目

新建BookCategoryModule.cs

using Volo.Abp.Localization;
using Volo.Abp.Modularity;
using Volo.Abp.VirtualFileSystem;
using Volo.Abp;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.DependencyInjection;
using Volo.Abp.AspNetCore.Mvc;
using BookApp.Localization;

namespace BookCategory
{
    [DependsOn(
        typeof(AbpVirtualFileSystemModule),
        typeof(AbpAspNetCoreMvcModule)
    )]
    public class BookCategoryModule: AbpModule
    {
        public override void ConfigureServices(ServiceConfigurationContext context)
        {
            var hostingEnvironment = context.Services.GetHostingEnvironment();
            var configuration = context.Services.GetConfiguration();

            Configure<AbpVirtualFileSystemOptions>(options =>
            {
                options.FileSets.AddEmbedded<BookCategoryModule>();//添加程序集到虚拟文件系统

                if (hostingEnvironment.IsDevelopment())
                {

                }
            });

            Configure<AbpLocalizationOptions>(options =>
            {
                options.Resources
                    .Add<BookCategoryResource>("en")
                    .AddVirtualJson("/Localization/BookCategory"); //这里必需添加,不然本地化时找不到相应的json文件
            });
        }

        public override void OnApplicationInitialization(ApplicationInitializationContext context)
        {

        }
    }
}

新建BookCategoryResource.cs


using Volo.Abp.Localization;

namespace BookApp.Localization;

[LocalizationResourceName("BookCategory")]
public class BookCategoryResource
{

}

新建资源文件

修改mvc项目的HomeController.cs中的Privacy方法

        public IActionResult Privacy()
        {
            var resourcePath = "/Localization/BookCategory/en.json";
            var fileInfo = _fileProvider.GetFileInfo(resourcePath);

            if (fileInfo.Exists)
            {
                using (var stream = fileInfo.CreateReadStream())
                using (var reader = new StreamReader(stream))
                {
                    var content = reader.ReadToEnd();
                    return Content(content);
                }
            }

            return Content("Resource not found");
        }

此时我们就可以访问BookCategory类库中的资源文件

修改mvc项目中的视图Index.cshtml

@using Microsoft.Extensions.Localization
@using Microsoft.AspNetCore.Mvc.Localization
@using BookApp.Controllers
@using BookApp.Localization

@inject IHtmlLocalizer<BookStoreResource> HtmlLocalizer
@inject IStringLocalizer<BookCategoryResource> StringLocalizer

@{
    ViewData["Title"] = "Home Page";
}

<div>string: @StringLocalizer["AppName"]</div>

<div>html: @HtmlLocalizer["AppName"]</div>

IStringLocalizer
StringLocalizer 这里就可以读取BookCategory类库中的资源文件

作者

吴晓阳(手机:13736969112微信同号)

AI之旅
开篇
之后的第一篇本准备写一篇简单的技术分享,却遇到一个「意外」,这个意外惊喜让园子的「AI之旅」起步即迈出重要一步。

经过3个多月的接触,园子最近终于和
Chat2DB
达成了战略合作,以下内容是 Chat2DB 创始人发布的战略合作公告博文:

在AI技术的浪潮中,我们时常思考,如何让工具更加智能,如何让开发者的工作更加高效。我们与博客园的战略合作,正是一次回应这样的思考。Chat2DB,一款专注于数据库管理和数据分析的AI驱动工具,与博客园联手,致力于为广大开发者提供一系列智能、贴心的开发工具。通过开源免费加增值服务的模式,我们希望让更多开发者的技术生涯更加顺畅、充满活力。

从EasyExcel到Chat2DB:技术人的初心与坚守

回首过往,作为EasyExcel的作者,我曾在技术的长路上与许多同道中人并肩前行。EasyExcel诞生于解决复杂报表需求的初衷,但成长于开源社区的热爱与贡献之中。它不仅仅是一款简单的Excel处理工具,更代表了我对开发者社区的承诺——用技术为大家提供最简洁高效的工具,减少繁琐工作,提升生产力。多年来,和开源社区的伙伴们交流、协作,让我深刻体会到技术人那种为了理想共同拼搏的情谊。

然而,技术的发展从未停止脚步。我们在去年见证了人工智能技术的快速迭代,这让我意识到AI在数据库领域的潜力。于是,Chat2DB应运而生(
https://chat2db-ai.com/zh-CN
)。这个项目并非只是单一地关注数据库管理方面,而是借助AI的能力,让数据管理、数据分析变得更智能、更高效。我们希望开发者在面临复杂的数据库场景时,能够少些繁琐,多些智能和人性化体验。

与博客园的邂逅:认同与合作的源起

在技术人的圈子里,提到博客园,几乎无人不知。

作为国内最有影响力的技术社区之一,博客园不仅是许多开发者学习、交流的聚集地,更是一个充满温暖与活力的家。多年来,博客园用无数的技术文章、交流讨论、分享会等形式,陪伴着一代又一代的技术人走过成长路程。我自己也曾在博客园中写下随笔、阅读他人的经验分享。对于博客园,我有着深深的认同与敬意。它不仅是一个技术平台,更是一片孕育梦想的土壤。

在今年7月,一个偶然的机会,我看到博客园关于“救园”的文章,深受触动。那种为了守护社区的坚定与不懈努力让我再次体会到“社区”二字的深刻含义。于是,我主动与博客园联系,探讨合作的可能性。因为我们有太多共同的地方:同样都喜欢写代码,同样都怀揣着技术情怀,同样希望能为技术人带来更多价值。于是,在杭州的一个夏夜,我们第一次见面,便产生了许多共鸣。经过几轮深入探讨,双方决定共同推动一场围绕“开源+社区+AI”的合作,共同为开发者提供更智能化的工具支持,提升生产力,增添创作乐趣。

战略合作的意义:从共鸣到共创

我们这次合作的初衷,是希望通过战略性协作,将我们各自的优势汇聚,为开发者带来更为便捷、智能的工具体验。Chat2DB凭借AI驱动的数据库管理与数据分析能力,与博客园丰富的社区资源和技术积累,可以共同推动“AI+社区”的融合,真正助力开发者的成长。这不仅是一次商业合作,更是一次基于认同和信念的携手共进。

博客园希望在未来通过产品化路径,为社区带来更多的服务和价值。而我希望通过与博客园的合作,推广AI数据库管理工具在开发者群体中的普及率,让更多开发者在日常工作中享受到AI带来的便利和效率提升。合作的背后,是我们对技术的热爱与信仰,是我们对技术人长久以来的支持与陪伴。通过这样的战略合作,我们相信可以实现“开源免费+增值服务”模式的成功落地,既让工具更高效地服务开发者,又能让社区更具活力。

前路漫漫,合作未来可期

在这条前路上,我们深知困难重重。AI技术虽然日新月异,但如何让它真正服务于人、服务于开发者是一个需要长期努力的问题。我们和博客园也经历了多次合作方案的调整,每一次的碰面交流,每一个思考的细节,都是为了让合作真正落地。10月31日,我们终于敲定了具体方案,这段时间的努力没有白费,这份承诺也将持续下去。

未来,我们将围绕开发者的需求,共同探索并与更多AI开发工具厂商展开合作,为社区引入更多优质的AI技术工具和资源,丰富博客园的工具生态,共同开拓「AI+社区」驱动的开发者工具市场,持续为开发者们提供更多样化、更高效的开发支持和服务。此外,博客园将继续围绕社区开展多种互动活动,进一步增强社区氛围,让更多开发者从中获益。

结语:共创AI时代的开发者新篇章

在技术变革的路上,我们不会孤单。我们与博客园的合作,只是一个开始。未来,更多的技术人将加入这场旅程,共同书写AI时代的开发者新篇章。我们希望,这条路上的每一个人,都能够在智能工具与温暖社区的双重支持下,走得更远、飞得更高。一起携手,迎接更智能、更开放、更美好的技术未来。

Chat2DB 官方公众号同步发布
https://mp.weixin.qq.com/s/d4nfBLcJEfUJvxp_S2unbA