#簡介?
深入學(xué)習(xí)Flask作為RestFul服務(wù)端的架構(gòu)思路。了解Flask設(shè)計哲學(xué)、應(yīng)用場景。包含從開發(fā)環(huán)境搭建、項目架構(gòu)到生產(chǎn)環(huán)境部署的完整教學(xué)。
#目錄
1 Flask簡介?
2 為什么選擇Flask?
3 開發(fā)環(huán)境搭建?
4 快速構(gòu)建應(yīng)用?
5 無藍本路由分離式架構(gòu)?
6 關(guān)于數(shù)據(jù)預(yù)加載和內(nèi)存消耗?
7 數(shù)據(jù)庫的開放性選擇?
8 加密、驗證等基礎(chǔ)模塊的實現(xiàn)?
9 節(jié)點映射和裝飾器的使用?
10 并發(fā)處理及網(wǎng)絡(luò)模型的選擇?
11 服務(wù)端部署方案詳解
#一、 Flask簡介
Flask是一個使用python編寫的輕量級web框架,誕生于2010年,作者是Armin Ronacher。Flask的核心部分是Werkzeug和Jinja2。Werkzeug是由Flask作者自己編寫的一個基于python WSGI協(xié)議的網(wǎng)絡(luò)服務(wù)包。在最初完成werkzeug的時候,Armin Ronacher曾向Bottle(python另一個著名的web框架)的作者推薦使用自己的werkzeug,然而因為設(shè)計理念的不同的遭到了拒絕,于是AR親自操刀上陣,完成了這個github上人氣最高的python web框架(目前star數(shù)量已超越django)。有趣的是,F(xiàn)lask和Bottle這兩個名字都有瓶子的意思。
#二、 為什么選擇Flask
Flask最初的設(shè)計目的是面向需求簡單的小應(yīng)用,比如博客、公司官網(wǎng)、小論壇等。Flask本身相當(dāng)于一個提供web基礎(chǔ)服務(wù)(路由分發(fā)、http協(xié)議解析、html模板渲染)的內(nèi)核,其他幾乎所有功能都需要依賴于第三方的拓展來實現(xiàn)。相對于django這個全覆蓋式服務(wù)框架,F(xiàn)lask對于入門pythonWeb開發(fā)的新手來說會更難應(yīng)用。很多人學(xué)習(xí)Flask時都會被一開始構(gòu)建第一個helloworld應(yīng)用時的快速簡潔所迷惑,認為flask極其簡單,以至于到了之后需要拓展一個flask的業(yè)務(wù)功能時,卻因為無法將其完善成一個django式的通用項目結(jié)構(gòu)和放棄。Flask社區(qū)雖然后續(xù)推出了Flask-Mail、Flask-Login等模塊,但依然鼓勵開發(fā)者通過Flask-extension的機制去自己造輪子或者選擇更適合業(yè)務(wù)場景的模塊。
得益于Flask的設(shè)計哲學(xué),其超高的拓展性更加適合未來的程序設(shè)計風(fēng)格。如今人工智能、大數(shù)據(jù)、云計算等新概念全面普及,軟件的需求不再是傳統(tǒng)的增刪改查,F(xiàn)lask作為云服務(wù)的后端時,能夠被自由改造成不同形態(tài)的項目結(jié)構(gòu),嵌入各類自定義模塊。不論是數(shù)據(jù)庫、數(shù)據(jù)加密、權(quán)限校驗、網(wǎng)絡(luò)模式、前端模板,都可以融入到Flask的體系之中。
需要特別一提的是,F(xiàn)lask默認僅綁定jinja2模板渲染模塊,但由于Flask現(xiàn)在更傾向于作為前后端分離工程的后端,所以理論上來說,前端選擇vue、react、bootstrap等框架都是支持的。
如果您已經(jīng)通過學(xué)習(xí)Flask并能夠搭建自己的一套完整的業(yè)務(wù)框架體系,那么后續(xù)的開發(fā)效率也將大幅度提升,因為基于python語言的特性和Flask的模塊設(shè)計,你可以使用比其他框架少很多的篇幅、代碼量完成一個常規(guī)項目。
所以,我推薦如下幾種條件的人可以深入學(xué)習(xí)并應(yīng)用Flask: 1 對任意傳統(tǒng)web框架有一定了解,比如django,springboot,rails等。 2 軟件開發(fā)的業(yè)務(wù)需求多變,趨于易耦合。 3 追求高自由度的開發(fā)模式。 4 單純對這個新興框架感興趣。 5 敏捷開發(fā)式團隊
#三、 開發(fā)環(huán)境搭建 由于基于WSGI的server基本上依賴于Linux環(huán)境,所以建議開發(fā)者使用ubuntu或者centos進行開發(fā)和部署。以下教程將以ubuntu為例子:
1 通過Anaconda配置Python開發(fā)環(huán)境,前往https://www.anaconda.com/download/#linux下載最新的python發(fā)布版本。下載完成后是一個shell文件,直接shell anaconda.sh 執(zhí)行即可。如果有權(quán)限問題,先執(zhí)行 chmod +x. 期間所有配置選擇默認的就行。
2 修改~/.basrhc,在文件最下方添加
export PYTHON_HOME=/home/your_user_rootpath/anaconda3
export PATH=${PYTHON_HOME}/bin
終端輸入命令使新環(huán)境變量生效
source ~/.basrhc
3 在終端輸入python,如果有版本提示即是安裝成功。
python
Python 3.6.5 |Anaconda, Inc.| (default, Apr 29 2018, 16:14:56)
[GCC 7.2.0] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>>
#四、 快速構(gòu)建應(yīng)用 如果你對完全未接觸過Flask,建議先跟著官方文檔敲下案例。 Flask官方網(wǎng)站:https://palletsprojects.com/p/flask/中文版文檔:https://dormousehole.readthedocs.io/en/latest/
在掌握Flask各個基礎(chǔ)模塊的使用后,我們再對Flask的工程結(jié)構(gòu)進行梳理。不難發(fā)現(xiàn),F(xiàn)lask最初的設(shè)計是趨向于構(gòu)建一個單文件應(yīng)用的,但在實際開發(fā)中,將代碼分割到多個文件進行管理則更為合理。在不加入藍圖(flask官方的一個拓展模塊)的情況下,較難實現(xiàn)分離式項目結(jié)構(gòu)。
如果選擇加入blueprint來構(gòu)建你的Flask項目,那么參照官方文檔的說明即可。blueprint本質(zhì)上是通過注冊一個全局__name__參數(shù)并綁定相應(yīng)的路由和模板,然后通過內(nèi)置鉤子函數(shù)在Flask應(yīng)用生成前注冊到全局視圖層之中。由于是靜態(tài)配置,一旦載入后,藍圖將不支持拔插,也就是說注冊藍圖生成的資源目錄和組件都會隨著Flask應(yīng)用的啟動而一直存在,不管你是否去調(diào)用。
由于本教程針對的是構(gòu)建restful服務(wù),Flask僅作為提供api的服務(wù),而不執(zhí)行模板渲染的任務(wù),也就是說不會涉及到視圖層的管理,所以我提供了一種新設(shè)計方案,并將模板工程上傳到了github,同時將快速構(gòu)建工具上傳到了pip官方源。
工程源碼:https://github.com/redtreeai/red-flask快速構(gòu)建工具:redflask
在配置有python標準開發(fā)環(huán)境的機器上,通過終端輸入命令來快速構(gòu)建一個red-flask工程。
pip install redflask==0.1.5
redflask -b [your_project_name]
基于這個示范工程,我會講解一種較容易上手的Flask企業(yè)項目基礎(chǔ)架構(gòu)方案。
#五、 無藍本路由分離式架構(gòu)
如果你已經(jīng)構(gòu)建了一個redflask工程,并且通過IDE工具(推薦使用pycharm/vscode)打開,你會看到工程結(jié)果,如下圖:
通過工程的readme文件我們看到整體的架構(gòu)說明如下:
一個基于Flask搭建的Restful_Api服務(wù)框架,實現(xiàn)了路由分離的功能但無需依賴于Flask的藍本。提供了常用的http請求類型和文件傳輸類型的相關(guān)接口。提供了較簡單的異步任務(wù)分發(fā)方案。提供了最常用的用戶密碼校驗?zāi)K和token驗證模塊。集成了Gunicorn+Gevent的服務(wù)器自動配置方案。數(shù)據(jù)庫端使用Sqlalchemy和Redis。提供了Flask作為Https服務(wù)的解決方案。解決了Flask跨域問題。
#### 使用說明
1. 本地調(diào)試,直接python run.py (請在setting.yaml中配置IP為localhost)
2. 部署調(diào)試,chmod +x test.sh? 然后 ./test.sh
3. 部署生產(chǎn), chmod +x start.sh 然后 ./start.sh
#### 軟件架構(gòu)
controller : 路由控制器,用來注解并分配工程的api地址和分發(fā)對應(yīng)的接口任務(wù),其作用類似于Java SpringBoot中的restcontroller。子下的文件目錄根據(jù)對應(yīng)業(yè)務(wù)類型進行區(qū)分。
database: 包含各類數(shù)據(jù)庫鏈接和對象映射的基本配置,以及數(shù)據(jù)庫其他模式(如主從、池化、分布式等)的配置都在此處生效。
Exceptions: 用來自定義繼承或重寫python的錯誤類型,并編寫特定的處理方式。
Resource: 用于存放工程中使用到的各類靜態(tài)文本數(shù)據(jù)和多媒體數(shù)據(jù),還有機器學(xué)習(xí)模型。
Service: 用于編寫API實際業(yè)務(wù)邏輯的文件目錄,Controller下的api接口一般會從此處調(diào)用具體服務(wù),其目錄結(jié)構(gòu)與controller相似。
Utils: 泛用工具類及全局裝飾器的存儲目錄。
其他工程文件詳解:
test_case 測試案例寫在這里
__init__.py? red_flask工程的基本配置文件,各項配置須在此初始化
requirements.txt? 記錄依賴模塊 方便pip安裝
run.py? 服務(wù)啟動入口,wsgi配置文件
start.sh? 服務(wù)啟動腳本
test.sh 測試腳本
configm.py? Gunicorn網(wǎng)絡(luò)模型配置腳本
setting.yaml? 用于自適應(yīng)部署的配置文件
本章我們先重點講解__init__.py,controller/apis.py這兩個文件。前者這是整個flask工程的初始化配置文件,后者是flask實現(xiàn)路由分離后接口控制器的標準形式文件。
init.py(markdown語法問題,此處省略下劃線)
# -*- coding: utf-8 -*-
'''
Flask 初始化配置文件,包含基本配置,ORM,數(shù)據(jù)緩存,模型預(yù)加載,控制器注冊等,注意業(yè)務(wù)邏輯順序
'''
from flask_cors import CORS? #添加CORS組件 允許跨域訪問
from flask import Flask, request,make_response,send_from_directory
import os
import yaml
'''
Flask基礎(chǔ)配置部分
'''
# 初始化Flask對象
app = Flask(__name__)
# 初始化session 會話? 需要配置key
app.secret_key = '\xca\x0c\x86\x04\x98@\x02b\x1b7\x8c\x88]\x1b\xd7"+\xe6px@\xc3#\\'
#實例化 cors
CORS(app, supports_credentials=True)
#其他組件注冊
request = request
make_response = make_response
send_from_directory = send_from_directory
'''
適應(yīng)性配置,方便本地調(diào)試和部署線上
'''
#獲取服務(wù)器基本配置信息
setting = yaml.load(open('setting.yaml'))
SERVER_IP = setting['SERVER_IP']
#獲取部署目錄,ROOT_PATH即可作為工程中的絕對路徑根目錄,方便業(yè)務(wù)邏輯調(diào)用
ROOT_PATH = os.popen('pwd','r',1).read()
ROOT_PATH = str(ROOT_PATH).replace('\n','')
'''
數(shù)據(jù)庫對象的創(chuàng)建和預(yù)加載,包含Redis/MongoDB等皆應(yīng)在此提前實例化,如果需要從resource預(yù)先緩存數(shù)據(jù),
例如讀取txt/csv等文件,也可以在此處預(yù)先加載,以供全局調(diào)用。
'''
# from sqlalchemy import create_engine? #加載配置文件內(nèi)容
# from sqlalchemy.ext.declarative import declarative_base
# from sqlalchemy.orm import sessionmaker
#
# from database.sqlalchemy import _mysql? # 連接數(shù)據(jù)庫的數(shù)據(jù)
#
# engine_mysql = create_engine(_mysql.DB_URI, echo=False, pool_recycle=3600) # 創(chuàng)建引擎
# Base_mysql = declarative_base(engine_mysql)
# DBSession_mysql = sessionmaker(bind=engine_mysql) # sessionmaker生成一個session類 此后DBSession_mysql將可在全局作為一個數(shù)據(jù)庫會話對象持續(xù)服務(wù),不用重復(fù)創(chuàng)建
#
'''
所有的控制器在此處注冊方可生效
'''
#注冊控制器
from controller import apis
可以看到,該Flask應(yīng)用的實例化過程遵循一個基本原則,先聲明并實例化基本庫,然后載入第三方插件或數(shù)據(jù)集,最后再將接口控制器進行注冊生效。這樣的設(shè)計方式可以讓Flask在服務(wù)啟動前預(yù)載入一些會在后期被頻繁調(diào)用的對象函數(shù)或數(shù)據(jù)到內(nèi)存中,實現(xiàn)復(fù)用效果,而不用頻繁重新實例化。另外控制器能在文件底部進行一個集成管理,免去了繁瑣的藍本注冊方案。無需在路由文件和初始文件中配置注冊藍本即可實現(xiàn)路由分離的功能。原理相當(dāng)于是通過python的path管理將Flask工程重新打包成一個單文件應(yīng)用。
apis.py
# -*- coding: utf-8 -*-
'''
flask作為restful服務(wù)時,常用的一些接口類型
'''
from __init__ import app
from __init__ import request,send_from_directory
import json
from utils.decorator.token_maneger import auth_check
#最簡單的接口
@app.route('/',methods=['GET'])
def welcome():
? return 'Hello World'
#GET方式傳遞參數(shù)
@app.route('/api/get',methods=['GET'])
def get1():
? page = request.args.get('page')
? return 'this is page'+str(page)
#POST方式傳遞參數(shù)
@app.route('/api/post',methods=['POST'])
def post1():
? # request.json 只能夠接受方法為POST、Body為raw,header 內(nèi)容為 application/json類型的數(shù)據(jù)
? # json.loads(request.dada) 能夠同時接受方法為POST、Body為 raw類型的 Text
? username = request.json('username')
? password = json.loads(request.data)['password']
? if username=='admin' and password == 'admin':
? ? ? res = {'code':200,'msg':'suceess','data':username}
? ? ? return json.dumps(res)
? res = {'code': 401, 'msg': 'error'}
? return json.dumps(res)
#POST方式傳遞文件流
@app.route('/api/uploadfile',methods=['POST'])
def uploadfile():
? try:
? ? ? f = request.files['file']
? ? ? #f.save('path')
? ? ? # 獲取文件名
? ? ? fname = request.form.get("fname")
? ? ? res = {'code': 200, 'msg': 'suceess', 'fname': fname}
? ? ? return json.dumps(res)
? except Exception as err:
? ? ? print(err)
? ? ? res = {'code': 401, 'msg': 'error'}
? ? ? return json.dumps(res)
#提供下載
@app.route('/api/download',methods=['GET'])
def download():
? return send_from_directory('/directory', 'file', as_attachment=True)
#當(dāng)使用auth_check裝飾時,接口就需要通過token校驗??捎脕韺崿F(xiàn)登錄狀態(tài)控制等功能,
# 另外,當(dāng)工程中含有多個auth_check裝飾的接口時,需要添加不同的節(jié)點命名
@app.route('/api/get1',endpoint='getn1' ,methods=['GET'])
@auth_check
def get1a():
? page = request.args.get('page')
? return 'this is page'+str(page)
@app.route('/api/get2',endpoint='getn2' ,methods=['GET'])
@auth_check
def get2a():
? page = request.args.get('page')
? return 'this is page'+str(page)
apis.py文件提供了Flask工程在路由分離結(jié)構(gòu)下,控制器文件的標準寫法。并提供了部分常用功能的接口范例。從文件中我們可以看到,apis.py先從__init__.py中引入了實例化的Flask應(yīng)用和基礎(chǔ)模塊對象,同時,引入了python系統(tǒng)模塊組的json庫和utils目錄下用戶自定義的第三方工具類,再去執(zhí)行接口相關(guān)的業(yè)務(wù)邏輯。從設(shè)計角度上來說,控制器盡量只負責(zé)為service層(業(yè)務(wù)邏輯層)轉(zhuǎn)發(fā)用戶上報到接口的數(shù)據(jù)并返回處理結(jié)果,而不直接在控制器中執(zhí)行業(yè)務(wù)邏輯。比如我們在控制器中編寫了一個校驗用戶登錄信息的接口,那么實際上他只需要把用戶的帳號密碼傳遞給來自service目錄下對應(yīng)的服務(wù)文件就行了,然后返回service文件對應(yīng)函數(shù)的處理結(jié)果。
在實際的業(yè)務(wù)場景中,controller目錄下建議再進行多級目錄分類,然后再根據(jù)需求注冊到主文件中。那么,在實現(xiàn)路由分離的場景下,整個系統(tǒng)的基礎(chǔ)還少了哪個部分呢?沒錯,就是server的啟動了,由于python的web_server大都遵循wsgi協(xié)議,所以我們把flask工程的啟動部分也單獨分離出來編寫,以便整合調(diào)試不同的網(wǎng)絡(luò)模型。在redflask工程中,由run.py和configm.py這兩個文件來控制工程的調(diào)試和部署。
run.py
# -*- coding: utf-8 -*-
# @File? : run.py
# @Author: redtree
# @Date? : 18-6-27
# @Desc? :? 服務(wù)啟動入口,自動化判斷部署環(huán)境并配置UWGI代理,
from __init__ import app
from __init__ import SERVER_IP
if SERVER_IP=='localhost':
? ? #本地調(diào)試
? ? app.run(host='0.0.0.0', port=5000, debug=True, threaded=True)
? ? #app.run()
? ? # ssl_context = (
? ? #? ? './server.crt',
? ? #? ? './server_nopwd.key')
else:
? ? from werkzeug.contrib.fixers import ProxyFix
? ? # 線上服務(wù)部署? 對接gunicorn
? ? app.wsgi_app = ProxyFix(app.wsgi_app)
由于flask在作為本地調(diào)試服務(wù)時,采用的是自帶的werkzeug服務(wù)器,而部署線上時,一般采用gunicorn或者uwsgi作為代理,所以我們需要配置一套自動部署的方案。在之前的__init__.py文件中,我們通過setting.yaml文件獲取到了當(dāng)前機器的配置信息,從而使得Flask啟動時能夠自動識別應(yīng)該開啟調(diào)試模式還是生產(chǎn)模式,當(dāng)屬于生產(chǎn)模式時,我們應(yīng)該將werkzeug服務(wù)器掛載為代理轉(zhuǎn)發(fā)模式,對接到生產(chǎn)類服務(wù)器上,如gunicorn。
當(dāng)部署生產(chǎn)環(huán)境時,網(wǎng)絡(luò)優(yōu)化的任務(wù)則會交由configm.py文件進行調(diào)控,再后面章節(jié)中細講。
#六、 關(guān)于數(shù)據(jù)預(yù)加載和內(nèi)存消耗 之前提到,F(xiàn)lask工程可以把模塊、數(shù)據(jù)集都在服務(wù)啟動前預(yù)加載到內(nèi)存之中,類似機器學(xué)習(xí)生成的二進制模型之類,通常我們會伴隨著服務(wù)啟動變將其載入內(nèi)存,加快后面接口執(zhí)行相關(guān)任務(wù)時的資源調(diào)用速度。由于flask本身內(nèi)核的內(nèi)存資源占用極小,合理的運用這項行為可以提高項目的整體服務(wù)性能。
在Flask工程中,通常我會把需要預(yù)加載的資源放到resource目錄下,包含一些字典文件、靜態(tài)資源和多媒體資源等。然后在database目錄下注冊一個用于管理靜態(tài)資源的模塊,假設(shè)為res_manager.py。這個模塊可以將資源進行分類讀取,轉(zhuǎn)換成列表或字典的形式存到內(nèi)存中,然后在__init__.py文件中實例化供全局調(diào)用。
同理,數(shù)據(jù)庫類型的資源也可以使用這樣的方案,只要你能通過python支持數(shù)據(jù)庫交互,包含mysql,mongoDB,redis等。
#七、數(shù)據(jù)庫的開放性選擇 redflask提供了兩種數(shù)據(jù)庫的簡易交互方案,redis和mysql。我們先看redis:
redis.py
# 導(dǎo)入redis模塊,通過python操作redis 也可以直接在redis主機的服務(wù)端操作緩存數(shù)據(jù)庫
#當(dāng)redis IO操作較為頻繁的時候,應(yīng)將redis對象在FLask工程中全局初始化。
import redis
def connect_redis():
? ? # host是redis主機,需要redis服務(wù)端和客戶端都啟動 redis默認端口是6379
? ? r = redis.Redis(host='localhost', port=6379, decode_responses=True)
? ? return r
def add_kv(redis_obj,r_key,r_value):
? ? redis_obj.set(r_key, r_value)
? ? return 'success'
def get_by_key(redis_obj,r_key):
? ? print(redis_obj[r_key])
? ? print(redis_obj.get(r_key))? # 取出鍵name對應(yīng)的值
? ? print(type(redis_obj.get(r_key)))
? ? return 'success'
然后是mysql,這里我們選擇了sqlalchemy這個python最通用的orm框架。
_mysql.py
# -*- coding: utf-8 -*-
'''
基于sqlalchemy模塊的orm基礎(chǔ)配置,當(dāng)性能不佳時,建議使用原生sql語句重寫核心模塊
'''
#調(diào)試模式是否開啟
DEBUG = False
SQLALCHEMY_TRACK_MODIFICATIONS = False
#session必須要設(shè)置key
SECRET_KEY='~XHH!jmN]LWX/,?RTA0Zr98j/3yX R'
#mysql數(shù)據(jù)庫連接信息,這里改為自己的賬號? #直連模式啟動
HOSTNAME = '127.0.0.1'
PORT = '3310'
DATABASE = 'dbname'
USERNAME = 'root'
PASSWORD = 'root'
#鏈接模式
DB_URI = 'mysql+pymysql://{}:{}@{}:{}/{}?charset=utf8'.format(USERNAME,PASSWORD,HOSTNAME,PORT,DATABASE)
在配置好數(shù)據(jù)庫的鏈接信息后,便可以根據(jù)業(yè)務(wù)表建立對象模型文件,如sys_user.py:
import json
from __init__ import Base_mysql
from sqlalchemy import (Column, String, Integer)
# 定義好一些屬性,與user表中的字段進行映射,并且這個屬性要屬于某個類型
class Sys_user(Base_mysql):
? ? __tablename__ = 'sys_user'
? ? xid = Column(Integer, primary_key=True)
? ? userid = Column(String(50))
? ? nickname = Column(String(50))
? ? salt = Column(String(16))
? ? password=Column(String(32))
? ? del_flag= Column(Integer)
? ? create_time=Column(Integer)
? ? update_time=Column(Integer)
? ? role = Column(String(50))
? ? create_user = Column(String(50))
? ? ban_flag = Column(Integer)
? ? def __repr__(self):
? ? ? ? get_data = {"id":self.id, "userid":self.userid, "nickname":self.nickname, "salt":self.salt,
? ? ? ? ? ? ? ? ? ? "password":self.password, "del_flag":self.del_flag, "create_time":self.create_time, "update_time":self.update_time,"role":self.role,"create_user":self.create_user
? ? ? ? ? ? ? ? ? ? ,"ban_flag":self.ban_flag}
? ? ? ? get_data = json.dumps(get_data)
? ? ? ? return get_data
在數(shù)據(jù)庫基礎(chǔ)配置和表映射都建立好后,我們便可以在init.py中注冊并實例化相關(guān)組件,然后在各類utils或service文件中調(diào)用sqlalchemy組件進行curd操作了。如果遇到性能瓶頸時,建議使用pymysql編寫原生sql語句處理業(yè)務(wù)。
from sqlalchemy import create_engine? #加載配置文件內(nèi)容
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.orm import sessionmaker
from database.sqlalchemy import _mysql? # 連接數(shù)據(jù)庫的數(shù)據(jù)
engine_mysql = create_engine(_mysql.DB_URI, echo=False, pool_recycle=3600) # 創(chuàng)建引擎
Base_mysql = declarative_base(engine_mysql)
DBSession_mysql = sessionmaker(bind=engine_mysql) # sessionmaker生成一個session類 此后DBSession_mysql將可在全局作為一個數(shù)據(jù)庫會話對象持續(xù)服務(wù),不用重復(fù)創(chuàng)建
#八、 加密/驗證等基礎(chǔ)模塊的實現(xiàn) 在redflask工程的utils目錄下,提供了幾項通用工具類,包含加密驗證工具和裝飾器。
首先是encrypt.py:
# -*- coding: utf-8 -*-
'''
此工具提供了一個常用的用戶密碼加密方案,基于md5和隨機鹽值.
'''
import random
import string
import hashlib
# 獲取由4位隨機大小寫字母、數(shù)字組成的salt值
def create_salt():
? ? salt = ''.join(random.sample(string.ascii_letters + string.digits, 4))
? ? return salt
# 獲取原始密碼+salt的md5值
def md5_password(password,salt):
? ? trans_str = password+salt
? ? md = hashlib.md5()
? ? md.update(trans_str.encode('utf-8'))
? ? return md.hexdigest()
此工具提供了一個通用數(shù)據(jù)庫用戶密碼加密方案,你可以自定義使其更復(fù)雜化,甚至模仿實現(xiàn)類似java,shiro的效果。
然后是裝飾器部分,先看dasyncio.py:
# -*- coding: utf-8 -*-
'''
? 基于python Threading模塊封裝的異步函數(shù)裝飾器
'''
from threading import Thread
import time
'''
async_call為一次簡單的異步處理操作,裝飾在要異步執(zhí)行的函數(shù)前,再調(diào)用該函數(shù)即可執(zhí)行單次異步操作(開辟一條新的線程)
'''
def async_call(fn):
? ? def wrapper(*args, **kwargs):
? ? ? ? Thread(target=fn, args=args, kwargs=kwargs).start()
? ? return wrapper
'''
async_pool為可定義鏈接數(shù)的線程池裝飾器,可用于并發(fā)執(zhí)行多次任務(wù)
'''
def async_pool(pool_links):
? ? def wrapper(func):
? ? ? ? def sub_wrapper(*args,**kwargs):
? ? ? ? ? ? for x in range(0,pool_links):
? ? ? ? ? ? ? ? Thread(target=func, args=args, kwargs=kwargs).start()
? ? ? ? ? ? ? ? #func(*args, **kwargs)
? ? ? ? return sub_wrapper
? ? return wrapper
'''
async_retry為自動重試類裝飾器,不支持單獨異步,但可嵌套于 call 和 pool中使用
'''
def async_retry(retry_times,space_time):
? ? ? ? def wrapper(func):
? ? ? ? ? ? def sub_wrapper(*args, **kwargs):
? ? ? ? ? ? ? ? try_times = retry_times
? ? ? ? ? ? ? ? while try_times > 0:
? ? ? ? ? ? ? ? ? ? try:
? ? ? ? ? ? ? ? ? ? ? ? func(*args, **kwargs)
? ? ? ? ? ? ? ? ? ? ? ? break
? ? ? ? ? ? ? ? ? ? except Exception as e:
? ? ? ? ? ? ? ? ? ? ? ? print(e)
? ? ? ? ? ? ? ? ? ? ? ? time.sleep(space_time)
? ? ? ? ? ? ? ? ? ? ? ? try_times = try_times - 1
? ? ? ? ? ? return sub_wrapper
? ? ? ? return wrapper
# 以下為測試案例代碼
#
# @async_call
# def sleep2andprint():
#? ? time.sleep(2)
#? ? print('22222222')
#
# @async_pool(pool_links=5)
# def pools():
#? ? time.sleep(1)
#? ? print('hehe')
#
#
# @async_retry(retry_times=3,space_time=1)
# def check():
#? ? a = 1
#? ? b ='2'
#? ? print(a+b)
#
# def check_all():
#? ? print('正在測試async_call組件')
#? ? print('111111')
#? ? sleep2andprint()
#? ? print('333333')
#? ? print('若3333出現(xiàn)在22222此前,異步成功')
#? ? print('正在測試async_pool組件')
#? ? pools()
#? ? print('在一秒內(nèi)打印出5個hehe為成功')
#? ? print('正在測試async_retry組件')
#? ? check()
#? ? print('打印三次異常則成功')
#
# check_all()
異步裝飾器可以使工程中的任意函數(shù)單獨開辟一條額外的線程去執(zhí)行特殊業(yè)務(wù),比如用戶行為紀錄,以確保用戶的在執(zhí)行操作時不受系統(tǒng)紀錄行為的結(jié)果影響。
token_manager.py:
# -*- coding: utf-8 -*-
'''
服務(wù)端token驗證器
'''
import time
import base64
import hmac
from __init__ import make_response,request
#生成token
def generate_token(key, expire=1800):
? ? r'''
? ? ? ? @Args:
? ? ? ? ? ? key: str (用戶給定的key,需要用戶保存以便之后驗證token,每次產(chǎn)生token時的key 都可以是同一個key)
? ? ? ? ? ? expire: int(最大有效時間,單位為s)
? ? ? ? @Return:
? ? ? ? ? ? state: str
? ? '''
? ? ts_str = str(time.time() + expire)
? ? ts_byte = ts_str.encode("utf-8")
? ? sha1_tshexstr? = hmac.new(key.encode("utf-8"),ts_byte,'sha1').hexdigest()
? ? token = ts_str+':'+sha1_tshexstr
? ? b64_token = base64.urlsafe_b64encode(token.encode("utf-8"))
? ? return b64_token.decode("utf-8")
#token校驗
def certify_token(key, token):
? ? r'''
? ? ? ? @Args:
? ? ? ? ? ? key: str
? ? ? ? ? ? token: str
? ? ? ? @Returns:
? ? ? ? ? ? boolean
? ? '''
? ? token_str = base64.urlsafe_b64decode(token).decode('utf-8')
? ? token_list = token_str.split(':')
? ? if len(token_list) != 2:
? ? ? ? return False
? ? ts_str = token_list[0]
? ? if float(ts_str) < time.time():
? ? ? ? # token expired
? ? ? ? return False
? ? known_sha1_tsstr = token_list[1]
? ? sha1 = hmac.new(key.encode("utf-8"),ts_str.encode('utf-8'),'sha1')
? ? calc_sha1_tsstr = sha1.hexdigest()
? ? if calc_sha1_tsstr != known_sha1_tsstr:
? ? ? ? # token certification failed
? ? ? ? return False
? ? # token certification success
? ? return True
#token校驗
def auth_check(func):
? ? def inner(*args,**kwargs):
? ? ? ? try:
? ? ? ? ? ? key = request.cookies.get('key')
? ? ? ? ? ? token = request.cookies.get('token')
? ? ? ? ? ? if certify_token(key,token) == True:
? ? ? ? ? ? ? ? pass
? ? ? ? ? ? else:
? ? ? ? ? ? ? ? return 'Auth-Error'
? ? ? ? except Exception as e :
? ? ? ? ? ? return 'Auth-Error'
? ? ? ? return func(*args,**kwargs)
? ? return inner
提供了auth_check裝飾器,用于校驗用戶的請求合法性或者客戶端cookie的有效期。
pub_decorator.py中則是提供了一些關(guān)于失敗任務(wù)自動重新調(diào)度的解決方案,由于大部分程序在出現(xiàn)異常時會有對應(yīng)的拋出和解決方案,這里就不細說,感興趣的小伙伴自行翻看下源碼便可理解。
值得一提的是,在引入裝飾器修飾接口函數(shù)時,會涉及到一個問題:flask路由的節(jié)點沖突。所以,當(dāng)有多個接口同時調(diào)用一個裝飾器時,必須為接口的路由配置不同的節(jié)點名,請看下一章。
#九、節(jié)點映射和裝飾器的使用 回到apis.py文件中來,我們看文件的最下面部分。
#當(dāng)使用auth_check裝飾時,接口就需要通過token校驗??捎脕韺崿F(xiàn)登錄狀態(tài)控制等功能,
# 另外,當(dāng)工程中含有多個auth_check裝飾的接口時,需要添加不同的節(jié)點命名
@app.route('/api/get1',endpoint='getn1' ,methods=['GET'])
@auth_check
def get1a():
? page = request.args.get('page')
? return 'this is page'+str(page)
@app.route('/api/get2',endpoint='getn2' ,methods=['GET'])
@auth_check
def get2a():
? page = request.args.get('page')
? return 'this is page'+str(page)
flask默認將路由的的函數(shù)名作為endpoint的標識名。通常情況下不會出現(xiàn)路由之間節(jié)點沖突的問題。但當(dāng)兩個路由都被裝飾器修飾時,endpoint會讀取到類似匿名函數(shù)的節(jié)點名從而分配給默認的greeting節(jié)點上引發(fā)沖突。所以要為不同路由手動添加endpoint。
#十、并發(fā)處理及網(wǎng)絡(luò)模型的選擇
大部分web框架在設(shè)計時,為了方便本地調(diào)試代碼,會封裝一個簡易httpserver,少部分web框架本身就是一個強力的webserver(tonardo)。werkzeug對于Flask來說,便是對wsgi實現(xiàn)了一個簡單封裝,讓flask能專注于application的業(yè)務(wù)邏輯本身,將網(wǎng)絡(luò)層的模式設(shè)計交由其他模塊拓展。在本次教程中,我會對python常用的httpserver及網(wǎng)絡(luò)模型做個簡單介紹,但不會深入講解,重點依然是讓讀者快速上手框架的整體結(jié)構(gòu)并用于實戰(zhàn)。
wsgi:Web Server Gateway Interface。 關(guān)于wsgi,這里我摘錄了廖大的一段解釋,講得很清楚了。
(摘錄至廖雪峰個人網(wǎng)站)
了解了HTTP協(xié)議和HTML文檔,我們其實就明白了一個Web應(yīng)用的本質(zhì)就是:
瀏覽器發(fā)送一個HTTP請求;
服務(wù)器收到請求,生成一個HTML文檔;
服務(wù)器把HTML文檔作為HTTP響應(yīng)的Body發(fā)送給瀏覽器;
瀏覽器收到HTTP響應(yīng),從HTTP Body取出HTML文檔并顯示。
所以,最簡單的Web應(yīng)用就是先把HTML用文件保存好,用一個現(xiàn)成的HTTP服務(wù)器軟件,接收用戶請求,從文件中讀取HTML,返回。Apache、Nginx、Lighttpd等這些常見的靜態(tài)服務(wù)器就是干這件事情的。
如果要動態(tài)生成HTML,就需要把上述步驟自己來實現(xiàn)。不過,接受HTTP請求、解析HTTP請求、發(fā)送HTTP響應(yīng)都是苦力活,如果我們自己來寫這些底層代碼,還沒開始寫動態(tài)HTML呢,就得花個把月去讀HTTP規(guī)范。
正確的做法是底層代碼由專門的服務(wù)器軟件實現(xiàn),我們用Python專注于生成HTML文檔。因為我們不希望接觸到TCP連接、HTTP原始請求和響應(yīng)格式,所以,需要一個統(tǒng)一的接口,讓我們專心用Python編寫Web業(yè)務(wù)
也就是說,wsgi被作為python最通用的http協(xié)議接口,大量的python_web_application 與 python_http_server都是基于wsgi構(gòu)建的。而其中最常用的server便是uwsgi和gunicorn。
他們之間的異同點主要如下: 1 uwsgi和gunicorn 都過 pre-fork 方式增加 server 并發(fā)處理能力。 2 在不采用網(wǎng)絡(luò)模型優(yōu)化的情況下,兩者皆采用默認形態(tài),在高并發(fā)情況下,gunicorn的響應(yīng)時間會快于uwsgi,但丟包率稍高,且吞吐量較低。 3 gunicorn的配置較為簡單,且具備較多網(wǎng)絡(luò)模型可選擇:select、epoll、gevent、meinheld等。 4 gunicorn默認僅支持部署unix系統(tǒng),uwsgi則支持部署windows。 5 兩者都能完美搭配nginx使用。
由于gunicorn對gevent和meinheld這兩個目前性能最優(yōu)秀的通用網(wǎng)絡(luò)模型有較好的支持,且配置啟動方式極其簡潔,redflask選用gunicorn作為WSGIserver。
網(wǎng)絡(luò)模型的選擇: gevent vs meinheld,兩者都是基于python的greenlet庫實現(xiàn)協(xié)程。
meinheld:greenlet(協(xié)程) + picoev(高性能網(wǎng)絡(luò)庫)
gevent:greenlet(協(xié)程) + libevent(高性能網(wǎng)絡(luò)庫)
協(xié)程:又稱微線程,纖程。 協(xié)程的這種“掛起”和“喚醒”機制實質(zhì)上是將一個過程切分成了若干個子過程,給了我們一種以扁平的方式來使用事件回調(diào)模型。優(yōu)點:共享進程的上下文,一個進程可以創(chuàng)建百萬,千萬的coroutine。
picoev vs libevent picoev在項目下有把picoev和libevent這些庫做對比,作者也提了一下為什么picoev的速度會這么快。主要有兩個原因。
1. picoev幾乎所有順序結(jié)構(gòu)都是用數(shù)組實現(xiàn)的,索引訪問速度比libevent的鏈表快很多。
2. picoev采用了環(huán)形隊列+vector+bitmap來實現(xiàn)定時事件的檢測。
好了,我們再來看下gevent和meinheld的設(shè)計理念不同之處:
Gevent
1. 先通過Timer最小堆(以時間為排序的鍵)找出至少要等待的時間。(代碼中的timeout_next()函數(shù))。
2. 通過select發(fā)送這些事件fd到內(nèi)核并設(shè)置時間為1中所求的等待時間。然后把select返回的就緒事件放到就緒列表。(對應(yīng) evsel->dispatch(base, evbase, tv_p))。
3. 然后把現(xiàn)在超時的計時事件放到就緒列表。(對應(yīng)gettime(base, &base->tv_cache))。
4. 最后調(diào)用處理函數(shù)處理就緒列表中的事件(timeout_process(base))。
MeinHeld
概要:雙輪詢異步非阻塞模型
MeinHeld是目前python_web網(wǎng)絡(luò)框架中的性能怪獸,douban、知乎都應(yīng)用了這個設(shè)計模式。
MeinHeld基于Gunicorn的基礎(chǔ)上,能根據(jù)不同機器的cpu內(nèi)核性能自動調(diào)整worker進程(的數(shù)量),
并且每個worker下的工作子進程也會根據(jù)cpu作調(diào)整,保持在一個最佳性能區(qū)間。以下是meinheld的幾個優(yōu)秀之處:
1 meinheld的master_worker可設(shè)置為damon模式,即守護進程模式。unix系統(tǒng)中,守護進程將擁有最高級的內(nèi)存使用權(quán)限,master_worker可以自動托起掛死進程,重新分配內(nèi)存。
2 meinheld 的 雙輪巡機制,首先是slave_worker間的輪詢,meinheld采用了master和slave間的雙向交互,一旦slave進程上報請求結(jié)果,將自動進入空閑隊列,master在作完記錄后優(yōu)先向空閑進程分配http請求。其次,每個worker也對自己的子線程進行輪詢,每輪輪巡完畢后會向master_worker上報空閑thread數(shù)量和上次請求完成時間點。 雙輪巡的機制能夠完美的利用所有線程,達到最佳訪問性能。
(下面是關(guān)于meinheld的timeout_隊列原理圖和一段說明,部分摘自csdn,但原作者不詳,若有侵權(quán)請聯(lián)系作者。)
對于timeout環(huán)形隊列,每經(jīng)過resolution時間就往后移動一塊,當(dāng)前隊頭永遠指向剛剛到達時間的事件塊,如圖當(dāng)前處理的是2,那么說明隊列頭在2,那么再經(jīng)過resolution時間就會到3,根據(jù)時間不斷后移,循環(huán)利用。
1. 在處理每一塊timeout里面注冊的事件時,遍歷所有不為0的vector,得出對應(yīng)的fd。圖中已經(jīng)寫的很清楚的,其實原理和16進制一樣簡單。插入一個事件的時間復(fù)雜度為O(1),遍歷所有在timeout塊的注冊事件時間復(fù)雜度等價為O(n)[注:這里n為timeout里面注冊事件的個數(shù)],對比libevent的最小堆O(logn)插入,每次處理一個后調(diào)整堆的復(fù)雜度O(logn)處理n個就為O(nlogn),確實是高效很多。
2. 還有一個高效的地方在于,meinheld是檢測到有一個事件就馬上處理(無阻塞),不像gevent掛起等待最小等待時間到達(阻塞),然后才對所有就緒事件隊列里面的事件進行處理,不過這也導(dǎo)致了meinheld不能設(shè)定事件處理的優(yōu)先級。
簡單來說就是,master進程將任務(wù)分發(fā)給上一輪上報后空閑thread數(shù)量最多的slaver,接到請求的slaver分發(fā)給空閑thread后重新上報給master.反復(fù)操作。另外當(dāng)線程死鎖時,master會釋放掉slaver重建worker.? ?
現(xiàn)在,我們來舉個直觀的例子,假設(shè)現(xiàn)在有一個足球場,有個教練在一邊的底線負責(zé)給球員傳遞足球,場上的球員接到球后要將球帶到對面的球門里并返回上報結(jié)果。
1 、當(dāng)Flask僅作為一個簡易server時,可以這么理解:
? ? 只有一個教練和一個球員在執(zhí)行這項任務(wù)。
2 、當(dāng)引入gunicorn代理時,假設(shè)我們fork4個worker,這時候是這樣:
? ? 還是只有一個教練,但多了4個球員來完成這項任務(wù)。教練依舊是把球往地上一扔,4個人誰跑得快先搞定任務(wù)就拿走下個球。
3 、當(dāng)引入gunicorn+gevent模式的時候,我們引入組員的概念,還是有4個球員,但是他們通過自己的手段各自號召了若干個小弟來完成任務(wù):
? ? 這時候教練會紀錄每個球員之間完成任務(wù)的速度,比如A1分鐘4個,B3C2D1這樣子,那么教練會優(yōu)先把球分配給之前做得快球員,可能一次性給多個球過去。
4 、當(dāng)使用meinheld時,假設(shè)有一個教練,4個組,每組人數(shù)若干:
? ? 教練一開始看哪個組的空閑人數(shù)比較多,就優(yōu)先發(fā)給那個組的組長,組長接過球后再把球分發(fā)給空閑的組員。另外,教練還要充當(dāng)全程保姆,一旦發(fā)現(xiàn)哪個組干活效率出問題了,或者不同的球員踢了同一個球,或者有球員受傷了,他會立馬替換新球員。
OK,說了這么多,我們來看一下如果在redflask工程中編寫gunicorn代理的基礎(chǔ)配置,這是根目錄下的configm.py文件:
'''
Gunicorn的配置文件,默認選用gevent模型,有興趣的話可以自己研究下更強大的meinheld。
'''
import multiprocessing
#ssl證書 如果需要部署https協(xié)議的服務(wù)則使用下列命令
#certfile="server.crt"
#keyfile="server_nopwd.key"
# 監(jiān)聽本機的5000端口
bind = '0.0.0.0:5000'
preload_app = True
# 開啟進程
workers=4
#workers = multiprocessing.cpu_count() * 2 + 1
# 每個進程的開啟線程
threads = 10
backlog = 2048
# 工作模式為meinheld
#worker_class = "egg:meinheld#gunicorn_worker"
#gevent
worker_class = "gevent"
# debug=True
# 如果不使用supervisord之類的進程管理工具可以是進程成為守護進程,否則會出問題
daemon = False
# 進程名稱
proc_name = 'red_flask.pid'
# 進程pid記錄文件
pidfile = 'app_pid.log'
loglevel = 'debug'
logfile = 'log/red_flask_debug.log'
accesslog = 'log/red_flask_access.log'
access_log_format = '%(h)s %(t)s %(U)s %(q)s'
可以看出,gunicorn的配置文件十分簡潔。通過更改worker_class即可在gevent和meinheld中進行模式切換,另外還能根據(jù)cpu內(nèi)核數(shù)量進行進程調(diào)控,支持https等。
在編寫完成configm.py后,我們便可以通過gunicorn啟動flask的生產(chǎn)服務(wù)。
1、pip install -r requirements.txt (包含gunicorn、gevent、meinheld等依賴)
2、終端輸入 gunicorn -c configm.py run:app
3、通常在linux環(huán)境下,我們會編寫一個shell文件,通過nohup命令將gunicorn服務(wù)部署到系統(tǒng)后臺。
#十一、 服務(wù)端部署方案詳解 在商用場景中,flask+gunicorn的組合并不夠完善,通常我們還需要結(jié)合另外兩個軟件,即Nginx+Supervisor。
Nginx是一個高性能的Http和反向代理服務(wù)器,對wsgiServer完美支持。通常我們會把后端服務(wù)掛載在Nginx的端口服務(wù)下,讓Nginx進行請求轉(zhuǎn)發(fā)。同時,Nginx還具備執(zhí)行靜態(tài)資源處理,資源限制,gzip,負載均衡等功能,十分強大。
Supervisor則是一個由python編寫的后臺進程管理工具,程序托管在supervisor的時候可以實現(xiàn)自重啟,緩加載更新等功能。
下面就來介紹如何在一臺linux服務(wù)器上部署redflask服務(wù):(以下以ubuntu為例子)
1、通過git或scp將代碼上傳到服務(wù)器。(略) 2、通過gunicorn啟動redflask。(略) 3、安裝部署nginx,并配置代理到gunicorn 4、安裝部署supervisor,并配置gunicorn服務(wù)
Nginx的對應(yīng)配置
如何安裝
如果您是ubuntu系統(tǒng),并且不需求nginx的最新特性,那么完全可以通過apt市場進行快速安裝。 安裝nginx前,需要先安裝相關(guān)依賴,包括:
安裝gcc g++的依賴庫 ubuntu平臺可以使用如下命令。
1 gcc g++ 模塊
apt-get install build-essential
apt-get install libtool
2 pcre 依賴
sudo apt-get update
sudo apt-get install libpcre3 libpcre3-dev
3 zlib 依賴
apt-get install zlib1g-dev
4 ssl 依賴庫
apt-get install openssl
然后就是
sudo apt-get install nginx
ubuntu會自動幫你完成系統(tǒng)服務(wù)的配置,之后可以通過命令:
修改nginx配置:
sudo vim /etc/nginx/nginx.conf?
啟動服務(wù)
service nginx start
打開瀏覽器,訪問本機ip地址,看到welcome to nginx界面,表示安裝成功。
NGINX主要功能
1、靜態(tài)HTTP服務(wù)器
首先,Nginx是一個HTTP服務(wù)器,可以將服務(wù)器上的靜態(tài)文件(如HTML、圖片)通過HTTP協(xié)議展現(xiàn)給客戶端。
server {
listen80; # 端口號
location / {
root /usr/share/nginx/html; # 靜態(tài)文件路徑
}
}
2、反向代理服務(wù)器
server {
listen80;
location / {
proxy_pass http://192.168.20.1:8080; # 應(yīng)用服務(wù)器HTTP地址
}
}
3、負載均衡
upstream myapp {
server192.168.20.1:8080; # 應(yīng)用服務(wù)器1
server192.168.20.2:8080; # 應(yīng)用服務(wù)器2
}
server {
listen80;
location / {
proxy_pass http://myapp;
}
}
以上配置會將請求輪詢分配到應(yīng)用服務(wù)器,也就是一個客戶端的多次請求,有可能會由多臺不同的服務(wù)器處理??梢酝ㄟ^ip-hash的方式,根據(jù)客戶端ip地址的hash值將請求分配給固定的某一個服務(wù)器處理。
upstream myapp {
ip_hash; # 根據(jù)客戶端IP地址Hash值將請求分配給固定的一個服務(wù)器處理
server192.168.20.1:8080;
server192.168.20.2:8080;
}
server {
listen80;
location / {
proxy_pass http://myapp;
}
}
另外,服務(wù)器的硬件配置可能有好有差,想把大部分請求分配給好的服務(wù)器,把少量請求分配給差的服務(wù)器,可以通過weight來控制。
upstream myapp {
server192.168.20.1:8080weight=3; # 該服務(wù)器處理3/4請求
server192.168.20.2:8080; # weight默認為1,該服務(wù)器處理1/4請求
}
server {
listen80;
location / {
proxy_pass http://myapp;
}
}
4、虛擬主機有的網(wǎng)站訪問量大,需要負載均衡。然而并不是所有網(wǎng)站都如此出色,有的網(wǎng)站,由于訪問量太小,需要節(jié)省成本,將多個網(wǎng)站部署在同一臺服務(wù)器上。
例如將www.aaa.com和www.bbb.com兩個網(wǎng)站部署在同一臺服務(wù)器上,兩個域名解析到同一個IP地址,但是用戶通過兩個域名卻可以打開兩個完全不同的網(wǎng)站,互相不影響,就像訪問兩個服務(wù)器一樣,所以叫兩個虛擬主機。
server {
listen80default_server;
server_name _;
return444; # 過濾其他域名的請求,返回444狀態(tài)碼
}
server {
listen80;
server_name www.aaa.com; # www.aaa.com域名
location / {
proxy_pass http://localhost:8080; # 對應(yīng)端口號8080
}
}
server {
listen80;
server_name www.bbb.com; # www.bbb.com域名
location / {
proxy_pass http://localhost:8081; # 對應(yīng)端口號8081
}
}
簡易爬蟲過濾方案
location / {
if ($http_user_agent ~* "python|curl|java|wget|httpclient|okhttp") {
return 503;
}
? ? #正常處理
...
}
這個是在廖雪峰博客上看到的,原文為: 現(xiàn)在的網(wǎng)絡(luò)爬蟲越來越多,有很多爬蟲都是初學(xué)者寫的,和搜索引擎的爬蟲不一樣,他們不懂如何控制速度,結(jié)果往往大量消耗服務(wù)器資源,導(dǎo)致帶寬白白浪費了。
其實Nginx可以非常容易地根據(jù)User-Agent過濾請求,我們只需要在需要URL入口位置通過一個簡單的正則表達式就可以過濾不符合要求的爬蟲請求:
變量$http_user_agent是一個可以直接在location中引用的Nginx變量。~*表示不區(qū)分大小寫的正則匹配,通過python就可以過濾掉80%的Python爬蟲
其他功能
自行探索吧。
supervisor的對應(yīng)配置服務(wù)端運維的重點,系統(tǒng)中各類進程的守護者。由于不支持python3的環(huán)境,所以配置會稍微麻煩一點,我們需要通過virtualenv 創(chuàng)建一個python2虛擬環(huán)境。
pip install virtualenv
首先在自定義目錄下創(chuàng)建 super 虛擬環(huán)境
virtualenv --distribute -p /usr/bin/python2 super
cd super
source bin/activate? ? #激活虛擬環(huán)境
./bin/pip install supervisor# 安裝supervier
echo_supervisord_conf > supervisor.conf? # 生成 supervisor 默認配置文件
#熱后便可以通過以下命令對supervisor進行操作:
supervisord -c supervisor.conf #通過配置文件啟動supervisor
supervisorctl -c supervisor.conf status #察看supervisor的狀態(tài)
supervisorctl -c supervisor.conf reload 重新載入 #配置文件
supervisorctl -c supervisor.conf start [all]|[appname] #啟動指定/所有 supervisor管理的程序進程
supervisorctl -c supervisor.conf stop [all]|[appname] 關(guān)閉指定/所有 supervisor管理的程序進程
supervisor.conf中配置對應(yīng)的gunicorn應(yīng)用,其他類型的應(yīng)用配置也類似,比如java的springboot。
[program:start_gunicorn]
command=/your/bin/path/to/gunicorn -w 4 -b 0.0.0.0:5000 -k gevent run:app
directory:/home/ubuntu/project/test1/ #run.py所在的目錄
autostart=true
redirect_stderr=true
user=root
directory=/usr/local/qs-project/web
stdout_logfile=/var/log/test_test.log
如果要啟用supervisor的web端:9001端口.
需要注釋掉 配置文件中
[inet_http_server] 的所有子配置項
以及
[supervisorctl] 中的前四項
deactivate? #退出環(huán)境
至此,教程結(jié)束,第一次編寫小冊,如有錯誤或不足,歡迎指正、交流。