一个脚本实现全量增量备份,并推送到远端备份中心服务器

2019独角兽企业重金招聘Python工程师标准>>> hot3.png

摘要

由于工作需要,刚好需要这样一个功能的脚本,主要解决:

1. 不想在crontab中调度两条备份任务,一个做全量一个做增量

2. 如果每小时做增量,凌晨4点做全量,那会导致crontab写的复杂,增量需要跳过凌晨4点

3. 自动推备份文件到远程的备份中心服务器

 

前提

1. 操作系统CentOS,其他没测,当然理论上没问题

2. 本地运行MySQL/MariaDB数据库服务

3. 安装必要的包

3.1 percona-toolkit

3.2 percona-xtrabackup

4. 演示服务器

4.1 10.0.0.2 CentOS 7.3 x86_64 / MariaDB 10.1.21 + percona-toolkit-3.0.2 + percona-xtrabackup-2.3.6 数据库

4.2 10.0.0.3 CentOS 7.3 x86_64 备份

 

步骤

1. 打通服务器互信

1.1 登录到数据库服务器,在终端执行ssh-keygen -t rsa

# ssh-keygen -t rsa
Generating public/private rsa key pair.
Enter file in which to save the key (/root/.ssh/id_rsa): 
Enter passphrase (empty for no passphrase): 
Enter same passphrase again: 
Your identification has been saved in /root/.ssh/id_rsa.
Your public key has been saved in /root/.ssh/id_rsa.pub.
The key fingerprint is:
SHA256:Nwh9Ao11ea3HcRS49wcygYmEfwQxUUL2RVS04QMZHMc root@rx-wj39106
The key's randomart image is:
+---[RSA 2048]----+
|      .B@*oBBO*oo|
|      o+o==.==Eo.|
|      ..o.o. +=o |
|       ..+. +.+o |
|        S.o  +...|
|         . .    o|
|                .|
|                 |
|                 |
+----[SHA256]-----+

1.2 复制公钥到备份服务器ssh-copy-id -i ~/.ssh/id_rsa.pub  root@10.0.0.3

# ssh-copy-id -i ~/.ssh/id_rsa.pub  root@10.0.0.3
/bin/ssh-copy-id: INFO: Source of key(s) to be installed: "/root/.ssh/id_rsa.pub"
/bin/ssh-copy-id: INFO: attempting to log in with the new key(s), to filter out any that are already installed
/bin/ssh-copy-id: INFO: 1 key(s) remain to be installed -- if you are prompted now it is to install the new keys
root@10.0.0.3's password: Number of key(s) added: 1Now try logging into the machine, with:   "ssh 'root@10.0.0.3'"
and check to make sure that only the key(s) you wanted were added.

1.3 测试登录 ssh root@10.0.0.3

# ssh root@10.0.0.3
Last login: Thu May 18 11:10:47 2017 from 10.0.0.2

第一步完成。

 

2. 准备备份

2.1 登录到数据库服务器,创建备份专用用户,并授权

# mysql -h 127.0.0.1 -u root -p mysql
Enter password: 
Reading table information for completion of table and column names
You can turn off this feature to get a quicker startup with -AWelcome to the MariaDB monitor.  Commands end with ; or \g.
Your MariaDB connection id is 541
Server version: 10.1.21-MariaDB MariaDB ServerCopyright (c) 2000, 2016, Oracle, MariaDB Corporation Ab and others.Type 'help;' or '\h' for help. Type '\c' to clear the current input statement.MariaDB [mysql]> CREATE USER backup@'127.0.0.1' IDENTIFIED BY 'changeme';
Query OK, 0 rows affected (0.08 sec)MariaDB [mysql]> GRANT SELECT, RELOAD, LOCK TABLES, REPLICATION CLIENT, SHOW VIEW ON *.* TO backup@'127.0.0.1';
Query OK, 0 rows affected (0.00 sec)MariaDB [mysql]>

2.2 保存下面脚本为/usr/bin/backup-database.sh

