Archive for the ‘Twisted’ Category

纠结及其他

Monday, January 5th, 2009

07年初进入现在的公司,不久后用twisted写了一个http开发框架,提供内部系统之间的http接口服务。说是框架,其实很底层,基本相当于http服务器+URL分发。所以,开发的目的也很明确,就是性能。

在一年多的应用以来,发现很多问题,依次解决,改进了许多。现在提供一些性能参数吧,给需要对python开发系统,进行性能估算的朋友一点数据。

服务器就是四核志强 E5420,2.50GHz,内存4G。除了日志基本没有硬盘IO。

跑hello world的时候,50并发可以达到1800req/s。关闭accesslog日志以后,可以达到2600req/s。当然,这个服务器只能用一个CPU核心。

在应用中,是4个twistd实例在load balance后面一起干活,如上述配置的一台服务器。刚才统计出来的,过去24小时中处理完成约2000万次的http接口调用。调用的后面是有数据库查询的,一般来说都不只一次查询。实际看到的速度接近峰值的一小时内最高可以做约140万次http接口调用,折合389req/s。

总的来说,数据库依旧是几乎所有系统的瓶颈。

再就是高速运行情况下的问题。

默认的twisted使用的是select(),并发性能不要太指望。甚至并发数稍微高一点的时候就会报什么file descriptor out of range of select()的错误。总之默认的twisted能承受的并发数很有限。

twisted是有epoll支持的,你可以在启动twistd时选择epollreactor。不过貌似并不稳定。使用epoll()方式以后,我的印象大约是每几千个请求就会报出一个文件描述符的什么错误。另外,就是在关闭程序时有个语法错误,这个绝对是twisted的bug。从2.5.0升级到8.1.0之后,关闭服务器那个报出的bug依然存在。而twisted-8.1.0在启动时还要很傻的在安装位置写一个什么缓存文件。普通用户根本没有权限,所以每次启动服务器时都会报错,但是服务器可以照常启动。

我对twisted的容忍基本限于如此了。这段时间感觉libevent很优雅,假如我的雅兴还没过去,我会尝试给libevent附带的http服务器加上python binding,也许性能会更高。

纠结啊,纠结

Tuesday, July 29th, 2008

豆瓣上的twisted小组是我建立的,发现一个朋友把twisted称为“纠结”,发现很切题,我喜欢。

那个累死人的项目接近尾声以来,帖子越来越水了。

twisted线程池的一种实现方式

Monday, July 28th, 2008

一个具有并发控制的计数器,控制当前线程数量。线程数不足时就用reactor.callInThread()启动线程。并任线程自然结束。当然,需要在让主控函数形成循环来不停的生成新的线程。这种循环比较适合用reactor.callLater()来实现,而不是用while True的循环。毕竟twisted是事件驱动的,如果一个函数死循环了,那么其他函数就没有执行的机会了。

在对twisted中使用线程,多次尝试之后,我就是使用了上面的方法。感觉比以前靠谱了很多。

不写代码,使用twistd实现ftp服务器

Sunday, July 27th, 2008

用twisted很久了,常常惊叹于其内置的N多小功能,每次发现都有惊喜。今天调试程序期间看了一眼twistd这个程序的参数,发现ftp等等一堆子命令。尝试一番后还真的启动了一个临时的ftp服务器,对调试有很大帮助。使用方法如下:

twistd ftp

这时启动的ftp服务器的HOME目录为/usr/local/ftp,匿名用户名为anonymous,密码为随便的一个邮箱地址,端口号为2121。

可以通过如下命令查看此服务器的其他参数:

twistd ftp –help

比较有用的就是–root,来指定登陆的根目录。还有就是–port指定端口号。

不过要注意的是,这个服务器仅用于调试,不要应用于产品环境,因为它并没有做安全性方面的工作。

在Windows下会抛出一个ImportError异常。虽然我尝试修改源代码,不过看来还是不支持。再次鄙视一下M$。

在Twisted中使用线程

Wednesday, April 16th, 2008

在Twisted中使用线程

译者: gashero

目录

1   以线程安全的模式运行代码

