基于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
相关产品推荐
相关产品推荐

