Archive for September 30th, 2007

在基于Pylons的服务器上测试使用DBUtils前后的性能对比

Sunday, September 30th, 2007

在基于Pylons的服务器上测试使用DBUtils前后的性能

目录

为了测试使用DBUtils实现的数据库连接池的性能,在Pylons写了一个测试服务,并将服务器架设在lab2服务器上,使用apache ab进行服务器压力测试。

1   测试环境

lab2服务器,MySQL 4.1.20,Pylons 0.9.4.1,apache ab 2.0.59。

为了确保测试的可靠性,每次测试之前都重启服务器。

在Pylons上假设的应用有3个URL用于测试,分别如下:

URL 说明
/testdb/test1 不使用连接池,每次访问都建立对数据库的连接
/testdb/test2 使用DBUtils.PersistentDB连接池,线程专用连接
/testdb/test3 使用DBUtils.PooledDB连接池,线程间共享连接

测试代码如下:

from helloworld.lib.base import *
import time
import random
import MySQLdb
import DBUtils.PersistentDB
import DBUtils.PooledDB

conn_kwargs={'host':'192.168.1.239','user':'ro','passwd':'','db':'test','port':3306}
sql="""SELECT * FROM test_table WHERE id=%d"""
persist=DBUtils.PersistentDB.PersistentDB(dbapi=MySQLdb,maxusage=1000,**conn_kwargs)
pooled=DBUtils.PooledDB.PooledDB(dbapi=MySQLdb,maxusage=1000,**conn_kwargs)

def query(conn):
    cur=conn.cursor()
    cur.execute(sql%(random.randint(1,1000)))
    data=cur.fetchall()
    cur.close()
    return data

class TestdbController(BaseController):
    def index(self):
        return Response('index')

    def test1(self):
        conn=MySQLdb.connect(**conn_kwargs)
        data=query(conn)
        conn.close()
        return Response(str(data))

    def test2(self):
        conn=persist.connection()
        data=query(conn)
        conn.close()
        return Response(str(data))

    def test3(self):
        conn=pooled.connection()
        data=query(conn)
        conn.close()
        return Response(str(data))

2   10线程并发

一共10000次测试,测试所用命令如下:

./ab -n 10000 -c 10 http://192.168.1.239:5000/testdb/test*

测试结果如下:

测试目标 总时间 请求处理速度 平均处理时间 错误率 100% 99% 90% 50%
/test1 32.764 305.22 32.764 ms 10.32% 776 237 40 29
/test2 27.895 358.49 27.895 ms 10.00% 3032 222 31 22
/test3 29.513 338.83 29.513 ms 10.46% 3037 58 36 27

3   50线程并发

一共10000次测试,测试所用命令如下:

./ab -n 10000 -c 50 http://192.168.1.239:5000/testdb/test*

测试结果如下:

测试目标 总时间 请求处理速度 平均处理时间 错误率 100% 99% 90% 50%
/test1 32.786 305.00 163.932 ms 9.48% 21031 3048 49 31
/test2 27.884 358.62 139.424 ms 9.65% 9227 3032 33 22
/test3 29.256 341.81 146.281 ms 9.88% 3654 328 151 136

4   远程10线程并发

一共10000次测试,测试所用命令如下:

./ab -n 10000 -c 10 http://192.168.1.241:5000/testdb/test*

测试结果如下:

测试目标 总时间 请求处理速度 平均处理时间 错误率 100% 99% 90% 50%
/test1 24.891 401.75 24.891 ms 9.07% 3035 44 31 22
/test2 21.652 461.85 21.652 ms 9.86% 256 59 26 19
/test3 23.952 432.99 23.095 ms 9.59% 239 38 28 22

5   远程50线程并发

一共10000次测试,测试命令如下:

./ab -n 10000 -c 50 http://192.168.1.241:5000/testdb/test*

测试结果如下:

测试目标 总时间 请求处理速度 平均处理时间 错误率 100% 99% 90% 50%
/test1 24.915 401.36 124.575 ms 9.82% 9280 3033 53 27
/test2 21.521 464.66 107.607 ms 9.47% 9621 3022 32 20
/test3 22.808 438.45 114.038 ms 9.11% 9107 145 114 95

6   干扰因素

测试过程中发现,MySQL服务器的同时并发连接数一直没有超过10,所以在进行50线程并发操作时可能会出现一些干扰。

7   单线程测试

使用代码如下:

import time
import random
import MySQLdb
import DBUtils.PersistentDB
import DBUtils.PooledDB

conn_kwargs={'host':'192.168.1.239','user':'ro','passwd':'','db':'test','port':3306}
sql="""SELECT * FROM test_table WHERE id=%d"""
persist=DBUtils.PersistentDB.PersistentDB(dbapi=MySQLdb,maxusage=1000,**conn_kwargs)
pooled=DBUtils.PooledDB.PooledDB(dbapi=MySQLdb,maxusage=1000,**conn_kwargs)

