并发200_Shell脚本实现并发和超时控制

点击蓝字 关注我们

01 背景

从事Linux主机建设和运维的同事们在工作中应该经常会遇到批量修改配置信息或部署应用环境的需求,需要根据需求依次登录目标主机执行一些命令或脚本,使用shell脚本的循环语句是实现这一需求最直观方式。但是普通的for或do while循环都是串行执行的,脚本耗时每个循环耗时*循环次数,在较大规模实施或者目标语句耗时较长的情况下,串行方式的循环脚本执行时间也不容忽视。

要减少执行串行循环的耗时,自然要考虑如何用并行方式解决。在shell之外有一些现成的管理部署工具如parallel、ansible、puppet、saltstack都能解决并发执行多任务的问题,但生产系统一般不允许随意安装新软件,因而我们这里只讨论不借助工具,只使用shell脚本如何实现并发执行多任务。

并发和超时是我们在日常运维中经常碰到的问题。由于线上机器较多,数据量较大,一般我们在开发运维脚本的时候,都会加入并发机制以减少脚本的运行时间。通常用shell做并发无非是把一个进程扔到后台去执行。但这样做就会面临一个问题:如何有效的控制并发度,以防并发太高影响机器的负载。

02 并发管理

要实现可控的并发,就需要进程间通信来保证进程能够知晓正在运行的进程数量。众所周知,Linux的进程间通信的方法大概分为六种:

1、信号( Singal )

2、管道 ( Pipe ) 及命名管道(FIFO)

3、信号量 ( Semaphore )

4、共享内存 ( SharedMessage)

5、消息队列 ( MessageQueue )

6、套接字 ( Socket )

本方案采用了管道进行进程间的通信,因此只对管道方法进行介绍。管道其实可以分为无名管道和命名管道。

无名管道(Pipe)使用相对简单,但是只能够连接相关管道,由前一个进程负责管道的创建,后一个进程负责管道的回收,它在处理多个进程通信时就力不从心了;

命名管道则可以连接不相关的进程,它在文件系统中是一个特殊的设备文件,可以脱离其他进程而独立存在。顾名思义,命名管道(FIFO)是一个先入先出的队列,进程在写入时将数据加入队列尾部,读出时读取队列头部。命名管道是阻塞的,当没有数据可读时系统会将读进程挂起直到有数据写入管道,同理,当管道满时写进程也将一直被阻塞直到管道中的数据被取走。

命名管道的这一特性使我们很容易的就可以用它写出一个基于生产者消费者模型的程序。我们应用的基本思路就是首先初始化一个命名管道,假定我们的并发度为 N,则在进程开始时先向命名管道中写入 N 个数据,后续我们的后台进程在运行前都要先从命名管道中读取一个数据,然后进入后台执行,在执行结束后再向命名管道中写入一个数据。若命名管道中数据为空,则表示已经有 N 个进程在后台执行。此时再有进程来读取命名管道时便会被阻塞,无法继续执行。这就达到了并发度控制的目的。

整体思路如下图所示:

25e46949ee955a10dbcf6b8cab937dbd.png

程序代码及注释如下所示:

#!/bin/bash#设置并发度Thread=100#模拟机器的数量MachineNum=200 ## 要给200台机器下发相同的命令fun(){    sleep 3}Concurrency(){    #创建一个管道文件,用来进行进程间的交互    tmp_fifofile="/tmp/$$.fifo"    mkfifo ${tmp_fifofile}    #把文件描述符 6 指向创建的管道文件    exec 6<>${tmp_fifofile}    #删除管道文件    #绑定后,该文件就可以删除了    rm ${tmp_fifofile}    #向管道文件中输入 Thread个空格,即生产资源    for ((i=0;i<${Thread};i++));do echo; done >&6 #对所有机器执行循环    for ((j=0;j<${MachineNum};j++)); do        #从共享管道中读出一个空格,即消费资源,管道为空时后续程序会挂起等待        read -u6 #read -u3 i 的意思是从 3 号 fd (file descriptor,文件描述符)中读一行数据到 i 变量中        #把程序放入后台执行        { fun && {        #根据返回结果判断程序执行情况          echo "${j} success!"          } || {          echo "${j} failed!"          }          #程序运行结束后向管道输入一个空格,即产生一个资源供后续进程消费          echo >&6        } &    done    #等待所有后台程序退出    wait    #删除文件描述符    exec 6>&- #n&- 表示将 n 号输出关闭}Concurrency

