Odoo中文社区可以通过以下三个域名访问:shine-it.net , odoocn.org,odoo.net.cn

原论坛用户的基本信息和发帖这里都予以保留,请注意:原论坛用户无需重新注册新用户,但是您的密码需要重置

开发人员可以登录gitter讨论组: http://gitter.im/odoo-china/Talk, 需要github账号

如果您登录系统碰到问题,请在微信公众号留言:

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性能。本人才疏学浅,代码中如有臭虫或错误之处,还望各位童靴斧正。

    [code]
    #! /usr/bin/python#! /usr/bin/python
    # -- coding: utf-8 --
    #
    # OE压力测试小工具  www.360yun.info
    #

    # 导入 openerprpc 模块。
    # 纯属个人习惯,如果您是使用apt或者python setup.py 等方式安装的话,
    # 可以无视下面2行
    import sys,os
    sys.path.append (os.path.abspath(os.path.join(os.path.dirname(file), 'openerprpc-1.0.1')))
    import openerprpc

    import threading
    import Queue
    import datetime
    import time

    #参数设置
    JOBS_COUNT = 1000 #任务总数
    THREAD_LIMIT = 100 #并发线程数
    HOST = '127.0.0.1' #主机IP
    PROTOCOL = 'netrpc' #可选值 netrpc xmlrpc
    PORT = 'auto' #端口
    DATABASE = 'test' #数据库
    LOGIN = 'admin' #用户名
    PASSWORD = 'admin' #密码
    USER_ID = None #用户id

    #队列
    jobs = Queue.Queue(0)
    def thread(num):
        while True:
            try:
                job = jobs.get(False)
            except Queue.Empty:
                return

            #连接OE Server
            conn = openerprpc.get_connection(HOST, protocol=PROTOCOL, port=PORT,
                                            database=DATABASE, login=LOGIN,
                                            password=PASSWORD, user_id=USER_ID)
            #取第一个partner 记录
            company = conn.get_object('res.partner').read([(1)],[('name')])
            print 'job %s thread %s : %s ' % (str(job) , str(num), str(company))
            time.sleep(1/1000) # 休息1毫秒


    def main():
        #将任务压入队列
        for job in range(JOBS_COUNT):
            jobs.put(job)

        start_time = datetime.datetime.now()

        #启动线程
        for n in range(THREAD_LIMIT):
            t = threading.Thread(target = thread, kwargs = {'num': n})
            t.start()

        #等待线程结束
        while threading.activeCount() > 1:
            pass

        end_time = datetime.datetime.now()

        print '\r\nStart at %s' % str(start_time)
        print 'End  at %s' % str(end_time)
        print '\r\nTotal Time: %s' % str(end_time - start_time)

    if name == "main":
        main()
    [/code]

            上面的程序使用官方的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模块介绍。

            按照上面的程序代码,就可以运行了么?当然可以运行,如果你没有改动我的程序的话,很快你就会得到以下错误:
    [code]
    error: [Errno 104] Connection reset by peer
    [/code]
      神马?不会吧,这明显是Socket Server 撑不住啊。难道连100并发都搞不定?难道某童靴一语言中?莫慌,打开OE的源码来看看(这就是开源的好处啊,哇哈哈)......中间省略数万字....  下面是解决办法:

            1、编辑 openerp-server-6.0.2/bin/service/netrpc_server.py ,找到以下代码(112行):
    [code]
    class TinySocketServerThread(threading.Thread,netsvc.Server):
        def init(self, interface, port, secure=False):
            threading.Thread.init(self, name="NetRPCDaemon-%d"%port)
            netsvc.Server.init(self)
            self.__port = port
            self.__interface = interface
            self.socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
            self.socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
            self.socket.bind((self.__interface, self.__port))
            #self.socket.listen(5)
            #此处修改为128 或更大 这个是Socket 队列,不是监听端口,详情请Google之。
            self.socket.listen(128)
            self.threads = []
            netsvc.Logger().notifyChannel("web-services", netsvc.LOG_INFO,
                            "starting NET-RPC service at %s port %d" % (interface or '0.0.0.0', port,))
    [/code]

            2、编辑 openerp-server-6.0.2/bin/service/http_server.py ,找到以下代码(80 行):

    [code]
    class ThreadedHTTPServer(ConnThreadingMixIn, SimpleXMLRPCDispatcher, HTTPServer):

        encoding = None
        allow_none = False
        allow_reuse_address = 1
        _send_traceback_header = False
        i = 0

        def init(self, addr, requestHandler, proto='http',
                    logRequests=True, allow_none=False, encoding=None, bind_and_activate=True):
            self.logRequests = logRequests

            SimpleXMLRPCDispatcher.init(self, allow_none, encoding)
            HTTPServer.init(self, addr, requestHandler)

            self.numThreads = 0
            self.proto = proto
            self.__threadno = 0

            #此处添加一行
            self.socket.listen(128)

            # [Bug #1222790] If possible, set close-on-exec flag; if a
            # method spawns a subprocess, the subprocess shouldn't have
            # the listening socket open.
            if fcntl is not None and hasattr(fcntl, 'FD_CLOEXEC'):
                flags = fcntl.fcntl(self.fileno(), fcntl.F_GETFD)
                flags |= fcntl.FD_CLOEXEC
                fcntl.fcntl(self.fileno(), fcntl.F_SETFD, flags)
    [/code]
            3、编辑OE Server 的配置文件openerp-server.conf,在最后添加以下代码:
    [code]
    ; 数据库连接池数量
    db_maxconn = 128
    [/code]
            这个是数据库的连接池数量,出处请察看源码openerp-server-6.0.2/bin/sql_db.py 第258行处,默认值是64。如果你启动OE没有使用配置文件,那么你只好修改源码了。sql_db.py 的代码节选如下:(使用配置文件的话,此处无须修改,只是举例说明db_maxconn这个参数不是凭空得来)
    [code]
    class ConnectionPool(object):
    #################中间省略无数代码######################
        def init(self, maxconn=64):
            self._connections = []
            self._maxconn = max(maxconn, 1)
            self._lock = threading.Lock()
    #################中间省略无数代码######################
    _Pool = ConnectionPool(int(tools.config['db_maxconn']))

    [/code]
            经过三个参数的修改,我们的测试程序终于可以正常运行了。但,程序很有可能输出异常,虽然程序可以运行。异常情况如下:
    [code]UnpicklingError: Global and instance pickles are not supported.[/code]
            尤其是第一次启动测试程序,再次启动,就没事了。貌似线程占用资源问题,不知道是不是要加个线程锁。百思不得其解啊,求各位高手给个说法。

            测试结果

    1、NET-RPC 100并发 1000总任务
    [code]
    Start at 2011-08-12 20:16:45.058085
    End  at 2011-08-12 20:16:52.233411

    Total Time: 0:00:07.175326
    [/code]
    2、XML-RPC 100并发 1000总任务 (运行这个测试请把测试代码中参数修改为 PROTOCOL = 'xmlrpc' #可选值 netrpc xmlrpc)
    [code]
    Start at 2011-08-12 20:20:21.405651
    End  at 2011-08-12 20:20:32.149523

    Total Time: 0:00:10.743872
    [/code]
            从上面的测试结果看,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 中也提到这点。
    [code]
    When you are changing the structure of your OpenERP installation (adding and
    removing modules, perhaps changing labels), you might find the web client to be
    irritating because of its use of caching.
    [/code]

    2、如果在部署OE应用是,如果需要高并发,建议调整OE Server 中netrpc_server 和 http_server 的 socket.listen,数据库连接池 db_maxconn也建议你调整到更高。在默认情况下,OE Server 的确不能支持100并发或更多。(如果你也同时使用OE Web Client,请参照之前发表的一篇文章 Openerp压力测试:Openerp到底能支撑多大的用户数? 进行调整)

    3、开放源代码的力量是无穷的,本文调整参数的例子就很好的说明一切。假如你部署的ERP系统,随着业务量的增长遇上性能瓶颈是,估计大部分商业公司给你的建议都是升级到更高、更新的版本,或是购买更强的硬件服务器。没有开放源代码,你甚至都不知道哪儿出问题。

    以上转自本人博客 [url=http://www.360yun.info/blog/910961413.html]http://www.360yun.info/blog/910961413.html[/url]。测试代码打包的下载,请到本人博客。

    谢谢!



  •         之前发表的一篇文章 Openerp压力测试:Openerp到底能支撑多大的用户数? ,转贴到OpenERP中文社区和OpenERP QQ群(69195329)之后,得到了许多童靴的关注,尤其是很多前辈的鼓励和转贴。这里就不一一道谢了。总之,谢谢大家!
            也有善意的质疑,某童靴就指出,“不代表 OE server 的 xmlrpc 性能的.  中间或者有 cherrypy 的缓存在起作用.  并发100 已经相当牛X了.”,某童靴同时也提出,“期待 清沙出个 xmlrpc/netrpc 直接性能测试版本....射射....”。我表示,完全没有鸭梨。

            今天偷得半日浮生,想起某童靴的教导,不由的觉得一种鸭梨油然而生,!^@!A*&* 今天天气晴朗,万里乌云,小朋友们高高兴兴的。。。(原谅我吧,一有鸭梨,总是想起当年有个可怜的小朋友在憋课堂作文的时候)。废话少说,今天写了一小段Python代码,用于测试OpenERP NET-XML 和 XML-RPC性能。本人才疏学浅,代码中如有臭虫或错误之处,还望各位童靴斧正。

    [code]
    #! /usr/bin/python#! /usr/bin/python
    # -- coding: utf-8 --
    #
    # OE压力测试小工具  www.360yun.info
    #

    # 导入 openerprpc 模块。
    # 纯属个人习惯,如果您是使用apt或者python setup.py 等方式安装的话,
    # 可以无视下面2行
    import sys,os
    sys.path.append (os.path.abspath(os.path.join(os.path.dirname(file), 'openerprpc-1.0.1')))
    import openerprpc

    import threading
    import Queue
    import datetime
    import time

    #参数设置
    JOBS_COUNT = 1000 #任务总数
    THREAD_LIMIT = 100 #并发线程数
    HOST = '127.0.0.1' #主机IP
    PROTOCOL = 'netrpc' #可选值 netrpc xmlrpc
    PORT = 'auto' #端口
    DATABASE = 'test' #数据库
    LOGIN = 'admin' #用户名
    PASSWORD = 'admin' #密码
    USER_ID = None #用户id

    #队列
    jobs = Queue.Queue(0)
    def thread(num):
        while True:
            try:
                job = jobs.get(False)
            except Queue.Empty:
                return

            #连接OE Server
            conn = openerprpc.get_connection(HOST, protocol=PROTOCOL, port=PORT,
                                            database=DATABASE, login=LOGIN,
                                            password=PASSWORD, user_id=USER_ID)
            #取第一个partner 记录
            company = conn.get_object('res.partner').read([(1)],[('name')])
            print 'job %s thread %s : %s ' % (str(job) , str(num), str(company))
            time.sleep(1/1000) # 休息1毫秒


    def main():
        #将任务压入队列
        for job in range(JOBS_COUNT):
            jobs.put(job)

        start_time = datetime.datetime.now()

        #启动线程
        for n in range(THREAD_LIMIT):
            t = threading.Thread(target = thread, kwargs = {'num': n})
            t.start()

        #等待线程结束
        while threading.activeCount() > 1:
            pass

        end_time = datetime.datetime.now()

        print '\r\nStart at %s' % str(start_time)
        print 'End  at %s' % str(end_time)
        print '\r\nTotal Time: %s' % str(end_time - start_time)

    if name == "main":
        main()
    [/code]

            上面的程序使用官方的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模块介绍。

            按照上面的程序代码,就可以运行了么?当然可以运行,如果你没有改动我的程序的话,很快你就会得到以下错误:
    [code]
    error: [Errno 104] Connection reset by peer
    [/code]
      神马?不会吧,这明显是Socket Server 撑不住啊。难道连100并发都搞不定?难道某童靴一语言中?莫慌,打开OE的源码来看看(这就是开源的好处啊,哇哈哈)......中间省略数万字....  下面是解决办法:

            1、编辑 openerp-server-6.0.2/bin/service/netrpc_server.py ,找到以下代码(112行):
    [code]
    class TinySocketServerThread(threading.Thread,netsvc.Server):
        def init(self, interface, port, secure=False):
            threading.Thread.init(self, name="NetRPCDaemon-%d"%port)
            netsvc.Server.init(self)
            self.__port = port
            self.__interface = interface
            self.socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
            self.socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
            self.socket.bind((self.__interface, self.__port))
            #self.socket.listen(5)
            #此处修改为128 或更大 这个是Socket 队列,不是监听端口,详情请Google之。
            self.socket.listen(128)
            self.threads = []
            netsvc.Logger().notifyChannel("web-services", netsvc.LOG_INFO,
                            "starting NET-RPC service at %s port %d" % (interface or '0.0.0.0', port,))
    [/code]

            2、编辑 openerp-server-6.0.2/bin/service/http_server.py ,找到以下代码(80 行):

    [code]
    class ThreadedHTTPServer(ConnThreadingMixIn, SimpleXMLRPCDispatcher, HTTPServer):

        encoding = None
        allow_none = False
        allow_reuse_address = 1
        _send_traceback_header = False
        i = 0

        def init(self, addr, requestHandler, proto='http',
                    logRequests=True, allow_none=False, encoding=None, bind_and_activate=True):
            self.logRequests = logRequests

            SimpleXMLRPCDispatcher.init(self, allow_none, encoding)
            HTTPServer.init(self, addr, requestHandler)

            self.numThreads = 0
            self.proto = proto
            self.__threadno = 0

            #此处添加一行
            self.socket.listen(128)

            # [Bug #1222790] If possible, set close-on-exec flag; if a
            # method spawns a subprocess, the subprocess shouldn't have
            # the listening socket open.
            if fcntl is not None and hasattr(fcntl, 'FD_CLOEXEC'):
                flags = fcntl.fcntl(self.fileno(), fcntl.F_GETFD)
                flags |= fcntl.FD_CLOEXEC
                fcntl.fcntl(self.fileno(), fcntl.F_SETFD, flags)
    [/code]
            3、编辑OE Server 的配置文件openerp-server.conf,在最后添加以下代码:
    [code]
    ; 数据库连接池数量
    db_maxconn = 128
    [/code]
            这个是数据库的连接池数量,出处请察看源码openerp-server-6.0.2/bin/sql_db.py 第258行处,默认值是64。如果你启动OE没有使用配置文件,那么你只好修改源码了。sql_db.py 的代码节选如下:(使用配置文件的话,此处无须修改,只是举例说明db_maxconn这个参数不是凭空得来)
    [code]
    class ConnectionPool(object):
    #################中间省略无数代码######################
        def init(self, maxconn=64):
            self._connections = []
            self._maxconn = max(maxconn, 1)
            self._lock = threading.Lock()
    #################中间省略无数代码######################
    _Pool = ConnectionPool(int(tools.config['db_maxconn']))

    [/code]
            经过三个参数的修改,我们的测试程序终于可以正常运行了。但,程序很有可能输出异常,虽然程序可以运行。异常情况如下:
    [code]UnpicklingError: Global and instance pickles are not supported.[/code]
            尤其是第一次启动测试程序,再次启动,就没事了。貌似线程占用资源问题,不知道是不是要加个线程锁。百思不得其解啊,求各位高手给个说法。

            测试结果

    1、NET-RPC 100并发 1000总任务
    [code]
    Start at 2011-08-12 20:16:45.058085
    End  at 2011-08-12 20:16:52.233411

    Total Time: 0:00:07.175326
    [/code]
    2、XML-RPC 100并发 1000总任务 (运行这个测试请把测试代码中参数修改为 PROTOCOL = 'xmlrpc' #可选值 netrpc xmlrpc)
    [code]
    Start at 2011-08-12 20:20:21.405651
    End  at 2011-08-12 20:20:32.149523

    Total Time: 0:00:10.743872
    [/code]
            从上面的测试结果看,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 中也提到这点。
    [code]
    When you are changing the structure of your OpenERP installation (adding and
    removing modules, perhaps changing labels), you might find the web client to be
    irritating because of its use of caching.
    [/code]

    2、如果在部署OE应用是,如果需要高并发,建议调整OE Server 中netrpc_server 和 http_server 的 socket.listen,数据库连接池 db_maxconn也建议你调整到更高。在默认情况下,OE Server 的确不能支持100并发或更多。(如果你也同时使用OE Web Client,请参照之前发表的一篇文章 Openerp压力测试:Openerp到底能支撑多大的用户数? 进行调整)

    3、开放源代码的力量是无穷的,本文调整参数的例子就很好的说明一切。假如你部署的ERP系统,随着业务量的增长遇上性能瓶颈是,估计大部分商业公司给你的建议都是升级到更高、更新的版本,或是购买更强的硬件服务器。没有开放源代码,你甚至都不知道哪儿出问题。

    以上转自本人博客 [url=http://www.360yun.info/blog/910961413.html]http://www.360yun.info/blog/910961413.html[/url]。测试代码打包的下载,请到本人博客。

    谢谢!



  • 沙发~~~辛苦!


  • 管理员

    LZ我用你代码测试过,没改self.socket.listen(5)和db_maxconn = 128也可以运行,但是多次出现了下面错误(而且就算改了源码,错误依旧),

    [code]error: (10061, 'Connection refused')[/code]



  • [quote author=Joshua link=topic=2556.msg8470#msg8470 date=1313225229]
    LZ我用你代码测试过,没改self.socket.listen(5)和db_maxconn = 128也可以运行,但是多次出现了下面错误(而且就算改了源码,错误依旧),

    [code]error: (10061, 'Connection refused')[/code]


    [/quote]

    error: (10061, 'Connection refused')

    连接被拒绝,socket 的等待队列默认值才5,多于5个并发就挂了啊,所以才需要修改 socket.listen。


  • 管理员

    LZ 很奇怪,我改了还是一样出现的错误



  • 三个地方都改了吗?改完后要重启OE Server

    另外,麻烦你把测试程序的参数设置和完整的错误提示贴出来。谢谢


  • 管理员

    lz参数设置就是按照你上面所设置的,这个是其中一个出错信息。
    我用的源码也是6.0.2
    [code]Exception in thread Thread-99:
    Traceback (most recent call last):
      File "C:\Python25\lib\threading.py", line 486, in __bootstrap_inner
        self.run()
      File "C:\Python25\lib\threading.py", line 446, in run
        self.__target(*self.args, **self.kwargs)
      File "D:\eclipse_workspace\oes602\bin\test\yaliceshi.py", line 44, in thread
        company = conn.get_object('res.partner').read([(1)],[('name')])
      File "D:\eclipse_workspace\oes602\bin\test\openerprpc-1.0.1\openerprpc_init
    .py", line 240, in proxy
        self.connection.check_login()
      File "D:\eclipse_workspace\oes602\bin\test\openerprpc-1.0.1\openerprpc_init
    .py", line 209, in check_login
        self.user_id = Service(self.connector,"common").login(self.database, self.login, self.password)
      File "D:\eclipse_workspace\oes602\bin\test\openerprpc-1.0.1\openerprpc_init
    .py", line 178, in proxy
        result = self.connector.send(self.service_name, method, *args)
      File "D:\eclipse_workspace\oes602\bin\test\openerprpc-1.0.1\openerprpc_init
    .py", line 76, in send
        return getattr(service, method)(*args)
      File "C:\Python25\lib\xmlrpclib.py", line 1147, in call
        return self.__send(self.__name, args)
      File "C:\Python25\lib\xmlrpclib.py", line 1437, in __request
        verbose=self.__verbose
      File "C:\Python25\lib\xmlrpclib.py", line 1183, in request
        self.send_content(h, request_body)
      File "C:\Python25\lib\xmlrpclib.py", line 1297, in send_content
        connection.endheaders()
      File "C:\Python25\lib\httplib.py", line 860, in endheaders
        self._send_output()
      File "C:\Python25\lib\httplib.py", line 732, in _send_output
        self.send(msg)
      File "C:\Python25\lib\httplib.py", line 699, in send
        self.connect()
      File "C:\Python25\lib\httplib.py", line 683, in connect
        raise socket.error, msg
    error: (10061, 'Connection refused')[/code]



  • openerprpc 报错,非测试程序问题。很有可能是你的python 版本偏低,性能不佳导致。

    建议你先调低并发数试试,THREAD_LIMIT = 10 ,先从10并发开始试试看。

    我注意到你的python 版本是 2.5,建议你升级到2.6 或 2.X 系列的更高版本。(我使用的是Python 2.6.6 on Debian 6.1)。



  • 不错.... 赞一个先......

    windows 下的线程不太好....

    OE 应该是有线程锁的... 我没有仔细阅读过代码.. 我关注 addons 比较多些...



  • 另外, 别某童鞋某童鞋了... 我叫 mrshelly 叫我 shelly 也成..

    贴子LZ可以 Add Tag 的. 加一些 Tags 会比较利于BBS检索...



  • 谢谢shelly 的关注。有您的首肯,以后我在文章中就直接引用您的名字。谢谢!



  • opnerprpc  官方 貌似木有了啊


登录后回复
 

与 Odoo 中文社区 的连接断开,我们正在尝试重连,请耐心等待