You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在存储设备的SSH会话中并行执行多条命令?

优化存储卷批量处理脚本的并行方案

针对4万+存储卷的串行处理效率问题,以下从并行执行、减少SSH开销、代码精简三个维度给出落地优化方案:


一、用GNU Parallel实现SSH并行执行

GNU Parallel是shell环境下轻量高效的并行任务工具,可直接将串行循环改为并行,同时通过并发数控制避免存储设备过载。

改造要点

  1. 将单卷处理逻辑封装为独立函数
  2. 读取CSV数据后用Parallel分发任务
  3. 调整并发数(建议10-20,根据设备负载测试确定)

优化后代码示例

#!/bin/bash
PATH="/bin:/usr/bin:/usr/local/bin:/usr/local/sbin:/usr/sbin"
export PATH

# 单卷处理函数
process_volume() {
    local proj="$1"
    local vols=$(echo "$proj" | awk -F: '{print $2}')
    local ldap_proj=$(echo "$proj" | awk -F: '{print $1}')
    local ssh_server=""

    # 用关联数组替代重复的服务器匹配逻辑
    declare -A server_map=(
        [fxn1]=dbcl101 [fxn2]=dbcl201 [fxn3]=dbcl301 [fxn4]=dbcl401
        [fxn5]=dbcl501 [fxn6]=dbcl601 [fxn7]=dbcl701 [fxn8]=dbcl804
        [fxn017]=dbcl031 [fxn015]=dbcl032 [fxn016]=dbcl034 [fxn014]=dbcl028
    )

    # 匹配对应存储服务器
    for prefix in "${!server_map[@]}"; do
        if [[ "$vols" =~ "$prefix" ]]; then
            ssh_server="${server_map[$prefix]}"
            break
        fi
    done

    [[ -z "$ssh_server" ]] && return

    # 获取卷类型
    volstyle=$(ssh "$ssh_server" "row 0; vol show $vols -fields volume-style-extended" | awk '/fxn/{print $3}')

    if [[ "$volstyle" == "flexvol" ]]; then
        echo -n "$ldap_proj $vols "
        ssh "$ssh_server" "row 0;set -unit MB; \
        vol show $vols -fields performance-tier-inactive-user-data; \
        vol show-footprint $vols -fields volume-blocks-footprint-bin0,volume-blocks-footprint-bin1,volume-blocks-footprint" \
        | awk '/fxn/{print $3, $4, $5}'
    elif [[ "$volstyle" == "flexgroup" ]]; then
        echo -n "$ldap_proj $vols "
        flexgroupvol="${vols}__*"
        ssh "$ssh_server" "row 0;set -unit MB; \
        vol show $vols -fields performance-tier-inactive-user-data; \
        vol show-footprint $flexgroupvol -fields volume-blocks-footprint-bin0,volume-blocks-footprint-bin1,volume-blocks-footprint" \
        | awk '/^fxn/{ s1+=$3; s2+=$4; s3+=$5; if (FNR == 8) { print $3 } } END { print s1, s2, s3 }'
    fi
}

# 保留原日志文件生成逻辑
today=$(date +%Y%m%d)
number=0
datafile="/home/archive_dashboard/storage_footprint-$today"
while [ -e "$datafile" ]; do
    printf -v datafile '%s-%02d' "/home/archive_dashboard/storage_footprint-$today" "$(( ++number ))"
done
printf 'Will use "%s" as filename\n' "$datafile"
touch "$datafile"

# 并行处理CSV数据,并发数设为15(可调整)
awk -F ',' 'BEGIN{getline; NR--} {gsub(/"/,""); printf $2":"$NF"\n"}' volumes.csv | parallel --jobs 15 process_volume {} > "$datafile.log1"

# 保留原日志格式化逻辑
sed -i -e 's/\r//g; s/-/0/g; s/MB//g' "$datafile.log1"
sed '/^[[:space:]]*$/d' "$datafile.log1"| paste -d " " - - > "$datafile.log2"
awk '{$1=$1}1' OFS="," "$datafile.log2" > "${datafile}"
sed -i '1s/^/Project_Name,Volume_Name,InactiveTier,PhysicalBlocks,SSDtier,GRIDtier\n/' "${datafile}"
rm -rf "$datafile.log2" "$datafile.log1"
exit

