开源软件架构-Twisted

Twisted 是用 Python 实现的基于事件驱动的网络引擎框架。Twisted 诞生于2000年初,在当时的网络游戏开发者看来,无论他们使用哪种语言,手中都鲜有可兼顾扩展性及跨平台的网络库。Twisted 的作者试图在当时现有的环境下开发游戏,这一步走的非常艰难,他们迫切地需要一个可扩展性高、基于事件驱动、跨平台的网络开发框架,为此他们决定自己实现 一个,并从那些之前的游戏和网络应用程序的开发者中学习,汲取他们的经验教训。

原文地址:http://www.aosabook.org/en/twisted.html;作者:Jessica McKellar

Twisted 支持许多常见的传输及应用层协议,包括 TCP、UDP、SSL/TLS、HTTP、IMAP、 SSH、IRC 以及 FTP。就像 Python 一样,Twisted 也具有“内置电池”(batteries-included) 的特点。Twisted 对于其支持的所有协议都带有客户端和服务器实现,同时附带有基于命令行的工具,使得配置和部署产品级的 Twisted 应用变得非常方便。

为什么需要 Twisted

2000年时,Twisted 的作者 Glyph 正在开发一个名为 Twisted Reality 的基于文本方式的多人在线游戏。这个游戏采用 Java 开发,里面尽是一堆线程-每个连接就有3个线程处理。处理输入的线程会在读操作上阻塞,处理输出的线程将在一些写操作上阻塞,还有一个“逻辑”线程将在等待定时器超时或者事件入队列时休眠。随着玩家们在虚拟世界中移动并交互时,线程出现死锁,缓存被污染,程序中的加锁逻辑几乎从来就没对过-采用多线程使得整个软件变得复杂、漏洞百出而且极难扩展。

为了寻求其他的解决方案,作者发现了 Python,特别是 Python 中用于对流式对象比如 socket 和 pipe 进行多路 I/O 复用的 select 模块(UNIX 规范第3版(SUSv3) 述了 select)。那时,Java 并没有提供操作系统的 select 接口或者任何其他的异步 I/O API(针对非阻塞式 I/O 的包 java.nio 已经在 J2SE 1.4中加入了,2002年发布)。通过用 Python 中的 select 模块快速搭建起游戏的原型,这迅速降低了程序的复杂度,并且比多线程版本要更加可靠。

Glyph 迅速转向了 Python、select 以及基于事件驱动的编程。他使用 Python 的 select 模块为游戏编写了客户端和服务器。但他想要的还不止于此。从根本上说,他希望能将网络行为转变为对游戏中的对象的方法调用。如果你能在游戏中收取邮件会怎样,就像 Nethack mailer 这种守护进程一样?如果游戏中的每位玩家都拥有一个主页呢?Glyph 发现他需要优秀的 IMAP 以及 HTTP 客户端和服务器的 Python 实现,而这些都要采用 select。

他首先转向了 Medusa,这是一个在90年代中期开发的平台,在这里可以采用 Python 中的 asyncore 模块来编写网络服务。asyncore 是一个异步化处理 socket 的模块,在操作系统的 select API 之上构建了一个调度器和回调接口。

这对于 Glyph 来说是个激动人心的发现,但 Medusa 有两个缺点:

  1. 这个项目到2001年就不再维护了,那正是 glyph 开发 Twisted Reality 的时候。
  2. asyncore 只是对 socket 的一个薄封装层,应用程序的编写者仍然需要直接操作 socket。这意味着程序可移植性的担子仍然落在程序员自己身上。此外,那时 asyncore 对 Windows 的支持还有问题,Glyph 希望能在 Windows 上运行一个带有图形用户界面的客户端。

Glyph 需要自己实现一个网络引擎平台,而且他意识到 Twisted Reality 已经打开了问题的大门,这和他的游戏一样有趣。

随着时间的推移,Twisted Reality 这个游戏就演化成了 Twisted 网络引擎平台。它可以做 到当时 Python 中已有的网络平台所无法做到的事情:

  • 使用基于事件驱动的编程模型,而不是多线程模型。
  • 跨平台:为主流操作系统平台暴露出的事件通知系统提供统一的接口。
  • “内置电池”的能力:供流行的应用层协议实现,因此 Twisted 马上就可为开发人员所用。
  • 符合 RFC 规范,已经通过健壮的测试套件证明了其一致性。
  • 能很容易的配合多个网络协议一起使用。
  • 可扩展。