#!/bin/bash
# 数据库自适应备份脚本
# 主要功能对innobackupex做了二次封装,一个脚本实现对整个服务器的全量和增量备份
# 通过crontab调度,可以每小时或者每隔一小时执行,默认在凌晨4点做全备,其他时段
# 做增量备份。
# 脚本采用流的方式推送备份文件到远端服务器,所以需要打通服务器互信
# 当然脚本也获取了当前的星期/月/日,可以自行做其他策略,比如每周一次全备,则只
# 需要在全备的判断中增加对于周的检测# 显示使用帮助
usage () {echo "Usage: ${0} [OPTION...]"echo "  -H, --help          This option displays a help screen and exits."echo "  -n, --compress-threads=#"echo "                      This option specifies the number of worker threads that"echo "                      will be used for parallel compression. It is passed"echo "                      directly to the xtrabackup child process."echo "  -e, --extra-lsndir=name"echo "                      This option specifies the directory in which to save an"echo "                      extra copy of the \"xtrabackup_checkpoints\" file. The"echo "                      option accepts a string argument. It is passed directly"echo "                      to xtrabackup's --extra-lsndir option. See the xtrabackup"echo "                      documentation for details."echo "  -d, --incremental-basedir=name"echo "                      This option specifies the directory containing the full"echo "                      backup that is the base dataset for the incremental"echo "                      backup.  The option accepts a string argument."echo "  -r, --parallel=#    On backup, this option specifies the number of threads"echo "                      the xtrabackup child process should use to back up files"echo "                      concurrently.  The option accepts an integer argument. It"echo "                      is passed directly to xtrabackup's --parallel option. See"echo "                      the xtrabackup documentation for details."echo "  -s, --stream=name   This option specifies the format in which to do the"echo "                      streamed backup.  The option accepts a string argument."echo "                      The backup will be done to STDOUT in the specified"echo "                      format. Currently, the only supported formats are tar and"echo "                      xbstream. This option is passed directly to xtrabackup's"echo "                      --stream option."echo "  -u, --user=name     This option specifies the MySQL username used when"echo "                      connecting to the server, if that's not the current user."echo "                      The option accepts a string argument. See mysql --help"echo "                      for details."echo "  -h, --host=name     This option specifies the host to use when connecting to"echo "                      the database server with TCP/IP.  The option accepts a"echo "                      string argument. See mysql --help for details."echo "  -P, --port=#        This option specifies the port to use when connecting to"echo "                      the database server with TCP/IP.  The option accepts a"echo "                      string argument. See mysql --help for details."echo "  -p, --password=name This option specifies the password to use when connecting"echo "                      to the database. It accepts a string argument.  See mysql"echo "                      --help for details."echo "  -S, --socket=name   This option specifies the socket to use when connecting"echo "                      to the local database server with a UNIX domain socket."echo "                      The option accepts a string argument. See mysql --help"echo "                      for details."echo "  -b, --backup-dir    The local backup directory for temp use"echo "  -R, --remote-server The remote server with SSH where you want to put the backup"echo "                      file into."echo "  -D, --remote-dir    The backup directory on remote server"echo "  -v, --version       Output version information and exit."
}# 读取命令后参数
OPTS=`getopt -o Hvh:P:u:p:S:n:e:d:r:m:R:b:D: --long help,version,host:,port:,user:,password:,socket:,compress-threads:,extra-lsndir:,incremental-basedir:,parallel:,stream:,remote-server:,backup-dir:,remote-dir: -n 'parse-options' -- "$@"`if [ $? != 0 ]
thenexit 1 
fieval set -- "$OPTS"# 参数默认值设定
HELP=0
VERSION=0
MYSQL_HOST='127.0.0.1'
MYSQL_PORT=3306
MYSQL_USER='backup'
MYSQL_PASS=''
MYSQL_SOCK=''
INCREMENTAL=0
COMPRESS_THREADS=8
EXTRA_LSNDIR=''
INCREMENTAL_BASEDIR=''
PARALLEL=4
STREAM='xbstream'
REMOTE_SERVER=''
BACKUP_DIR='/var/tmp'
REMOTE_DIR=''# 参数赋值
while true
docase "$1" in-H | --help                ) HELP=1;                 break   ;; # 显示帮助信息,无需解析更多参数直接退出-v | --version             ) VERSION=1;              break   ;; # 显示版本信息,无需解析更多参数直接退出-h | --host                ) MYSQL_HOST=$2;          shift 2 ;; # 备份的主机,默认localhost-P | --port                ) MYSQL_PORT=$2;          shift 2 ;; # 服务端口,默认3306-u | --user                ) MYSQL_USER=$2;          shift 2 ;; # 登录用户,默认backup-p | --password            ) MYSQL_PASS=$2;          shift 2 ;; # 登录密码-S | --socket              ) MYSQL_SOCK=$2;          shift 2 ;; # 嵌套字文件位置-n | --compress-threads    ) COMPRESS_THREADS=$2;    shift 2 ;; # 压缩线程数,默认开启压缩-e | --extra-lsndir        ) EXTRA_LSNDIR=$2;        shift 2 ;; # 检查点信息保存的位置,存在则覆盖-d | --incremental-basedir ) INCREMENTAL_BASEDIR=$2; shift 2 ;; # -r | --parallel            ) PARALLEL=$2;            shift 2 ;; # 并发子进程数量-m | --stream              ) STREAM=$2;              shift 2 ;; # 数据流格式,默认xbstream-R | --remote-server       ) REMOTE_SERVER=$2;       shift 2 ;; # 远程服务器信息,比如root@10.0.0.1-b | --backup-dir          ) BACKUP_DIR=$2;          shift 2 ;; # 本地工作目录,默认/var/tmp-D | --remote-dir          ) REMOTE_DIR=$2;          shift 2 ;; # 远程备份路径,比如/data/backup-- ) shift; break ;;* ) break ;;esac
done# 显示版本
if [[ $VERSION -eq 1 ]]
thenecho "MySQL Adaptive Backup v1.0.0"exit 0
fi# 显示帮助
if [[ $HELP -eq 1 ]]
thenusageexit 0
fi# 对参数进行判断,如果没有提供则报错并退出
if ! [ -n "${EXTRA_LSNDIR}" ]
thenecho "Please specify the action you want to run with -e or -extra_lsndir"exit 1;
fiif ! [ -n "${REMOTE_SERVER}" ]
thenecho "Please specify the action you want to run with -R or --remote-server"exit 1
fiif ! [ -n "${BACKUP_DIR}" ]
thenecho "Please specify the action you want to run with -b or --backup-dir"exit 1
fiif ! [ -n "${REMOTE_DIR}" ]
thenecho "Please specify the action you want to run with -D or --remote-dir"exit 1
fiif ! [ -n "${INCREMENTAL_BASEDIR}" ]
thenecho "Please specify the action you want to run with -d or --incremental-basedir"exit 1
fi# 开始备份
d=$(date +"%Y-%m-%d %H:%M:%S") # 当前时间,写日志需要使用
h=$(date +"%k")                # 当前的小时数,24小时制
d=$(date +"%-d")               # 当前日期的天,可用用于更复杂的备份策略
m=$(date +"%-m")               # 当前日期的月,可以用于更复杂的备份策略
w=$(date +"%u")                # 当前日期的周,可以用于更复杂的备份策略
t=$(date +"%Y_%m_%d_%H_%M")    # 备份文件名上的时间戳echo "[${d}] ----------------------------------------" >> /var/tmp/backup.log# 生成备份命令
CMD="innobackupex --compress"
CMD="${CMD} --host=${MYSQL_HOST}"
CMD="${CMD} --port=${MYSQL_PORT}"
CMD="${CMD} --user=${MYSQL_USER}"
CMD="${CMD} --password=${MYSQL_PASS}"
CMD="${CMD} --compress-threads=${COMPRESS_THREADS}"
CMD="${CMD} --parallel=${PARALLEL}"
CMD="${CMD} --extra-lsndir=${EXTRA_LSNDIR}"
CMD="${CMD} --stream=xbstream"if [[ ${h} -eq 4 ]] # 每天凌晨4:00做全备,其他时间做增量
thenecho "[${d}] Make full backup" >> /var/tmp/backup.log
elseecho "[${d}] Make incremental backup" >> /var/tmp/backup.logCMD="${CMD} --incremental"CMD="${CMD} --incremental-basedir=${INCREMENTAL_BASEDIR}"
fiCMD="${CMD} ${BACKUP_DIR}"
CMD="${CMD} | ssh ${REMOTE_SERVER}"
CMD="${CMD} \"cat - > ${REMOTE_DIR}_${t}.xbs\""
echo "[${d}] ${CMD}" >> /var/tmp/backup.log# 执行备份操作
eval $CMDexit 0

