Openerp压力测试:多线程直连OE Server NET-RPC/XML-RPC端口测试
-
之前发表的一篇文章 Openerp压力测试:Openerp到底能支撑多大的用户数? ,转贴到OpenERP中文社区和OpenERP QQ群(69195329)之后,得到了许多童靴的关注,尤其是很多前辈的鼓励和转贴。这里就不一一道谢了。总之,谢谢大家!
也有善意的质疑,某童靴就指出,“不代表 OE server 的 xmlrpc 性能的. 中间或者有 cherrypy 的缓存在起作用. 并发100 已经相当牛X了.”,某童靴同时也提出,“期待 清沙出个 xmlrpc/netrpc 直接性能测试版本....射射....”。我表示,完全没有鸭梨。
今天偷得半日浮生,想起某童靴的教导,不由的觉得一种鸭梨油然而生,!^@!A*&* 今天天气晴朗,万里乌云,小朋友们高高兴兴的。。。(原谅我吧,一有鸭梨,总是想起当年有个可怜的小朋友在憋课堂作文的时候)。废话少说,今天写了一小段Python代码,用于测试OpenERP NET-XML 和 XML-RPC性能。本人才疏学浅,代码中如有臭虫或错误之处,还望各位童靴斧正。<br />#! /usr/bin/python#! /usr/bin/python<br /># -*- coding: utf-8 -*-<br />#<br /># OE压力测试小工具 www.360yun.info<br />#<br /><br /># 导入 openerprpc 模块。<br /># 纯属个人习惯,如果您是使用apt或者python setup.py 等方式安装的话,<br /># 可以无视下面2行<br />import sys,os<br />sys.path.append (os.path.abspath(os.path.join(os.path.dirname(__file__), 'openerprpc-1.0.1')))<br />import openerprpc<br /><br />import threading<br />import Queue<br />import datetime<br />import time<br /><br />#参数设置<br />JOBS_COUNT = 1000 #任务总数<br />THREAD_LIMIT = 100 #并发线程数<br />HOST = '127.0.0.1' #主机IP<br />PROTOCOL = 'netrpc' #可选值 netrpc xmlrpc<br />PORT = 'auto' #端口<br />DATABASE = 'test' #数据库<br />LOGIN = 'admin' #用户名<br />PASSWORD = 'admin' #密码<br />USER_ID = None #用户id<br /><br />#队列<br />jobs = Queue.Queue(0)<br />def thread(num):<br /> while True:<br /> try:<br /> job = jobs.get(False)<br /> except Queue.Empty:<br /> return<br /><br /> #连接OE Server<br /> conn = openerprpc.get_connection(HOST, protocol=PROTOCOL, port=PORT,<br /> database=DATABASE, login=LOGIN,<br /> password=PASSWORD, user_id=USER_ID)<br /> #取第一个partner 记录<br /> company = conn.get_object('res.partner').read([(1)],[('name')])<br /> print 'job %s thread %s : %s ' % (str(job) , str(num), str(company))<br /> time.sleep(1/1000) # 休息1毫秒<br /><br /><br />def main():<br /> #将任务压入队列<br /> for job in range(JOBS_COUNT):<br /> jobs.put(job)<br /><br /> start_time = datetime.datetime.now()<br /><br /> #启动线程<br /> for n in range(THREAD_LIMIT):<br /> t = threading.Thread(target = thread, kwargs = {'num': n})<br /> t.start()<br /><br /> #等待线程结束<br /> while threading.activeCount() > 1:<br /> pass<br /><br /> end_time = datetime.datetime.now()<br /><br /> print '\r\nStart at %s' % str(start_time)<br /> print 'End at %s' % str(end_time)<br /> print '\r\nTotal Time: %s' % str(end_time - start_time)<br /><br />if __name__ == "__main__":<br /> main()<br />
上面的程序使用官方的openerprpc模块连接到OE Server,支持NET-RPC和XML-RPC连接方式,使用很简单,例子请看上面的代码。下载地址是http://pypi.python.org/pypi/openerprpc 。下载之后用 python setup.py 安装。当然,也可以不安装,代码中用sys.path.append添加openerprpc的路径即可,如同上面的代码。
OpenERP中文社区论坛里面也介绍了其他如oersted 等,不过下载来看了下只支持NET-RPC。GOOGLE之后发现原来官方也有出rpc客户端模块,缺点就是官方的openerprpc模块没有文档也没有样例。呵呵,有机会在来写写openerprpc模块介绍。
按照上面的程序代码,就可以运行了么?当然可以运行,如果你没有改动我的程序的话,很快你就会得到以下错误:<br />error: [Errno 104] Connection reset by peer<br />
神马?不会吧,这明显是Socket Server 撑不住啊。难道连100并发都搞不定?难道某童靴一语言中?莫慌,打开OE的源码来看看(这就是开源的好处啊,哇哈哈)......中间省略数万字.... 下面是解决办法:
1、编辑 openerp-server-6.0.2/bin/service/netrpc_server.py ,找到以下代码(112行):<br />class TinySocketServerThread(threading.Thread,netsvc.Server):<br /> def __init__(self, interface, port, secure=False):<br /> threading.Thread.__init__(self, name="NetRPCDaemon-%d"%port)<br /> netsvc.Server.__init__(self)<br /> self.__port = port<br /> self.__interface = interface<br /> self.socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)<br /> self.socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)<br /> self.socket.bind((self.__interface, self.__port))<br /> #self.socket.listen(5)<br /> #此处修改为128 或更大 这个是Socket 队列,不是监听端口,详情请Google之。<br /> self.socket.listen(128)<br /> self.threads = []<br /> netsvc.Logger().notifyChannel("web-services", netsvc.LOG_INFO, <br /> "starting NET-RPC service at %s port %d" % (interface or '0.0.0.0', port,))<br />
2、编辑 openerp-server-6.0.2/bin/service/http_server.py ,找到以下代码(80 行):<br />class ThreadedHTTPServer(ConnThreadingMixIn, SimpleXMLRPCDispatcher, HTTPServer):<br /><br /> encoding = None<br /> allow_none = False<br /> allow_reuse_address = 1<br /> _send_traceback_header = False<br /> i = 0<br /><br /> def __init__(self, addr, requestHandler, proto='http',<br /> logRequests=True, allow_none=False, encoding=None, bind_and_activate=True):<br /> self.logRequests = logRequests<br /><br /> SimpleXMLRPCDispatcher.__init__(self, allow_none, encoding)<br /> HTTPServer.__init__(self, addr, requestHandler)<br /><br /> self.numThreads = 0<br /> self.proto = proto<br /> self.__threadno = 0<br /><br /> #此处添加一行<br /> self.socket.listen(128)<br /><br /> # [Bug #1222790] If possible, set close-on-exec flag; if a<br /> # method spawns a subprocess, the subprocess shouldn't have<br /> # the listening socket open.<br /> if fcntl is not None and hasattr(fcntl, 'FD_CLOEXEC'):<br /> flags = fcntl.fcntl(self.fileno(), fcntl.F_GETFD)<br /> flags |= fcntl.FD_CLOEXEC<br /> fcntl.fcntl(self.fileno(), fcntl.F_SETFD, flags)<br />
3、编辑OE Server 的配置文件openerp-server.conf,在最后添加以下代码:<br />; 数据库连接池数量<br />db_maxconn = 128<br />
这个是数据库的连接池数量,出处请察看源码openerp-server-6.0.2/bin/sql_db.py 第258行处,默认值是64。如果你启动OE没有使用配置文件,那么你只好修改源码了。sql_db.py 的代码节选如下:(使用配置文件的话,此处无须修改,只是举例说明db_maxconn这个参数不是凭空得来)<br />class ConnectionPool(object):<br />#################中间省略无数代码######################<br /> def __init__(self, maxconn=64):<br /> self._connections = []<br /> self._maxconn = max(maxconn, 1)<br /> self._lock = threading.Lock()<br />#################中间省略无数代码######################<br />_Pool = ConnectionPool(int(tools.config['db_maxconn']))<br /><br />
经过三个参数的修改,我们的测试程序终于可以正常运行了。但,程序很有可能输出异常,虽然程序可以运行。异常情况如下:UnpicklingError: Global and instance pickles are not supported.
尤其是第一次启动测试程序,再次启动,就没事了。貌似线程占用资源问题,不知道是不是要加个线程锁。百思不得其解啊,求各位高手给个说法。
测试结果
1、NET-RPC 100并发 1000总任务<br />Start at 2011-08-12 20:16:45.058085<br />End at 2011-08-12 20:16:52.233411<br /><br />Total Time: 0:00:07.175326<br />
2、XML-RPC 100并发 1000总任务 (运行这个测试请把测试代码中参数修改为 PROTOCOL = 'xmlrpc' #可选值 netrpc xmlrpc)<br />Start at 2011-08-12 20:20:21.405651<br />End at 2011-08-12 20:20:32.149523<br /><br />Total Time: 0:00:10.743872<br />
从上面的测试结果看,XML-RPC所花时间是NET-RPC时间的 1.497335731 倍。与我上一篇OE测试结果相近。
结论
1、某童靴,你赢了!”不代表 OE server 的 xmlrpc 性能的. 中间或者有 cherrypy 的缓存在起作用“,您的观点是正确的。OE Web Client 在通过RPC获取对象之后,会把对象缓存在OE Web Client 自己的 cache中。官方文档 http://doc.openerp.com/v6.0/book/1/1_1_Inst_Config/1_1_Inst_Config_architecture.html 中也提到这点。<br />When you are changing the structure of your OpenERP installation (adding and<br />removing modules, perhaps changing labels), you might find the web client to be<br />irritating because of its use of caching.<br />
2、如果在部署OE应用是,如果需要高并发,建议调整OE Server 中netrpc_server 和 http_server 的 socket.listen,数据库连接池 db_maxconn也建议你调整到更高。在默认情况下,OE Server 的确不能支持100并发或更多。(如果你也同时使用OE Web Client,请参照之前发表的一篇文章 Openerp压力测试:Openerp到底能支撑多大的用户数? 进行调整)
3、开放源代码的力量是无穷的,本文调整参数的例子就很好的说明一切。假如你部署的ERP系统,随着业务量的增长遇上性能瓶颈是,估计大部分商业公司给你的建议都是升级到更高、更新的版本,或是购买更强的硬件服务器。没有开放源代码,你甚至都不知道哪儿出问题。
以上转自本人博客 [检测到链接无效,已移除] 。测试代码打包的下载,请到本人博客。
谢谢! -
[quote author=Joshua link=topic=2556.msg8470#msg8470 date=1313225229]
LZ我用你代码测试过,没改self.socket.listen(5)和db_maxconn = 128也可以运行,但是多次出现了下面错误(而且就算改了源码,错误依旧),error: (10061, 'Connection refused')
[/quote]
error: (10061, 'Connection refused')
连接被拒绝,socket 的等待队列默认值才5,多于5个并发就挂了啊,所以才需要修改 socket.listen。 -
lz参数设置就是按照你上面所设置的,这个是其中一个出错信息。
我用的源码也是6.0.2Exception in thread Thread-99:<br />Traceback (most recent call last):<br /> File "C:\Python25\lib\threading.py", line 486, in __bootstrap_inner<br /> self.run()<br /> File "C:\Python25\lib\threading.py", line 446, in run<br /> self.__target(*self.__args, **self.__kwargs)<br /> File "D:\eclipse_workspace\oes602\bin\test\yaliceshi.py", line 44, in thread<br /> company = conn.get_object('res.partner').read([(1)],[('name')])<br /> File "D:\eclipse_workspace\oes602\bin\test\openerprpc-1.0.1\openerprpc\__init__.py", line 240, in proxy<br /> self.connection.check_login()<br /> File "D:\eclipse_workspace\oes602\bin\test\openerprpc-1.0.1\openerprpc\__init__.py", line 209, in check_login<br /> self.user_id = Service(self.connector,"common").login(self.database, self.login, self.password)<br /> File "D:\eclipse_workspace\oes602\bin\test\openerprpc-1.0.1\openerprpc\__init__.py", line 178, in proxy<br /> result = self.connector.send(self.service_name, method, *args)<br /> File "D:\eclipse_workspace\oes602\bin\test\openerprpc-1.0.1\openerprpc\__init__.py", line 76, in send<br /> return getattr(service, method)(*args)<br /> File "C:\Python25\lib\xmlrpclib.py", line 1147, in __call__<br /> return self.__send(self.__name, args)<br /> File "C:\Python25\lib\xmlrpclib.py", line 1437, in __request<br /> verbose=self.__verbose<br /> File "C:\Python25\lib\xmlrpclib.py", line 1183, in request<br /> self.send_content(h, request_body)<br /> File "C:\Python25\lib\xmlrpclib.py", line 1297, in send_content<br /> connection.endheaders()<br /> File "C:\Python25\lib\httplib.py", line 860, in endheaders<br /> self._send_output()<br /> File "C:\Python25\lib\httplib.py", line 732, in _send_output<br /> self.send(msg)<br /> File "C:\Python25\lib\httplib.py", line 699, in send<br /> self.connect()<br /> File "C:\Python25\lib\httplib.py", line 683, in connect<br /> raise socket.error, msg<br />error: (10061, 'Connection refused')