def query(conn):
    cur=conn.cursor()
    cur.execute(sql%(random.randint(1,1000)))
    data=cur.fetchall()
    cur.close()
    return data

def print_now():
    print time.strftime("%H:%M:%S")
    return

def test1(times):
    print_now()
    for i in range(0,times):
        conn=MySQLdb.connect(**conn_kwargs)
        query(conn)
        conn.close()
    print_now()
    return

def test2(times):
    print_now()
    for i in range(0,times):
        conn=persist.connection()
        query(conn)
        conn.close()
    print_now()
    return

def test3(times):
    print_now()
    for i in range(0,times):
        conn=pooled.connection()
        query(conn)
        conn.close()
    print_now()
    return

8   单线程测试

执行10000次查询,进入Python交互模式,调用各个函数并传递执行次数,每次执行过后重启MySQL服务器:

# python -i ttss.py
>>> test1(10000)
18:59:30
18:59:40
>>> test2(10000)
19:00:16
19:00:19
>>> test3(10000)
19:00:46
19:00:49

可见查询次数太少,以致难以精确测定时间,所以执行100000次查询,过程如下:

# python -i ttss.py
>>> test1(100000)
19:01:57
_mysql_exceptions.OperationalError: (2003, "Can't connect to MySQL server on '192.168.1.239' (99)")

连续两次都出现异常,之后改为30000也是如此。出现这个异常之后数据库服务器不经过重启就无法再使用了。经过测试发生这种连接异常之后,还是可以使用mysql客户端登录本机的MySQL服务器的。所以改为20000次查询,过程如下:

# python -i ttss.py
>>> test1(20000)
19:06:47
19:07:07
>>> test2(20000)
19:28:23
19:28:28
>>> test3(20000)
19:29:27
19:29:34

测试远程连接MySQL服务器:

# python -i ttss.py
>>> test1(10000)
20:25:23
20:25:57
>>> test2(10000)
20:27:18
20:27:26
>>> test3(10000)
20:27:46
20:27:56

9   结论

总体上来看,使用了DBUtils之后数据库的访问性能有了很大的提高。

Python并行编程之 Parallel Python文档页

Sunday, September 30th, 2007

Parallel Python文档页

原文: http://www.parallelpython.com/content/view/15/30/

目录

1   PP 1.4.2 模块API

class

Server

class Server

并行Python SMP执行服务器类

方法定义:

__del__(self)

__init__(self,ncpus=’autodetect’,ppservers=(),secret=None,loglevel=30,logstream=<open file ‘<stderr>’,mode ‘w’>)

创建服务器实例

ncpus – 本机要启动的工作进程数量,如果省略则为处理器数量

ppservers – 可用的PP服务器

secret – 网络连接密码,省略则使用缺省密码,简易在所有连接中使用一个自定义密码

loglevel – logging模块的日志级别

logstream – 日志目标

使用ncpus=1则所有任务串行执行。要获取最好性能则使用’autodetect’值,或手动设置CPU数量。

get_ncpus(self)

获取工作进程数量

get_stats(self)

返回所有工作执行状态字典

print_stats(self)

打印工作执行状态,用于集群测试标准。

set_ncpus(self,ncpus=’autodetect’)

设置工作进程数量

ncpus – 工作进程数量,如果省略则设置为系统处理器数量

submit(self,func,args=(),depfuncs=(),modules=(),callback=None,callbackargs=(),group=’default’,globals=None)

提交函数到执行队列

func – 需要执行的函数

args – func函数的执行参数tuple

depfuncs – 待执行函数需要调用的函数tuple

modules – 需要被import导入的模块名字tuple

callback – 在函数执行完成之后的回调函数,参数为callbackargs+(result,)

callbackargs – 附加的回调函数参数

group – 工作组,用于当 wait(group) 被调用,等待工作组全部工作结束时

globals – 字典,所有模块和函数共享与导入的,例如 globals=globals()

wait(self,group=None)

等待工作组中所有工作结束,如果忽略group参数则等待所有工作结束

数据和其他属性:

default_port = 6000

default_secret = ‘epo20pdosl;dksldkmm’

Data

copyright = ‘Copyright (c) 2005-2007 Vitalii Vanovschi. All rights reserved’

version = ‘1.4.2’

2   快速开始,SMP

  1. 导入pp模块:

    import pp
  2. 启动PP执行服务器,按照处理器数量指定工作进程数:

    job_server=pp.Server()
  3. 提交需要进行并行计算的任务:

    f1=job_server.submit(func1,args1,depfuncs1,modules1)
    f2=job_server.submit(func2,args2,depfuncs2,modules2)
    f3=job_server.submit(func3,args3,depfuncs3,modules3)
    # ...etc...
  4. 取回结果:

    r1=f1()
    r2=f2()
    r3=f3()
    # ...etc...