Twisted中的大部分代码都不是线程安全的。例如protocol向transport写入数据就不是线程安全的。因此我们需要一种方法来在主事件循环中进行调度。者可以使用函数 twisted.internet.interfaces.IReactorThreads.callFromThread 来实现:

from twisted.internet import reactor

def notThreadSafe(x):
    """做一些非线程安全的事情"""
    # ...

def threadSafeScheduler():
    """以线程安全方式运行"""
    reactor.callFromThread(notThreadSafe,3) #将会运行notThreadSafe(3)在主时间循环中

Note

译者注

callFromThread 意指从线程调用,这个方法是供线程调用的,并且使其指定的函数加入到主事件循环中执行。比如worker线程可以调用此方法将提交结果的函数加入到主事件循环中。这样就可以确保多线程的运行worker,而有可以使用线程安全的方式提交结果。

2   在线程中运行代码

有时我们希望在线程中运行代码,比如阻塞的存取API。Twisted提供了这样做的方法在 IReactorThread API 中。附加的工具在包 twisted.internet.threads 中提供。这些方法允许我们把任务排队以后在线程池中运行。

例如,在线程中运行一个函数,我们可以:

from twisted.internet import reactor

def aSillyBlockingMethod(x):
    import time
    time.sleep(2)
    print x

# 在线程中运行
reactor.callInThread(aSillyBlockingMethod,"2 secodns have passed")

Note

译者注

callInThread 意指在线程中运行,调用该方法需要在主事件循环中,而执行其传入的函数则是在线程中。可以与上一节提供的 callFromThread`结合使用,即在worker线程函数中调用 `callFromThread 提交结果。

3   工具函数

工具函数作为 twisted.internet.reactor 的一部分API提供,但是并不是在 twisted.internet.threads 中实现的。

如果我们有多个方法需要在线程中以队列方式运行,我们可以做:

from twisted.internet import threads

def aSillyBlockingMethodOne(x):
    import time
    time.sleep(2)
    print x

def aSillyBlockingMethodTwo(x):
    print x

# 排队后在线程中运行两个方法
commands=[(aSillyBlockingMethodOne,["calling first"])]
commands.append((aSillyBlockingMethodTwo,["and the second"],{}))
threads.callMultipleInThread(commands)

如果我们希望得到函数的运行结果,那么我们可以使用Deferred:

from twisted.internet import threads

def doLongCalculation():
    # ... do long calculation here ...
    return 3

def printResult(x):
    print x

# 在线程中运行,并且通过 defer.Deferred 获取结果
d=threads.deferToThread(doLongCalculation)
d.addCallback(printResult)

如果你希望在reactor线程中调用一个方法,并且获取结果,你可以使用 blockingCallFromThread

from twisted.internet import threads,reactor,defer
from twisted.web.client import getPage
from twisted.web.error import Error

def inThread():
    try:
        result=threads.blockingCallFromThread(reactor,getPage,"http://twistedmatrix.com/")
    except Error,exc:
        print exc
    else:
        print result
    reactor.callFromThread(reactor.stop)

reactor.callInThread(inThread)
reactor.run()

blockingCallFromThread 将会返回对象或者抛出异常,或者通过抛出到传递给他的函数。如果传递给它的函数返回了一个Deferred,他会返回Deferred回调的值或者抛出异常到errback。

4   管理线程池

线程池是在 twisted.python.threadpool.ThreadPool 中实现的。

我们可以修改线程池的大小,增加或者减少可用线程的数量,可以这么做:

from twisted.internet import reactor
reactor.suggestThreadPoolSize(30)

缺省的线程池大小依赖于使用的reactor,缺省的reactor使用最小为5个,最大为10个。在你改变线程池尺寸之前,确保你理解了线程和他们的资源使用方式。

在Twisted2.5.0中使用线程

译者: gashero

刚才翻译了对应版本8.0.0的Twisted的线程指南,但是我还是在用2.5.0,所以这里只记录与8.0.0的差异,不做重新翻译。

当你开始使用线程之前,确保你在启动程序的时候使用了如下:

from twisted.python import threadable
threadable.init()

