eventlet这个强悍的东东,看到我同事的一些整理。故贴出来,大家一起分享~

motivation

114.113.199.11服务器上nova服务中基于python eventlet实现的定时任务(periodic_task)和 心跳任务(report_state)都是eventlet的一个greenthread实例.

目前服务器上出现了nova定时任务中某些任务执行时间过长而导致心跳任务不能准时运行的问题.

如果eventlet是一个完全意义上的类似线程/进程的并发库的话, 不应该出现这个问题, 需要研究 eventlet的并发实现, 了解它的并发实现原理, 避免以后出现类似的问题.

分析

经过阅读eventlet源代码, 可以知道eventlet主要依赖另外2个python package:

  • greenlet
  • python-epoll (或其他类似的异步IO库, 如poll/select等)

主要做了3个工作:

  • 封装greenlet
  • 封装epoll
  • 改写python标准库中相关的module, 以便支持epoll

epoll

epoll是linux实现的一个基于事件的异步IO库, 在之前类似的异步IO库poll上改进而来.

下面两个例子会演示如何用epoll将阻塞的IO操作用epoll改写为异步非阻塞. (取自官方文档)

blocking IO

    import socket

    EOL1 = b'\n\n'
EOL2 = b'\n\r\n'
response = b'HTTP/1.0 200 OK\r\nDate: Mon, 1 Jan 1996 01:01:01 GMT\r\n'
response += b'Content-Type: text/plain\r\nContent-Length: 13\r\n\r\n'
response += b'Hello, world!' serversocket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
serversocket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
serversocket.bind(('0.0.0.0', 8080))
serversocket.listen(1) try:
while True:
connectiontoclient, address = serversocket.accept()
request = b''
while EOL1 not in request and EOL2 not in request:
request += connectiontoclient.recv(1024)
print('-'*40 + '\n' + request.decode()[:-2])
connectiontoclient.send(response)
connectiontoclient.close()
finally:
serversocket.close()

这个例子实现了一个简单的监听在8080端口的web服务器. 通过一个死循环不停的接收来自8080端口 的连接, 并返回结果.

需要注意的是程序会在

connectiontoclient, address = serversocket.accept()

这一行block住, 直到获取到新的连接, 程序才会继续往下运行.

同时, 这个程序同一个时间内只能处理一个连接, 如果有很多用户同时访问8080端口, 必须要按先后 顺序依次处理这些连接, 前面一个连接成功返回后, 才会处理后面的连接.

下面的例子将用epoll将这个简单的web服务器改写为异步的方式

non-blocking IO by using epoll

import socket, select

EOL1 = b'\n\n'
EOL2 = b'\n\r\n'
response = b'HTTP/1.0 200 OK\r\nDate: Mon, 1 Jan 1996 01:01:01 GMT\r\n'
response += b'Content-Type: text/plain\r\nContent-Length: 13\r\n\r\n'
response += b'Hello, world!' serversocket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
serversocket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
serversocket.bind(('0.0.0.0', 8080))
serversocket.listen(1)
serversocket.setblocking(0) epoll = select.epoll()
epoll.register(serversocket.fileno(), select.EPOLLIN) try:
connections = {}; requests = {}; responses = {}
while True:
events = epoll.poll(1)
for fileno, event in events:
if fileno == serversocket.fileno():
connection, address = serversocket.accept()
connection.setblocking(0)
epoll.register(connection.fileno(), select.EPOLLIN)
connections[connection.fileno()] = connection
requests[connection.fileno()] = b''
responses[connection.fileno()] = response
elif event & select.EPOLLIN:
requests[fileno] += connections[fileno].recv(1024)
if EOL1 in requests[fileno] or EOL2 in requests[fileno]:
epoll.modify(fileno, select.EPOLLOUT)
print('-'*40 + '\n' + requests[fileno].decode()[:-2])
elif event & select.EPOLLOUT:
byteswritten = connections[fileno].send(responses[fileno])
responses[fileno] = responses[fileno][byteswritten:]
if len(responses[fileno]) == 0:
epoll.modify(fileno, 0)
connections[fileno].shutdown(socket.SHUT_RDWR)
elif event & select.EPOLLHUP:
epoll.unregister(fileno)
connections[fileno].close()
del connections[fileno]
finally:
epoll.unregister(serversocket.fileno())
epoll.close()
serversocket.close()