2.3 在数据库服务器端使用系统任务调度

# crontab -l
0 */1 * * * sh /usr/bin/backup-database.sh -p changeme -e /root/backup -R root@10.0.0.3 -D /root/backup -b /root/backup -d /root/backup

测试是每个整点做备份,也可以根据自身的需求更改

2.4 查看日志

备份脚本会写日志到/var/tmp/backup.log,这个后续考虑通过命令行更改

# cat /var/tmp/backup.log
[2017-05-18 04:00:01] ----------------------------------------
[2017-05-18 04:00:01] Make full backup
[2017-05-18 04:00:01] innobackupex --compress --host=127.0.0.1 --port=3306 --user=backup --password=changeme --compress-threads=8 --parallel=4 --extra-lsndir=/root/backup --stream=xbstream /root/backup /root/backup | ssh root@10.0.0.3 "cat - > /root/backup_2017_05_18_04_00.xbs"
[2017-05-18 05:00:01] ----------------------------------------
[2017-05-18 05:00:01] Make incremental backup
[2017-05-18 05:00:01] innobackupex --compress --host=127.0.0.1 --port=3306 --user=backup --password=changeme --compress-threads=8 --parallel=4 --extra-lsndir=/root/backup --stream=xbstream --incremental --incremental-basedir=/root/backup /root/backup | ssh root@10.0.0.3 "cat - > /root/backup_2017_05_18_05_00.xbs"
[2017-05-18 06:00:01] ----------------------------------------
[2017-05-18 06:00:01] Make incremental backup
[2017-05-18 06:00:01] innobackupex --compress --host=127.0.0.1 --port=3306 --user=backup --password=changeme --compress-threads=8 --parallel=4 --extra-lsndir=/root/backup --stream=xbstream --incremental --incremental-basedir=/root/backup /root/backup | ssh root@10.0.0.3 "cat - > /root/backup_2017_05_18_06_00.xbs"
[2017-05-18 07:00:01] ----------------------------------------
[2017-05-18 07:00:01] Make incremental backup
[2017-05-18 07:00:01] innobackupex --compress --host=127.0.0.1 --port=3306 --user=backup --password=changeme --compress-threads=8 --parallel=4 --extra-lsndir=/root/backup --stream=xbstream --incremental --incremental-basedir=/root/backup /root/backup | ssh root@10.0.0.3 "cat - > /root/backup_2017_05_18_07_00.xbs"
[2017-05-18 08:00:01] ----------------------------------------
[2017-05-18 08:00:01] Make incremental backup
[2017-05-18 08:00:01] innobackupex --compress --host=127.0.0.1 --port=3306 --user=backup --password=changeme --compress-threads=8 --parallel=4 --extra-lsndir=/root/backup --stream=xbstream --incremental --incremental-basedir=/root/backup /root/backup | ssh root@10.0.0.3 "cat - > /root/backup_2017_05_18_08_00.xbs"
[2017-05-18 09:00:01] ----------------------------------------
[2017-05-18 09:00:01] Make incremental backup
[2017-05-18 09:00:01] innobackupex --compress --host=127.0.0.1 --port=3306 --user=backup --password=changeme --compress-threads=8 --parallel=4 --extra-lsndir=/root/backup --stream=xbstream --incremental --incremental-basedir=/root/backup /root/backup | ssh root@10.0.0.3 "cat - > /root/backup_2017_05_18_09_00.xbs"
[2017-05-18 10:00:01] ----------------------------------------
[2017-05-18 10:00:01] Make incremental backup
[2017-05-18 10:00:01] innobackupex --compress --host=127.0.0.1 --port=3306 --user=backup --password=changeme --compress-threads=8 --parallel=4 --extra-lsndir=/root/backup --stream=xbstream --incremental --incremental-basedir=/root/backup /root/backup | ssh root@10.0.0.3 "cat - > /root/backup_2017_05_18_10_00.xbs"
[2017-05-18 11:00:01] ----------------------------------------
[2017-05-18 11:00:01] Make incremental backup
[2017-05-18 11:00:01] innobackupex --compress --host=127.0.0.1 --port=3306 --user=backup --password=changeme --compress-threads=8 --parallel=4 --extra-lsndir=/root/backup --stream=xbstream --incremental --incremental-basedir=/root/backup /root/backup | ssh root@10.0.0.3 "cat - > /root/backup_2017_05_18_11_00.xbs"