Twisted 架构概览

Twisted 是一个事件驱动型的网络引擎。由于事件驱动编程模型在 Twisted 的设计哲学中占有重要的地位,因此这里有必要花点时间来回顾一下究竟事件驱动意味着什么。

事件驱动编程是一种编程范式,这里程序的执行流由外部事件来决定。它的特点是包含一个事件循环,当外部事件发生时使用回调机制来触发相应的处理。另外两种常见的编程范式是(单线程)同步以及多线程编程。

让我们用例子来比较和对比一下单线程、多线程以及事件驱动编程模型。图中展示了随着时间的推移,这三种模式下程序所做的工作。这个程序有3个任务需要完成,每个任务都在等待 I/O 操作时阻塞自身。阻塞在 I/O 操作上所花费的时间已经用灰色框标示出来了。

开源软件架构-Twisted

在单线程同步模型中,任务按照顺序执行。如果某个任务因为 I/O 而阻塞,其他所有的任务都必须等待,直到它完成之后它们才能依次执行。这种明确的执行顺序和串行化处理的行为是很容易推断得出的。如果任务之间并没有互相依赖的关系,但仍然需要互相等待的话这就使得程序不必要的降低了运行速度。

在多线程版本中,这3个任务分别在独立的线程中执行。这些线程由操作系统来管理,在多处理器系统上可以并行处理,或者在单处理器系统上交错执行。这使得当 个线程阻塞在某个资源的同时其他线程得以继续执行。与完成类似功能的同步程序相比,这种方式更有效率,但程序员必须写代码来保护共享资源,防止其被多个线程同时访问。多线程程序更加难以推断,因为这类程序不得不通过线程同步机制如锁、可重入函数、线程局部存储或者其他机制来处理线程安全问题,如果实现不当就会导致出现微妙且令人痛不欲生的 bug。

在事件驱动版本的程序中,3个任务交错执行,但仍然在一个单独的线程控制中。当处理 I/O 或者其他昂贵的操作时,注册一个回调到事件循环中,然后当 I/O 操作完成时继续执行。回调描述了该如何处理某个事件。事件循环轮询所有的事件,当事件到来时将它们分配给等待处理事件的回调函数。这种方式让程序尽可能的得以执行而不需要用到额外的线程。事件驱动型程序比多线程程序更容易推断出行为,因为程序员不需要关心线程安全问题。

当我们面对如下的环境时,事件驱动模型通常是一个好的选择:

  1. 程序中有许多任务
  2. 任务之间高度独立(因此它们不需要互相通信,或者等待彼此)
  3. 在等待事件到来时,某些任务会阻塞

当应用程序需要在任务间共享可变的数据时,这也是一个不错的选择,因为这里不需要采用同步处理。

网络应用程序通常都有上述这些特点,这使得它们能够很好的契合事件驱动编程模型。

重用已有的应用

在 Twisted 创建之前就已经有了许多针对多种流行的网络协议的客户端和服务器实现了。为什么 Glyph 不直接用 Apache、IRCd、BIND、OpenSSH 或者任何其他已有的应用,而要为 Twisted 从头开始重新实现各个协议的客户端和服务器呢?

问题在于所有这些已有的实现都存在有从头写起的网络层代码,通常都是 C 代码。而应用层代码直接同网络层耦合在一起,这使得它们非常难以以库的形式来复用。当要一起使用这些组件时,如果希望在多个协议中暴露相同的数据,则它们必须以黑盒的形式来看待,这使得开发者根本没机会重用代码。此外,服务器和客户端的实现通常是分离的,彼此之间不共享代码。要扩展这些应用,维护跨平台的客户端-服务器兼容性的难度本不至于这么大。

Twisted 中的客户端和服务器是用 Python 开发的,采用了一致性的接口。这使得开发新的客户端和服务器变得很容易实现,可以在客户端和服务器之间共享代码,在协议之间共享应用逻辑,以及对某个实现的代码做测试。

