搭建高性能web服务

要求:

  1. 安装实验环境与工具:nginx、gunicorn、gevent、curl、ab(apache 压力测试工具)等等
  2. 测试用程序: 自学教程 4.2 读写字典服务 definitions_readwrite.py。 请按需要在此基础上改写程序,必须使用 MongoDB 数据库。
  3. 准备 4 台电脑,记录下每台电脑配置。其中一台,安装 MongoDB ,不提供其他服务。
  4. 使用测试工具,测试单进程同步、Tornado 异步(需要改写程序和异步驱动)、GEvent 异步(需要使用wsgi方式启动)方式的性能。
  5. 使用测试工具,将前一步骤性能最好的方式,与多进程、多线程同步方式(用gunicorn wsgi方式启动)对比性能。
  6. 用 4 台电脑,组成一个性能最强的 web 服务

相关代码:

definitions_readwrite.py  

 1 import tornado.httpserver
 2 import tornado.ioloop
 3 import tornado.options
 4 import tornado.web
 5 import tornado.wsgi
 6 
 7 from pymongo import MongoClient
 8 from tornado.options import define, options
 9 define("port", default=8001, help="run on the given port", type = int)
10 
11 class Application(tornado.web.Application):
12     def __init__(self):
13         handlers = [(r"/(\w+)", WordHandler)]
14         #client = MongoClient('127.0.0.1', 27017)
15         client = MongoClient('172.18.40.194', 27017)
16         self.db = client["definitions"]
17         print self.db
18         tornado.web.Application.__init__(self, handlers, debug=True)
19 
20 class WordHandler(tornado.web.RequestHandler):
21     def get(self, word):
22         coll = self.application.db.words
23         word_doc = coll.find_one({"word": word})
24         if word_doc:
25             del word_doc["_id"]
26             self.write(word_doc)
27         else:
28             self.set_status(404)
29 
30     def post(self, word):
31         definition = self.get_argument("definition")
32         coll = self.application.db.words
33         word_doc = coll.find_one({"word": word})
34         if word_doc:
35             word_doc['definition'] = definition
36             coll.save(word_doc)
37         else:
38             word_doc = {'word': word, 'definition': definition}
39             coll.insert(word_doc)
40         del word_doc["_id"]
41         self.write(word_doc)
42 
43 # if __name__ == '__main__':
44 #     tornado.options.parse_command_line()
45 #     http_server = tornado.httpserver.HTTPServer(Application())
46 #      http_server.listen(options.port)
47 #      tornado.ioloop.IOLoop.instance().start()
48 
49 wsgi_app = tornado.wsgi.WSGIAdapter(Application())
View Code

tornado_definitions_readwirte.py

 1 import tornado.httpserver
 2 import tornado.ioloop
 3 import tornado.options
 4 import tornado.web
 5 import tornado.httpclient
 6 import tornado.gen
 7 import asyncmongo
 8 import logging
 9 import tornado.wsgi
10 import gevent
11 
12 from tornado.options import define, options
13 define("port", default=8001, help="run on the given port", type = int)
14 
15 class Application(tornado.web.Application):
16     def __init__(self):
17         handlers = [(r"/(\w+)", WordHandler)]
18         #self.db = asyncmongo.Client(pool_id='mydb', host='172.18.40.194', port=27017, dbname='definitions')
19         self.db = asyncmongo.Client(pool_id='mydb', host='127.0.0.1', port=27017, dbname='definitions')
20         print self.db
21         tornado.web.Application.__init__(self, handlers, debug=True)
22 
23 class WordHandler(tornado.web.RequestHandler):
24     word = ''
25 
26     @tornado.web.asynchronous
27     def get(self, word):
28         coll = self.application.db.words
29         coll.find_one({'word':word}, callback=self.query_callback1)
30 
31     def query_callback1(self, response, error):
32         word_doc = response
33         logging.info(response)
34         if word_doc:
35             del word_doc["_id"]
36             self.write(word_doc)
37         else:
38             self.set_status(404)
39         self.finish()
40 
41     @tornado.web.asynchronous
42     def post(self, word):
43         self.word = word
44         coll = self.application.db.words
45         coll.find_one({'word': word}, callback=self.query_callback2) #
46 
47     def query_callback2(self, response, error):
48         logging.info("haha")
49         coll = self.application.db.words
50         word_doc = response
51         definition = self.get_argument("definition")
52         word = self.word
53         if word_doc:
54             responce = coll.remove({"word": word}, callback=self.delete_callback)
55         else:
56             logging.info("not exit\n")
57             word_doc = {'word': word, 'definition': definition}
58             coll.insert(word_doc, callback=self.insert_callback)
59 
60     def delete_callback(self, response, error):
61         coll = self.application.db.words
62         word = self.word
63         definition = self.get_argument("definition")
64         word_doc = {'word': word, 'definition': definition}
65         coll.insert(word_doc, callback=self.insert_callback);
66 
67     def insert_callback(self, response, error):
68         word = self.word
69         definition = self.get_argument("definition")
70         word_doc = {'word': word, 'definition': definition}
71         self.write(word_doc)
72         logging.info("insert:")
73         logging.info(response)
74         self.finish()
75 
76 if __name__ == "__main__":
77      tornado.options.parse_command_line()
78      http_server = tornado.httpserver.HTTPServer(Application())
79      http_server.listen(options.port)
80      tornado.ioloop.IOLoop.instance().start()
81 #gevent.monkey.patch_all()
82 #wsgi_app = tornado.wsgi.WSGIAdapter(Application())
View Code