查找如何使任务并行化,参考例子 例子

3   快速开始,集群

  1. 在各个计算节点上启动并行Python执行服务器:

    node-1> ./ppserver.py
    node-2> ./ppserver.py
    node-3> ./ppserver.py
  2. 导入pp模块:

    import pp
  3. 创建集群中计算节点列表(就是运行ppserver.py的机器):

    ppservers=("node-1","node-2","node-3")
  4. 使用计算节点列表和其他参数来启动执行服务器:

    job_server=pp.Server(ppservers=ppservers)
  5. 提交并行执行任务,同SMP的例子

  6. 取回结果,同SMP的例子

4   高级指南,集群

  1. 在各个计算节点上启动并行Python计算服务器,监听本地35000端口,仅接受密码正确的连接:

    node-1> ./ppserver.py -p 35000 -i 192.168.0.101 -s "mysecret"
    node-2> ./ppserver.py -p 35000 -i 192.168.0.102 -s "mysecret"
    node-3> ./ppserver.py -p 35000 -i 192.168.0.103 -s "mysecret"
  2. 导入pp模块,同SMP例子

  3. 创建集群中计算节点列表,就是运行ppserver.py的机器:

    ppservers=("node-1:35000","node-2:35000","node-3:35000")
  4. 启动pp执行服务器,同时指定集群列表和验证密码:

    job_server=pp.Server(ppservers=ppservers,secret="mysecret")
  5. 提交并行计算任务,同SMP例子

  6. 取回结果,同SMP例子

  7. 显示执行状态:

    job_server.print_stats()

5   命令行参数,ppserver.py

Usage: ppserver.py [-hd] [-i interface] [-p port] [-w nworkers] [-s secret]
Options:
-h                 : 显示这个帮助信息
-d                 : 调试
-i interface       : 监听所用的网口
-p port            : 监听端口
-w nworkers        : 工作进程数量
-s secret          : 认证密码

Python并行编程之 Parallel Python首页

Sunday, September 30th, 2007

Parallel Python首页

原文: http://www.parallelpython.com/

目录

1   简介

PP 是一个Python模块,提供了在SMP(多CPU或多核)和集群(通过网络连接的多台计算机)上并行执行Python代码的机制。轻量级,易于安装,并集成了其他软件。PP也是一个用纯Python代码实现的跨平台,开放源码模块。

2   功能

  • 在SMP和集群上并行执行Python代码
  • 易于理解和实现的基于工作的并行机制,便于把穿行应用转换成并行的
  • 自动构造最佳配置(默认时工作进程数量等同于系统处理器数量)
  • 动态处理器分配(允许运行时改变工作处理器数量)
  • 函数的工作缓存(透明的缓存机制确保后续调用降低负载)
  • 动态负载均衡(任务被动态的分配到各个处理器上)
  • 基于SHA的连接加密认证
  • 跨平台移植(Windows/Linux/Unix)
  • 开放源代码

3   开发动机

现代Python程序已经广泛的应用在商业逻辑,数据分析和科学计算等方面。其中广泛应用着SMP(多处理器或多核)和集群(通过网络连接的多台计算机),市场需要并行的执行的Python代码。

在SMP计算机上编写并行程序最简单的方法是使用多线程。尽管如此,使用 ‘thread’ 和 ‘threading’ 模块仍然无法在字节码一级实现并行。因为Python解释器使用GIL(全局解释器锁)来在内部禁止并行执行。这个GIL限制你在SMP机器上同一时间也只能执行一条字节码指令。

PP 模块正是为了解决这个问题而来,提供简单的方式实现并行Python应用。 ppsmp 在内部使用 进程IPC (进程间通信)来组织并行计算。并处理了所有内部的细节和复杂性,你的应用程序只需要提交工作任务并取回结果就可以了。这也是编写并行程序的最简单的方法。

为了更好的实现,所有使用 PP 的软件通过网络来连接和协作。跨平台和动态负载均衡使得 PP 可以轻松组织多平台、异构的集群计算环境。

4   安装

任何平台:下子阿模块压缩包,解压,运行setup脚本:

python setup.py install

Windows:下载和执行安装包。

5   文档

模块API

快速开始,SMP

快速开始,集群

高级指南,集群

命令行参数,ppserver.py

6   例子

并行Python的使用方法

7   下载

并行Python下载

8   技术支持

并行Python讨论区 提供帮助和支持。

9   帮助我们扩展,链接我们

<a href='http://www.parallelpython.com'>Parallel Python</a>

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应用一样。