您的位置:首页 > 数据库 > Mongodb

利用ab测试工具对Tornado下使用mongodb驱动的性能简单测试分析

2017-12-18 18:42 537 查看
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
框架在高并发,操作数据库代码较多时,表现的性能更好。

相关代码
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: