SSH批量采集设备信息时序列号重复写入问题求助
解决多线程SSH采集设备信息时序列号重复及文件写入问题
看起来你遇到的核心问题是多线程共享全局变量导致的数据覆盖,再加上文件写入逻辑的小疏漏,才会出现序列号重复、数据混乱的情况。毕竟要处理9000多台设备,线程安全和正确的IO操作至关重要,我来帮你一步步修复:
问题分析
- 全局变量的线程安全隐患:你定义的
serial_number、hardware_address、ip_address、result都是全局变量,多个线程会同时读写这些变量。比如线程A刚获取到设备X的序列号,线程B马上把这个变量改成设备Y的,当线程A准备写入时,就会用错线程B的序列号,导致重复。 - 文件写入逻辑错误:
- 每次处理一个IP都执行
with open(..., 'w')写表头,这会清空文件并重复写入表头,多线程下还会互相覆盖内容。 result变量没有在每次处理完一个IP后重置,会不断累积所有设备的数据,导致后续写入时重复追加之前的内容。
- 每次处理一个IP都执行
- 多线程写入文件的竞态条件:多个线程同时追加写入文件,可能会导致内容错乱(比如两条记录挤在同一行)。
修复后的代码
我调整了代码逻辑,去掉全局变量,改用局部变量存储单台设备的信息,添加文件写入锁,并且只初始化一次表头:
import paramiko import time import threading from queue import Queue stb_ips = "stb_ip_alive_test.txt" stb_serial_number_filename = 'stb_serial_numbers.txt' # 添加文件写入锁,避免多线程同时写入冲突 file_lock = threading.Lock() def init_output_file(): # 只初始化一次表头,放在主线程执行 with open(stb_serial_number_filename, 'w') as f: f.write('ip_address,serial_number,hardware_address\n') def stb_serial_number(q): ssh = paramiko.SSHClient() ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy()) try: while True: ip = q.get() print('Connecting to ', ip) # 每个设备的信息用局部变量存储,避免线程间干扰 serial_number = '' hardware_address = '' ip_address = '' ssh.connect(ip, port=10022, username='xxxx', password='xxxx', banner_timeout=70, timeout=70) # 获取序列号 stdin, stdout, stderr = ssh.exec_command('cat /tmp/hostname\n') for line in stdout.readlines(): serial_number = line.strip() break # 假设hostname文件只有一行序列号 # 获取MAC和IP stdin, stdout, stderr = ssh.exec_command('/sbin/ifconfig eth0\n') for mac_address_line in stdout.readlines(): if mac_address_line.startswith('eth0'): tmp = mac_address_line.split() hardware_address = tmp[4] if 'inet' in mac_address_line: tmp = mac_address_line.split() ip_address = tmp[1].lstrip('addr:') # 生成当前设备的记录 device_record = f"{ip_address},{serial_number},{hardware_address}\n" # 加锁写入,避免多线程冲突 with file_lock: with open(stb_serial_number_filename, 'a') as f: f.write(device_record) q.task_done() except (TimeoutError, ConnectionResetError, paramiko.SSHException) as e: # 错误日志也加锁,避免多线程写入混乱 with file_lock: with open(f'STB_Log_File_{time.strftime("%m%d%Y%H%M%S")}.txt', 'a') as file_index: file_index.write(f"Error connecting to {ip}: {str(e)}\n") finally: # 确保SSH连接关闭,避免资源泄漏 ssh.close() def queue_input(q): with open(stb_ips, 'r') as f: ip_addresses = f.read().splitlines() for ip in ip_addresses: q.put(ip) q.join() if __name__ == '__main__': # 先初始化输出文件,写表头 init_output_file() q = Queue(maxsize=5) thread_count = 5 for i in range(thread_count): t = threading.Thread(target=stb_serial_number, args=(q,), daemon=True) t.start() producer_thread = threading.Thread(target=queue_input, args=(q,)) producer_thread.start() q.join()
关键改进点
- 去掉全局变量:每个线程处理单台设备时,用局部变量存储该设备的序列号、MAC、IP,完全避免线程间的数据覆盖。
- 文件写入锁:用
threading.Lock()确保同一时间只有一个线程写入文件,解决多线程IO的竞态问题。 - 表头只写一次:在主线程初始化时就写入表头,不会重复覆盖或重复写入。
- 及时关闭SSH连接:用
finally块确保无论是否出现异常,SSH连接都会被关闭,避免资源泄漏。 - 错误日志优化:错误信息也加锁写入,同时记录具体的异常内容,方便排查问题。
这样修改后,应该就能正确采集每台设备的唯一序列号、MAC和IP,而且文件写入也不会出现混乱了,处理9000多台设备也能稳定运行。
内容的提问来源于stack exchange,提问作者Shane Heupel
相关产品推荐
相关产品推荐

