小言_互联网的博客

学习笔记:使用nginx的反向代理和缓存技术提升tornado的吞吐量

457人阅读  评论(0)

在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
查看评论
* 以上用户言论只代表其个人观点,不代表本网站的观点或立场