这回让Twisted以线程安全的方式初始化,不过仍然注意,Twisted的大部分仍然不是线程安全的。

以线程安全的方式运行代码中初始化多了两行:

from twisted.python import threadable
threadable.init(1)

2.5.0的文档中没有 blockingCallFromThread 的例子。也许根本就没有这个方法。

实际我下载文档的版本是2.4.0,不过应该与2.5.0一样的。

使用Twisted创建SSL-WEB服务器

Tuesday, January 22nd, 2008

使用Twisted创建SSL-WEB服务器

翻译: gashero

Twisted web howto中并没有提及如何创建SSL加密的WEB服务器。其实这个过程很简单,只是需要一些信息而已,所以这里讲一下。

首先,创建SSL服务器需要一个私钥文件,和一个服务器证书。这里假设你是个开发者,只想要一个测试系统,所以也没必要去搞一个正式的公共证书来。OpenSSL就是你想要的。首先,我们生成一个私钥文件:

$ openssl genrsa > privkey.pem

然后生成一个自签名的SLL证书:

$ openssl req -new -x509 -key privkey.pem -out cacert.pem -days 1000

好了,第一个挑战已经搞定了。下一步就是创建服务器了。假设你已经有了一个twisted web服务器,所以你也知道该在哪个端口监听。我们导入 ssl 模块,创建一个SSL服务器上下文,然后调用 reactor 的 listenSSL 方法:

from twisted.internet import reactor,ssl
sslContext=ssl.DefaultOpenSSLContextFactory(
    '/path/to/privkey.pem',
    '/path/to/cacert.pem',
)
reactor.listenSSL(port,site,contextFactory=sslContext)

同样在 twisted.application 中也很容易创建带有上下文的站点,不过这里就懒得介绍了。

gashero附加的内容:

需要使用ssl的功能还需要另外安装一堆东西。包括openssl(0.9.8g)、pyOpenSSL(0.6.0)。安装openssl时,debian源里面没有dev版本,只能自己编译,注意配置时要修改安装路径:

$ ./config --prefix=/usr/local

这样以后就可以正确的编译安装pyOpenSSL了,否则默认情况下找不到OpenSSL。

Twisted的WEB开发

Sunday, September 30th, 2007

Twisted的WEB开发

作者: gashero <harry.python@gmail.com>

目录

1   简介

在WEB开发中,偶尔需要对HTTP协议更多底层细节进行控制,这时的django/web.py等等显然无法满足要求,所以只好求助于Twisted了。使用Twisted进行WEB开发,其实更合适的叫法应该是基于HTTP服务器的开发,因为Twisted相对底层,所以可以控制的东西也比较底层。

在Twisted的技术体系中,这个WEB开发实际上要涉及到HTTPChannel、HTTPFactory、Request三个层次的开发,以下详诉。

HTTP协议参考 RFC2616

2   Twisted技术体系

Twisted技术体系包含2个层次:协议和工厂。协议负责连接成功以后对交互的处理,而工厂则是负责连接过程。在HTTP协议中,连接之后还有个生成HTTP请求报文的过程,所以构造出了一个Request对象来处理具体的一个HTTP请求的报文。

在HTTP中的请求报文处理对象是 twisted.web.http.Request 类;HTTP的协议类是 twisted.web.http.HTTPChannel ;HTTP工厂是 twisted.web.http.HTTPFactory 。

3   一个简单的例子

节选自《Twisted网络编程必备》:

from twisted.web import http

class MyRequestHandler(http.Request):
    pages={
        '/':'<h1>Home</h1>Home Page',
        '/test':'<h1>Test</h1>Test Page',
        }
    def process(self):
        if self.pages.has_key(self.path):
            self.write(self.pages[self.path])
        else:
            self.setResponseCode(http.NOT_FOUND)
            self.write("<h1>Not Found</h1>Sorry, no such page.")
        self.finish()

class MyHttp(http.HTTPChannel):
    requestFactory=MyRequestHandler

class MyHttpFactory(http.HTTPFactory):
    protocol=MyHttp

if __name__=="__main__":
    from twisted.internet import reactor
    reactor.listenTCP(8000,MyHttpFactory())
    reactor.run()