Reactor 模式

Twisted 实现了设计模式中的反应堆(reactor)模式,这种模式在单线程环境中调度多个事件源产生的事件到它们各自的事件处理例程中去。

Twisted 的核心就是 reactor 事件循环。Reactor 可以感知网络、文件系统以及定时器事件。它等待然后处理这些事件,从特定于平台的行为中抽象出来,并提供统一的接口,使得在网络协议栈的任何位置对事件做出响应都变得简单。

基本上 reactor 完成的任务就是:

Twisted 目前在所有平台上的默认 reactor 都是基于 poll API 的(UNIX 规范第3版(SUSv3) 中述)。此外,Twisted 还支持一些特定于平台的高容量多路复用 API。这些 reactor 包括基于 FreeBSD 中 kqueue 机制的 KQueue reactor,支持 epoll 接口的系统(目前是 Linux 2.6)中的 epoll reactor,以及基于 Windows 下的输入输出完成端口的 IOCP reactor。

在实现轮询的相关细节中,Twisted 需要考虑的包括:

  • 网络和文件系统的限制
  • 缓冲行为
  • 如何检测连接丢失
  • 出现错误时的返回值

Twisted 的 reactor 实现同时也考虑了正确使用底层的非阻塞式 API,并正确处理各种边界情况。由于 Python 中没有暴露出 IOCP API,因此 Twisted 需要维护自己的实现。

管理回调链

回调是事件驱动编程模型中的基础,也是 reactor 通知应用程序事件已经处理完成的方式。随着程序规模不断扩大,基于事件驱动的程序需要同时处理事件处理成功和出错的情况,这使得程序变得越来越复杂。若没有注册一个合适的回调,程序就会阻塞,因为这个事件处理的过程绝不会发生。出现错误时需要通过应用程序的不同层次从网络栈向上传递回调链。

下面是两段 Python 伪码,分别是同步和异步模式下获取 URL 的玩具代码。让我们相互比较一下这两个版本,看看基于事件驱动的程序有什么缺陷:

以同步的方式获取 URL:

以异步的方式获取 URL:

在异步版的 URL 获取器中,reactor.run()启动 reactor 事件循环。在同步和异步版程序中,我们假定 getPage 函数处理获取页面的工作。如果获取成功就调用 processPage,如果尝试获取页面时出现了 Exception(异常),logError 就得到调用。无论哪种情况,最后都要调用 finishProcessing。

异步版中的 logError 回调正对应于同步版中的 try/except 块。对 processPage 的回调对应于 else 块,无条件回调的 finishProcessing 就对应于 finally 块。

在同步版中,代码结构直接显示出有一个 try/except 块,logError 和 processPage 这两者间只会取其一调用一次,而 finishProcessing 总是会被调用一次。在异步版中需要由程序员自己负责正确调用成功和失败情况下的回调链。如果由于编程错误,在 processPage 或者 logError 的回调链之后没有调用 finishProcessing,reactor 事件循环将永远不会停止,程序就会卡住。

这个玩具式的例子告诉我们在开发 Twisted 的头几年里这种复杂性令程序员感到非常沮丧。而 Twisted 应对这种复杂性的方式是新增一个称为 Deferred(延迟)的对象。

Deferreds

Deferred 对象以抽象化的方式表达了一种思想,即结果还尚不存在。它同样能够帮助管理产生这个结果所需要的回调链。当从函数中返回时,Deferred 对象承诺在某个时刻函数将产生一个结果。返回的 Deferred 对象中包含所有注册到事件上的回调引用,因此在函数间只需要传递这一个对象即可,跟踪这个对象比单独管理所有的回调要简单的多。

Deferred 对象包含一对回调链,一个是针对操作成功的回调,一个是针对操作失败的回调。初始状态下 Deferred 对象的两条链都为空。在事件处理的过程中,每个阶段都为其添加处理成功的回调和处理失败的回调。当一个异步结果到来时,Deferred 对象就被“激活”,那么处理成功的回调和处理失败的回调就可以以合适的方式按照它们添加进来的顺序依次得到调用。

异步版 URL 获取器采用 Deferred 对象后的代码如下:

在这个版本中调用的事件处理函数与之前相同,但它们都注册到了一个单独的 Deferred 对象上,而不是分散在代码各处再以参数形式传递给 getPage。

Deferred 对象创建时包含两个添加回调的阶段。第一阶段,addCallbacks 将 processPage 和 logError 添加到它们各自归属的回调链中。然后 addBoth 再将 finishProcessing 同时添加到这两个回调链上。用图解的方式来看,回调链应该如图所示:

开源软件架构-Twisted

Deferred 对象只能被激活一次,如果试图重复激活将引发一个异常。这使得 Deferred 对象的语义相当接近于同步版中的 try/except 块。从而让异步事件的处理能更容易推断,避免由于针对单个事件的回调调用多了一个或少了一个而产生微妙的 bug。

理解 Deferred 对象对于理解 Twisted 程序的执行流是非常重要的。然而当使用 Twisted 为我们提供的针对网络协议的高层抽象时,通常情况下我们完全不需要直接使用 Deferred 对象。

Deferred 对象所包含的抽象概念是非常强大的,这种思想已经被许多其他的事件驱动平台所借用,包括 jQuery 、Dojo 和 Mochikit。

Transports

Transports 代表网络中两个通信结点之间的连接。Transports 负责描述连接的细节,比如连接是面向流式的还是面向数据报的,流控以及可靠性。TCP、UDP 和 Unix 套接字可作为 transports 的例子。它们被设计为“满足最小功能单元,同时具有最大程度的可复用性”,而且从协议实现中分离出来,这让许多协议可以采用相同类型的传输。Transports 实现了 ITransports 接口,它包含如下的方法:

将 transports 从协议中分离出来也使得对这两个层次的测试变得更加简单。可以通过简单地写入一个字符串来模拟传输,用这种方式来检查。

Protocols

Protocols 述了如何以异步的方式处理网络中的事件。HTTP、DNS 以及 IMAP 是应用层协议中的例子。Protocols 实现了 IProtocol 接口,它包含如下的方法:

我们最好以一个例子来说明 reactor、protocols 以及 transports 这三者之间的关系。以下 是完整的 echo 服务器和客户端的实现,首先来看看服务器部分:

接着是客户端部分:

运行服务器端脚本将启动一个 TCP 服务器,监听端口8000上的连接。服务器采用的是 Echo 协议,数据经 TCP transport 对象写出。运行客户端脚本将对服务器发起一个 TCP 连接,回显服务器端的回应然后终止连接并停止 reactor 事件循环。这里的 Factory 用来对连接的双方生成 protocol 对象实例。两端的通信是异步的,connectTCP 负责注册回调函数到 reactor 事件循环中,当 socket 上有数据可读时通知回调处理。

Applications

Twisted 是用来创建具有可扩展性、跨平台的网络服务器和客户端的引擎。在生产环境中,以标准化的方式简化部署这些应用的过程对于 Twisted 这种被广泛采用的平台来说是非常重要的一环。为此,Twisted 开发了一套应用程序基础组件,采用可重用、可配置的方式来部署 Twisted 应用。这种方式使程序员避免堆砌千篇一律的代码来将应用程序同已有的工具整合在一起,这包括精灵化进程(daemonization)、日志处理、使用自定义的 reactor 循环、对代码做性能剖析等。

应用程序基础组件包含4个主要部分:服务(Service)、应用(Application)、配置管理(通过 TAC 文件和插件)以及 twistd 命令行程序。为了说明这个基础组件,我们将上一节的 Echo 服务器转变成一个应用。

Service

Service 就是 IService 接口下实现的可以启动和停止的组件。Twisted 自带有 TCP、FTP、HTTP、SSH、DNS 等服务以及其他协议的实现。其中许多 Service 都可以注册到单独的应用中。IService 接口的核心是:

我们的 Echo 服务使用 TCP 协议,因此我们可以使用 Twisted 中 IService 接口下默认的 TCPServer 实现。

Application

Application 是处于最顶层的 Service,代表了整个 Twisted 应用程序。Service 需要将其自身同 Application 注册,然后就可以用下面我们将介绍的部署工具 twistd 搜索并运行应用程序。我们将创建一个可以同 Echo Service 注册的 Echo 应用。