可以看到, 例子中首先使用serversocket.setblocking(0)将socket设为异步的模式, 然后 用select.epoll()新建了一个epoll, 接着用epoll.register(serversocket.fileno(), select.EPOLLIN) 将该socket上的IO输入事件(select.EPOLLIN)注册到epoll里. 这样做了以后, 就可以将 上面例子中会在socket.accept()这步阻塞的Main Loop改写为基于异步IO事件的epoll循环了.

events = epoll.poll(1)

简单的说, 如果有很多用户同时连接到8080端口, 这个程序会同时accept()所有的socket连接, 然后通过这行代码将发生IO事件socket放到events中, 并在后面循环中处理. 没有发生IO事件的 socket不会在loop中做处理. 这样使用epoll就实现了一个简单的并发web服务器.

注意, 这里提到的并发, 和我们通常所理解线程/进程的并发并不太一样, 更准确的说, 是 IO多路复用 .

greenlet

greentlet是python中实现我们所谓的"Coroutine(协程)"的一个基础库.

看了下面的例子就明白了.

    from greenlet import greenlet

    def test1():
print 12
gr2.switch()
print 34 def test2():
print 56
gr1.switch()
print 78 gr1 = greenlet(test1)
gr2 = greenlet(test2)
gr1.switch()

输出

  1.  
    12
  2.  
    56
  3.  
    34

程序先分别为两个函数定义了2个greenlet: gr1和gr2.

gr1.switch()显式切换到gr1上执行, gr1中输出"12"后gr2.switch()显式切换到gr2上执行 输出56, 又gr1.switch()显式切换到gr1上, 输出34. test1()执行结束, gr1 die. 于是 test2()里的78不会输出.

可以发现greenlet仅仅是实现了一个最简单的"coroutine", 而eventlet中的greenthread是在 greenlet的基础上封装了一些更high-level的功能, 比如greenlet的调度等.

eventlet.green

从epoll的运行机制可以看出, 要使用异步IO, 必须要将相关IO操作改写成non-blocking的方式. 但是我们用eventlet.spawn()的函数, 并没有针对epoll做任何改写, 那eventlet是怎么实现 异步IO的呢?

这也是eventlet这个package最凶残的地方, 它自己重写了python标准库中IO相关的操作, 将它们 改写成支持epoll的模式, 放在eventlet.green中.

比如说, socket.accept()被改成了这样

  1.  
    def accept(self):
  2.  
    if self.act_non_blocking:
  3.  
    return self.fd.accept()
  4.  
    fd = self.fd
  5.  
    while True:
  6.  
    res = socket_accept(fd)
  7.  
    if res is not None:
  8.  
    client, addr = res
  9.  
    set_nonblocking(client)
  10.  
    return type(self)(client), addr
  11.  
    trampoline(fd, read=True, timeout=self.gettimeout(),
  12.  
    timeout_exc=socket.timeout("timed out"))

然后在eventlet.spawn()的时候, 通过 一些高阶魔法和"huge hack", 将这些改写过得模块"patch"到spawn出的greenthread上, 从而 实现epoll的IO多路复用, 相当凶残.

eventlet并发机制分析

前面说了这么多, 这里可以分析一下eventlet的并发机制了.

eventlet的结构如下图所示

  1.  
    _______________________________________
  2.  
    | python process |
  3.  
    | _________________________________ |
  4.  
    | | python thread | |
  5.  
    | | _____ ___________________ | |
  6.  
    | | | hub | | pool | | |
  7.  
    | | |_____| | _____________ | | |
  8.  
    | | | | greenthread | | | |
  9.  
    | | | |_____________| | | |
  10.  
    | | | _____________ | | |
  11.  
    | | | | greenthread | | | |
  12.  
    | | | |_____________| | | |
  13.  
    | | | _____________ | | |
  14.  
    | | | | greenthread | | | |
  15.  
    | | | |_____________| | | |
  16.  
    | | | | | |
  17.  
    | | | ... | | |
  18.  
    | | |___________________| | |
  19.  
    | | | |
  20.  
    | |_________________________________| |
  21.  
    | |
  22.  
    | _________________________________ |
  23.  
    | | python thread | |
  24.  
    | |_________________________________| |
  25.  
    | _________________________________ |
  26.  
    | | python thread | |
  27.  
    | |_________________________________| |
  28.  
    | |
  29.  
    | ... |
  30.  
    |_______________________________________|

其中的hub和greenthread分别对应eventlet.hubs.hub和eventlet.greenthread, 本质都是 一个greenlet的实例.

hub中封装前面提到的epoll, epoll的事件循环是由hub.run()这个方法里实现. 每当用户调用 eventlet.spawn(), 就会在当前python线程的pool里产生一个新的greenthread. 由于greenthread 里的IO相关的python标准库被改写成non-blocking的模式(参考上面的socket.accept()).

每当greenthread里做IO相关的操作时, 最终都会返回到hub中的epoll循环, 然后根据epoll中的 IO事件, 调用响应的函数. 具体如下面所示.

greenthread.sleep(), 实际上也是将CPU控制权交给hub, 然后由hub调度下一个需要运行的 greenthread.

    # in eventlet.hubs.poll.Hub

    def wait(self, seconds=None):
readers = self.listeners[READ]
writers = self.listeners[WRITE] if not readers and not writers:
if seconds:
sleep(seconds)
return
try:
presult = self.poll.poll(int(seconds * self.WAIT_MULTIPLIER))
except select.error, e:
if get_errno(e) == errno.EINTR:
return
raise
SYSTEM_EXCEPTIONS = self.SYSTEM_EXCEPTIONS for fileno, event in presult:
try:
if event & READ_MASK:
readers.get(fileno, noop).cb(fileno)
if event & WRITE_MASK:
writers.get(fileno, noop).cb(fileno)
if event & select.POLLNVAL:
self.remove_descriptor(fileno)
continue
if event & EXC_MASK:
readers.get(fileno, noop).cb(fileno)
writers.get(fileno, noop).cb(fileno)
except SYSTEM_EXCEPTIONS:
raise
except:
self.squelch_exception(fileno, sys.exc_info())
clear_sys_exc_info()

总结

eventlet实现的并发和我们理解的通常意义上类似线程/进程的并发是不同的, eventlet实现的"并发" 更准确的讲, 是 IO多路复用 . 只有在被eventlet.spawn()的函数中存在可以 支持异步IO 相关的操作, 比如说读写socket/named pipe等时, 才能不用对被调用的函数做任何修改而实现 所谓的"并发".

如果被eventlet.spawn()的函数中存在大量的CPU计算或者读写普通文件, eventlet是无法对其 实现并发操作的. 如果想要在这样的greenthread间实现类似"并发"运行的效果, 需要手动的在函数 中插入greenthread.sleep().

