前言
fpsync
是基于fpart
和rsync
包装的一个迁移工具,由shell脚本编写。
fpsync
底层利用原生的rsync
来做数据的备份工作,利用fpart
来做待备份目录的解析。
源码的实现上,整体逻辑非常清晰,分为了两大部分。一部分:用来做参数的解析和环境合法性检测;另一部分:结合fpart
和rsync
来做备份操作,由多个备份作业同时进行。
下面,主要介绍fpsync
做多个作业并发备份的实现思想。
fpsync
首先介绍fpart
和rsync
的基本使用,以及两个第三方工具必须依赖的参数信息。
一、 rsync
fpsync
内部原生的rsync
备份命令。
# 不使用-r参数,代表只拷贝单个文件
# 1. -l 链接 -p 权限 -t 时间 -g 属组 -o owner -D same as --devices --specials
# 2. 打印详细信息
# 3. don't map uid/gid values by user/group name
$ rsync -lptgoD -v --numeric-ids /mnt/Rsync-src /mnt/Rsync-dest
二、fpart
fpsync
内部原生的fpart
切分目录命令。
# 进入到待备份目录中
# -o ${FPART_PARTSTMPL} fpart生成的打包文件放置的共享目录位置
# -0 如果使用-o选项,那么在每一个生成的文件末尾后面添加/
# -e 在目录中添加/
# -L 启动实时模式
# -W 调用回调函数
# ${FPART_POSTHOOK} 执行rsync的回调函数
$ cd /mnt/Rsync-dest
$ fpart -o ${FPART_PARTSTMPL} -0 -e ${OPT_FPART} ${FPART_MODEOPTS} -L -W ${FPART_POSTHOOK} .
若需要,在fpart
解析目录的同时并发执行rsync
,做多个任务迁移工作。可以使用如下方法:
fpart -L -f 10000 -x '.snapshot' -x '.zfs' -zz -o /tmp/part.out -W \
'/usr/local/bin/sem -j 3 "rsync -av --files-from=${FPART_PARTFILENAME}" /mnt/Rsync-source/ /mnt/Rsync-dest/'
而在fpsync
脚本中,相对使用了温文尔雅的方式替换了parallel
,来实现并发。
fpsync代码逻辑
fpsync
后台拥有了2个进程,一个是fpart
负责将待备份的目录进行解析,并将备份作业放入到作业队列中;另一个是job_queue_loop
,取出作业队列中备份作业来执行真正的备份。
fpsync
默认情况下,会在/tmp/fpsync
目录下放置4个子文件夹,来存储备份作业的信息。
$ pwd
/tmp/fpsync
$ ll
total 0
drwxr-xr-x. 3 root root 30 Dec 8 00:08 log # 主进程日志
drwxr-xr-x. 3 root root 30 Dec 8 00:08 parts # fpart解析目录生成的bag
drwxr-xr-x. 3 root root 30 Dec 8 00:08 queue # 存放备份任务的任务队列
drwxr-xr-x. 3 root root 30 Dec 8 00:08 work # 已经执行和正在执行备份任务的信息
几个主要函数
一、主函数,启动job_queue_loop
和fpart
。
# 1. 启动进程job_queue_loop,等待queue中的备份任务
job_queue_loop&
# When not resuming a previous job, start fpart
# 不是恢复先前的工作,则开始fpart
# 2. 启动进程fpart, 用来解析源目录卷,并生成待备份任务放置到queue中。
# 重点是,-W回调中,实际是将待备份rsync的命令重定向到queue中
if [ -z "${OPT_JOBNAME}" ]
then
echo_log "1" "===> Analyzing filesystem..."
# Start fpart from src_dir/ directory and produce jobs within
# ${JOBS_QUEUEDIR}/
#echo "FPART_LOGFILE: ${FPART_LOGFILE}"
#echo "OPT_FPMAXPARTFILES ${OPT_FPMAXPARTFILES}" ===> 2000
#echo "FPART_PARTSTMPL ${FPART_PARTSTMPL}" ===> /tmp/fpsync/parts/16ID/part
#echo "FPART_BIN ${FPART_BIN}" ===> fpart命令
# -0 使用选项-o时,文件名以空(\0)字符结尾
# -e 在目录中添加/
#echo "FPART_MODEOPTS ${FPART_MODEOPTS}" ===> -zz
# -W 将rsync的命令重定向到JOBS_QUEUEDIR/FPART_PARTNUMBER当中
cd "${OPT_SRCDIR}" && \
${SUDO} "${FPART_BIN}" \
$([ ${OPT_FPMAXPARTFILES} -gt 0 ] && echo -f "${OPT_FPMAXPARTFILES}") \
$({ { is_num "${OPT_FPMAXPARTSIZE}" && [ ${OPT_FPMAXPARTSIZE} -gt 0 ] ;} || is_size "${OPT_FPMAXPARTSIZE}" ;} && echo -s "${OPT_FPMAXPARTSIZE}") \
-o "${FPART_PARTSTMPL}" -0 -e ${OPT_FPART} ${FPART_MODEOPTS} -L \
-W "${FPART_POSTHOOK}" . 2>&1 | \
tee -a "${FPART_LOGFILE}"
fi
# 3. fpart进程接续,在queue队列中放置fp_done标志fpart结束
# Tell job_queue_loop that crawling has finished
job_queue_fp_done
# Wait for job_queue_loop to terminate
# Use an active wait to allow signal processing (^T)
# 4. 等待所有迁移任务的结束
echo_log "1" "===> Waiting for sync jobs to complete..."
while [ ! -f "${JOBS_QUEUEDIR}/sl_done" ]
do
sleep 0.2
done
# Display final status
# 展示最终的结束状态
[ ${OPT_VERBOSE} -ge 1 ] && siginfo_handler
二、迁移函数,job_queue_loop
# Main jobs' loop: pick up jobs within the queue directory and start them
# 循环执行job的主函数,查询出队列目录中的jobs然后启动
job_queue_loop () {
echo_log "2" "===> [QMGR] Starting queue manager"
# trap 用来捕捉信号
# Trap SIGINT
trap 'job_queue_loop_sigint_handler' 2
# Ignore SIGINFO from within loop, handled by the parent (master) process
trap '' 29
# WORK_NUM OPT_JOBS
# echo "WORK_NUM ${WORK_NUM}" ===> 0
# echo "OPT_JOBS ${OPT_JOBS}" ===> 3
local _NEXT=""
# 1. fp_done表示fpart进程结束, sl_stop表示rsync进程结束
# 补充:_NEXT按照时间倒序读取queue中的文件,所以读到fp_done时,代表所有的备份工作都已经执行完成了
while [ "${_NEXT}" != "fp_done" ] && [ "${_NEXT}" != "sl_stop" ]
do
local _PID=""
# 2. 当前执行备份作业没有超过上限时,启动一个备份作业
# OPT_JOBS限制执行rsync的个数
if [ ${WORK_NUM} -lt ${OPT_JOBS} ]
then
# 3. 获取下一个待要执行的任务, 并将备份任务移动到work中
_NEXT="$(job_queue_next)"
echo "NEXT ${_NEXT}"
if [ -n "${_NEXT}" ] && \
[ "${_NEXT}" != "fp_done" ] && \
[ "${_NEXT}" != "sl_stop" ]
then
# 本地执行
if [ -z "${OPT_WRKRS}" ]
then
echo_log "2" "=> [QMGR] Starting job ${JOBS_WORKDIR}/${_NEXT} (local)"
# 4. 启动备份作业
# 后台执行 jobs_workdir下的任务
/bin/sh "${JOBS_WORKDIR}/${_NEXT}" &
# 后台进程pid 任务名 local 加入到work list中
work_list_push "$!:${_NEXT}:local"
# 忽略,ssh模式
else
local _NEXT_HOST="$(work_list_pick_next_free_worker)"
work_list_trunc_next_free_worker
echo_log "2" "=> [QMGR] Starting job ${JOBS_WORKDIR}/${_NEXT} -> ${_NEXT_HOST}"
"${SSH_BIN}" "${_NEXT_HOST}" '/bin/sh -s' \
< "${JOBS_WORKDIR}/${_NEXT}" &
work_list_push "$!:${_NEXT}:${_NEXT_HOST}"
fi
fi
fi
work_list_refresh
sleep 0.2
done
if [ "${_NEXT}" = "fp_done" ]
then
echo_log "2" "<=== [QMGR] Done submitting jobs. Waiting for them to finish."
else
echo_log "2" "<=== [QMGR] Stopped. Waiting for jobs to finish."
fi
wait
echo_log "2" "<=== [QMGR] Queue processed"
# Set the 'sl_done' (sync done) flag to let the master process go
# 5. 用来标志rsync全部结束
job_queue_sl_done
}