如何在Linux上并行运行多个命令

时间:2020-03-21 11:48:02  来源:igfitidea点击:

传统上,计算机一次只能做一件事情。
它通常不执行多任务处理。
好吧,在大多数情况下(单核计算机意味着具有一个CPU的计算机),计算机给我们一种错觉,那就是同时发生了许多事情。

我们可能在一台计算机上同时运行多个任务,但是该计算机将始终一次执行我们任务列表中的一个任务。
但是它将快速切换到下一个任务,然后切换到下一个任务,然后再切换到下一个任务(因此基本上可以说多个任务正在逐个进行)。

任务之间的切换如此之快,以至于我们很难注意到。
一台典型的计算机将在一秒钟之内完成几百次任务之间的切换。
我们现在可以想象为什么我们会错觉到同时执行多个任务。

这种切换通常称为上下文切换。
当有太多任务在排队等待CPU时,我们就说“机器处于负载状态”。
在本文中,我们将讨论可用于在Linux系统上并行执行多个进程的方法。

最好的示例是在一台机器上跨10台服务器执行命令。
如果我们一步一步走,将花费大量时间。
但是,如果我们有一种方法可以在所有服务器上并行运行命令,那么可以节省大量时间。
再次,从计算机/CPU的角度来看,它主要一次处理一个任务,但是会继续在任务之间切换,这发生得太快了,我们可以很好地处理多个任务同时进行的情况。

让我们深入研究Linux中可用的不同方法,以同时(在Parallel中)执行命令。
第一个是使用bash shell作业控制机制。
我们只需执行命令,然后让shell将其放在后台,然后继续执行下一个命令(当第一个命令已经在后台执行时),然后执行下一个,依此类推。

为了说明这一点,我在对象存储中只有很少的垃圾文件。
可以同时将所有这些文件下载到Linux机器上,以测试并查看并行工作的方式。

使用Bash Shell并行运行命令

我基本上需要执行3个wget命令并使它们并行运行。
最好的方法是将所有wget命令放在一个脚本中,然后执行该脚本。
这里唯一要注意的是将所有这些wget命令放在后台(shell后台)。
请参阅下面的简单脚本文件。

root@instance-10:/tmp# cat test_parallel.sh
#!/bin/bash
wget https://storage.googleapis.com/test-bucket-sarath/junkfile1 &
wget https://storage.googleapis.com/test-bucket-sarath/junkfile2 &
wget https://storage.googleapis.com/test-bucket-sarath/junkfile3 &

注意每个命令结尾处的&。
这会将命令置于后台,然后执行下一个命令(将其放在后台)并继续执行下一个命令,依此类推。

我们可以确认所有这些命令正在使用另一个Shell同时执行,并查看进程列表(在我们的示例中,它应该显示3个wget命令以及三个不同的进程)。

root@instance-10:~# ps aux|grep wget
root      4985 16.2  0.1  27732  5080 pts/0    D+   07:05   0:00 wget https://storage.googleapis.com/test-bucket-sarath/junkfile1
root      4986 15.8  0.1  27732  5164 pts/0    D+   07:05   0:00 wget https://storage.googleapis.com/test-bucket-sarath/junkfile2
root      4987 16.6  0.1  27732  5228 pts/0    D+   07:05   0:00 wget https://storage.googleapis.com/test-bucket-sarath/junkfile3
root      4994  0.0  0.0  10480  2140 pts/1    S+   07:05   0:00 grep --color=auto wget

从上面的输出中我们可以清楚地看到我们的三个wget命令正在并行运行。

如果需要分批或者分块执行多个进程,则可以使用称为“ wait”的shell内置命令。
见下文。