TAC 文件

当在一个普通的 Python 文件中管理 Twisted 应用程序时,需要由开发者负责编写启动和停止 reactor 事件循环以及配置应用程序的代码。在 Twisted 的基础组件中,协议的实现都是在一个模块中完成的,需要使用到这些协议的 Service 可以注册到一个 Twisted 应用程序配置文件中(TAC 文件)去,这样 reactor 事件循环和程序配置就可以由外部组件来进行管理。

要将我们的 Echo 服务器转变成一个 Echo 应用,我们可以按照以下几个简单的步骤来完成:

  1. 将 Echo 服务器的 Protocol 部分移到它们自己所归属的模块中去。
  2. 在 TAC 文件中:
    •      创建一个 Echo 应用。
    •      创建一个 TCPServer 的 Service 实例,它将使用我们的 EchoFactory,然后同前面创建的应用完成注册。

管理 reactor 事件循环的代码将由 twistd 来负责,我们下面会对此进行讨论。这样,应用程序的代码就变成这样了:

echo.py 文件:

echo_server.tac 文件:

twistd

twistd(读作“twist-dee”)是一个跨平台的用来部署 Twisted 应用程序的工具。它执行 TAC 文件并负责处理启动和停止应用程序。作为 Twisted 在网络编程中具有“内置电池”能力的一部分,twistd 自带有一些非常有用的配置标志,包括将应用程序转变为守护进程、定义日志文件的路径、设定特权级别、在 chroot 下运行、使用非默认的 reactor,甚至是在 profiler 下运行应用程序。

我们可以像这样运行这个 Echo 服务应用:

在这个简单的例子里,twistd 将这个应用程序作为守护进程来启动,日志记录在 twistd.log 文件中。启动和停止应用后,日志文件内容如下:

通过使用 Twisted 框架中的基础组件来运行服务,这么做使得开发人员能够不用再编写类似守护进程和记录日志这样的冗余代码了。这同样也为部署应用程序建立了一个标准的命令行接口。

Plugins

对于运行 Twisted 应用程序的方法,除了基于 TAC 文件外还有一种可选的方法,这就是插件系统。TAC 系统可以很方便的将 Twisted 预定义的服务同应用程序配置文件注册,而插件系统能够方便的将用户自定义的服务注册为 twistd 工具的子命令,然后扩展应用程序的命令行接口。

使用插件系统时:

  1. 由于只有 plugin API 需要保持稳定,这使得第三方开发者能很容易地扩展软件。
  2. 插件发现能力已经集成到系统中了。插件可以在程序首次运行时加载并保存,每次程序启动时会重新触发插件发现过程,或者也可以在程序运行期间反复轮询新插件,这使得在程序已经启动后我们还可以判断是否有新的插件安装上了。

当使用 Twisted 插件系统来扩展软件时,我们要做的就是创建 IPlugin 接口下实现的对象并将它们放到一个特定的位置中,这里插件系统知道该如何去找到它们。

我们已经将 Echo 服务转换为一个 Twisted 应用程序了,而将其转换为一个 Twisted 插件也是非常简单直接的。在我们之前的 Echo 模块中,除了包含有 Echo 协议和 EchoFactory 的定义之外,现在我们还要添加一个名为 twistd 的目录,其中还包含着一个名为 plugins 的子目录,这里正是我们需要定义 echo 插件的地方。通过这个插件,我们可以启动一个 echo 服务,并将需要使用的端口号作为参数指定给 twistd 工具。

现在,我们的 Echo 服务器将作为一个服务选项出现在 twistd –help 的输出中。运行 twistd echo –port=1235将在端口1235上启动一个 Echo 服务器。

Twisted 还带有一个可拔插的针对服务器端认证的模块 twisted.cred,插件系统常见的用途就是为应用程序添加一个认证模式。我们可以使用 twisted.cred 中现成的 AuthOptionMixin 类来添加针对各种认证的命令行支持,或者是添加新的认证类型。比如,我们可以使用插件系统来添加基于本地 Unix 密码数据库或者是基于 LDAP 服务器的认证方式。

twistd 工具中附带有许多 Twisted 所支持的协议插件,只用一条单独的命令就可以完成启 动服务器的工作了。这里有一些通过 twistd 启动服务器的例子:

这条命令将在8080端口启动一个 HTTP 服务器,在当前目录中负责处理静态和动态页面请求。

这条命令在端口5553上启动一个 DNS 服务器,解析指定的文件 hosts 中的域名,这个文件的内容格式同/etc/hosts 一样。

这条命令在端口2222上启动一个 SSH 服务器。ssh 的密钥必须独立设定。

这条命令启动一个 ESMTP POP3 服务器,为本地主机接收邮件并保存到指定的 emails 目录下。

我们可以方便的通过 twistd 来搭建一个用于测试客户端功能的服务器,但它同样是可装载的、产品级的服务器实现。

在部署应用程序的方式上,Twisted 通过 TAC 文件、插件以及命令行工具 twistd 的部署方式已经获得了成功。但是有趣的是,对于大多数大型 Twisted 应用程序来说,部署它们仍然需要重写一些这类管理和监控组件;Twisted 的架构并没有对系统管理员的需求呈现出太多的友好性。这也反映了一个事实,那就是对于系统管理员来说 Twisted 历来就没有太多架构可言,而这些系统管理员才是部署和维护应用程序的专家。在这方面,Twisted 在未来架构设计的决策上需要更积极的征求这类专家级用户的反馈意见。

反思与教训

Twisted 最近刚刚渡过了其10周年的诞辰。自项目成立以来,由于受2000年早期的网络游戏启发,目前的 Twisted 已经在很大程度上实现了作为一个可扩展、跨平台、事件驱动的网络引擎的目标。Twisted 广泛使用于生产环境中,从 Google、卢卡斯电影到 Justin.TV 以及 Launchpad 软件协作平台都有在使用。Twisted 中的服务器端实现是多个开源软件的 核心,包括 BuildBot、BitTorrent 以及 TahoeLAFS。

Twisted 从最初开发到现在,其架构已经经历了几次大的变动。Deferred 对象作为一个关键部分被增加了进来。如前文所述,这是用来管理延后的结果以及相应的回调链。还有一个重要的部分被移除掉了,在目前的实现中已经几乎看不到任何影子了,这就是 Twisted 应用持久化(Twisted Application Persistence)。

Twisted 应用持久化

Twisted 应用持久化(TAP)是指将应用程序的配置和状态保存在一个 pickle 中。要运行采 用了这种方案的应用需要两个步骤:

  1. 使用 mktap 工具创建一个代表该应用的 pickle(该工具现已废弃不用)。
  2. 使用 twistd 命令行工具进行 unpickle 操作,然后运行该应用。

这个过程是受 Smalltalk images 的启发,因为我们讨厌那种临时性的且难以使用的专用配置语言,不希望它们在项目中不断扩散。我们更希望在 Python 中表示配置的细节。

很快,TAP 文件就引入了不必要的复杂性。修改 Twisted 中的类并不会使 pickle 中这些类的实例得到改变。在 pickle 对象上使用新版本的类方法或属性时可能会使整个应用崩溃。因此“升级版”的概念得以引入,即将 pickle 对象升级到新的 API 版本。但这就会出现升级版本的矩阵化现象,出现各种不同版本的 pickle 对象,因此单元测试时需要维护涵盖所有可能的升级路径。想全面地跟踪所有的接口变化依然很难,而且容易出错。

TAP 以及相关的组件全部被废除了,最终从 Twisted 中完全剔除掉。取而代之的是 TAC 文件和插件系统。TAP 这个缩写被重新定义为 Twisted Application Plugin(Twisted 应用插 件),如今已经很难在 Twisted 中找到 pickle 系统的踪迹了。

我们从 TAP 的惨败中得到的教训是:如果可维护性要达到合理化的程度,则持久性数据就需要有一个明确的模式。更一般的是,我们学到了如何为项目增加复杂度:为了解决某个问题而需要引入一个新系统时,我们要正确理解这个方案的复杂性,并经过测试。新系统所带来的价值应该明显大于其复杂性。确保了这一点之后我们才能将方案付诸于项目中。

web2:重构的教训

