网络工程师的Python之路——netdev(异步并行)
off999 2024-10-18 08:07 62 浏览 0 评论
弈心:从事计算机网络工作11年(新加坡7年,沙特4年),2013年考取CCIE,在新加坡先后任职于AT&T,新加坡交通部,苹果,Equinix,苏格兰皇家银行等大型企业、银行和政府部门。 目前供职于“世界第一土豪大学“沙特阿卜杜拉国王科技大学(KAUST),担任Senior Network Engineer,为KAUST校史上第一位也是唯一一位华人IT部门高级职员。2019年6月在知乎发布了华语圈第一本专门为编程零基础的网络工程师量身打造的Python教程《网络工程师的Python之路》。
对网工来说,我们通常必须借助paramiko,netmiko,NAPALM或者pyntc等这些第三方开源模块才能通过SSH或者各种API来登录、操作、管理各种网络设备。很遗憾的是,由于异步在Python中引入较晚(Python 3.4过后才支持异步),上述所有这些模块都不支持异步。也就意味着在不使用多线程的情况下,运行Python脚本的主机必须一台一台地登录设备执行代码。假设登录一台交换机执行配置需耗时5秒的话,那么在拥有1000台交换机的大型企业网里就要耗时5000秒才能执行完脚本,效率太低。
2019年4月,受netmiko项目的启发,俄罗斯网络运维开发工程师Sergey Yakovlev在netmiko的基础上开发了一个叫做netdev的开源模块,该模块依赖于netmiko,并且需要至少Python3.5以上才能运行,最大的特点是支持对网络设备进行异步登录和操作。
在讲解netdev的用法前,首先我们需要知道什么是“同步”(Synchronous),什么是“异步”(Asynchronous),以及为什么使用异步能够帮助我们提升我们创建的Python脚本的工作效率。
1. 同步vs异步
所谓同步,可以理解为每当系统执行完一段代码或者函数后,系统将一直等待该段代码或函数返回的值或消息,直到系统接收到返回的值或消息后才继续往下执行下一段代码或者函数,在等待返回值或消息的期间,程序处于阻塞状态,系统将不做任何事情。
本文前面所有涉及到管理多个设备的实验中,我们都是将设备的IP地址预先写入一个名为“ip_list.txt”的文本文件,然后在脚本里使用open()函数将其打开,然后调用readlines()函数并配合for循环读取每个设备的IP地址,然后通过paramiko或者netmiko一台设备接一台地完成SSH登录。像这样Python一次只能登录一台设备,只有在完成一台设备的配置后才能继续登录下一台设备继续配置的方式就是一种典型的“同步”。
而异步则恰恰相反,系统在执行完一段代码或者函数后,不用阻塞性地等待返回的值或消息,而是继续执行下一段代码或函数,在同一时间段里执行多个任务(而不是傻傻的等着一件事情做完并且直到结果出来了以后才去做下件事情),将多个任务并行,从而提高程序的执行效率。如果你有读过数学家华罗庚的《统筹方法》,一定不会对其中所举的例子感到陌生:同样是沏茶的步骤,因为烧水需要一段时间,你不用等水煮沸了过后才来洗茶杯、倒茶叶(类似“同步”),而是在等待烧水的过程中就把茶杯洗好,把茶叶倒好,等水烧开了就能直接泡茶喝了,这里烧水、洗茶杯、倒茶叶三个任务是在同一个时间段内并行完成的,这就是一种典型的“异步”。
2. 单线程vs多线程
过去的Python并不支持异步,因为不管是同步还是异步,它们都是在单线程下完成的。而之前在Python中已经有了多线程(Multithreading)的存在,程序可以启动多个线程同时完成多个任务,如果一个线程阻塞,其他线程并不受影响,程序并不会卡死。后来Python在3.4.x版本中开始加入了异步,为什么要加入呢?因为多线程虽然效率很高,但是程序在切换线程的时候会占用系统资源,产生额外的开销。另外因为异步只用了一个线程,不用担心多线程复杂的锁机制(Lock Mechanism),这也是异步被加入进Python的原因之一。关于多线程及其锁机制的话题超出了本文的讨论范围,读者可以自己参阅其他的材料深入学习。
3. 异步在Python中的应用
自从Python在3.4.x版本起开始支持异步后,关于异步的Python语法几经更改,在Python3.4、Python3.5、Python3.7中异步的实现方式有很大的不同,本文后面的例子中都将基于Python 3.8.2来讲解异步的使用。
要了解异步在Python中的应用,必须知道什么是协程(Corountine),什么是任务(Task),什么是可等待对象(Awaitable Object)。
协程可以理解为线程的优化,我们可以把协程看成一种微线程。它是一种比线程更节省资源、效率更高的系统调度机制。而异步就是基于协程实现的。在Python中实现协程的模块主要有asyncio,gevent和tornado,使用较多的是asyncio。首先来看下面的例子:
#coding=utf-8
import asyncio
import time
async def main():
print('hello')
await asyncio.sleep(1)
print('world')
print (f"程序于 {time.strftime('%X')} 开始执行")
asyncio.run(main())
print (f"程序于 {time.strftime('%X')} 执行结束") - 在Python中,我们通过在def语句前加上async语句来将一个函数定义为协程函数,在上面的例子中,main()现在被定义为了协程函数。
- 这里的“await asyncio.sleep(1)”表示临时中断当前的函数一秒钟,如果程序里还有其他函数的话,继续执行下一个函数,直到下一个函数执行完毕后,再返回来执行这个main()程序,因为这里我们除了一个main()函数之外没有其他的函数了,所以在print('hello')后,main()函数休眠了1秒钟,然后继续print(‘world’)。
- 协程函数不是普通的函数,这里我们不能直接用“main()”来调用它,我们需要使用“asyncio.run(main())”才能执行该协程函数。
- 这里我们配合time模块的strftime()函数来记录程序开始前的时间和程序结束后的时间,可以看到总共耗时确实是1秒钟。
这里需要注意的是不要把 “await asyncio.sleep(1)”和“time.sleep(1)”弄混,后者是在“同步”中使用的休眠操作,前者是在“异步”中使用的,因为这里我们只有main()一个函数需要执行,所以你暂时感受不到这两者有什么区别,不用着急,继续来看下面的两个例子你就知道了:
#coding=utf-8
import asyncio
import time
async def say_after(what, delay):
print(what)
await asyncio.sleep(delay)
async def main():
print (f"程序于 {time.strftime('%X')} 执行结束")
await say_after('hello',1)
await say_after('world',2)
print (f"程序于 {time.strftime('%X')} 执行结束")
asyncio.run(main())- 这里我们在协程函数main()的基础上加入了另外一个函数say_after(),同样的,我们用async将它定义为了协程函数。
- 我们在main()函数中两次调用say_after()函数,因为say_after()是一个协程函数,因此在调用它时,前面必须加上await。
- 当main()第一次调用say_after()函数时,我们首先打印出hello,然后休眠1秒,第二次调用say_after()函数时,我们打印出world,然后再休眠两秒,两次调用完毕后总共花费了3秒钟来运行完整个程序, 如下:
这里你会说,第一次花了1秒,第二次花了2秒,总共3秒时间,这没节省时间啊,两此调用的say_after()函数并没有并行啊,这和同步有什么区别?别急,继续往下看:
#coding=utf-8
import asyncio
import time
async def say_after(what, delay):
await asyncio.sleep(delay)
print(what)
async def main():
task1 = asyncio.create_task(say_after('hello',1))
task2 = asyncio.create_task(say_after('world',2))
print (f"程序于 {time.strftime('%X')} 开始执行")
await task1
await task2
print (f"程序于 {time.strftime('%X')} 执行结束")
asyncio.run(main())- 要实现异步并行,需要将协程函数打包成一个任务(Task),这里我们使用asyncio的create_task()函数将say_after()打包了两次,并分别赋值给task1和task2两个变量。然后使用await来调用task1和task2两个任务。
- 运行脚本后可以看到,因为task1和task2是并行执行的,所以程序总共只耗时两秒钟即告完成(06:57:04到06:57:06)!
最后来说说什么是可等待对象(Awaitable Object),可等待对象的定义很简单:如果一个对象可以在await 语句中使用,那么它就是可等待对象。可等待对象主要有三种类型:协程、任务以及Future。协程和任务前面已经讲到了,Future不在本文的讨论范围内,读者可以自己参阅其他的材料深入学习。
4. netdev的安装和应用
截至2020年5月,netdev支持7家厂商的12种操作系统:
- Cisco IOS
- Cisco IOS XE
- Cisco IOS XR
- Cisco ASA
- Cisco NX-OS
- HP Comware (like V1910 too)
- Fujitsu Blade Switches
- Mikrotik RouterOS
- Arista EOS
- Juniper JunOS
- Aruba AOS 6.X
- Aruba AOS 8.X
- Terminal
Netdev可以通过pip直接下载安装(因为netdev依赖于netmiko,下载netdev前请确认你的Python主机已经安装了netmiko):
下载完成后进入Python编辑器,如果import netdev没有报错则说明安装成功:
在使用netdev进行异步操作之前,我们先来做个“同步”和“异步”的对比试验,首先我们用传统的“同步”方式,通过netmiko对5台交换机(192.168.2.11--192.168.2.15)下的“line vty 5 15”配置“login local”,并统计从脚本开始运行到脚本执行完成所耗费的时间,然后我们再使用netdev,通过“异步”的方式对同样的5台交换机做同样的配置,并计时,最后将两次耗时相比较,看看孰优孰劣。
传统的“同步”方案的脚本如下(交换机192.168.2.11 – 192.168.2.15的IP的地址被保存在一个名叫ip_list.txt的文件里):
from netmiko import ConnectHandler
import time
f = open('ip_list.txt')
start_time = time.time()
for ips in f.readlines():
ip = ips.strip()
SW = {
'device_type': 'cisco_ios',
'ip': ip,
'username': 'python',
'password': '123',
}
connect = ConnectHandler(**SW)
print ("Sucessfully connected to " + SW['ip'])
config_commands = ['line vty 5 15','login local','exit']
output = connect.send_config_set(config_commands)
print (output)
print ('Time elapsed: %.2f'%(time.time()-start_time))执行脚本看效果:
可以看到,通过“同步”方式一台一台登录5台交换机完成配置总共耗时了45.02秒。
接下来我们再来看netdev的表现,代码如下:
import asyncio
import netdev
import time
async def task(dev):
async with netdev.create(**dev) as ios:
commands = ["line vty 5 15", "login local","exit"]
out = await ios.send_config_set(commands)
print(out)
async def run():
devices = []
f = open('ip_list.txt')
for ips in f.readlines():
ip = ips.strip()
dev = { 'username' : 'python',
'password' : '123',
'device_type': 'cisco_ios',
'host': ip
}
devices.append(dev)
tasks = [task(dev) for dev in devices]
await asyncio.wait(tasks)
start_time = time.time()
asyncio.run(run())
print ('Time elapsed: %.2f'%(time.time()-start_time))执行代码看效果:
可以看到通过netdev提供的“异步”方式,我们仅仅耗时5.15秒便跑完了脚本,对5台交换机完成了同样的配置!
另外,除了netdev外,pexpect模块也支持异步并行,有兴趣的读者可以自行参阅其他资料了解。
相关推荐
- u盘怎么取消读写保护(优盘怎么去掉读写保护)
-
如果您的U盘启动了读写保护,那么就无法进行数据的读取和写入操作。以下是一些可能的解决方法:1.检查开关或按钮:一些U盘有物理开关或按钮,用于启用或禁用读写保护。您可以检查一下U盘上是否有这样的开关或...
-
- 打印机脱机无法打印怎么办(打印机脱机无法打印故障处理)
-
打印机脱机无法打印怎么办?在使用打印机的过程中,经常会遇到打印机无法打印的问题,如果你的打印机已经正常使用了一段时间,而是现在打印机无法打印了,那么很可能是你的打印机脱机了。我们该怎么办呢?首先我们拿到打印机,要把它的电源线,USB打印线与...
-
2025-11-12 03:51 off999
- 激活码怎么激活(激活码怎么激活steam)
-
首先,启动电脑,在键盘按下“Win+R”,然后“运行”程序。然后,在“运行”的对话框输入“regedit”,回车确定输入命令然后,在窗口的左侧菜单选择“HKEY_LOCAL_MACHINE\SOFTW...
- 电脑动不动就卡住不动怎么回事
-
可能出现卡死原因:1、病毒引起,使你的电脑检测通过的程序太多,CPU主频性能不能充分发挥出来。2、温度过高,散热不好,使CPU性能下降。3、内存条太小,内存缺陷。5、可能设置了开机后自动登陆太多,自动...
- 笔记本风扇声音大怎么办(笔记本风扇声音非常大)
-
1.清理笔记本风扇灰尘一般而言,新买来的风扇总是噪声较小,而使用一段时间后会明显变大。其实,灰尘是造成风扇噪音上升的重要原因之一,因为无孔不入的灰尘总能钻进不完全密闭的机箱。当CPU风扇高速旋转时,漩...
- 如何添加无线网络打印机(如何添加无线网络打印机连接)
-
要添加网络打印机,您可以按照以下步骤进行操作:1.确保网络设置:首先,请确保您的计算机和打印机都已连接到同一个局域网或无线网络中,并且网络连接正常。确保您已经知道网络打印...
- 戴尔电脑一键重装系统(戴尔怎么一键重装系统)
-
若您需要重装戴尔系统,可以按照以下步骤进行操作:首先备份重要数据,然后获取系统安装介质,可以是光盘或USB驱动器。接下来,进入BIOS设置,将启动顺序调整为从安装介质启动。重启电脑后,按照屏幕提示进行...
- 电脑ip地址配置异常怎么修复
-
如果您发现IP地址配置异常,可以按照以下步骤尝试解决:1.检查网络连接:首先检查计算机、路由器或交换机等设备的网线、电源和连接状态是否正常,并确保网络设备正确连接。2.确认IP地址:检查您的计算机...
- 怎么把win7电脑恢复出厂设置
-
1.首先我们打开电脑找到“计算机”点击打开。2.进入页面然后我们点击“Windows7(C:)”打开C盘。3.我们在C盘界面找到Windows7并点击打开。4.进入到Win7文件夹中找到并双击“Sys...
- ctrl c 和 ctrl v 怎么按(一键复制粘贴)
-
左手小指按Ctrl键,食指按C键或者V键具体在按Ctrl+C的时候,无名指放在Z键上,中指放在X键上,食指按C键如果你也用这种方式的话,可能和我一样,第一次按的时候不习惯手指这样去分工的感觉,但是你...
- 玩游戏cpu温度多少正常(玩游戏cpu温度多少正常 贴吧)
-
在游戏过程中,CPU温度的正常范围通常在40°C至80°C之间。然而,具体的正常温度取决于CPU型号、散热系统和环境条件等因素。一般来说,如果CPU温度超过80°C,就可能存在过热的风险,需要采取措施...
- idm下载器(如何卸载idm下载器)
-
截至2023年9月3日,IDM(InternetDownloadManager)是一款非常受欢迎的下载工具,但它并没有被禁用。IDM可以帮助用户更快速、稳定地下载文件,提供了多线程下载、断点续传等...
欢迎 你 发表评论:
- 一周热门
-
-
抖音上好看的小姐姐,Python给你都下载了
-
全网最简单易懂!495页Python漫画教程,高清PDF版免费下载
-
Python 3.14 的 UUIDv6/v7/v8 上新,别再用 uuid4 () 啦!
-
python入门到脱坑 输入与输出—str()函数
-
飞牛NAS部署TVGate Docker项目,实现内网一键转发、代理、jx
-
宝塔面板如何添加免费waf防火墙?(宝塔面板开启https)
-
Python三目运算基础与进阶_python三目运算符判断三个变量
-
(新版)Python 分布式爬虫与 JS 逆向进阶实战吾爱分享
-
慕ke 前端工程师2024「完整」
-
失业程序员复习python笔记——条件与循环
-
- 最近发表
- 标签列表
-
- python计时 (73)
- python安装路径 (56)
- python类型转换 (93)
- python进度条 (67)
- python吧 (67)
- python的for循环 (65)
- python格式化字符串 (61)
- python静态方法 (57)
- python列表切片 (59)
- python面向对象编程 (60)
- python 代码加密 (65)
- python串口编程 (77)
- python封装 (57)
- python写入txt (66)
- python读取文件夹下所有文件 (59)
- python操作mysql数据库 (66)
- python获取列表的长度 (64)
- python接口 (63)
- python调用函数 (57)
- python多态 (60)
- python匿名函数 (59)
- python打印九九乘法表 (65)
- python赋值 (62)
- python异常 (69)
- python元祖 (57)