#!/bin/bash
wget https://storage.googleapis.com/test-bucket-sarath/junkfile1 &
wget https://storage.googleapis.com/test-bucket-sarath/junkfile2 &
wget https://storage.googleapis.com/test-bucket-sarath/junkfile3 &
wait
wget https://storage.googleapis.com/test-bucket-sarath/junkfile1 &
wget https://storage.googleapis.com/test-bucket-sarath/junkfile2 &
wget https://storage.googleapis.com/test-bucket-sarath/junkfile3 &
wget https://storage.googleapis.com/test-bucket-sarath/junkfile1 &
wget https://storage.googleapis.com/test-bucket-sarath/junkfile2 &
wget https://storage.googleapis.com/test-bucket-sarath/junkfile3 &
wait

前三个命令wget命令将并行执行。
“ wait”将使脚本等到这3个脚本完成。
完成后,脚本将同时运行接下来的6个命令,并等待其完成,依此类推。

我们可以修改脚本,使其更加通用,如下所示。

root@instance-10:/tmp# cat test_parallel.sh
#!/bin/bash
for task in "$@"; do {
  $task &
} done

现在,我们可以使用如下所示的脚本运行任意数量的命令。

./test_parallel.sh "cmd1 arg1 arg2 arg3" "cmd2 arg1" "cmd3 arg1 arg2"

如何使用Xargs同时运行多个进程?

我们可以用来并行运行进程的下一个方法是常规的xargs命令。
Xargs支持一个选项,用于指定要同时运行的进程数。
见下文。

seq 1 3 | xargs -I{} -n 1 -P 3 wget https://storage.googleapis.com/test-bucket-sarath/junkfile{}

seq命令将简单地在三行中给出1、2和3作为输出。
然后使用标准Linux管道将其作为输入传递给xargs。
我们在xargs中使用了选项-I来删除通常在命令末尾添加的空格字符。

如果没有-I {}和最后一个junkfile {},则xargs将使用文件名“ junkfile 1,junkfile 2等”(我们需要删除空格)而不是junkfile1,junkfile2等构造我们的命令。

我们可以使用另一个终端并像我们之前所做的那样,快速确认3个进程正在并行运行(就像我们通过-P 3一样)。
见下文。

root@instance-10:/tmp# ps aux|grep wget
root      2197  0.0  0.0   6376   772 pts/0    S+   04:42   0:00 xargs -I{} -n 1 -P 3 wget https://storage.googleapis.com/test-bucket-sarath/junkfile{}
root      2198 11.0  0.1  27732  5160 pts/0    D+   04:42   0:01 wget https://storage.googleapis.com/test-bucket-sarath/junkfile1
root      2199  7.6  0.1  27732  5232 pts/0    D+   04:42   0:01 wget https://storage.googleapis.com/test-bucket-sarath/junkfile2
root      2200  6.4  0.1  27732  5040 pts/0    D+   04:42   0:00 wget https://storage.googleapis.com/test-bucket-sarath/junkfile3
root      2209  0.0  0.0  10480  2180 pts/1    S+   04:42   0:00 grep --color=auto wget

wget只是我们在此处用于本教程的一个示例。
将wget与这三个垃圾文件一起使用的原因之一是使该进程保持活动状态几分钟,以便我们可以确认并行执行(由于文件很大,因此这些过程将需要几分钟的时间来完成)。
我们可以将wget替换为用例中适用的任何内容。

如何使用GNU Parallel同时运行命令?

除此之外,还有GNU的工具,该工具旨在并行执行作业。
它称为GNU并行。
可以通过以下命令安装(取决于Linux发行版)。

对于基于RedHat的系统:

yum install parallel

对于基于Debian的系统(Ubuntu):

apt-get install parallel

我们可以将GNU Parallel用于以下一些用例。
这些用例很好地涵盖了常规的基于shell的系统管理员活动。

  • 可以作为输入传递给并行命令的文件的列表,以对所有文件并行执行一些操作
  • 我们可以提供一个IP地址/主机名列表,在该列表上我们需要并行启动命令
  • 链接/URL列表(类似于我们在上面的xargs和shell中看到的wget示例)

