1 前言
从OpenDaylight转到RYU以来一直都没有机会好好学习RYU的源码,只学会了编写简单的Application。但是如果要熟悉一个控制器,就要熟悉它的运行原理,熟悉它数据结构,熟悉它的设计模式等等。最近终于有时间好好看RYU的代码,但在看代码的过程中却发现RYU并不简单,其编码风格也非常优雅,非常值得学习。本篇博文主要讲述RYU中使用到的eventlet。
2 从RYU开始
运行ryu的在/cmd中没有找到之后,在/bin中找到了两个可执行文件:ryu和ryu-manager。打开ryu-manager,显示如下:时候,命令是:ryu-manager app.py。第一个要找到就是ryu-manager到底会触发什么程序。
找到/ryu/cmd/manager.py,发现这个文件中的main()函数是整个ryu的入口函数。这个main()函数的内容主要是完成了RYU的初始化配置和启动。Configure使用了oslo,这个在后续的博文中应该会提到。初始化的构成主要包括将app_list里面的内容加入App_Manager的列表中,然后开启协程去协调这些APP完成工作。hub是from ryu.lib import hub的。继续查看ryu/lib/hub.py。最终找到许多关于eventlet的内容。在hub.py中定义了Event,StreamServer和WSGIServer等类,还有一些重要的重要函数如spawn()等。为了更好地学习RYU,学习coroutine和eventlet就非常有必要了。
2.1 Coroutine
协程[coroutine]是一个程序组件。相比subroutine, coroutine更一般。coroutine相对与thread而言,又不一样。thread是资源抢占式的存在,而coroutine是通过yield来转移执行权,协程之间是平等的,没有等级关系。multi-thread一旦开始运行,就无法确定某一时刻到底是哪一个thread在占用cpu,临界资源也要加互斥锁。而coroutine则是需要程序员自己决定程序如何运行,同时也需要自己负责程序的风险。协程和线程一样,只共享堆,不共享栈。
2.2 Eventlet
eventlet是一个可以提供高性能并发处理能力的python库。我们可以在/usr/lib/python2.7/dist-packages/eventlet中找到对应的文件。为了更好的理解eventlet的内容,我花了半天认真地抄了一遍官网的Socket Connect例子。该实例源代码访问官网即可,本例中只是对将urls进行如下修改,urls = ['www.muzixing.com', 'www.baidu.com', 'www.python.org']。代码中对指定url发送了GET请求。重点在与eventlet.GreenPile()的使用。其中GreenPile类源码如下所示:
从__init__函数可以看出,GreenPile内部有一个GreenPool对象和一个Queue对象:waiters。GreenPool的作用相当与线程池的作用,这点后续会继续介绍。上述例子用到的spawn函数完成了协程(被称之为green thread)的启动。可以看出spawn函数的参数是(函数,参数),在上述例子中为: pile.spawn(geturl, x)。从spawn函数中,也可以看出spawn()方法的返回值被保存在waiters队列中。next()方法的实现使其具有迭代性质。
2.2.1 GreenPool
下面的例子使用到了GreenPool类,完成了一个非常暴力的迭代爬虫,理论上,如果你让他去爬取某一个网站,然后不去管它,它会从这个网站出发,找到所有的链接,然后跳到各自的链接,然后继续迭代,直到最后把整个互联网的网站都爬一遍。而且,它不尊重你网站的robot.txt,这意味这它什么都会爬取。
首先爬虫从http://www.muzixing.com网站开始搜索url。然后继续迭代寻找url,不断扩大查找范围。实验结果如下所示:
图1:迭代爬虫显示信息
从上图可以看到爬虫抓取了www.muzixing.com的网页中存在的url如http://ikimi.net,然后我们可以看到爬虫又跳到了http://ikimi.net上爬取页面的其他url,如http://www.ikimi.net/wp-includes。如果将起始页面换成bbs.byr.cn会发现爬虫会以更快的速度在整个互联网蔓延开来!
上述例子中可以学习到GreenPool类的使用。GreenPool可以类比于线程池,这有利于理解。在GreenPool中的元素都是GreenThread。其中最重要的函数是spawn/spawn_n函数。
该函数启动了一个GreenThread,参数是需要执行的function和function对应的参数。返回值是执行该函数的GreenThread类。
spawn_n函数功能上差不多,只是返回的是None。其他的函数举例简单说明如下:
■waitall():等待所有greenthread执行完毕。
■running(): 返回目前正在执行的greenthread。
■imap():从迭代器中取出数据項作为func的参数去执行,并返回结果。
■starmap(): 和imap类似,但是取参数的方式有所差异。从openstack nova 基础知识——eventlet中摘取举例如下:
■free(): 返回当前可获取的greenthread的数目。
以上代码上的with语句是python中的一个非常方便的关键字。使用with关键字可以让代码更严谨且简洁。其封装了__enter__()函数和__exit__()函数,用于执行信息和退出处理。其等价于以下代码:
上述是关于GreenPool类的使用案例,使用该类可以高效完成并发操作。
2.2.2 Convenience
接下来再介绍一个更好玩的程序,多人群聊程序,可以让我们在学习eventlet的时候充满成就感。代码如下:
try语句块中完成了服务端socket的建立和监听。然后在while循环中完成了消息的处理。
首先关注第一个函数:eventlet.listen((addr,port))。在eventlet文件夹中,打开__init__文件可以查看到一些为了方便而初始化的定义,举例如下:
所以我们直接可以使用eventlet.listen调用convenience.listen函数。listen函数完成了一个server socket的绑定和监听。
socket.accept()函数将返回一个(connection,address)元组。socket.makefile([mode[, bufsize]])返回一个文件对象用于读写缓存。
eventlet.spawn_n函数将read_chat_forever函数及其三个参数作为参数,创建GreenThread去执行任务。eventlet主要完成的工作就是帮助你如何去协调你的任务,而不是去实现你的任务,这一点在这里得到体现。其实对比于线程池就容易理解读多了。
试验结果截图如下:
图2:多人群聊server运行界面
图3:多人群聊client运行界面
从client运行界面可以看出不同的用户发送的信息会以IP:message的形式展示出来,代码很简单,但是非常有趣。
以上例子均可以在官网找到,读者可以到官网去查看更多案例。
2.2.3 Patcher
Patch是eventlet中的一个重要模块。用于替换系统自带的模块。其中有import_patched和monkey_patch两个函数,后者可以提供运行时替换。具体例子可以查看openstack nova 基础知识——eventlet。
3 回到RYU
前两行代码调用了hub.patch()函数,查看hub.py中发现patch = eventlet.monkey_patch,实现了运行时替换模块。
接下来的CONF文件由于oslo的内容比较多,所以会在后续博文中详细介绍。首先关注main()函数的主要内容。
从CONF文件中取出app信息,存在app_lists内,若没有启动其他app,则默认启动ofp_handler应用,用于处理基础的事件,如协议协商等。然后声明一个AppManager的类,用于初始化和管理APP。load_apps函数完成了APP的加载。最后try语句块中的joinall()使得进程需要等待所有的services完成之后才能退出。至此RYU初始运行学习完成,后续的博文将分别介绍:oslo, 事件处理机制,RYUAPP类以及RYU数据结构和API使用等内容。
4 后语
Evenlet是个不错的python库,简单却很高效。相比于thread,coroutine的行为是可控的,切换成本也要更小。在单核情况下,coroutine要比thread开销小,但是multithread可以在多CPU的情况下发挥更大的能力。RYU是使用Python编写的控制器,比同样使用Python编写的POX,无论从代码的规范,优雅度,还是从性能上,都有很大的优势,此外,这个纯SDN控制器对OpenFlow协议的支持可以说是最稳定,最全面的。虽然我还会继续研究ONOS,学习大型分布式框架。但是RYU会成为我开发Application的利器。相比之下,Java编写的ODL,过于复杂和不稳定。新生儿ONOS相比之下用户体验更好,且没有使用YANG,大大降低了学习难度。周一的时候,还在Docker中安装了ONOS,并使用Cbench测试对比了ONOS和RYU的吞吐量。同样环境下,单节点的ONOS性能几乎是RYU的两倍,这让我有些忧伤。也许匕首只适合敏捷作战,而大刀才是开疆扩土的利器吧。
本文来源于SDNLAB,可点击此阅读原文。如果您对本文感兴趣,可参与以下互动方式与作者近距离交流。
微信(账号:SDNLAB)
QQ群
SDN研究群(214146842)
OpenDaylight研究群(194240432)