利用ab测试工具对Tornado下使用mongodb驱动的性能简单测试分析
Blog地址:https://www.jiangdog.com/blog/tornado-motor-ab
背景
在利用Python开发web项目时,可以使用pymongo
、mongoengine
等库连接mongodb。当使用Tornado
框架时,为了利用其异步的特性,还可以使用motor
来作为数据库驱动。无论是mongoengine
还是motor
实际上应该都是对pymongo
的封装。
-
mongoengine
提供了ORM映射的功能,将Document
封装成类,通过实例的方法来操作Document
。 -
motor
能够和Tornado
或协程配合使用,异步调用提升性能。
参考网上的文章并自己写了一些简单的代码来测试在使用Tornado
框架下,这三种数据库驱动的性能。
测试工具
使用ab
来做这次简单测试。
- CentOS下安装可以参考:How to install Apache Benchmark on CentOS?,实际上就是两条命令
yum provides /usr/bin/ab
yum install httpd-tools
- windows下安装:https://www.apachehaus.com/cgi-bin/download.plx 下载相应版本的apache安装包,并解压安装到指定目录。
ab基本参数介绍:
- -n 指定总共发起的请求数量。
- -c 同时发起的请求数量,并发请求数。
- -p 指定post方式提交的数据的文件,文件内容格式为
a=1&b=2
。 - -T 指定content-type
测试服务器配置及环境
服务器是利用了阿里云的基本的云服务器ECS实例:
- 1核 1GB 带宽1Mbps
- CentOS 7.2
mongodb和测试web服务都是安装或部署在该实例上:
- mongodb 3.4
- python 3.5.1
- tornado 4.4.2
- pymongo 3.6.0
- mongoengine 0.15.0
- motor 1.1
编写简单的服务端代码并利用ab进行测试
pymongo:
#!/usr/bin/python
# -*- coding:utf-8 -*-
import tornado.web
import tornado.gen
from pymongo import MongoClient
from tornado.httpserver import HTTPServer
class MessageListHandler(tornado.web.RequestHandler):
def get(self):
message_list = self.settings['db'].messages.find().sort([('_id', -1)]).limit(50)
self.render('msg_list.html', message_list=message_list)
class MessageAddHandler(tornado.web.RequestHandler):
def get(self):
self.render('msg_add.html')
def post(self):
msg = self.get_argument('msg')
res = self.settings['db'].messages.insert_one({'msg': msg})
self.redirect('/message/list/')
class Application(tornado.web.Application):
def __init__(self):
handlers = [
(r'/message/list/', MessageListHandler),
(r'/message/add/', MessageAddHandler),
]
db = MongoClient('mongodb://127.0.0.1:27017', maxPoolSize=200).test_motor
settings = dict(
db=db,
debug=False,
)
tornado.web.Application.__init__(self, handlers, **settings)
def main():
app = Application()
httpserver = HTTPServer(app, xheaders=True)
httpserver.listen(80)
tornado.ioloop.IOLoop.instance().start()
if __name__ == '__main__':
main()
- get请求查询信息列表。
- post请求添加数据:
mongoengine:
#!/usr/bin/python
# -*- coding:utf-8 -*-
import tornado.web
import tornado.gen
from mongoengine import Document, StringField, connect
from tornado.httpserver import HTTPServer
class Messages(Document):
msg = StringField(required=True)
class MessageListHandler(tornado.web.RequestHandler):
def get(self):
message_list = Messages.objects.filter().order_by('-id').limit(50)
self.render('msg_list.html', message_list=message_list)
class MessageAddHandler(tornado.web.RequestHandler):
def get(self):
self.render('msg_add.html')
def post(self):
msg = self.get_argument('msg')
res = Messages(msg=msg).save()
self.redirect('/message/list/')
class Application(tornado.web.Application):
def __init__(self):
handlers = [
(r'/message/list/', MessageListHandler),
(r'/message/add/', MessageAddHandler),
]
connect('test_motor', host='127.0.0.1', port=27017, maxPoolSize=200)
settings = dict(
debug=False,
)
tornado.web.Application.__init__(self, handlers, **settings)
def main():
app = Application()
httpserver = HTTPServer(app, xheaders=True)
httpserver.listen(80)
tornado.ioloop.IOLoop.instance().start()
if __name__ == '__main__':
main()
- get请求数据列表:
-
post添加数据:
motor:
#!/usr/bin/python
# -*- coding:utf-8 -*-
import motor
import tornado.web
import tornado.gen
from tornado.httpserver import HTTPServer
class MessageListHandler(tornado.web.RequestHandler):
@tornado.gen.coroutine
def get(self):
message_list = yield self.settings['db'].messages.find().sort([('_id', -1)]).limit(50).to_list(length=None)
self.render('msg_list.html', message_list=message_list)
class MessageAddHandler(tornado.web.RequestHandler):
def get(self):
self.render('msg_add.html')
@tornado.gen.coroutine
def post(self):
msg = self.get_argument('msg')
res = yield self.settings['db'].messages.insert_one({'msg': msg})
self.redirect('/message/list/')
class Application(tornado.web.Application):
def __init__(self):
handlers = [
(r'/message/list/', MessageListHandler),
(r'/message/add/', MessageAddHandler),
]
db = motor.motor_tornado.MotorClient('mongodb://127.0.0.1:27017', maxPoolSize=200).test_motor
settings = dict(
db=db,
debug=False,
)
tornado.web.Application.__init__(self, handlers, **settings)
def main():
app = Application()
httpserver = HTTPServer(app, xheaders=True)
httpserver.listen(80)
tornado.ioloop.IOLoop.instance().start()
if __name__ == '__main__':
main()
- get请求数据列表:
- post新增数据:
分析相关的测试数据
由上述简单测试不难发现,在n1000c200
(总请求1000,并发200)的情况下各项测试数据相差并不是很大。
接着更改指标测试了n5000c500
和n8000c8000
,发现依旧差异不大,自认为再测下去也没什么意义。
发现每次测试接收数据速率一直都是70kb/s左右,发现是否和带宽限制有关,改到服务器本地和内网进行简单测试。
-
n1000c200
:-
pymongo
:
-
mongoengine
:
-
motor
:
-
由上述不难发现,当传输速度足够时mongoengine
相比起pymongo
和motor
的处理性能下降了很多很多。
继续更改测试指标试图发现pymongo
和motor
的差异。
-
n5000c500
:-
pymongo
:
-
motor
:
-
值得一提的是在ab
进行n5000c500
测试的时候,使用pymongo
时经常会出现apr_socket_recv: Connection reset by peer (104)
错误,且更大数量级请求和并发时一直出现导致所有请求无法完成,查了资料也没能解除这个限制;但在使用motor
时,基本没有出现这个错误信息,且对于n8000c800
也能正常测试,如下图。
考虑到很多情况下不会有这么多并发和请求,重新测试了现对较少请求和并发(n1000c50
等)的情况。
-
pymongo
:
-
mongoengine
:
-
motor
:
依旧是pymongo
和motor
表现的出色,mongoengine
感觉差了一个档次。
End
- 不管是在请求总量较少并发量小(如总请求1000,并发50、甚至总请求200,并发20)的情况下,
pymongo
和motor
始终比mongoengine
表现的出色。使用pymongo
和motor
服务器处理每次请求的速度甚至比mongoengine
快一倍,QPS和用户平均等待时间也相差很多。 - 但在使用
mongoengine
编写代码时,提供了现成的类似ORM的作用,可能编写代码相对较为方便;而pymongo
原生的始终是通过字典来操作的;motor
也是通过字典切需要结合tornado
的异步特性来使用。 -
pymongo
和motor
在上述较少数据量简单测试代码是表现的都很出色,相差不大(或者在相对少请求量和并发时,pymongo
比motor
略微好一点点),但在请求量很大并发大时,由于一些未能解决的错误,没有进行很好的完整的测试。之后可能需要添加更加复杂的逻辑代码,可能需要增大数据量,增加每次请求中访问数据库的次数,理论上应该motor
会表现的更好。 - 简单增加了一些无用的逻辑,单纯增加访问数据库的次数,再对
pymongo
和motor
进行一些测试。
class MessageListHandler(tornado.web.RequestHandler):
def get(self):
# message_list = self.settings['db'].messages.find().sort([('_id', -1)]).limit(50)
# message_list = self.settings['db'].messages.find().sort([('_id', -1)]).limit(500)
message_list = []
for i in range(10):
message_list.extend(self.settings['db'].messages.find().sort([('_id', -1)]).limit(50))
self.render('msg_list.html', message_list=message_list)
class MessageListHandler(tornado.web.RequestHandler):
@tornado.gen.coroutine
def get(self):
# message_list = yield self.settings['db'].messages.find().sort([('_id', -1)]).limit(50).to_list(length=None)
# message_list = yield self.settings['db'].messages.find().sort([('_id', -1)]).limit(500).to_list(length=None)
message_list = []
for i in range(10):
temp_list = yield self.settings['db'].messages.find().sort([('_id', -1)]).limit(50).to_list(length=None)
message_list.extend(temp_list)
self.render('msg_list.html', message_list=message_list)
motor
反而处理性能变的更差了。
参考链接:
- What are the Tornado and Mongodb blocking and asynchronous considerations?
- https://emptysqua.re/blog/introducing-motor-an-asynchronous-mongodb-driver-for-python-and-tornado/
- Apache ab测试工具使用方法(无参、get传参、post传参)
- tornado的mongo驱动选择,pymongo,motor,asyncmongo还是其他?
- Tornado 中 PyMongo Motor MongoEngine 的性能测试,这两个测试结果,一个pymongo好,一个motor好。
后续
重新在公司内部测试服务器(具体配置未知,但配置肯定比之前的阿里云高)用python 2.7进行了测试,测试代码也进行了稍作优化,增加了一定的业务逻辑,同时利用mongostat
简单监测了mongodb的各项数据,结果发现motor
框架在高并发,操作数据库代码较多时,表现的性能更好。
相关代码
上一篇: Apache ab的使用
下一篇: 压测工具ab的简单使用-总结