与其他很多框架不同,TwistedWEB只有一个核心的请求处理类Request,各个针对不同的URL的请求也要通过这里来分发。而这个类只要重载 process() 方法就可以了,期间的很多数据都可以通过self来引用。

请求的处理流程也就是判断对不同URL的不同处理,然后向客户端写入响应信息,并在最后调用关闭请求。步骤如下:

  1. 过滤URL, self.path
  2. self.write(data) 向客户端写入数据
  3. self.finish() 关闭响应

4   Twisted WEB Request参考

来自分析 twisted.web.http.http.py 源代码。

4.1   请求

包含请求的数据,这里都是指Request类的成员。

channel :包含上级的HTTP协议对象。

transport :通信对象。

method :HTTP方法,如GET和POST。

uri :全部请求的URI。

path :具体的请求路径,不含参数。

args :请求参数,包括URL参数和POST参数。格式如 {'key':['val1','val2'],}

received_headers :请求报文的头字段。

received_cookies :请求报文的cookie。

content :请求报文的实体主体,文件对象。

clientproto :发出请求的客户端的HTTP版本。

client :?

host :?

getHeader(key) :获取请求的头字段。

getCookie(key) :获取请求的cookie。

getAllHeaders() :所有请求的头字段字典,就是返回received_headers。

getRequestHostname() :请求的host字段,不含端口号。

getHost() :原始请求的通信地址,返回host。

getClientIP() :获取客户端IP。

getUser() :获取basic验证中的用户名。

getPassword() :获取basic验证中的密码。

getClient() :?

4.2   响应

包含响应的数据,这里都是Request类的成员。

headers :字典,包含响应报文的头字段。

cookies :字典,包含响应报文的cookie。

finish() :结束响应报文。

write(data) :向客户端发送数据,经过了HTTP包装了。

addCookie(k,v,expires=None,domain=None,path=None,max_age=None,comment=None,secure=None) :为响应报文添加一个cookie。

setResponseCode(code,message=None) :设置响应代码,code参考常量定义。

setHeader(k,v) :设置头字段。

redirect(url) :HTTP重定向。

setLastModified(when) :设置缓存超时,when的值为长整型的那个时间。

setETag(etag) :设置缓存标志,用于在内容更改时让用户有所发觉。

setHost(host,port,ssl=0) :设置请求地址。用于代理服务器的重定向。

4.3   常量

没有响应主体的code:

NO_BODY_CODES=(204,304)

responses=RESPONSES :字典,保存了各个响应码的对应提示信息。

响应报文中的响应码:

OK=200 :请求处理成功,最常见的响应代码,但是正因为常见,所以默认就是这个了,也无须设置到setResponseCode。

NOT_MODIFIED=304 :请求的资源没有没有修改过,用于浏览器缓存。

BAD_REQUEST=400 :请求报文有语法错误。

UNAUTHORIZED=401 :尚未认证,要求用户输入认证信息。

FORBIDDEN=403 :禁止访问。

NOT_FOUND=404 :请求的资源不存在。

INTERNAL_SERVER_ERROR=500 :服务器内部错误。

NOT_IMPLEMENTED=501 :该功能尚未实现。

BAD_GATEWAY=502 :请求路径错误。

4.4   HTTPChannel

构造函数无参数,处理HTTP报文。

requestFactory=Request :指定了请求报文处理工厂。

4.5   HTTPFactory

__ini__(logPath=None,timeout=60*60*12) :构造函数可以设置日志和超时。

buildProtocol(addr) :内部的构造协议对象的方法,不要调用。

protocol=HTTPChannel :设置协议对象。

5   比较完善的开发模式

建立一个Request类的子类作为请求工厂,或者说请求发布器,其中有识别不同的URL并的能力,通过字典找到该URL对应的函数,调用这个函数并传递self参数。每个具体的请求处理函数也只有1个request参数,返回数据都是直接写入request.write()中。

一般来说请求工厂的process()中需要设置响应类型,如网页的:

self.setHeader("Content-Type","text/html; charset=GB2312")

同时也要在没有对应的URL时告知客户端找不到:

self.setResponseCode(http.NOT_FOUND)
self.write("<h1>Not Found</h1>Sorry, no such page.")
self.finish()

至于self.finish()放在各个请求处理函数中还是放在process(),就是个人爱好问题了。比较推荐放在process()中。

提取请求参数的重点在request.args字典,每个键都是映射到一个列表,为了适应HTTP提交中一个键对应多个值的情况,当然,你也可以只取第一个值。

6   以resource方式提供WEB资源

  1. 每个资源都是 twisted.web.resource.Resource 的子类
  2. 可以自己定义构造函数
  3. 要重载 render(self,request) 方法来响应请求
  4. render 方法中的request对象实际就是Request的实例

一个例子:

from twisted.web import resource,static,server

class HomePage(resource.Resource):

    def render(self,request):
        request.write("Home Page")
        return

    def getChild(self,path,request):
        return AnotherPage() #另外一个Resource的子类

if __name__=="__main__":
    from twisted.internet import reactor
    root=resource.Resource()
    root.putChild('',HomePage())
    root.putChild('color',ColorRoot())
    root.putChild('style.css',static.File('style.css'))
    site=server.Site(root)
    reactor.listenTCP(8000,site)
    reactor.run()

可以通过各个Resource的构造参数传入path参数,用以后来寻找下级Resource的参数。

Note

关于Resource还有很多细节,但是对本文意义不大,所以略。

7   总结

总的来说,用Twisted来开发更适合于开发个框架,而不是直接做WEB应用,有如一直都很少有人直接用 mod_python 来开发WEB应用一样。

使用twisted.python.log日志

Monday, September 10th, 2007

使用twisted.python.log日志

目录

版本: 2.4.0

1   简单使用

Twisted提供了一个简单而且可移植的日志系统叫做 twisted.python.log 。它包含3个函数:

msg

记录一条信息,例如:

from twisted.python import log
log.msg("hello, world")

err

把错误写入日志,包括traceback信息。你可以传递一个 failure 或者异常的实例,甚至什么都没有。如果传递其他的东西则会通过 repr() 函数获得字符串来显示。如果什么都不传递则会自动构造一个 Failure 实例,一般用于 except 子句:

try:
    x=1/0
except:
    log.err() #会自动记录ZeroDivisionError

startLogging

通过一个类似文件对象来开始日志,例如:

log.startLogging(open("/var/log/foo.log",'w'))

或者:

log.startLogging(sys.stdout)

缺省条件下,startLogging会同时将输出重定向到sys.stdout和sys.stderr。你可以在startLogging中设置setStdout=False来禁用这个功能。

1.1   日志与twistd

如果你使用twistd来运行你的程序作为后台进程,则他会自动托管startLogging,甚至还会自动轮训日志。查看 twistd and tac 一节或twistd的man手册了解更多。

1.2   日志文件

twisted.python.logfile 模块提供了一些可以同startLogging共同使用的类,例如 DailyLogFile ,提供了以天为单位的日志轮询。

2   编写日志监视器

日志监视器是Twisted日志系统的基础。一个日志监视器的例子是供startLogging使用的 FileLogObserver ,可以把事件写入文件中。一个日志监视器是可调用的,并且只接受一次字典作为参数。随后你可以用它来接收所有的日志事件(当然也会给其他日志监视器):

twisted.python.log.addObserver(yourCallable)

字典至少有2个项目:

message

日志信息,一个列表或字符串,被log.msg或log.err传递过来的。

isError

一个布尔值,如果为True时就是从log.err过来的。如果为True说明字典当中还会有个Failure对象。

其他项目是自动被加入的:

printed

这条信息是从sys.stdout中捕获的,例如这条信息是从print输出的。如果isError同样为True,则是从sys.stderr来的。

你可以通过 log.msg 或 log.err 传递附加项目到事件字典。标准的日志监视器将会忽略他们不用的字典参数。

Note

注意

  • 不要在日志监视器中抛出异常,否则会挂掉。
  • 不要在日志监视器中阻塞,尤其是在主线程中。这将会导致很多问题。
  • 日志监视器需要线程安全。