学习笔记:使用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()

启动该脚本后,使用浏览器访问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]
101220.878.1910.819
501294.0238.6390.773
801302.6261.4150.768
901267.3371.0160.789
1001305.6976.5880.766
1101244.3688.3990.804
1201290.9792.9540.775
150495.69302.6062.017
200504.87396.1441.981
300532.26563.6321.879
500505.32989.4731.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,来模拟访问数据库的操作,对不启用缓存和启用缓存进行压力测试:

并发数不启用缓存的吞吐量启用缓存的吞吐量
1035.101239.45
5037.321247.42
8037.391251.62
9038.011243.70
10037.831256.48
11038.111248.20
12037.971247.26
15038.351187.58
20038.381233.15
30038.51620.97
50038.52630.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,每次都可以得到最新的时间。

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

天元浪子

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值