前言
本文意在表達(dá):如何用 python 寫(xiě)好 web server
在這里铭污,不會(huì)手把手告訴你怎么寫(xiě)代碼,也不會(huì)告訴你什么是高級(jí)的 web server膀篮。
我所寫(xiě)的僅僅是嘹狞,我眼里的 python web server。
希望能對(duì)你有所幫助誓竿,有所啟示刁绒。
歡迎指正。
需要額外提示的是烤黍,該文主要討論的是無(wú)狀態(tài)服務(wù)的編寫(xiě)知市。有狀態(tài)的不在此處討論。
環(huán)境
文檔編寫(xiě)時(shí)間:2020.05.29
wsgi app: flask
wsgi server: gunicorn
模式:前后端分離速蕊,此處僅討論后端
正文
web server 可以分為兩個(gè)部分:框架 和 業(yè)務(wù)功能
大多時(shí)候框架部分的代碼嫂丙,都是在解決如何使用好一個(gè)第三方框架,而非自己去實(shí)現(xiàn)一個(gè)框架规哲。
業(yè)務(wù)功能部分便是實(shí)現(xiàn)具體業(yè)務(wù)功能的代碼了跟啤。
每個(gè)部分解決自己的問(wèn)題,讓工作有條不紊的進(jìn)行。沒(méi)有孰優(yōu)孰劣之分隅肥,業(yè)務(wù)功能代碼會(huì)更加考驗(yàn)?zāi)愕拇a邏輯竿奏。
業(yè)務(wù)功能部分
我想大多數(shù)人接觸 web server 時(shí)首先接觸的便是這塊內(nèi)容,或者正處于這個(gè)階段腥放,所以先提這部分內(nèi)容泛啸。
對(duì)于實(shí)現(xiàn)業(yè)務(wù)功能,有兩個(gè)點(diǎn)需要著重考慮:規(guī)范秃症、模塊化編寫(xiě)
規(guī)范
規(guī)范涉及:方法命名格式規(guī)范候址、url 格式規(guī)范、接口文檔規(guī)范种柑、接口數(shù)據(jù)格式規(guī)范以及 pep8
每個(gè)規(guī)范的具體參考在文章的最后【擴(kuò)展章節(jié)】給出岗仑。
在協(xié)作開(kāi)發(fā)中首先制定好規(guī)范十分重要,這是你必須應(yīng)該做的事聚请,哪怕只有你自己在開(kāi)發(fā)荠雕。沒(méi)有規(guī)范的代碼,后期維護(hù)將一團(tuán)糟驶赏。
模塊化編寫(xiě)
模塊化代碼舞虱,可以增加代碼的可閱讀性,增加代碼的擴(kuò)展性母市,增加代碼的健壯性矾兜。
個(gè)人偏向于將總體代碼分為三層(非照搬MVC):路由層、控制層患久、接口層椅寺。
每層負(fù)責(zé)自己的事情
路由層(Router):
- 定義 apidoc (接口文檔)
- 全局 try catch
- 定義 url 及 url 的入口。
控制層(Manager):
- 輸入?yún)?shù)檢查
- 顯式定義輸入?yún)?shù)(在這一層將 request 拆解出來(lái)蒋失,不要再往下傳遞 request)
- 代碼邏輯層(組合訪問(wèn)下層接口返帕,使用多線程之類)
接口層(Client,該層的方法功能盡可能遵守單一職責(zé)篙挽,以便上層組合使用)
- 與外部交互
- 格式化返回參數(shù)
框架部分
作為框架的搭建者荆萤,需要額外考慮如下問(wèn)題:
- gunicorn 配置調(diào)優(yōu)(提供并行能力,推薦多進(jìn)程)
- 日志:
1)標(biāo)準(zhǔn)輸出的內(nèi)置 debug 信息及格式铣卡。(框架自帶的debug链韭,比如 gunicorn 的 debug,一般輸出到屏幕)
2)編寫(xiě)程序時(shí)手動(dòng)記錄的日志格式煮落。(輸出到文件敞峭,比如記錄 exception、traceback)
3)自動(dòng)記錄下接口訪問(wèn)信息蝉仇。(輸出到文件或者屏幕旋讹,可記錄訪問(wèn)端地址殖蚕、接口花費(fèi)時(shí)間等) - 線程池(解決并發(fā) IO 等待)
- 提供配置(單獨(dú)編寫(xiě)實(shí)現(xiàn)配置對(duì)象,方便程序內(nèi)使用相應(yīng)配置)
- 打包方式(setup 等)
- 應(yīng)用啟動(dòng)方式 (通過(guò) argparse 增加 -h沉迹、--conf 之類命令方便啟動(dòng)睦疫,注冊(cè)進(jìn) systemd 等)
- 項(xiàng)目基礎(chǔ)文檔,包含整體架構(gòu)設(shè)計(jì)鞭呕、某些細(xì)節(jié)功能設(shè)計(jì)(比如權(quán)限設(shè)計(jì))蛤育、如何構(gòu)建開(kāi)發(fā)環(huán)境等。
進(jìn)階
到這里琅拌,一個(gè)小型的中規(guī)中矩缨伊、能滿足大部分場(chǎng)景的 web server 基本齊全了摘刑。單節(jié)點(diǎn)萬(wàn)級(jí)并發(fā)进宝,多節(jié)點(diǎn)到十萬(wàn)并發(fā)也是沒(méi)太大問(wèn)題的。
為了讓它可以擁有進(jìn)化的能力枷恕,我們需要看的更遠(yuǎn)党晋,了解更多的東西。
以上的內(nèi)容完全基于個(gè)人經(jīng)驗(yàn)的沉淀徐块。以下將要介紹的東西未玻,僅供各位一看,并非來(lái)源于長(zhǎng)年累月的經(jīng)驗(yàn)胡控。
這個(gè)時(shí)候扳剿,我們應(yīng)該考慮分布式服務(wù)了。
分布式服務(wù)主要需要解決的問(wèn)題是:組件之間如何進(jìn)行通信昼激。
目前組件間主流通信方式如下:
- 以 restful 方式進(jìn)行通信庇绽。
- 以消息隊(duì)列(中間件)進(jìn)行通信。
restful 方式
該方式就是以 http 訪問(wèn)接口來(lái)實(shí)現(xiàn)功能橙困,就是普普通通的 web server瞧掺。
缺點(diǎn):
- 需要自己維護(hù)高可用。也就是凡傅,你得想明白辟狈,如果 3個(gè)A(A1,A2,A3)服務(wù)同時(shí)部署,由誰(shuí)來(lái)對(duì)外進(jìn)行服務(wù)夏跷,如果A1掛了哼转,由誰(shuí)來(lái)接手服務(wù)。這是高可用
- 需要自己維護(hù)負(fù)載均衡槽华。將一波流量分別引流至不同的服務(wù)節(jié)點(diǎn)上释簿,降低單個(gè)節(jié)點(diǎn)的壓力,這是負(fù)載均衡硼莽。(什么都不是一蹴而就的庶溶,首先考慮你現(xiàn)在需要這個(gè)嗎)
- 需要自己維護(hù)橫向擴(kuò)展性煮纵。如果現(xiàn)在已有3個(gè)節(jié)點(diǎn)同樣的A服務(wù),但是性能不夠用偏螺,我再加個(gè)A服務(wù)節(jié)點(diǎn)行疏,變成4節(jié)點(diǎn)同樣的服務(wù),你如何應(yīng)對(duì)套像?需要重啟現(xiàn)有節(jié)點(diǎn)嗎酿联?會(huì)中斷現(xiàn)有應(yīng)用嗎?(可結(jié)合服務(wù)發(fā)現(xiàn)解決)
- 需要仔細(xì)考慮認(rèn)證設(shè)計(jì)夺巩。由于組件可以獨(dú)立對(duì)外部提供服務(wù)贞让,因此如果一次請(qǐng)求跨越資源則需要重復(fù)的認(rèn)證。
這是目前中規(guī)中矩的分布式實(shí)現(xiàn)方案柳譬,成熟且原始喳张。
消息隊(duì)列方式
消息隊(duì)列,有的地方也稱之為中間件美澳,但是個(gè)人偏向直接這么稱呼销部,中間件是一個(gè)很寬泛的概念。
缺點(diǎn):
- 組件不能獨(dú)立對(duì)外提供服務(wù)制跟。
- 十分依賴消息隊(duì)列的性能舅桩,消息隊(duì)列十分可能成為你集群服務(wù)的瓶頸。
- 一定程度上的緊耦合雨膨,一段代碼既是生產(chǎn)者又是消費(fèi)者擂涛,這種情況下你的代碼邏輯會(huì)有點(diǎn)糟糕。
優(yōu)點(diǎn):
- 統(tǒng)一的入口聊记。實(shí)際上這既是缺點(diǎn)撒妈,也是優(yōu)點(diǎn)。統(tǒng)一的入口可以讓認(rèn)證變的十分高效簡(jiǎn)單甥雕,但是相對(duì)來(lái)說(shuō)統(tǒng)一入口踩身,會(huì)稍微紊亂代碼結(jié)構(gòu)。
- 顯著的增加并發(fā)社露、異步能力挟阻。消息可以堆積,然后慢慢去處理峭弟,當(dāng)然堆積處理不當(dāng)就是一場(chǎng)雪崩附鸽。
- 簡(jiǎn)單配置即可實(shí)現(xiàn)高可用、負(fù)載均衡瞒瘸、擴(kuò)展性坷备。
融合實(shí)現(xiàn)
前面提的兩種方式,可以融合起來(lái)對(duì)外提供服務(wù)情臭。
- 獨(dú)立的組件以 restful 風(fēng)格通信省撑。
- 組件內(nèi)部以消息隊(duì)列的方式進(jìn)行通信赌蔑,增加異步、并發(fā)能力竟秫。
- 額外增加服務(wù)發(fā)現(xiàn)(比如采用 etcd)娃惯,提供整個(gè)集群服務(wù)的高可用、負(fù)載均衡肥败、擴(kuò)展性趾浅。
這將會(huì)造就一個(gè)龐大而復(fù)雜的系統(tǒng)。除了認(rèn)證有弊端外(重復(fù)認(rèn)證)馒稍,似乎無(wú)所不能皿哨。
有一個(gè)好消息,一個(gè)跨語(yǔ)言的微服務(wù)框架(也稱服務(wù)網(wǎng)格)istio 正在變的越來(lái)越成熟纽谒。它可以幫助你簡(jiǎn)便的實(shí)現(xiàn)服務(wù)注冊(cè)证膨,服務(wù)之間的通信,服務(wù)的管理等佛舱,值得持續(xù)關(guān)注椎例,持續(xù)學(xué)習(xí)挨决。
擴(kuò)展
python 編寫(xiě) web server请祖,性能夠嗎?
性能足夠。
如果你的代碼涉及大量計(jì)算脖祈,十分敏感性能肆捕,不建議使用 python。但是如果你愿意接受調(diào)庫(kù)(C庫(kù))盖高,python 也依然性能足夠慎陵。
規(guī)范參考
方法命名規(guī)范
# 例如:user_create (創(chuàng)建用戶)
<資源>_<動(dòng)作>
url 格式規(guī)范
# 例如:/app1/project/user_add?project_id=123&user_id=456 (向app1 的項(xiàng)目中添加用戶)
/<1級(jí)資源>/<2級(jí)資源>/<3級(jí)資源>_<3級(jí)資源動(dòng)作>
目前有很多規(guī)范是將 uuid 放入前半部分中。例如:
/app1/<project_id>/user_add?user_id=456
這類 url 的前半部分十分可能包含過(guò)長(zhǎng)的 uuid(32位)喻奥,從而造成實(shí)際的 url 可讀性十分差席纽。因此個(gè)人傾向于將所有的 uuid 作為參數(shù)傳遞。
接口文檔規(guī)范
接口文檔撞蚕,在小型的開(kāi)發(fā)團(tuán)隊(duì)當(dāng)中均由個(gè)人編寫(xiě)润梯、維護(hù)。
可以采用 apidoc 之類的工具去方便的生成接口文檔甥厦。
個(gè)人常用接口文檔規(guī)范如下:
@api {GET} /kdcloud/networks?detail=true 獲取網(wǎng)絡(luò)列表
@apiGroup network
@apiName network_list
@apiDescription
接口描述
@apiParamExample {json} 參數(shù)示例
Args: detail=true 表示獲取更加詳細(xì)的信息
Headers: {
"token": "1234",
"project_id": "1234",
"role": "",
}
Body: 無(wú)
@apiSuccessExample {json} 成功返回值示例
返回碼 200
{...}
@apiErrorExample {json} 失敗返回值示例
返回碼 500
{...}
效果參考:
接口數(shù)據(jù)格式規(guī)范
接口數(shù)據(jù)規(guī)范定義纺铭,某一類的接口返回固定的數(shù)據(jù)格式。這就看你們的偏好了刀疙。
比如通過(guò)http調(diào)用的接口數(shù)據(jù)結(jié)構(gòu)規(guī)范:
# 成功時(shí):
{
"status": "success",
"inventory": {}
}
# 失敗時(shí):
{
"status": "failure",
"error": ""
}
比如統(tǒng)一方法返回值結(jié)構(gòu):返回字典列表而不是對(duì)象列表之類舶赔。
pep8
python 開(kāi)源項(xiàng)目一般均會(huì)采用 pep8 檢查代碼格式。其中包含很多雜項(xiàng)谦秧,比如文件末尾空行竟纳,單行不能超過(guò)80字符之類撵溃。
建議一定使用該規(guī)范約束自己寫(xiě)代碼。和大牛接軌锥累,總比自己瞎想來(lái)的實(shí)際征懈。