GNU Parallel的设计考虑了xargs,因此大多数命令行选项和参数可能与xargs命令匹配。
让我们首先执行使用GNU parallel看到的wget示例。

seq 1 3 | parallel -j 5 -I{} wget https://storage.googleapis.com/test-bucket-sarath/junkfile{}

如果看到以上命令,则并行使用-j选项(与xargs非常相似,以指定要并行运行的进程数),以及-I选项(以去除默认的空格字符)。
毫无疑问,它将同时运行我们所有的3个wget命令。

我们是否要压缩当前目录中的所有文件(并行和同时)?
我们可以使用并行非常轻松地做到这一点。
下面显示的是一个实现此目的的示例。

ls | parallel -j 10 gzip

在上面的示例中,最多将同时进行10次压缩。
同样,要同时解压缩/压缩所有文件,请运行以下命令。

ls | parallel -j 10 gunzip

我们也可以使用以下方法(并行)gzip当前目录中的所有文件。
在下面的示例中,我们将作业数限制为10个,这些作业可以并行运行。

parallel -j 10 gzip ::: *

Parallel可以选择将文件作为参数传递,以便可以对文件中的条目运行命令。
例如,我们可以下载一个带有URL列表的文件。

root@instance-10:/tmp# cat list-of-urls.txt
https://storage.googleapis.com/test-bucket-sarath/junkfile1
https://storage.googleapis.com/test-bucket-sarath/junkfile2
https://storage.googleapis.com/test-bucket-sarath/junkfile3

parallel -j 10 -a list-of-urls.txt wget

上面的代码应并行下载文件“ list-of-urls.txt”中列出的URL。

并行还可以执行测试文件中指定的一系列命令。
请参阅下面的示例。
首先创建一个测试文件,其中包含很少的睡眠命令。

cat job.txt
sleep 100; echo "first"
sleep 100; echo "second"
sleep 100; echo "third"

现在让我们要求并行执行该文件中的所有命令。
可以如下所示进行。

parallel -j 10 :::: job.txt

如何在GNU Parallel中管理输出?

输出并行执行多个命令时的常见问题。
不同命令的输出不应混淆。
如果我们使用本文中介绍的第一种方法(即:使用shell作业控制机制),则实际上无法保证输出的顺序。
例如,让我们尝试使用&的shell方法对多个主机执行ping命令。

root@testserver:~# ping -c 3 10.12.2.40 & ping -c 3 172.17.0.1 & ping -c 3 127.0.0.1 &
PING 10.12.2.40 (10.12.2.40) 56(84) bytes of data.
64 bytes from 10.12.2.40: icmp_seq=1 ttl=64 time=0.027 ms
PING 127.0.0.1 (127.0.0.1) 56(84) bytes of data.
64 bytes from 127.0.0.1: icmp_seq=1 ttl=64 time=0.018 ms
PING 172.17.0.1 (172.17.0.1) 56(84) bytes of data.
64 bytes from 172.17.0.1: icmp_seq=1 ttl=64 time=0.020 ms
64 bytes from 10.12.2.40: icmp_seq=2 ttl=64 time=0.029 ms

我们可以清楚地看到输出完全混乱(这三个ping的输出混合在一起)。
现在让我们尝试并行处理,并防止输出混乱。
见下文。

