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

基于pexpect单SSH连接实现多子服务器并行ping测试的问题求助

问题根源与解决方案

原脚本问题分析

你的脚本之所以串行执行还出现阻塞,核心原因是pexpect的spawn对象不是线程安全的:多个线程同时操作同一个child实例,会导致命令发送顺序混乱、输出解析冲突。比如线程A刚发送ping vm1,线程B就发送ping vm2,两个命令的输出混在一起,child.expect('.*>')根本无法正确匹配提示符,最终导致阻塞;同时线程会互相等待操作child,实际变成串行执行。

单SSH连接下的并行ping方案

要在单SSH连接下实现并行ping,不需要用多线程抢同一个连接,而是利用shell的后台执行特性,让所有ping命令在远程shell后台同时运行,最后统一收集结果。

优化后脚本示例

import pexpect, time

cmd_list = ['vm1', 'vm2', 'vm3']
# 生成后台ping命令,将结果重定向到临时文件,同时记录PID
ping_cmds = []
temp_files = []
for idx, vm in enumerate(cmd_list):
    temp_file = f"/tmp/ping_result_{idx}.txt"
    temp_files.append(temp_file)
    # 后台执行ping,把输出和错误都写入临时文件,同时打印PID方便跟踪
    ping_cmd = f"ping -c10 {vm} > {temp_file} 2>&1 & echo $!"
    ping_cmds.append(ping_cmd)

try:
    handle = open('results.ping.txt', 'wb')
    child = pexpect.spawn('ssh -o StrictHostKeyChecking=no admin@server1', logfile=handle)
    child.expect('Password:')
    child.sendline('xxxxxxxx')
    child.expect('.*>')

    print(f'Start : {time.ctime()}')

    # 1. 发送所有后台ping命令
    pids = []
    for cmd in ping_cmds:
        child.sendline(cmd)
        # 匹配并记录后台进程PID
        child.expect(r'\d+')
        pid = child.match.group().decode()
        pids.append(pid)
        child.expect('.*>')

    # 2. 等待所有后台ping进程完成
    wait_cmd = f"wait {' '.join(pids)}"
    child.sendline(wait_cmd)
    child.expect('.*>')

    # 3. 收集所有临时文件的结果
    print("Collecting results...")
    for idx, temp_file in enumerate(temp_files):
        child.sendline(f"cat {temp_file}")
        # 匹配从cat输出到提示符的内容
        child.expect(f'(?s)(.*){child.prompt.decode()}')
        result = child.match.group(1).decode()
        print(f"=== Ping result for {cmd_list[idx]} ===")
        print(result)
        # 将结果写入本地文件
        with open(f'ping_{cmd_list[idx]}.txt', 'w') as f:
            f.write(result)
        # 删除远程临时文件
        child.sendline(f"rm {temp_file}")
        child.expect('.*>')

    print(f'End : {time.ctime()}')
    child.sendline('exit')
except Exception as e:
    print(f'Exception: {str(e)}')
    if 'child' in locals():
        child.sendline('exit')

方案说明

  • 所有ping命令通过&放到远程shell后台执行,真正实现并行,100台虚拟机的ping耗时会接近单台ping的时间(约10秒,因为-c10)。
  • 用临时文件保存每个ping的结果,避免输出混在一起;记录后台进程PID,确保所有ping完成后再收集结果。
  • 不需要多线程操作同一个pexpect实例,彻底避免了线程安全问题和阻塞。

备选方案:多独立SSH连接(适用于允许更多连接的场景)

如果NETAPP集群允许同时建立多个SSH连接,可以用线程池创建独立的pexpect连接,每个线程负责一个虚拟机的ping:

import pexpect, time
from concurrent.futures import ThreadPoolExecutor

def run_single_ping(vm):
    try:
        child = pexpect.spawn('ssh -o StrictHostKeyChecking=no admin@server1')
        child.expect('Password:')
        child.sendline('xxxxxxxx')
        child.expect('.*>')
        child.sendline(f'ping -c10 {vm}')
        # 匹配ping输出到提示符的内容
        child.expect(f'(?s)(.*){child.prompt.decode()}')
        result = child.match.group(1).decode()
        child.sendline('exit')
        print(f"=== Ping result for {vm} ===")
        print(result)
        with open(f'ping_{vm}.txt', 'w') as f:
            f.write(result)
    except Exception as e:
        print(f"Error pinging {vm}: {str(e)}")

cmd_list = ['vm1', 'vm2', 'vm3']
print(f'Start : {time.ctime()}')
# 控制并发数,避免对NETAPP集群造成压力
with ThreadPoolExecutor(max_workers=10) as executor:
    executor.map(run_single_ping, cmd_list)
print(f'End : {time.ctime()}')

注意事项

  • 要根据NETAPP集群的SSH连接限制设置max_workers,避免连接过多被拒绝。
  • 每个线程独立操作自己的spawn实例,不会有线程安全问题。

内容的提问来源于stack exchange,提问作者Saikat Dasgupta

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.20 01:03:30