学习笔记:使用nginx的反向代理和缓存技术提升tornado的吞吐量
在B/S应用中,页面缓存技术是提升服务能力的重要手段。页面缓存又分为浏览器缓存和服务端缓存两类,本文仅讨论Nginx服务器的页面缓存。Nginx服务缓存的基本原理是对客户请求过的资源建立本地副本,在一段合理时期内任何用户再次请求该资源时,Nginx服务器无需要再次向后端服务器发出请求,而是直接应答缓存的副本。因此,缓存技术可以明显降低后端服务器的负载,减轻网络传输负担,极大地提升响应速度。
1. tornado的吞吐能力
我们用一个最简单的例子,测试一下tornado的吞吐能力:
# -*- coding: utf-8 -*-
import os
import sys
import tornado.web
import tornado.ioloop
import tornado.httpserver
from tornado.options import parse_command_line
class Handler(tornado.web.RequestHandler): def get(self): self.write('我是tornado,我够快!')
class Application(tornado.web.Application): def __init__(self): handlers = [ (r"/", Handler) ] settings = dict( title='压力测试', debug=True, ) tornado.web.Application.__init__(self, handlers, **settings)
parse_command_line()
http_server = tornado.httpserver.HTTPServer(Application(), xheaders=True, max_buffer_size=504857600)
http_server.listen(80)
print('Web server is started')
tornado.ioloop.IOLoop.instance().start()
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
启动该脚本后,使用浏览器访问127.0.0.1,页面显示“我是tornado,我够快!”。这个例子没有使用文件读写、数据库读写等耗时的操作,更能反应出tornado本身的吞吐能力。
压力测试通常使用Apache自带的ab.exe,ab的使用方法为:
ab -n 请求数 -c 并发数 URL
- 1
下面是并发10个请求共计100个请求的压力测试:
ab -n 100 -c 10 http://127.0.0.1/
This is ApacheBench, Version 2.3 <$Revision: 1843412 $>
Copyright 1996 Adam Twiss, Zeus Technology Ltd, http://www.zeustech.net/
Licensed to The Apache Software Foundation, http://www.apache.org/
Benchmarking 127.0.0.1 (be patient).....done
Server Software: TornadoServer/6.0.3
Server Hostname: 127.0.0.1
Server Port: 9001
Document Path: /
Document Length: 22 bytes
Concurrency Level: 10
Time taken for tests: 0.107 seconds
Complete requests: 100
Failed requests: 0
Total transferred: 21700 bytes
HTML transferred: 2200 bytes
Requests per second: 937.09 [#/sec] (mean)
Time per request: 10.671 [ms] (mean)
Time per request: 1.067 [ms] (mean, across all concurrent requests)
Transfer rate: 198.58 [Kbytes/sec] received
Connection Times (ms) min mean[+/-sd] median max
Connect: 0 1 0.6 0 2
Processing: 4 9 3.0 9 18
Waiting: 2 9 3.2 8 18
Total: 4 10 3.1 9 19
WARNING: The median and mean for the initial connection time are not within a normal deviation These results are probably not that reliable.
Percentage of the requests served within a certain time (ms)
50% 9
66% 10
75% 13
80% 14
90% 15
95% 15
98% 18
99% 19
100% 19 (longest request)
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
它的输出中有以下关键信息:
- Concurrency Level: 并发数,使用-c参数指定的数量
- Time taken for tests: 测试用共用时长
- Complete requests: 完成的请求数
- Failed requests: 失败的请求数
- Total transferred: 共传输的数据量
- HTML transferred: 页面传输的数据量
- Requests per second: 平均每秒响应的请求数(吞吐量)
- Time per request: 用户平均请求等待时间 [ms]
- Time per request: 服务器平均处理时间 [ms]
- Transfer rate: 输入速率
我们发送10000次请求,用不同的并发数,多次进行测试,得到的结果如下表所示:
并发数 | 每秒响应的请求数(吞吐量) | 用户平均请求等待时间 [ms] | 服务器平均处理时间 [ms] |
---|---|---|---|
10 | 1220.87 | 8.191 | 0.819 |
50 | 1294.02 | 38.639 | 0.773 |
80 | 1302.62 | 61.415 | 0.768 |
90 | 1267.33 | 71.016 | 0.789 |
100 | 1305.69 | 76.588 | 0.766 |
110 | 1244.36 | 88.399 | 0.804 |
120 | 1290.97 | 92.954 | 0.775 |
150 | 495.69 | 302.606 | 2.017 |
200 | 504.87 | 396.144 | 1.981 |
300 | 532.26 | 563.632 | 1.879 |
500 | 505.32 | 989.473 | 1.979 |
从数据中可以看出,随着并发数量的增加,服务器平均处理时间和用户平均请求等待时间都在增加;并发小于100时,服务器还没有饱和,吞吐量还在增加;并发大于100后,服务器的处理能力开始受到影响,吞吐量开始下降。
我使用windows平台,在我的测试条件下,tornado每秒最多响应1305次请求。Linux平台上,tornado的表现要比windows平台好得多。
2. nginx的反向代理
代理服务器是架设在客户端和服务器之间的中间服务器,我们一般所说的代理是正向代理。正向代理是客户端的出口,客户端将请求发送给正向代理服务器,告诉正向代理服务器我要访问哪个服务器,然后正向代理服务器向目标服务器发送请求,并将响应返回给客户端。从服务器的角度看,它并不知道真正的请求是哪个客户端发出来的,有几个客户端,只从代理服务器接受请求。
与正向代理相反,反向代理是服务器的入口,客户端并不知道真正的服务器是哪个,有几个服务器,只知道反向代理服务器是哪个。它向反向代理服务器发送请求,反向代理服务器会有选择的把请求发送到其中的一台服务器,并将服务器的响应返回给客户端。
反向代理使服务器由一个变为多个,并为多个服务器提供统一的入口,可根据每个服务器的负载向负载最轻的服务器转发请求,这就是负载均衡。
nginx是一款优秀的反向代理服务器,可以从官网下载压缩包,解压后直接使用。
首先,我们修改一下服务器的代码,使之可以同时启动多个进程:
# -*- coding: utf-8 -*-
import os
import sys
import multiprocessing
import tornado.web
import tornado.ioloop
import tornado.httpserver
from tornado.options import parse_command_line
# 页面句柄
class Handler(tornado.web.RequestHandler): def get(self): self.write('我是tornado,我够快!')
class Application(tornado.web.Application): def __init__(self): handlers = [ (r"/", Handler), ] settings = dict( title='压力测试', debug=True, ) tornado.web.Application.__init__(self, handlers, **settings)
# 启动服务器
def start_web_server(port): parse_command_line() http_server = tornado.httpserver.HTTPServer(Application(), xheaders=True, max_buffer_size=504857600) http_server.listen(port) print('Web server is started on port %d.' % port) tornado.ioloop.IOLoop.instance().start()
if __name__ == "__main__": if len(sys.argv) == 1: start_web_server(80) else: try: ports = [int(port) for port in sys.argv[1].split(',')] except: try: a, b = sys.argv[1].split('-') ports = range(int(a), int(b) + 1) except: ports = list() print ('Parameter error.') multiprocessing.freeze_support() for port in ports: p = multiprocessing.Process(target=start_web_server, args=(port,)) p.start()
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
- 49
- 50
- 51
- 52
- 53
- 54
- 55
- 56
在命令行中输入如下命令,启动两个服务器进程,每个进程使用不同的端口:
python server.py 9001-9002
- 1
接下来,配置ngnix。nginx的配置并不复杂,可以复制解压目录下的conf/ngnix.conf,进行修改即可。
在http部分中添加upstream,语法为:
http { upstream 名称 { 负载均衡策略 server IP地址:端口 其它参数; }
}
- 1
- 2
- 3
- 4
- 5
- 6
其中可选的负载均衡策略有:
- ip_hash: 这种策略会把某一ip映射到一个固定的服务器,其优点是容易保持session的一致性,缺点是当该服务器负载过重后,也不能分流到其他服务器
- least_conn: 这种策略根据服务器连接的数量,选择连接数量最小的服务器,同一客户端不同的请求有可能会进入不同的服务器
- least_time: 这种策略计算每个服务器的响应时间,选择响应时间小短的服务器,同一客户端不同的请求有可能会进入不同的服务器
我选择least_time,配置如下:
upstream serv { least_conn; server 127.0.0.1:9001; server 127.0.0.1:9002;
}
- 1
- 2
- 3
- 4
- 5
将原来的location /的内容修改为如下内容:
proxy_pass http://serv$request_uri; #以下三行,目的是将代理服务器收到的用户的信息传到真实服务器上
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
- 1
- 2
- 3
- 4
- 5
- 6
其中proxy_pass后面的http://serv$request_uri中,serv为刚才配置的upstream的名称。
修改并删除了原来配置文件中的注释会,配置文件如下:
worker_processes 1;
events { worker_connections 1024;
}
http { sendfile on; keepalive_timeout 65; upstream serv { least_conn;
server 127.0.0.1:9001; server 127.0.0.1:9002; } server { listen 80; server_name localhost; location / { proxy_pass http://serv$request_uri; #以下三行,目的是将代理服务器收到的用户的信息传到真实服务器上 proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
} }
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
启动tornado,并进入配置文件的目录,使用如下命令启动nginx:
nginx -c nginx.conf
- 1
OK,反向代理配置完成。再次用ab进行压力测试, 结果并不像我们期望的那样,吞吐量成倍增长。这是因为tornado的IO几乎已经做到了极致,几乎比肩nginx,wondows平台上单台PC的吞吐量大致也就如此了。当tornado需要进行文件读写、数据库读写等耗时的操作时,多进程的反向代理才能体现出优势。
3. 使用缓存技术
除了反向代理,nginx还可以启用缓存技术,进一步提高服务能力。当客户端第一次请求某url时,nginx将请求转发给服务器,服务器返回后,nginx在本地创建缓存。在缓存未失效前,nginx不再转发请求,而是直接将缓存的内容返回给客户端,服务器的负载被转嫁到nginx上,而nginx的性能是非常出色的。
在nginx配置文件中设置缓存,语法为:
http { proxy_cache_path 缓存路径 keys_zone=缓存名称:缓存大小 levels=一级缓存名长度:二级缓存名长度 inactive=失活时间 max_size=最大大小; server { location url { proxy_cache 缓存名称; proxy_cache_min_uses 访问次数(url被访问多少次后进行缓存); proxy_cache_valid any 有效期; } }
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
修改后nginx的配置文件为:
worker_processes 1;
events { worker_connections 1024;
}
http { sendfile on; keepalive_timeout 65; upstream serv { least_conn;
server 127.0.0.1:9001; server 127.0.0.1:9002; server 127.0.0.1:9003; server 127.0.0.1:9004; } # 设置缓存路径 proxy_cache_path cache keys_zone=CACHE:10m levels=1:4 inactive=1m max_size=1g; server { listen 80; server_name localhost; location / { proxy_pass http://serv$request_uri; #以下三行,目的是将代理服务器收到的用户的信息传到真实服务器上 proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; # 缓存 proxy_cache CACHE; proxy_cache_min_uses 1; proxy_cache_valid any 1m;
} }
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
重启nginx,此时使用浏览器访问127.0.0.1,第一次nginx没有缓存,服务器端打印出了访问日志,再以后的访问,服务器不再打印日志,说明nginx缓存起到了作用。
我们在tornado服务器的代码中加入100毫秒的sleep,来模拟访问数据库的操作,对不启用缓存和启用缓存进行压力测试:
并发数 | 不启用缓存的吞吐量 | 启用缓存的吞吐量 |
---|---|---|
10 | 35.10 | 1239.45 |
50 | 37.32 | 1247.42 |
80 | 37.39 | 1251.62 |
90 | 38.01 | 1243.70 |
100 | 37.83 | 1256.48 |
110 | 38.11 | 1248.20 |
120 | 37.97 | 1247.26 |
150 | 38.35 | 1187.58 |
200 | 38.38 | 1233.15 |
300 | 38.51 | 620.97 |
500 | 38.52 | 630.94 |
可以看出,缓存技术对吞吐量的提升非常有效!
4. 缓存的副作用及解决方案
缓存,意味着不是最新的,如果某页面的内容的变化很快,使用缓存技术将导致客户端接收到错误的结果。如我增加一个url,输出服务器当前的时间:
# -*- coding: utf-8 -*-
import os
import sys
import time
import datetime
import multiprocessing
import tornado.web
import tornado.ioloop
import tornado.httpserver
from tornado.options import parse_command_line
# 页面句柄
class StaticHandler(tornado.web.RequestHandler): def get(self): time.sleep(0.1) self.write('我是tornado,我够快!') class VariableHandler(tornado.web.RequestHandler): def get(self): now = datetime.datetime.now() self.write(now.strftime("%Y-%m-%d %H:%M:%S"))
class Application(tornado.web.Application): def __init__(self): handlers = [ (r"/", StaticHandler), # 可以缓存的页面 (r"/variable", VariableHandler), # 禁止缓存的页面 ] settings = dict( title='压力测试', debug=True, ) tornado.web.Application.__init__(self, handlers, **settings)
# 启动服务器
def start_web_server(port): parse_command_line() http_server = tornado.httpserver.HTTPServer(Application(), xheaders=True, max_buffer_size=504857600) http_server.listen(port) print('Web server is started on port %d.' % port) tornado.ioloop.IOLoop.instance().start()
if __name__ == "__main__": if len(sys.argv) == 1: start_web_server(80) else: try: ports = [int(port) for port in sys.argv[1].split(',')] except: try: a, b = sys.argv[1].split('-') ports = range(int(a), int(b) + 1) except: ports = list() print ('Parameter error.') multiprocessing.freeze_support() for port in ports: p = multiprocessing.Process(target=start_web_server, args=(port,)) p.start()
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
- 49
- 50
- 51
- 52
- 53
- 54
- 55
- 56
- 57
- 58
- 59
- 60
- 61
- 62
- 63
- 64
- 65
- 66
此时浏览器访问127.0.0.1/variable,第一次出现了正确的时间,以后的1分钟以内,时间不再变化,等1分钟以后缓存过期,再访问出能得到新的时间。为了解决这个问题,可以在nginx配置中添加多个location,分别指定是否启用缓存即可:
worker_processes 1;
events { worker_connections 1024;
}
http { sendfile on; keepalive_timeout 65; upstream serv { least_conn;
server 127.0.0.1:9001; server 127.0.0.1:9002; server 127.0.0.1:9003; server 127.0.0.1:9004; } proxy_cache_path cache keys_zone=CACHE:1m levels=1:2 inactive=1m max_size=1g; server { listen 80; server_name localhost; location / { proxy_pass http://serv$request_uri; #以下三行,目的是将代理服务器收到的用户的信息传到真实服务器上 proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; # 缓存 proxy_cache CACHE; proxy_cache_min_uses 1; proxy_cache_valid any 1m;
} # 只转发请求,不进行缓存 location /variable { proxy_pass http://serv$request_uri; #以下三行,目的是将代理服务器收到的用户的信息传到真实服务器上 proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
} }
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
- 49
- 50
重启nginx后,再访问127.0.0.1/variable,每次都可以得到最新的时间。
文章来源: xufive.blog.csdn.net,作者:天元浪子,版权归原作者所有,如需转载,请联系作者。
原文链接:xufive.blog.csdn.net/article/details/104112302
- 点赞
- 收藏
- 关注作者
评论(0)