root@testserver:~# parallel -j 4 ping -c 3 ::: 10.12.2.40 172.17.0.1 127.0.0.1
PING 10.12.2.40 (10.12.2.40) 56(84) bytes of data.
64 bytes from 10.12.2.40: icmp_seq=1 ttl=64 time=0.026 ms
64 bytes from 10.12.2.40: icmp_seq=2 ttl=64 time=0.041 ms
64 bytes from 10.12.2.40: icmp_seq=3 ttl=64 time=0.025 ms
--- 10.12.2.40 ping statistics --
3 packets transmitted, 3 received, 0% packet loss, time 2000ms
rtt min/avg/max/mdev = 0.025/0.030/0.041/0.009 ms
PING 172.17.0.1 (172.17.0.1) 56(84) bytes of data.
64 bytes from 172.17.0.1: icmp_seq=1 ttl=64 time=0.021 ms
64 bytes from 172.17.0.1: icmp_seq=2 ttl=64 time=0.018 ms
64 bytes from 172.17.0.1: icmp_seq=3 ttl=64 time=0.019 ms
--- 172.17.0.1 ping statistics --
3 packets transmitted, 3 received, 0% packet loss, time 2000ms
rtt min/avg/max/mdev = 0.018/0.019/0.021/0.003 ms
PING 127.0.0.1 (127.0.0.1) 56(84) bytes of data.
64 bytes from 127.0.0.1: icmp_seq=1 ttl=64 time=0.016 ms
64 bytes from 127.0.0.1: icmp_seq=2 ttl=64 time=0.019 ms
64 bytes from 127.0.0.1: icmp_seq=3 ttl=64 time=0.023 ms
--- 127.0.0.1 ping statistics --
3 packets transmitted, 3 received, 0% packet loss, time 1998ms
rtt min/avg/max/mdev = 0.016/0.019/0.023/0.004 ms

基本上,只有在完成时,并行才会显示一个进程的完整输出。
输出的顺序对我们很重要吗?
如果希望输出也与输入顺序相同,则可以并行使用-k选项,如下所示。

parallel -k -j 4 ping -c 3 ::: 10.12.2.23 172.17.0.13 127.0.0.1

请记住这一事实,GNU Parallel会找出系统中可用的CPU内核数量,并且每个内核只能运行一个作业。
在示例中,我们使用了-j选项来覆盖此默认行为。
如果我们有100条命令要使用GNU并行执行,则作业将以较小的块执行。
块大小再次由-j选项确定。

如果要快速终止GNU并行,可以运行以下命令。
收到此信号后,GNU parallel将完成当前正在执行的块并退出。

killall -TERM parallel

在远程计算机列表上并行执行命令

老实说,当涉及到同时在服务器列表上执行远程命令时,我还没有发现GNU Parallel具有用户友好性。
为此,有诸如clustershell和pdsh之类的工具(同意GNU parallel具有诸如sshlogin和sshloginfile之类的参数),但是我没有找到直接的方法。
在我的测试案例中,其中一些命令不够稳定,因此建议不要针对多个服务器并行执行。

我将从clustershell开始,然后是pdsh。

使用适用于平台的命令可以轻松安装Clustershell。

基于RedHat的系统:

yum install clustershell

基于Ubuntu的系统:

apt-get install clustershell

顾名思义,它用于管理服务器集群。
基本上从集群中的所有计算机执行某些操作/获取信息。
与其他Linux实用程序类似,clustershell的配置文件位于/etc/clustershell/clush.conf中。
我在该文件中包含以下内容。

#cat /etc/clustershell/clush.conf
[Main]
fanout: 64
connect_timeout: 15
command_timeout: 0
color: auto
fd_max: 16384
history_size: 100
node_count: yes
verbosity: 1
ssh_user: ubuntu
ssh_path: /usr/bin/ssh
ssh_options: -oStrictHostKeyChecking=no

我们可以使用如下所示的clustershell在逗号分隔的服务器列表中执行我们感兴趣的命令。

#clush -l ubuntu -w 10.12.2.40,10.12.2.114 uname -r
10.12.2.114: 4.4.0-1041-aws
10.12.2.40: 3.13.0-48-generic

请注意以下事实:clush使用/home/$user/.ssh/id_rsa文件中的SSH私钥。
例如,如果以“ ubuntu”用户身份执行此命令,则使用的私钥将为/home/ubuntu/.ssh/id_rsa。
预期在使用clustershell执行命令的所有服务器上都将存在相应的公钥。