注意事项

  • 提前安装GNU Parallel(apt install parallel/yum install parallel)
  • 配置SSH免密登录到所有存储服务器,避免并行时弹出密码提示
  • 先小批量测试并发数,避免压垮存储设备

二、批量处理同服务器卷,减少SSH连接开销

原脚本每个卷建立一次SSH连接,4万次握手开销极大。可先按服务器分组,每组一次SSH连接处理所有卷,直接减少99%的连接次数。

改造要点

  1. 将CSV中的卷按服务器分组,生成每个服务器的卷列表
  2. 单SSH会话内批量执行所有卷的查询命令
  3. 远程批量处理,避免重复建立会话

核心分组逻辑示例

# 按服务器分组生成临时文件
awk -F ',' 'BEGIN{getline; NR--} {gsub(/"/,""); print $2":"$NF}' volumes.csv | while read proj; do
    vols=$(echo "$proj" | awk -F: '{print $2}')
    ldap_proj=$(echo "$proj" | awk -F: '{print $1}')
    ssh_server=""
    declare -A server_map=(
        [fxn1]=dbcl101 [fxn2]=dbcl201 [fxn3]=dbcl301 [fxn4]=dbcl401
        [fxn5]=dbcl501 [fxn6]=dbcl601 [fxn7]=dbcl701 [fxn8]=dbcl804
        [fxn017]=dbcl031 [fxn015]=dbcl032 [fxn016]=dbcl034 [fxn014]=dbcl028
    )
    for prefix in "${!server_map[@]}"; do
        if [[ "$vols" =~ "$prefix" ]]; then
            ssh_server="${server_map[$prefix]}"
            break
        fi
    done
    [[ -n "$ssh_server" ]] && echo "$ssh_server,$ldap_proj,$vols" >> /tmp/vols_by_server.csv
done

# 按服务器批量处理
sort -t, -k1,1 /tmp/vols_by_server.csv | awk -F ',' '{
    if ($1 != prev_server) {
        if (prev_server != "") print prev_server, vol_list, proj_list
        prev_server = $1; vol_list = $3; proj_list = $2
    } else {
        vol_list = vol_list " " $3; proj_list = proj_list " " $2
    }
} END {print prev_server, vol_list, proj_list}' | while read server vols projs; do
    # 一次SSH连接处理该服务器所有卷
    ssh "$server" "row 0; set -unit MB; \
    vol show -volume {$vols} -fields volume,volume-style-extended; \
    vol show -volume {$vols} -fields volume,performance-tier-inactive-user-data; \
    vol show-footprint -volume {$vols} -fields volume,volume-blocks-footprint-bin0,volume-blocks-footprint-bin1,volume-blocks-footprint" \
    | awk -v projs="$projs" '
        # 此处编写逻辑将批量返回数据映射到对应项目名,输出符合原格式的内容
        # 需根据存储设备的输出格式调整
    ' >> "$datafile.log1"
done

三、精简原脚本冗余代码

原脚本重复多次[[ $vols =~ fxnX ]]匹配逻辑,改用关联数组可大幅精简代码,减少出错概率:

declare -A server_map=(
    [fxn1]=dbcl101 [fxn2]=dbcl201 [fxn3]=dbcl301 [fxn4]=dbcl401
    [fxn5]=dbcl501 [fxn6]=dbcl601 [fxn7]=dbcl701 [fxn8]=dbcl804
    [fxn017]=dbcl031 [fxn015]=dbcl032 [fxn016]=dbcl034 [fxn014]=dbcl028
)

# 匹配服务器
for prefix in "${!server_map[@]}"; do
    if [[ "$vols" =~ "$prefix" ]]; then
        ssh_server="${server_map[$prefix]}"
        break
    fi
done

额外优化建议

  1. 超时控制:给SSH命令添加超时参数(ssh -o ConnectTimeout=10 -o ServerAliveInterval=5),避免任务卡住
  2. 错误日志:添加失败卷的记录逻辑,方便后续重试
  3. 预缓存卷类型:若卷类型不会频繁变化,可提前导出所有卷的类型到本地文件,避免每次查询
  4. 负载监控:并行执行时监控存储设备的CPU/内存负载,动态调整并发数

内容的提问来源于stack exchange,提问作者Karn Kumar

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.24 09:18:26