shell脚本实现多进程并发


0,首先来看一下普通脚本执行的情况

1
2
3
4
5
6
7
8
9
10
11
#!/usr/bin/env bash

for i in {1..255} #定义ip的范围
do
ip=118.25.100.$i #定义ip的网段
ping -c1 -W1 $ip &> /dev/null #ping一次,超时时间1秒
if [ $? -eq 0 ] #如果ping通了就输出下面内容
then
echo "$ip is alive..."
fi
done

这边执行结果已经出来了.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
[root@test-000 shell]# time bash normal.sh 
118.25.100.3 is alive...
118.25.100.5 is alive...
118.25.100.7 is alive...
118.25.100.8 is alive...
118.25.100.9 is alive...
........................
........................
........................
........................
118.25.100.246 is alive...
118.25.100.247 is alive...
118.25.100.249 is alive...
118.25.100.250 is alive...
118.25.100.252 is alive...
118.25.100.254 is alive...

real 2m11.793s
user 0m0.416s
sys 0m1.058s

从本次结果可以看出,此脚本执行时长为2分12秒,按照顺序一个一个执行的.

1,下面我们来对脚本进行一次简单的改良,让它实现多并发执行!

1
2
3
4
5
6
7
8
9
10
11
12
13
14
#!/usr/bin/env bash
for i in {1..255}
do
{
ip=118.25.100.$i
ping -c1 -W1 $ip &> /dev/null
if [ $? -eq 0 ]
then
echo "$ip is alive..."
fi
}&
done
wait #该命令是等待前面所有后台程序执行完毕
echo "all of finished..."

可以看到我只是在循环块前后加一个大括号并放在后台执行,再来看一下该脚本的执行情况!

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
[root@test-000 shell]# time bash normal.sh 
118.25.100.50 is alive...
118.25.100.17 is alive...
118.25.100.51 is alive...
118.25.100.42 is alive...
118.25.100.20 is alive...
118.25.100.47 is alive...
.........................
.........................
.........................
.........................
.........................
118.25.100.225 is alive...
118.25.100.219 is alive...
118.25.100.254 is alive...
118.25.100.229 is alive...
118.25.100.249 is alive...
118.25.100.246 is alive...
118.25.100.252 is alive...
all of finished...

real 0m1.901s
user 0m0.181s
sys 0m0.825s

从本次结果可以看出,此脚本执行时长为2秒不到,是乱序执行的,谁返回的结果快就把谁放在前面,经过简单的修改,该脚本的效率提升了60多倍!

2,那么问题来了,如果并发的数量过于庞大,系统是否能处理的过来呢?再来写个脚本试一下…

1
2
3
4
5
6
7
8
9
10
#!/usr/bin/env bash

for i in {1..65535} #并发数量为65535
do
{
tcping 118.25.100.250 $i |grep open
}&
done
wait
echo "all of finished..."

运行这个脚本看一下

1
2
3
4
5
6
7
8
9
10
[root@test-000 shell]# time bash normal.sh 
118.25.100.250 port 88 open.
118.25.100.250 port 25 open.
118.25.100.250 port 110 open.
118.25.100.250 port 3389 open.
118.25.100.250 port 10080 open.
#在这里我强行终止了程序,因为实在是太卡了!
real 11m57.992s
user 2m15.443s
sys 7m11.092s

执行脚本之前的内存与负载占用情况
这台主机的CPU是单核的
Snipaste_2019-11-26_13-16-38.png
Snipaste_2019-11-26_12-54-30.png
Snipaste_2019-11-26_13-14-15.png
执行脚本中的内存与负载占用情况
Snipaste_2019-11-26_13-15-18.png
Snipaste_2019-11-26_13-15-28.png
可以看到内存几乎被该脚本进程占满,而负载更是达到了恐怖的330+,要知道这台只是单核CPU啊,超出了300多倍的负载,如果这台主机还有跑其他业务的话肯定500了!

3,控制shell脚本并发的数量,既可以提高执行效率,又不会影响到线上业务.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
#!/usr/bin/env bash
#感兴趣的请自行百度"文件描述符"和"管道文件",这里不做解释
fifo_file=/tmp/$$_fifofile #定义管道文件路径
mkfifo $fifo_file #创建管道文件
exec 54<> $fifo_file #打开这个管道文件
rm -rf $fifo_file #删除这个管道文件

for i in {1..10} #并发的数量10个,"seq 1 10"等同于"{1..10}",前者可以使用变量赋值
do
echo >&54 #向管道文件里丢数据
done

for i in {1..65535} #定义任务数量
do
read -u 54 #从管道文件里读取文件,读取到内容就向下走,读取不到就停留等待
{
tcping -t1 118.25.100.250 $i
}&
echo >&54 #由于管道文件的数据拿一个少一个,所以我们要把数据再还给管道文件
done
wait #等待所有后台任务执行完毕
exec 54<&- #释放之前被删除的管道文件
echo "all of finished..."

执行这个脚本看一下结果

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
[root@test-000 shell]# bash normal.sh 
118.25.100.250 port 25 open.
118.25.100.250 port 88 open.
118.25.100.250 port 110 open.
118.25.100.250 port 41 user timeout.
118.25.100.250 port 32 user timeout.
118.25.100.250 port 38 user timeout.
118.25.100.250 port 44 user timeout.
118.25.100.250 port 45 user timeout.
118.25.100.250 port 35 user timeout.
....................................
....................................
....................................
....................................
....................................

Snipaste_2019-11-26_13-50-28.png
可以看到无论是内存占用或者负载情况都要比之前好很多!如果负载还是过高可以通过调整并发数量来控制.可以说很灵活了!以上是固定写法,只需要修改执行循环体内容,任务数量和并发数量即可.