2.5 登录备份中心服务器查看

# ls -lah
total 1644
-rw-------. 1 root root    880 Feb  7 16:58 anaconda-ks.cfg
-rw-r--r--  1 root root 4.2M May 18 04:01 backup_2017_05_18_04_00.xbs
-rw-r--r--  1 root root 315K May 18 05:01 backup_2017_05_18_05_00.xbs
-rw-r--r--  1 root root 315K May 18 06:01 backup_2017_05_18_06_00.xbs
-rw-r--r--  1 root root 315K May 18 07:01 backup_2017_05_18_07_00.xbs
-rw-r--r--  1 root root 315K May 18 08:01 backup_2017_05_18_08_00.xbs
-rw-r--r--  1 root root 315K May 18 09:01 backup_2017_05_18_09_00.xbs
-rw-r--r--  1 root root 344K May 18 10:01 backup_2017_05_18_10_00.xbs
-rw-r--r--  1 root root 348K May 18 11:01 backup_2017_05_18_11_00.xbs

 

本文仅是演示目的,目录没有做更多的规划,考虑在生产服务器,具体备份位置看磁盘挂载的位置,一般可能是/opt/data或者/data。

转载于:https://my.oschina.net/u/1261643/blog/903450

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/290342.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

地壳中元素含量排名记忆口诀_Nature:利用熔融包裹体的元素和同位素示踪俯冲带流体来源...