上面介绍了并发度的控制机制,引入并发可以显著减少程序的运行时间。但是会引入几个新的问题:

1)如何跟踪后台进程的运行情况;

2)如何防止后台程序 Hang 死。

对于程序的运行情况我们可以通过打印日志的方法来进行跟踪,但是当程序 Hang 死该如何解决呢。尽管我们的程序是并发的,由于我们加入了 wait 命令,一个后台进程 Hang 死足以导致我们的任务并发度控制代码调度超时,影响下游其他服务。基于此问题这里给出了一个超时控制的方法。通过该脚本我们可以在命令执行前设置超时时间,超过该时间仍未结束的进程我们直接 kill 掉,当然,具体的超时策略也可以根据应用的不同进行更改。

03 超时控制

程序的思路是把一个工作进程放在后台执行,然后另起一个监控进程,监控进程的主要 工作就是 sleep 一定时间后杀掉工作进程。首先最简单的方法就是在脚本前加一条命令 sleep $timeout && kill $$ &,然后后面写脚本的具体命令就好了。这样后面的命令执行超时的话就会被杀掉,但是这样会有一定风险,那就是会产生误杀和漏杀。如果程序提前执行完并未超时,那$$取到的进程号可能已经不存在,如果碰巧另一个进程使用了$$这个进程号的话那就真心悲剧了;另外一个风险就是脚本中放到后台运行的子进程是无法被杀掉的,而是会变成孤儿进程。

基于上面的分析,我们给出了一个解决方案。为了防止误杀程序会在 kill 之前进行检查, 只有当被杀的进程号存在并且被杀的进程是自身的子进程的话才会执行 kill 操作。若进程正常执行结束,要对监控进程进行回收,以保证主程序能够正常退出。至于漏杀,只要每次执 行后台命令时都加入超时控制就可以进行解决。

程序代码如下所示:

#!/bin/bash#控制命令执行的超时时间WAITING_TIME=2TimeOut() {#取到要执行的命令  Command=$*  #命令放到后台执行  ${Command} &  #取到后台执行命令的进程号  Commandpid=$!  #起一个监控进程,sleep WAITING_TIME 后 kill 主命令进程  {      sleep ${WAITING_TIME} ;      if ps -p ${Commandpid} >/dev/null 2>&1 ; then          #取后台进程的父进程的 PID          Commandppid=$(ps -p ${Commandpid} -o ppid= 2>/dev/null)          #如果后台进程的父进程的 PID 与本进程的 PID 相同,kill 之,          #做这次判断是为了防止误杀,这样只会杀掉本 shell 的子进程,对其他程序没有影响          if [ X${Commandppid//\ /} = X$$ ];then              kill -9 ${Commandpid};              echo "kill command"          fi       fi    } &    #取到监控进程的命令号    MonitorPid=$! #等待主命令运行结束(可能是正常执行完,也可能超时被 kill)    wait ${Commandpid}    #检查监控进程是否存在    if ps -p ${MonitorPid} >/dev/null 2>&1 ;then        #若存在说明主进程已经正常执行结束,需要 kill 监控进程 echo ${Command} finish!        MonitorPPid=$(ps -p ${MonitorPid} -o ppid= 2>/dev/null)        if [ X${MonitorPPid//\ /} = X$$ ];then            kill -9 ${MonitorPid}            echo "kill monitor"        fi    else        echo ${Command} timeout!    fi}TimeOut sleep 5

参考

https://blog.csdn.net/qq_32642039/article/details/78624210

https://www.sohu.com/a/240639277_700886

https://blog.csdn.net/bigdatahappy/article/details/11535113?utm_source=blogxgwz6

扫码关注我们

114a512058f10eb25c5244e51d41b828.png

微信号|DBA成长之路

分享生活和工作的点滴

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值