相关:SSH工作说明

如果我们使用node1.example.com或者类似格式的服务器,则可以使用快捷方式和正则表达式。
见下文。

#clush -l ubuntu -w node[1-2].example.com uname -r
node2.example.com: 4.4.0-1041-aws
node1.example.com: 3.13.0-48-generic

我们可以使用clsutershell使用--copy选项将文件并行复制到多个服务器。

clush -w node[1-2].example.com --copy /home/ubuntu/testfile

上面的命令会将文件/home/ubuntu/testfile复制到所有服务器上的相同位置

我们还可以使用文件/etc/clustershell/groups创建服务器分组(如果文件不存在,则创建该文件)。
下面显示了一个示例组文件。

# cat /etc/clustershell/groups
web: node[1,2].example.com
db: node[3-4].example.com

现在,我们可以通过调用组名(在本例中为web和db)来针对这些组执行命令。

#clush -l ubuntu -w @web uname -r
node2.example.com: 4.4.0-1041-aws
node1.example.com: 3.13.0-48-generic

# clush -l ubuntu -w @db uname -r
node4.example.com: 4.4.0-1041-aws
node3.example.com: 4.4.0-1041-aws

Clustershell支持一种交互模式,用于在多台计算机上执行命令。
这很有趣。
我们只需将-b选项传递给针对我们组之一的clush命令,就可以在这些组上交互式地触发命令。
下面是一个示例。

# clush -l ubuntu -w @web -b
Enter 'quit' to leave this interactive mode
Working with nodes: node[1-2].example.com
clush> uptime
--------------
node1.example.com
--------------
 00:09:09 up 709 days, 18:19,  1 user,  load average: 0.11, 0.05, 0.05
--------------
node2.example.com
--------------
 00:09:09 up 47 days, 21:18,  0 users,  load average: 0.00, 0.00, 0.00
clush> quit

与clustershell相似的是另一个名为pdsh的实用程序。
pdsh的想法类似于实用程序rsh,可用于在一个远程主机上执行某些操作。
但是,可以使用pdsh在多个主机上并行执行命令。

与clustershell相似,安装非常简单(一个apt-get或者yum命令取决于分发)。

基于RedHat的系统:

yum install pdsh

基于Debian/Ubuntu的系统:

apt-get install pdsh

首先要做的是告诉pdsh我们想使用SSH进行远程连接。
可以使用环境变量来完成。

export PDSH_RCMD_TYPE=ssh

要使其永久,也可以将其添加到用户的.bashrc文件中。
执行以下命令后,我们可以注销并登录以确认已设置环境变量并且该变量对用户可用。

echo "export PDSH_RCMD_TYPE=ssh" >> ~/.bashrc

让我们使用pdsh在node [1-2] .example.com上启动一个命令。
见下文。

# pdsh -l ubuntu -w node[1-2].example.com uname -r
node2: 4.4.0-1041-aws
node1: 3.13.0-48-generic

在不指定RCMD环境变量的情况下,我们也可以运行如下所示的命令。

root@jenkins:~# pdsh -w ssh:ubuntu@node[1-2].example.com uptime
node2:  00:32:34 up 47 days, 21:42,  0 users,  load average: 0.00, 0.00, 0.00
node1:  00:32:34 up 709 days, 18:43,  1 user,  load average: 0.08, 0.07, 0.05

我们可以与pdsh一起使用的另一个有趣的环境变量是WCOLL。
这将指定一个包含服务器列表的文件。

cat nodelist
node1.example.com
node2.example.com

export WCOLL=/root/nodelist

与前面的示例类似,我们可以将此示例添加到.bashrc中以使其永久。

# pdsh -l ubuntu uptime
node2:  00:36:46 up 47 days, 21:46,  0 users,  load average: 0.00, 0.00, 0.00
node1:  00:36:47 up 709 days, 18:47,  1 user,  load average: 0.09, 0.06, 0.05