使用 bash 进行服务闲置检查

重启服务之前往往需要检查一下是否还有未处理完的请求。此时可以使用 ss 命令查看端口是否还有 TCP 连接。例如:

1
2
3
pi@raspberrypi:~ $ ss -nt state established src 192.168.1.26:8888
Recv-Q Send-Q Local Address:Port Peer Address:Port
0 0 192.168.1.26:8888 192.168.1.25:50390

我们可以统计这条命令输出的行数,若小于 2,则说明服务已经处于闲置状态。脚本如下:

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
34
35
36
37
38
39
40
#!/bin/bash

# 获取本机 IP
host=$(ifconfig -a | grep inet | grep -v 127.0.0.1 | grep -v inet6 | awk '{print $2}' | tr -d "addr:")
# 超时时间
timeout=3600

check(){
port="$1"
begin=$(date +'%s')
while true
do
current=$(date +'%s')
duration=$(($current-$begin))
if [[ $duration -gt $timeout ]]; then
# 超时退出
result="TIMEOUT"
code=1
break
count=$(ss -nt state established src $host:$port | wc -l)
if [[ $count -lt 2 ]]; then
# 闲置退出
result="IDLE"
code=0
break
else
sleep 2
fi
done
echo "the result is $result"
return $code
}

if [[ $# -ne 1 ]]; then
echo "Usage: $0 [port]"
exit 1
else
check "$1"
exit $?
fi

如果 ss 命令发生错误怎么办?为了进一步完善脚本,我们需要加入异常处理机制。bash 中没有 try...catch.. 之类的语法,那么该如何捕获异常呢?下面介绍两个特殊的写法:

  • command1 || command2:当且仅当 command1 发生错误时执行 command2
  • command1 && command2:当且仅当 command1 没有错误时执行 command2

利用上面这两个特性,我们引入异常处理,把脚本改写成:

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
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
#!/bin/bash

set -o pipefail

# 获取本机 IP
host=$(ifconfig -a | grep inet | grep -v 127.0.0.1 | grep -v inet6 | awk '{print $2}' | tr -d "addr:")
# 超时时间
timeout=3600

check(){
port="$1"
begin=$(date +'%s')
while true
do
current=$(date +'%s')
duration=$(($current-$begin))
if [[ $duration -gt $timeout ]]; then
# 超时退出
result="TIMEOUT"
code=1
break
{
count=$(ss -nt state established src $host:$port | wc -l) &&
if [[ $count -lt 2 ]]; then
# 闲置退出
result="IDLE"
code=0
break
else
sleep 2
fi
}||{
# 异常退出
result="ERROR"
code=1
break
}
done
echo "the result is $result"
return $code
}

if [[ $# -ne 1 ]]; then
echo "Usage: $0 [port]"
exit 1
else
check "$1"
exit $?
fi

ss -nt state established src $host:$port | wc -l 利用了管道,而默认情况下,前面的 ss 命令出错并不会影响后面的 wc 命令的执行。为了捕获管道中的错误,我们在脚本开头添加了 set -o pipefail

现在我们的脚本可以检测一个服务实例是否闲置。如果我们需要同时检查多个实例该怎么办?如果可以并行地执行我们的 check 函数就好了。我们知道,在一条命令后面加上 &,就可以将它放到子 shell 中执行,达到多线程的效果。进一步地,我们可以用 wait 命令实现多线程同步。基于此,我们给出可以同时检查多个实例的 check_all 函数:

check_all(){
  ports="$#"
  anyfailed=0
  for port in $ports
  do
    check $port &
  done
  for pid in $(jobs -p)
  do
    wait $pid
    if [[ $? -ne 0 ]]; then
      anyfailed=1
    fi
  done
  return $anyfailed
}