摘要:Flask
抖棘,gunicorn
,nginx
净捅,docker
疑枯,gevent
,WSGI
整理一下Flask的部署相關(guān)代碼蛔六,以及各個(gè)組件的使用和一些原理
為什么需要Gunicorn
在開發(fā)時(shí)flask的run命令可以直接啟動(dòng)提供web服務(wù)荆永,實(shí)際上是由Werkzeug
提供的WSGI服務(wù)器
,相當(dāng)于Flask內(nèi)置了一個(gè)WSGI服務(wù)器国章,只適合在開發(fā)調(diào)試的時(shí)候使用具钥;在生產(chǎn)環(huán)境中需要一個(gè)更強(qiáng)健,性能更高的WSGI服務(wù)器液兽,WSGI服務(wù)器也被稱為獨(dú)立的WSGI容器
骂删,主流的WSGI容器有Gunicorn
和uWSGI
什么是WSGI服務(wù)器
Web Server Gateway Interface 的縮寫,即 Web 服務(wù)器網(wǎng)關(guān)接口四啰。Python web開發(fā)中宁玫,服務(wù)端程序分為兩個(gè)部分
-
服務(wù)器程序
:用來接收、整理客戶端發(fā)送的請求柑晒,比如Nginx -
應(yīng)用程序
:處理服務(wù)器程序傳遞過來的請求欧瘪,比如Flask,Django匙赞,Tornado
服務(wù)器程序和應(yīng)用程序互相配合
才能給用戶提供服務(wù)佛掖,而不同應(yīng)用程序(不同框架)會(huì)有不同的函數(shù)、功能罚屋。 此時(shí)就需要一個(gè)標(biāo)準(zhǔn)
苦囱,讓服務(wù)器程序和應(yīng)用程序都支持這個(gè)標(biāo)準(zhǔn),這樣二者就能很好的配合了脾猛,這個(gè)標(biāo)準(zhǔn)就是WSGI
撕彤,是python web開發(fā)的標(biāo)準(zhǔn),類似于協(xié)議,是web服務(wù)器程序與應(yīng)用程序解耦
的規(guī)范羹铅,這樣服務(wù)器程序和應(yīng)用程序就可以隨意組合
實(shí)現(xiàn)自己的web應(yīng)用蚀狰。它是服務(wù)器程序和應(yīng)用程序的一個(gè)約定
,規(guī)定了各自使用的接口和功能职员,以便二和互相配合麻蹋。
為什么需要Nginx
Nginx是Web服務(wù)器
,流行的Web服務(wù)器還有Apache焊切,Tengine等扮授,Web服務(wù)器主要負(fù)責(zé)和客戶端交換數(shù)據(jù),處理請求和響應(yīng),像Gunicorn這類WSGI服務(wù)器內(nèi)置了Web服務(wù)器专肪,但是內(nèi)置的Web服務(wù)器不夠強(qiáng)健刹勃,更流行的部署方式是采用一個(gè)常規(guī)的Web服務(wù)器運(yùn)行在前端,為WSGI服務(wù)器提供反向代理嚎尤。在Gunicorn之后再加一層Nginx有以下好處:
-
負(fù)載均衡
: 當(dāng)有多個(gè)應(yīng)用多臺(tái)機(jī)器時(shí)需要做負(fù)載均衡 -
靜態(tài)文件處理
:經(jīng)過配置之后荔仁,Nginx可以直接處理靜態(tài)文件請求而不用經(jīng)過Python服務(wù)器,Gunicorn或者Flask等對靜態(tài)資源的處理效率不如Nginx芽死,并且Nginx可以對靜態(tài)文件設(shè)置緩存 -
安全問題
:Gunicorn暴露在公網(wǎng)公網(wǎng)十分危險(xiǎn)乏梁,在Nginx擋在前面會(huì)安全不少 -
抗并發(fā)壓力
:前端多一層Nginx,可以吸收一些瞬時(shí)的并發(fā)請求作為請求緩沖关贵,讓Nginx先保持住連接遇骑,然后后端慢慢消化 -
支持的http協(xié)議更廣
:gunicorn的http解析可能有bug,Nginx處理更好 -
提供其他額外功能
:比如IP過濾等
使用Gunicorn作為容器啟動(dòng)Flask
安裝gunicorn坪哄,使用pip下載安裝
pip install gunicorn
如果以gevent模式運(yùn)行g(shù)unicorn质蕉,需要安裝gevent,版本20.9.0以上
pip install gevent==20.9.0
編寫gunicorn配置文件
root@ubuntu:~/myproject/pira_score_web_application# cat gun.conf.py
# gun.conf
bind = '0.0.0.0:5000'
workers = 5
backlog = 2048
worker_class = "gevent"
debug = False
proc_name = 'gunicorn.proc'
pidfile = './gunicorn.pid'
#accesslog = '/var/log/gunicorn/pira_score_web/detail.log'
#access_log_format = '%(h)s %(l)s %(u)s %(t)s'
#loglevel = 'info'
Gunicorn配置詳解
-
-c, --config
:啟動(dòng)時(shí)引入Gunicorn的配置文件路徑 -
-b, --bind
:Gunicorn與指定socket進(jìn)行綁定. -
--backlog
:未決連接的最大數(shù)量翩肌,即等待服務(wù)的客戶的數(shù)量模暗。必須是正整數(shù),一般設(shè)定在64~2048的范圍內(nèi)念祭,一般設(shè)置為2048
兑宇,超過這個(gè)數(shù)字將導(dǎo)致客戶端在嘗試連接時(shí)錯(cuò)誤 -
-w, --workers
:用于處理工作進(jìn)程
的數(shù)量,為正整數(shù)粱坤,默認(rèn)為1
隶糕。worker推薦的數(shù)量為當(dāng)前的CPU個(gè)數(shù)*2 + 1 -
-k, --worker-class
:要使用的工作模式,默認(rèn)為sync
站玄,可以使用其他模式比如gevent
枚驻,tornado
,但是需要額外pip安裝 -
--threads
:處理請求的工作線程
數(shù)株旷,使用指定數(shù)量的線程運(yùn)行每個(gè)worker
再登。為正整數(shù)尔邓,默認(rèn)為1
。 -
--reload
:代碼更新時(shí)將重啟工作锉矢,默認(rèn)為False
梯嗽。 -
-D,--daemon
:守護(hù)Gunicorn進(jìn)程后臺(tái)運(yùn)行,默認(rèn)False
沽损。 -
-p, --pid, pidfile
:設(shè)置pid文件的文件名灯节,如果不設(shè)置將不會(huì)創(chuàng)建pid文件。 -
proc_name
:設(shè)置進(jìn)程名绵估。
編寫gunicorn啟動(dòng)腳本
root@ubuntu:~/myproject/pira_score_web_application# cat run.sh
#! /bin/bash
cd /home/gp/myproject/pira_score_web_application
gunicorn -c gun.conf.py -D app:app
查看后臺(tái)gunicorn進(jìn)程pid
root@ubuntu:~/myproject/pira_score_web_application# cat gunicorn.pid
30322
root@ubuntu~/myproject/pira_score_web_application# ps -ef|grep `cat gunicorn.pid`
root 30322 1104 0 10:47 ? 00:00:00 /opt/anaconda3/bin/python /opt/anaconda3/bin/gunicorn -c gun.conf.py -D app:app
root 30325 30322 0 10:47 ? 00:00:00 /opt/anaconda3/bin/python /opt/anaconda3/bin/gunicorn -c gun.conf.py -D app:app
root 30326 30322 0 10:47 ? 00:00:00 /opt/anaconda3/bin/python /opt/anaconda3/bin/gunicorn -c gun.conf.py -D app:app
root 30327 30322 0 10:47 ? 00:00:00 /opt/anaconda3/bin/python /opt/anaconda3/bin/gunicorn -c gun.conf.py -D app:app
root 30328 30322 0 10:47 ? 00:00:00 /opt/anaconda3/bin/python /opt/anaconda3/bin/gunicorn -c gun.conf.py -D app:app
root 30329 30322 0 10:47 ? 00:00:00 /opt/anaconda3/bin/python /opt/anaconda3/bin/gunicorn -c gun.conf.py -D app:app
root 31611 21931 0 11:01 pts/3 00:00:00 grep --color=auto 30322
可見后臺(tái)一共有一個(gè)父進(jìn)程和5個(gè)子進(jìn)程炎疆,和5個(gè)workers對應(yīng),可以使用父進(jìn)程pid直接關(guān)閉gunicorn的所有進(jìn)程
root@ubuntu:~/myproject/pira_score_web_application# kill -9 `cat gunicorn.pid`
gevent和協(xié)程
gevent
:是一個(gè)基于協(xié)程的python網(wǎng)絡(luò)庫壹士,在遇到IO阻塞時(shí)磷雇,程序會(huì)自動(dòng)進(jìn)行切換,可以讓開發(fā)者用同步的方式寫異步IO代碼躏救。
協(xié)程
:是單線程下的并發(fā),又稱微線程螟蒸,是一種并發(fā)編程
模式盒使,協(xié)程并發(fā)的本質(zhì)是切換+保存狀態(tài)。
測試使用gevent運(yùn)行兩個(gè)阻塞IO任務(wù)七嫌,分別阻塞3秒少办,4秒;gevent使用spawn
定義一個(gè)協(xié)程任務(wù)诵原,接受任務(wù)名
和入?yún)?/code>英妓,是一個(gè)
異步任務(wù)
,使用join
等待協(xié)程執(zhí)行完畢退出绍赛,也可以調(diào)用joinall
方法傳入一個(gè)任務(wù)列表蔓纠。
from gevent import monkey
import gevent
import time
monkey.patch_all() # 合并成一行,專門用于打標(biāo)記
def eat(name):
print("%s is eating 1" % name)
# gevent.sleep(3) # gevent.sleep()和 time.sleep()效果一樣
time.sleep(3)
print("%s is eating 2" % name)
def play(name):
print("%s play 1" % name)
# gevent.sleep(4)
time.sleep(4)
print("%s play 2" % name)
start = time.time()
g1 = gevent.spawn(eat, "aaa") # 提交任務(wù) # spawn()第一個(gè)參數(shù)寫任務(wù)名吗蚌,后面直接參數(shù)就行(位置參數(shù)或關(guān)鍵字參數(shù)都可以)
g2 = gevent.spawn(play, "bbb") # gevent.spawn()是異步提交任務(wù)
g1.join()
g2.join() # 保證上面提交的兩個(gè)任務(wù)都執(zhí)行完畢了 # 協(xié)程是單線程的腿倚,需要再線程結(jié)束前等待g1和g2,要不然g1和g2還沒起來蚯妇,“主線程”就結(jié)束了,此時(shí)g1和g2也就不會(huì)再執(zhí)行了
# g1.join()和g2.join()可以合并成:
# gevent.joinall([g1,g2])
stop = time.time()
print(stop - start) # 4.002309322357178
執(zhí)行結(jié)果為最大阻塞時(shí)間4秒,如果是串行執(zhí)行為7秒紊馏。
gevent執(zhí)行過程分析:
(1)先啟任務(wù)1
:g1先起來富雅,執(zhí)行了第一個(gè)print,然后遇到了IO阻塞(gevent.sleep(3)),然后立馬就切到了 g2 提交的 play任務(wù)
(2)任務(wù)1阻塞切換任務(wù)2
:執(zhí)行 play中的第一個(gè)print陨收,接著又遇到了IO阻塞(gevent.sleep(4))饭豹,然后就又切到了 g1的eat任務(wù)
(3)來回切換
:此時(shí)g1的eat還是處于阻塞狀態(tài),接著就在兩個(gè)任務(wù)之間來回切
(4)分別等待協(xié)程執(zhí)行就緒
:直到 g1的eat 又處于就緒狀態(tài),打印 eat的第2個(gè)print墨状;執(zhí)行完 eat之后卫漫,g2的play還處于阻塞狀態(tài),然后等其阻塞結(jié)束后執(zhí)行 play的第2個(gè)print肾砂;
gevent監(jiān)測了多個(gè)任務(wù)之間的IO阻塞列赎,遇到IO阻塞就切走
Gunicorn前后壓測對比
使用壓測工具siege模擬并發(fā),先修改根目錄下.siege目錄下的配置文件siege.conf
設(shè)置最大并發(fā)量為1000镐确,先對比400并發(fā)下包吝,循環(huán)測試2次,請求之間無間隔的統(tǒng)計(jì)結(jié)果
flask自帶的WSGI服務(wù)器
root@ubuntu:~/.siege# siege -c 400 -r 2 -b "http://192.168.67.72:5000/北京優(yōu)勝輝煌教育科技有限公司.html"
** SIEGE 4.0.4
** Preparing 400 concurrent users for battle.
The server is now under siege...
Transactions: 4800 hits
Availability: 100.00 %
Elapsed time: 30.62 secs
Data transferred: 744.21 MB
Response time: 1.77 secs
Transaction rate: 156.76 trans/sec
Throughput: 24.30 MB/sec
Concurrency: 276.91
Successful transactions: 4800
Failed transactions: 0
Longest transaction: 29.01
Shortest transaction: 0.04
Gunicorn作為WSGI服務(wù)器
Gunicorn開啟5個(gè)進(jìn)程啟動(dòng)Flask源葫,工作模式使用gevent
root@ubuntu:~/.siege# siege -c 400 -r 2 -b "http://192.168.61.100:5000/北京優(yōu)勝輝煌教育科技有限公司.html"
** SIEGE 4.0.4
** Preparing 400 concurrent users for battle.
The server is now under siege...
Transactions: 4800 hits
Availability: 100.00 %
Elapsed time: 13.72 secs
Data transferred: 744.21 MB
Response time: 0.83 secs
Transaction rate: 349.85 trans/sec
Throughput: 54.24 MB/sec
Concurrency: 291.27
Successful transactions: 4800
Failed transactions: 0
Longest transaction: 12.04
Shortest transaction: 0.00
-
Transactions
:總計(jì)傳輸?shù)氖聞?wù)數(shù)诗越,請求這個(gè)url加上其他靜態(tài)文件一共發(fā)出6個(gè)請求,循環(huán)兩次息堂,一共400 × 12 = 4800 -
Elapsed time
:總耗時(shí)嚷狞, Flask總耗時(shí) 30.62秒,Gunicorn耗時(shí)13.72秒 -
Response time
:平均響應(yīng)時(shí)間荣堰,F(xiàn)lask 1.77秒床未, Gunicorn 0.83秒 -
Transaction rate
:TPS每秒傳輸事務(wù)數(shù),F(xiàn)lask 156振坚,Gunicorn 350
測試結(jié)果Gunicorn的處理速度和性能是Flask的2倍多薇搁,并且在模擬并發(fā)達(dá)到500及以上時(shí),請求Flask服務(wù)報(bào)錯(cuò)渡八,而Gunicorn運(yùn)行良好
Docker容器化部署服務(wù)器
在項(xiàng)目目錄下創(chuàng)建requirements.txt
啃洋,指定項(xiàng)目需要的Python包和版本
# requirements.txt
flask==1.1.1
Flask-SQLAlchemy==2.4.4
gevent==20.9.0
gunicorn==20.0.4
numpy==1.19.5
pymysql==1.0.0
SQLAlchemy==1.3.13
python-dotenv==0.15.0
Flask-Caching==1.9.0
在項(xiàng)目根目錄向創(chuàng)建Dockerfile,指定鏡像源屎鳍,在基礎(chǔ)鏡像中安裝requirements.txt包
FROM python:3.7
ENV PIPURL "https://pypi.tuna.tsinghua.edu.cn/simple"
ADD ./requirements.txt /home/
WORKDIR /home
RUN pip install --no-cache-dir -i ${PIPURL} -r requirements.txt
#CMD gunicorn -c gun.conf.py app:app
構(gòu)建鏡像
root@ubuntu:~/myproject/pira_score_web_application# docker build . -t=pira_score_web:latest
掛載flask項(xiàng)目根目錄下的所有文件到容器內(nèi)部啟動(dòng)
root@ubuntu:~/myproject/pira_score_web_application# docker run --rm -d -v `pwd`:/home -p 5001:5000 pira_score_web:latest
Nginx配置反向代理
ubuntu下安裝nginx
sudo apt-get install nginx
查看nginx運(yùn)行狀態(tài)
root@ubuntu:~# service nginx status
● nginx.service - A high performance web server and a reverse proxy server
Loaded: loaded (/lib/systemd/system/nginx.service; enabled; vendor preset: enabled)
Active: active (running) since Mon 2021-01-18 10:13:15 CST; 2s ago
Docs: man:nginx(8)
Process: 1141 ExecStop=/sbin/start-stop-daemon --quiet --stop --retry QUIT/5 --pidfile /run/nginx.pid (code=exited, status=0/SUCCESS)
Process: 1231 ExecStart=/usr/sbin/nginx -g daemon on; master_process on; (code=exited, status=0/SUCCESS)
Process: 1218 ExecStartPre=/usr/sbin/nginx -t -q -g daemon on; master_process on; (code=exited, status=0/SUCCESS)
Main PID: 1232 (nginx)
Tasks: 5 (limit: 4915)
CGroup: /system.slice/nginx.service
├─1232 nginx: master process /usr/sbin/nginx -g daemon on; master_process on;
├─1237 nginx: worker process
├─1238 nginx: worker process
├─1239 nginx: worker process
└─1240 nginx: worker process
開啟宏娄,停止,重啟nginx服務(wù)
root@ubuntu:~# service nginx start
root@ubuntu:~# service nginx stop
root@ubuntu:~# service nginx restart
給Flask應(yīng)用構(gòu)建Nginx配置文件哥艇,通常在/etc/nginx/sites-enabled
或者/etc/nginx/conf.d
目錄下創(chuàng)建單獨(dú)的配置文件绝编,而不直接在全局配置文件/etv/nginx/nginx.conf
直接創(chuàng)建,在nginx.conf中已經(jīng)引入這兩個(gè)路徑貌踏,這兩個(gè)路徑下的配置會(huì)被插入到全局配置文件中十饥。
include /etc/nginx/conf.d/*.conf;
include /etc/nginx/sites-enabled/*;
Nginx配置文件的基本結(jié)構(gòu)
Nginx配置文件為nginx.conf
,結(jié)構(gòu)如下
... #全局塊
events { #events塊
...
}
http #http塊
{
... #http全局塊
server #server塊
{
... #server全局塊
location [PATTERN] #location塊
{
...
}
location [PATTERN]
{
...
}
}
server
{
...
}
... #http全局塊
}
-
全局塊
:配置影響nginx全局的指令
祖乳。一般有運(yùn)行nginx服務(wù)器的用戶組逗堵,nginx進(jìn)程pid存放路徑,日志存放路徑眷昆,配置文件引入蜒秤,允許生成worker process數(shù)等汁咏,默認(rèn)如下
user www-data;
worker_processes auto;
pid /run/nginx.pid;
include /etc/nginx/modules-enabled/*.conf;
-
events塊
:配置影響nginx服務(wù)器
或與用戶的網(wǎng)絡(luò)連接
。有每個(gè)進(jìn)程的最大連接數(shù)作媚,選取哪種事件驅(qū)動(dòng)模型處理連接請求攘滩,是否允許同時(shí)接受多個(gè)網(wǎng)路連接,開啟多個(gè)網(wǎng)絡(luò)連接序列化等纸泡,默認(rèn)如下漂问。
events {
worker_connections 768;
# multi_accept on;
}
-
http塊
:可以嵌套多個(gè)server,配置代理
女揭,緩存
蚤假,日志定義
等絕大多數(shù)功能和第三方模塊的配置。如文件引入吧兔,mime-type定義磷仰,日志自定義,是否使用sendfile傳輸文件境蔼,連接超時(shí)時(shí)間灶平,單連接請求數(shù)等,在這個(gè)塊底部使用include
引入其他文件的server箍土。
include /etc/nginx/conf.d/*.conf;
include /etc/nginx/sites-enabled/*;
-
server塊
:配置虛擬主機(jī)
的相關(guān)參數(shù)民逼,一個(gè)http中可以有多個(gè)server
,可以在sites-enabled和conf.d下定義文件涮帘。 -
location塊
:配置請求的路由
,以及各種頁面的處理情況笑诅。 -
[PATTERN]
:設(shè)置location 的路徑匹配規(guī)則
调缨,根據(jù)不同的路徑分配給請求不同的處理方式,主要分為前綴匹配
和正則匹配
前綴匹配
精確前綴匹配: location = uri {...}
優(yōu)先前綴匹配: location ^~ uri {...}
普通前綴匹配: location uri {...}
正則匹配
大小寫敏感: location ~ uri {...}
大小寫不敏感: location ~* uri {...}
location的匹配順序以及優(yōu)先級(jí)
1.首先匹配=
2.其次匹配^~
3.再其次按照配置文件的順序進(jìn)行正則匹配
4.最后是交給/進(jìn)行通用匹配吆你,通用匹配記錄下最長的匹配作為命中的規(guī)則
server配置
server {
listen 80;
server_name 127.0.0.1;
access_log /var/log/nginx/access.log;
error_log /var/log/nginx/error.log;
location / {
proxy_pass http://127.0.0.1:5000;
proxy_redirect off;
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_set_header X-Forwarded-Proto $scheme;
#deny 127.0.0.1; #拒絕的ip
}
location /PiraScore/static {
alias /home/gp/myproject/pira_score_web_application/static/;
expires 30d;
add_header wall "use nginx cache";
}
}
server參數(shù)配置詳解
-
listen
:監(jiān)聽端口 -
server_name
:監(jiān)聽地址 -
access_log
:訪問日志地址 -
error_log
:錯(cuò)誤日志 -
proxy_pass
:請求轉(zhuǎn)向的目標(biāo)位置 -
proxy_redirect
:禁止所有的proxy_redirect指令 -
proxy_set_header
:用來設(shè)定被代理服務(wù)器接收到的header信息
語法:proxy_set_header field value;
field :為要更改的項(xiàng)目弦叶,也可以理解為變量的名字,比如host
value :為變量的值
-
Host
妇多,$host
:設(shè)置header信息中的Host伤哺,如果不設(shè)置則默認(rèn)host的值為proxy_pass后面跟的那個(gè)域名或者IP -
X-Real_IP
,$remote_addr
:用來設(shè)置被代理端接收到的遠(yuǎn)程客戶端IP者祖,如果不設(shè)置立莉,則header信息中并不會(huì)透傳遠(yuǎn)程真實(shí)客戶端的IP地址 -
X-Forwarded-For
,$proxy_add_x_forwarded_for
:用來設(shè)置被代理端接收到的遠(yuǎn)程客戶端IP七问,如果不設(shè)置蜓耻,則header信息中并不會(huì)透傳遠(yuǎn)程真實(shí)客戶端的IP地址 -
X-Forwarded-Proto
:用于識(shí)別識(shí)別實(shí)際用戶發(fā)出的協(xié)議是 http 還是 https
其中proxy_set_header
的幾行設(shè)置是反向代理的標(biāo)準(zhǔn)配置
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_set_header X-Forwarded-Proto $scheme;
-
deny
:設(shè)置拒絕的ip,設(shè)置了之后就403 forbidden了 -
location
:第一個(gè)規(guī)則是/
械巡,代表通用匹配刹淌,所有url如果沒用命中第二個(gè)location都會(huì)走這個(gè)規(guī)則處理 -
/PiraScore/static
:第二個(gè)規(guī)則是/PiraScore/static
饶氏,代表通用匹配,靜態(tài)文件的地址 -
alias
:設(shè)置url對應(yīng)文件系統(tǒng)的位置有勾,設(shè)置后會(huì)到定義的目錄中尋找資源疹启,alias后面必須要用/
結(jié)束,否則會(huì)找不到文件 -
expires
:30d設(shè)置緩存時(shí)間為30天 -
add_header
:用于設(shè)置header中的自定義信息蔼卡,變量名可以隨意指定喊崖,比如wall
nginx啟動(dòng)
使用命令檢查Nginx配置是否有語法錯(cuò)誤
root@ubuntu:/etc/nginx/sites-enabled# nginx -t
nginx: the configuration file /etc/nginx/nginx.conf syntax is ok
nginx: configuration file /etc/nginx/nginx.conf test is successful
重啟Nginx
root@ubuntu:/etc/nginx/sites-enabled# service nginx restart
訪問url http:127.0.0.1:80/PiraScore
成功,訪問靜態(tài)文件的url查看是否被對應(yīng)location處理成緩存菲宴,在響應(yīng)頭中查看是否有自定義內(nèi)容贷祈,存在!
也可以在日志中查看喝峦,日志中不應(yīng)該存在請求靜態(tài)文件的url势誊,因?yàn)殪o態(tài)文件走了Nginx緩存