/etc/nginx/nginx.config

 1 #user nginx;
 2 worker_processes 4;
 3 
 4 error_log /var/log/nginx/error.log;
 5 pid /var/run/nginx.pid;
 6 
 7 events {
 8     worker_connections 4096;
 9     use epoll;
10 }
11 
12 http {
13     # Enumerate all the Tornado servers here
14     upstream frontends {
15         server 172.18.43.53:8001 weight=3;
16         server 172.18.42.169:8001 weight=4;
17         server 172.18.43.132:8001 weight=3;
18     }
19 
20     include /etc/nginx/mime.types;
21     default_type application/octet-stream;
22 
23     access_log /var/log/nginx/access.log;
24 
25     keepalive_timeout 65;
26     proxy_read_timeout 200;
27     sendfile on;
28     tcp_nopush on;
29     tcp_nodelay on;
30     gzip on;
31     gzip_min_length 1000;
32     gzip_proxied any;
33     gzip_types text/plain text/html text/css text/xml
34                application/x-javascript application/xml
35                application/atom+xml text/javascript;
36 
37     # Only retry if there was a communication error, not a timeout
38     # on the Tornado server (to avoid propagating "queries of death"
39     # to all frontends)
40     proxy_next_upstream error;
41     
42     server {
43         listen 80;
44 
45         # Allow file uploads
46         client_max_body_size 50M;
47 
48         location ^~ /static/ {
49             root /var/www;
50             if ($query_string) {
51                 expires max;
52             }
53         }
54         location = /favicon.ico {
55             rewrite (.*) /static/favicon.ico;
56         }
57         location = /robots.txt {
58             rewrite (.*) /static/robots.txt;
59         }
60 
61         location / {
62             proxy_pass_header Server;
63             proxy_set_header Host $http_host;
64             proxy_redirect off;
65             proxy_set_header X-Real-IP $remote_addr;
66             proxy_set_header X-Scheme $scheme;
67             proxy_pass http://frontends;
68         }
69     }
70 }
View Code

环境配置:

$sudo pip install gunicorn
$sudo pip install greenlet
$sudo pip install eventlet
$sudo pip install gevent
$sudo apt-get install apache2-utils (用于压力测试)

实验步骤:

UML部署图:

以下的测试均在测试量为10000,并发量为100的情况下进行

1)进行单进程同步压力测试

  a)在A机上开启Mongodb服务, 命令如下:mongod.exe --dbpath "C:\Program Files\MongoDB\data\db" ;

  b)在B机中启动nginx;

  c)在C机, B机,D机中都运行python definition_readwrite.py作为应用服务器

  e)在C机中进行压力测试,命令为ab ab -n 1000 -c 10 http://localhost/  (localhost和端口号需要换成代理机的IP和端口号)

  f)可查看测试结果

2)其他的测试也类似,其中可通过查看如下的测试结果图比较

 

截图:

单进程同步:

tornado异步:

Gevent

多进程同步:

多线种同步:

说明 :1)要本地运行gunicorn时的命令一般为gunicorn code:application; 这样启动gunicorn,其默认作为一个监听127.0.01:8000的web server,可以通过本机访问,如果需要通过网络来访问gunicorn服务的话,需要使用-b命令来绑定不同的地址,同时设置监听端口,也就是需要使 用本机在网络中的IP地址加端口号;

  2)修改程序为tornado异步的,不仅方式要是异步的,同时也需要使用异步的mongodb;

  3) 用gevent运行程序,会自动将同步的程序的每个进程使其都支持IO阻塞异步的。

posted @ 2015-11-04 20:42  Kinthon  阅读(389)  评论(0编辑  收藏  举报