虽然这基本上不属于架构设计上的决策,但从项目管理的角度来看,重写 Twisted 的 Web实现对于 Twisted 的外在形象以及维护者对代码库中其他部分做架构改善的能力却有着长 远的影响,因此这里值得我们简单讨论一下。

在2000年中期,Twisted 的开发者决定完全重写 twisted.web API,在 Twisted 代码库中将其作为一个单独的项目实现,这就是 web2。web2将包含许多针对原有 twisted.web 的改善和 提升,包括完全支持 HTTP1.1,以及对流式数据的 API 支持。

web2最初只是试验性的项目,但最终被大型项目所采用,甚至意外的得以在 Debian 系统上打包发布。twisted.web 和 web2 的开发一直并行持续了多年,新用户常常被这两个并行的项目搞混,关于究竟应该使用哪种实现缺乏明确的提示,这使得新用户很沮丧。转换 到 web2 的情况从未出现,终于在2011 年开发者将其从代码库中移除,官方主页上再也看不到它了。web2中做出的一些改进也被慢慢地移植回 twisted.web 中。

Twisted 获得了难以导航且结构混乱,容易使新开发者感到困惑的“恶名”,这个印象部分归功于 web2。以至于数年之后,Twisted 社区仍然在同这种不和谐的名声做斗争。

我们从 web2 中汲取的教训是:从头开始重构一个项目通常都是糟糕的主意。但如果必须这么做,请确保开发者社区能够懂得这么做的长远意义,而且在用户社群中要有明确的选择该使用哪种实现。

如果 Twisted 能够倒退回 web2 的时代,开发者们应该会对 twisted.web 做一系列向后兼容型的修改而不是去重构。

紧跟互联网的浪潮

我们使用互联网的方式还在持续演进中。把多种协议的实现作为软件核心的一部分,这个技术决策使得 Twisted 背负了维护这些协议的沉重负担。随着标准的改变以及对新协议的采纳,原有的实现必须跟着演进,同时需要严格的保证向后兼容性。

Twisted 基本上是一个志愿者驱动型的项目,项目发展的限制因素不是技术社区的热情,而在于志愿者的时间。比如说,1999年的 RFC 2616 中定义了 HTTP 1.1 规范,而在 Twisted 的 HTTP 协议实现中增加对 HTTP 1.1 的支持却在2005年才开始,等到完成时已经是 2009 年了。1998年 RFC 2460 中定义了对 IPv6 的支持,而 Twisted 对其的支持还在进行中,但是直到 2011 年都未能合并进去。

随着所支持的操作系统的接口改变,实现也要跟着演进。比如,epoll 事件通知机制是在2002 年加入到 Linux 2.5.44 版中的,Twisted 随之也发展出基于 epoll 的 reactor 事件循环来利用这个新的系统接口。2007年时,苹果公司发布的 OS 10.5 Leopard 系统中,系统调用 poll 的实现居然不支持外设,对于苹果公司来说这个问题足以让他们在系统自带的 Python 中屏蔽掉 select.poll 接口。Twisted 不得不自行解决这个问题,并从那时起就对用户提供文档说明。

有时候,Twisted 的开发并没有紧跟网络世界的变化,有一些改进被移到核心层之外的程序库中去了。比如 Wokkel project,这是对 Twisted 的 Jabber/XMPP 支持的改进合集,已经作为“ 待合入”的独立项目有几年之久了,但还没有看到合入的希望。在 2009 年也曾经尝试过增加 WebSocket 到 Twisted 中,因为浏览器已经开始采纳对新协议的支持了。但开发计划最终却转到其他外部项目中去了,因为开发者们决定暂不包含新的协议,直到 IETF 把它从草案转变成标准以后再说。

所有这一切都在说明,库和附加组件的扩散有力的证明了 Twisted 的灵活性和可扩展性。通过采用严格的测试驱动开发策略以及文档化和编码规范标准,这样做能够帮助项目避免出现需要“回炉”的情况。在维护大量所支持的协议和平台的同时保持向后兼容性。Twisted 是一个成熟、稳定的项目,并继续保持有非常活跃的开发状态。

Twisted 期待着在下一个十年里成为你遨游互联网的引擎。

相关文章

发表评论

电子邮件地址不会被公开。 必填项已用*标注