需求背景:
领导要求小明备份数据库服务器里面的100个库(数据量在几十到几百G),需要以最快的时间完成(5小时内),并且不能影响服务器性能。
核心要点
命名管道
我们在shell脚本中多次使用管道符号’|
‘,这个叫做匿名管道,也就是说它并没有名字,而这里提到的管道叫做命名管道,功能和那个匿名管道基本上是一样的。命名管道,英文名First In First Out,简称FIFO。命名管道有下面特点:
- 在系统文件中,FIFO拥有名称,并且是以设备特殊文件的形式存在的;
- 任何进程都可以通过FIFO共享数据;
- 除非FIFO两端同事有读和写的进程,否则FIFO的数据流通将会阻塞;
- 匿名管道是由shell自动创建的,存在内核中,而FIFO则是由程序创建的(例如mkfifo命令),存在于文件系统中;
- 匿名管道是单向的字节流,而FIFO则是双向的字节流。
创建一个命名管道 mkfifo 管道名
向管道内写入数据 echo > 管道名
读取管道数据 cat 管道名
1
2
3
4
5
6
7# 这个使用虚拟窗口来演示命名管道的特点 命名管道中 echo为写入数据 cat为读取数据
$ screen
$ mkfifo 123.fifo
$ echo "1111" > 123.fifo # 此时被阻塞,运维我们只是在管道里写入内容,并没有其他进程读这个内容
Ctrl+a+d # 退出screen
$ cat 123.fifo # 此时可以看到1111内容,然后使用screen -r进入虚拟窗口发现刚才的echo那条命令已经结束了
也可以把命名管道和文件描述符绑定起来 exec fd<>管道名
1
2
3
4
5$ mkfifo test.fifo
$ exec 100<>test.fifo # 这样就可以把文件描述符fd100的读和写全部指定到test.fifo
$ ls -l /dev/fd/100 # 可以看到fd100已经指向导了/home/kun/test.fifo
lrwx------. 1 kun kun 64 3月 17 09:13 /dev/fd/100 -> /home/kun/test.fifo
read命令
在shell脚本中,read命令使用还是比较多的,典型的用法是和用户交互,如下1
2
3
4
5$ read -p "Please input a number:" n
Please input a number: 5
$ echo $n
5
如果不使用-p选项,也可以这样使用1
2
3
4
5$ read name # name为变量名,这样也是可以给那么变量赋值
kun
$ echo $name
kun
从文件描述符fd中读取输入数据赋值给变量 read -u fd 变量名
1
2
3$ screen
# 向文件描述符fd100中读取数据并赋值给a
$ read -u 100 a
注意,这里的fd100就是前面定义的test.fifo,如果fd10里面还没有任何的内容写入,那么该命令会卡着不动,因为fd100是一个命名管道文件,只有写入数据,read才会读到,否则就一直卡着,等待写入内容,当然,这个命名管道文件可以写入多行,先存储起来,然后等着read去读
1 | # >& fd 表示把数据写入fd中 |
wait命令
wait就是等待意思,即等待那些在没有完成的任务(主要是后台的任务),直到所有任务完成后,才会继续执行wait以后的命令。1
2$ sleep 5 &
$ wait # 此时会卡死不动,直到上面的后台命令执行完,才会有反应
结合命名管道和read实现多线程
命名管道有两个和明显的特点:
- 先进先出,比如上例中的我们给fd100写入两行内容,则第一次read第一行,第二次read第二行
- 有内容read则执行,没有便阻塞,上例中,read完两次后,如果再执行一次read,则他会一直卡,直到我们再次写入新的内容他才会read到
利用上面两个特点,可以实现shell的多线程,下面例子1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33#!/bin/bash
# 创建命名管道123.fifo文件
mkfifo 123.fifo
# 将命名管道123.fifo和文件描述符1000绑定,即fd1000的输入输出都是在123.fifo中
exec 1000<>123.fifo
# 连续向fd1000中写入两次空行
echo >& 1000
echo >& 1000
# 循环10次
for i in `seq 1 10`
do
# 每循环一次,读一次fd1000中的内容,即空行,只有读到空行了,才会执行{ }内的指令
# 每次循环都需要打印当前的时间,休眠1秒,然后再次向fd1000中写入空行,这样后续的read就会有内容了
# read指令不仅可以赋值,也可以跟一个函数,用{ }括起来,函数中是多条指令
read -u 1000
{
date +%T
echo $i
sleep 1
echo >& 1000
} & # 丢到后台去,这样10次很快就循环完,只不过这些任务是在后台跑着。由于我们一开始就向fd1000里写入了两个空行,所有read会一次性读到两行
done
# 等待所有后台任务执行完成
wait
# 删除fd1000
exec 1000>&-
# 删除命名管道
rm -rf 123.fifo
结果 每1秒输出2个数据1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
2011:59:01
2
11:59:01
1
11:59:02
3
11:59:02
4
11:59:03
5
11:59:03
6
11:59:04
7
11:59:04
8
11:59:05
9
11:59:05
10
代码内容
1 |
|