Nature:利用熔融包裹体的元素和同位素示踪俯冲带流体来源在汇聚板块边缘,大洋岩石圈通过俯冲作用携带挥发分(尤其是水)进入地幔。这些俯冲下去的水/流体控制着岩浆产物、地震活动、陆壳形成和资源成矿。但是,识别不同流体的来源(沉积物&#…

Windows 10开发基础——文件、文件夹和库(一)

Windows 10开发基础——文件、文件夹和库(一) 原文:Windows 10开发基础——文件、文件夹和库(一)主要内容: 1.枚举查询文件和文件夹 2.文本文件读写的三种方法——创建写入和读取文件 3.获得文件的属性 枚举查询文件和…

Sigmoid函数与逻辑回归

文章目录(1). Sigmoid函数的由来——伯努利分布的衍生物1.1 为什么会有 sigmoid 函数的出现?1.2 sigmoid 函数推导过程1.3 sigmoid 函数求导(2). 逻辑回归(Logistic Regression)2.1 逻辑回归算法的最终本质——求决策边界2.2 逻辑回归算法中的…

Avalonia跨平台入门第二十二篇之人脸检测

在前面分享的几篇中咱已经玩耍了Popup、ListBox多选、Grid动态分、RadioButton模板、控件的拖放效果、控件的置顶和置底、控件的锁定、自定义Window样式、动画效果、Expander控件、ListBox折叠列表、聊天窗口、ListBox图片消息、窗口抖动、语音发送、语音播放、语音播放问题、玩…

pkpm板按弹性计算还是塑性_PKPM中的S\R验算显红原因分析

PKPM软件砼结构施工图中的,梁的配筋面积中,SR验算,经常会有个别构件显红的情况。查了一下PKPM说明书,并没有针对此情况的详细说明。根据本人的实际经验,总结了一下解决此问题的主要方法:一.超筋SR的值显示为…

多智能体连续行为空间问题求解——MADDPG

目录1. 问题出现:连续行为空间出现2. DDPG 算法2.1 DDPG 算法原理2.2 DDPG 算法实现代码2.2.1 Actor & Critic2.2.2 Target Network2.2.3 Memory Pool2.2.4 Update Parameters(evaluate network)2.2.5 Update Parameters(targ…

在.NET 6 中如何创建和使用 HTTP 客户端 SDK

如今,基于云、微服务或物联网的应用程序通常依赖于通过网络与其他系统通信。每个服务都在自己的进程中运行,并解决一组有限的问题。服务之间的通信是基于一种轻量级的机制,通常是一个 HTTP 资源 API。从.NET 开发人员的角度来看,我…

ttl接地是高电平还是低电平_功放技术参数1——高电平

在汽车音响中的功放或者DSP再或者是DSP功放中我们都会遇到高电平信号或者低电平信号输入,我们该如何判断主机输出的到底是高电平信号还是低电平信号呢?我们可以用一个很简单的方法来鉴定,那就是主机输出能够直接驱动喇叭的为高电平信号输出&a…

MultiProcessing中主进程与子进程之间通过管道(Pipe)通信

Python 中 Multiprocessing 实现进程通信1. 如何建立主进程与子进程之间的通信管道?2. 为什么一定要将Pipe中的某些端close()?本文参考自:python 学习笔记 - Queue & Pipes,进程间通讯 1. 如何建立主进程与子进程之间的通信管道&#xf…

如何为 .NET 项目自定义强制代码样式规则

前言每个人都有自己的代码样式习惯:命名约定、大括号、空格、换行等。但是,作为一个团队来说,应该使用同样的代码样式规则。这样可以有效减少编译器的警告/建议,保证阅读代码的人员理解一致。今天我们介绍一种为单独的 .NET 项目定义代码样式…

我是如何帮助创业公司改进企业工作的

前段时间在一家创业公司实习,几十个人的团队,正处在规模逐渐扩大的阶段,但是整个公司的协作工作和日常管理却越来越麻烦,鉴于我以前对Saas和协作平台都有过一点研究,于是leader叫我去找一个“简单,好用&…

PHP单例模式(精讲)

2019独角兽企业重金招聘Python工程师标准>>> 首先我们要明确单例模式这个概念,那么什么是单例模式呢? 单例模式顾名思义,就是只有一个实例。作为对象的创建模式,单例模式确保某一个类只有一个实例,而且自行…

【QMIX】一种基于Value-Based多智能体算法

文章目录1. QMIX 解决了什么问题(Motivation)2. QMIX 怎样解决团队收益最大化问题(Method)2.1 算法大框架 —— 基于 AC 框架的 CTDE(Centralized Training Distributed Execution) 模式2.2 Agent RNN Netw…

增强型的for循环linkedlist_LinkedList的复习

先摘选一段Testpublic void test_LinkedList() { // 初始化100万数据 List list new LinkedList(1000000);// 遍历求和int sum 0;for (int i 0; i sum list.get(i); }}乍一看可能觉得没什么问题,但是这个遍历求和会非常慢。主要因为链表的数据结构…

3月更新来了!Windows 11正式版22000.556发布

面向 Windows 11 正式版用户,微软现已发布累积更新 KB5011493,更新后版本号升级至 Build 22000.556。主要变化1.微软正在改变 Windows 11 "开始"菜单中推荐模块有关 Office 文件的打开方式。如果文件被同步到 OneDrive,“开始”菜单…

[C/C++]重读《The C Programming Language》

第一次读这本书的时候是大三初,现在打算重读一遍!。 第一章 导言 1. 学习一门新程序设计语言的唯一途径就是用它来写程序。 2. 每个程序都从main函数的起点开始执行。 3. 在C语言中,所有变量必须先声明后使用。 4. C语言中的基本数据类型的大…

115怎么利用sha1下载东西_618“甩”度娘,拥抱115,体验和价格才是王道

网盘价钱​前天618,圈子里的朋友几乎都“甩”了度娘一巴掌,我才知道115搞活动,由原来500元1年的钻石会员,变成500元3年,算起来每天不到0.5元,确实比度娘实惠了很多,而且活动持续到6月底。自从发…

安装宝塔面板

安装宝塔面板: 1. 宝塔面板网站: https://www.bt.cn/ 2.安装教程 https://www.bt.cn/bbs/thread-1186-1-1.html 3.1 使用远程工具连接执行以下命令 yum install -y wget && wget -O install.sh http://download.bt.cn/install/install.sh &&…

【COMA】一种将团队回报拆分为独立回报的多智能体算法

文章目录1. COMA 解决了什么问题(Motivation)2. COMA 怎么解决独立回报分配问题(Method)2.1 核心思想 counterfactual baseline 的提出2.2 算法大框架 —— 基于 AC 框架的 CTDE(Centralized Training Distributed Exe…

C#解析Markdown文档,实现替换图片链接操作

前言又是好久没写博客了其实也不是没写,是最近在「做一个博客」,从2月21日开始,大概一个多星期的时间,疯狂刷进度,边写代码边写了一整系列的博客开发笔记,目前为止已经写了16篇了,然后上3月之后…