eventlet 模块搭建 WEB 服务器的更多相关文章

  1. Node.js 蚕食计划(二)—— 使用 http 模块搭建 Web 服务器

    Node.js 开发的目的就是为了用 JavaScript 编写 Web 服务器程序 这次就来介绍用 http 模块搭建服务器 一.项目构建 每个 Node 程序都可以看作一个模块,而每个模块都应该有 ...

  2. 轻松使用Nginx搭建web服务器

    如果读者以前做过web开发的话,就应该知道如何去搭建一个web服务器来跑你的web站点,在windows下你可能会选择去用IIS,十分的快捷,在linux下,你可能首先会想到apache,“一哥”( ...

  3. NodeMCU入门(4):搭建Web服务器,配置网络连接

    准备工作 1.NodeMCU模块 2.ESPlorer v0.2.0-rc6 3.NodeMCU-HTTP-Server 搭建web服务器 下载https://github.com/wangzexi/ ...

  4. CentOS 6.2下搭建Web服务器

    1Centos 6.2下搭建web服务器 如今,Linux在Web应用越来越广,许多企业都采用Linux来搭建Web服务器,这样即节省了购买正版软件的费用,而且还能够提高服务器的安全性. 之前我们介绍 ...

  5. Python搭建Web服务器,与Ajax交互,接收处理Get和Post请求的简易结构

    用python搭建web服务器,与ajax交互,接收处理Get和Post请求:简单实用,没有用框架,适用于简单需求,更多功能可进行扩展. python有自带模块BaseHTTPServer.CGIHT ...

  6. 使用 Node.js 搭建 Web 服务器

    使用Node.js搭建Web服务器是学习Node.js比较全面的入门教程,因为实现Web服务器需要用到几个比较重要的模块:http模块.文件系统.url解析模块.路径解析模块.以及301重定向技术等, ...

  7. Mac上一条命令搭建web服务器

    实际测试工作中偶尔会需要搭建Web服务器环境,由于Mac OS X自带了Apache和PHP环境,只需要简单的启动就可以. 开启Apache 开启Web服务器的方法有两种(默认启动端口号是80): 打 ...

  8. CentOS 6.3下搭建Web服务器

    准备前的工作: 1.修改selinux配置文件(/etc/sysconfig/selinux) 关闭防火墙 (1)把SELINUX=enforcing注释掉 (2)并添加SELINUX=disable ...

  9. nodejs搭建web服务器初级

    nodejs搭建简单的web服务器 1.1简介 Node.js是基于Chrome JavaScript运行时建立的一个平台,实际上它是对Google Chrome V8引擎进行了封装,它主要用于创建快 ...

随机推荐

  1. ArrayList的subList方法带来的坑

    最近在项目中遇到了一个问题,由一个对象序列化的结构,在反序列化时一直提示失败,真的百思不得其解啊.在对问题排查了好久之后,才发现是这个序列化的对象中的list调用了ArrayList的sublist方 ...

  2. Java实现简易聊天室

    Java实现简易聊天室 在学习<Java从入门到精通>这本书,网络通信,基于TCP实现的简易聊天室,我这里对书中的代码略做了修改,做个记录. 这里先放一下运行效果图,代码放在最后. 运行效 ...

  3. Python小练习:批量删除多个文件夹内的相同文件

    应用场景: 下载的多个文件夹是压缩包,解压后每个文件夹都有某个网站的推广链接,想要批量的删除该文件 使用环境:win7,python3.6 代码: 1.直接用for循环 由于os.walk()方法自带 ...

  4. Spring+SpringMVC+Hibernate 与 shiro 整合步骤

    目录 1. 业务需求分析 2. 创建数据库 3. 创建 maven webapp 工程 4. 创建实体类(POJO) 5. 配置 Hibernate 和 Mapping 5.1 Hibernate 主 ...

  5. PCA 从线性变换的角度理解

  6. CentOS6.7安装部署php5(详解安装选项与主配置文件)

    模块安装---PHP 编译环境:gcc  gcc-c++   pcre-devel  openssl-devel   libxml2   libxml2-devel   bzip   bzip-dev ...

  7. kafaka可视化工具kafkatool

    炒作就像动物世界的森林法则,专门攻击弱者,这种做法往往能够百发百中.                                                                   ...

  8. ansible之yum模块

    > YUM (/usr/lib/python2.7/site-packages/ansible/modules/packaging/os/yum.py) Installs, upgrade, d ...

  9. 获取对象State的方法

    一.通过Scaffold.of(context)可以获取父级最近的Scaffold Widget的State对象 二.通过GlobalKey来获取.步骤有两步: 给目标StatefulWidget添加 ...

  10. shell 编程生成日期文件;Server虚拟机上进行Web服务器配置

    shell 编程生成日期文件 1. 请编写一个脚本,命名为sh01.sh,其功能是: 键盘输入文件名(要求使用名字全拼作为文件名). 自动创建3个文件. 1个为系统当天日期(CCYYMMDD). 1个 ...