在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()
启动该脚本后,使用浏览器访问127.0.0.1,页面显示“我是tornado,我够快!”。这个例子没有使用文件读写、数据库读写等耗时的操作,更能反应出tornado本身的吞吐能力。
压力测试通常使用Apache自带的ab.exe,ab的使用方法为:
ab -n 请求数 -c 并发数 URL
下面是并发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)
它的输出中有以下关键信息:
- 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()
在命令行中输入如下命令,启动两个服务器进程,每个进程使用不同的端口:
python server.py 9001-9002
接下来,配置ngnix。nginx的配置并不复杂,可以复制解压目录下的conf/ngnix.conf,进行修改即可。
在http部分中添加upstream,语法为:
http {
upstream 名称 {
负载均衡策略
server IP地址:端口 其它参数;
}
}
其中可选的负载均衡策略有:
- 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;
}
将原来的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_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;
}
}
}
启动tornado,并进入配置文件的目录,使用如下命令启动nginx:
nginx -c nginx.conf
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 有效期;
}
}
}
修改后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;
}
}
}
重启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()
此时浏览器访问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;
}
}
}
重启nginx后,再访问127.0.0.1/variable,每次都可以得到最新的时间。
转载:https://blog.csdn.net/xufive/article/details/104112302