背景
前段時(shí)間在學(xué)校為了賺點(diǎn)零花錢镶骗,主導(dǎo)設(shè)計(jì)和落地了一個(gè)小型項(xiàng)目包竹,現(xiàn)在花點(diǎn)時(shí)間整理總結(jié)了一下后臺(tái)部分的設(shè)計(jì)修噪。限于篇幅不涉及具體代碼查库,方法論和怎么做我覺得更重要。如果你現(xiàn)在也面臨著小項(xiàng)目功能不算復(fù)雜黄琼、錢少樊销、人手不足、時(shí)間緊的情況脏款,那么希望這篇總結(jié)能給你帶來(lái)啟發(fā)和幫助围苫。整個(gè)期間,遵循了幾條基本原則:
- 使用順手和開發(fā)效率高的語(yǔ)言撤师,成熟的框架剂府。php我不熟,略過(guò)剃盾。Java適合大型項(xiàng)目比較規(guī)范成熟腺占,但開發(fā)起來(lái)太慢,略過(guò)痒谴。人生苦短衰伯,這里就選了Python 2.7,后臺(tái)框架用的Flask最新版本闰歪。
- 盡量多使用成熟的第三方服務(wù)嚎研。因?yàn)槿耸稚馘X也不多蓖墅,時(shí)間緊库倘,使用第三方服務(wù)不管從功能完善性临扮、安全性等角度來(lái)說(shuō),都是性價(jià)比最高的選擇教翩。比如支付模塊杆勇,我們使用了ping++等。
- 能具備一定的擴(kuò)展和伸縮性饱亿。如果使用者增多導(dǎo)致各種延時(shí)蚜退,能盡快地?cái)U(kuò)展后臺(tái)性能。這就要求在最開始設(shè)計(jì)階段要考慮到一定的擴(kuò)展性能需求彪笼。但需在一定的限度內(nèi)钻注,不能過(guò)于把事情考慮復(fù)雜,不然就沒法落地了配猫。所以在某一定限度的性能擴(kuò)展要支持幅恋,超過(guò)這個(gè)限度,我們可以假設(shè)那個(gè)時(shí)候整個(gè)系統(tǒng)已經(jīng)被重構(gòu)了泵肄。畢竟給多少錢捆交,做多少事。
- 后臺(tái)要注意安全性和邏輯性檢查腐巢。后臺(tái)開發(fā)應(yīng)當(dāng)有一種意識(shí):app端就是一個(gè)顯示模塊品追,各種必需的判斷和異常處理后臺(tái)都應(yīng)該存在,一切以后臺(tái)處理為準(zhǔn)冯丙。要多使用白名單模式肉瓦,符合我格式的請(qǐng)求才處理;其它一概不管银还。這里也要有一定的度风宁,執(zhí)迷于各種復(fù)雜檢查會(huì)拖慢項(xiàng)目進(jìn)度,無(wú)法按時(shí)交付蛹疯。數(shù)據(jù)庫(kù)操作要尤其注意戒财,一定要使用綁定參數(shù)來(lái)查詢,防止sql注入攻擊捺弦。
后臺(tái)架構(gòu)設(shè)計(jì)
后臺(tái)全部依賴騰訊云饮寞,下面逐一進(jìn)行下介紹:
- 云負(fù)載均衡。把所有app端打來(lái)的請(qǐng)求均勻地轉(zhuǎn)發(fā)給后面自己配置的云主機(jī)列吼。同時(shí)幽崩,經(jīng)過(guò)調(diào)研可以設(shè)置app端和云負(fù)載均衡間的通信走h(yuǎn)ttps,而云負(fù)載均衡處理后再轉(zhuǎn)發(fā)給后方的云主機(jī)是http請(qǐng)求寞钥。這里解決了數(shù)據(jù)傳輸過(guò)程中的安全問題慌申,而且沒有增加復(fù)雜性。
- Cloud Virtual Machine(云主機(jī))。每個(gè)云主機(jī)上面部署了后臺(tái)程序來(lái)真正處理請(qǐng)求蹄溉。
- 云主機(jī)上整個(gè)后臺(tái)的部署使用了:Nginx+Supervisor+Gunicorn+Gevent咨油。Nginx做一些請(qǐng)求處理、緩存等柒爵,將請(qǐng)求轉(zhuǎn)發(fā)給綁定到本地127.0.0.1地址上的Gunicorn役电。Gunicorn是支持wsgi協(xié)議的http server,默認(rèn)使用同步阻塞的網(wǎng)絡(luò)模型棉胀,那么這里會(huì)替換為Gevent異步模式法瑟,提升并發(fā)處理能力。Flask 程序就是一個(gè)wsgi應(yīng)用唁奢,被Gunicorn托管霎挟。Supervisor是用Python開發(fā)的一套通用的進(jìn)程管理程序,能將一個(gè)普通的命令行進(jìn)程變?yōu)楹笈_(tái)daemon麻掸,并監(jiān)控進(jìn)程狀態(tài)氓扛,異常退出時(shí)能自動(dòng)重啟。我們使用Supervisor來(lái)啟動(dòng)Gunicorn论笔。
- 云數(shù)據(jù)庫(kù)MySQL采郎。騰訊云租用的高可用MySQL集群,flask中配置好地址就ok狂魔。
- 云緩存Redis蒜埋。騰訊云租用的Redis緩存,flask中配置好地址就ok最楷。
這樣的設(shè)計(jì)簡(jiǎn)單整份、穩(wěn)定、高效籽孙,并提供了一定的擴(kuò)展能力烈评。假如一段時(shí)間之后,后臺(tái)撐不住了各種延時(shí)犯建,那么完全copy一臺(tái)云主機(jī)(幾分鐘在管理界面就能購(gòu)買一臺(tái)云主機(jī)讲冠,然后通過(guò)腳本來(lái)部署完成),再在負(fù)載均衡中配置好轉(zhuǎn)發(fā)就能立即提升后臺(tái)的處理能力适瓦。如果數(shù)據(jù)庫(kù)和緩存的性能有瓶頸竿开,系統(tǒng)會(huì)自動(dòng)報(bào)警,這時(shí)需要的可能就是在管理界面里花錢提升性能就好玻熙。畢竟在這種小中型項(xiàng)目中否彩,沒有什么問題是不能通過(guò)提升一倍服務(wù)器性能來(lái)解決的,如果有嗦随,那么就再提升一倍列荔。
Flask應(yīng)用結(jié)構(gòu)
|-app
|-api_1_0 # 1.0版本
|-__init__.py
|-common.py # 具體的各種處理邏輯類
...
|-models.py # 數(shù)據(jù)庫(kù)對(duì)應(yīng)類定義
|-errors.py # 一些異常錯(cuò)誤定義
|-__init__.py
|-logs # 日志
|-tests # 測(cè)試用例都在里面
|-test*.py
|-env # 虛擬環(huán)境
|-requirements.txt # 后臺(tái)依賴的所有組件,便于在其它電腦生成相同的環(huán)境
|-config.py # 定義幾套配置,開發(fā)環(huán)境贴浙、測(cè)試環(huán)境筷转、生產(chǎn)環(huán)境,每套環(huán)境按需設(shè)置數(shù)據(jù)庫(kù)地址啊等等
|-manage.py # 用于啟動(dòng)程序悬而、進(jìn)入shell調(diào)試模式、測(cè)試等等
|-README.md # 一定要把一些重要設(shè)計(jì)或者邏輯給記錄下來(lái)锭汛,否則一段時(shí)間后就忘了笨奠,也方便其他人了解
|-update_and_run.sh # 后臺(tái)更新腳本。在服務(wù)器上自動(dòng)pull最新代碼唤殴,安裝依賴插件般婆,再重新運(yùn)行后臺(tái)
開發(fā)過(guò)程中幾點(diǎn)體會(huì)可以分享下:
- git很方便,這是編程必需技能朵逝,不會(huì)的一定得學(xué)一下蔚袍。
- Flask很靈活自由,能隨自己的需要隨意選擇插件配名,對(duì)新手來(lái)說(shuō)可能會(huì)覺得不太友好啤咽。數(shù)據(jù)庫(kù)操作我們用的Flask-SQLAlchemy插件,就是SQLAlchemy基礎(chǔ)上的一個(gè)簡(jiǎn)單封裝渠脉。ORM真的巨好用宇整,能自動(dòng)幫我們按參數(shù)查詢,又將查詢出來(lái)的結(jié)果自動(dòng)轉(zhuǎn)換為對(duì)象等芋膘。但是在一些復(fù)雜操作各種join時(shí)鳞青,我覺得反而復(fù)雜了,因?yàn)檫€要去深究它自己那套規(guī)則为朋。所以我基本上是一些簡(jiǎn)單操作時(shí)用ORM來(lái)做臂拓,但一些復(fù)雜的操作就寫sql語(yǔ)句來(lái)直接執(zhí)行。而且我沒有在代碼中定義mysql表結(jié)構(gòu)习寸,是自己寫sql語(yǔ)句來(lái)建表的胶惰,然后在flask中直接映射mysql數(shù)據(jù)庫(kù)中已存在的表。我感覺這樣更爽一點(diǎn)霞溪,但是這個(gè)全憑個(gè)人習(xí)慣童番、愛好來(lái)自主選擇了。
- Unicode編碼是個(gè)坑威鹿。flask內(nèi)部處理的數(shù)據(jù)都是unicode編碼剃斧,網(wǎng)上可調(diào)自動(dòng)轉(zhuǎn)utf8,但我沒有成功忽你,有個(gè)設(shè)置項(xiàng)但啟動(dòng)失敗了幼东。所以在數(shù)據(jù)格式轉(zhuǎn)換上有點(diǎn)坑,都得encode('utf-8')一下。
- 最好多寫下測(cè)試用例根蟹。每個(gè)接口基本要有個(gè)測(cè)試用例保證正常功能脓杉,每次開發(fā)完一個(gè)功能,都跑一次全部測(cè)試用例简逮。沒問題再合并到master分支球散,這樣能最大程度防止影響到其它接口的代碼,帶來(lái)錯(cuò)誤散庶。而且用例只寫一次蕉堰,性價(jià)比還是蠻高的。
開發(fā)流程
可以簡(jiǎn)單說(shuō)下我們合作開發(fā)的流程悲龟,git服務(wù)我們使用了國(guó)內(nèi)的Coding屋讶。
- 認(rèn)領(lǐng)成功某功能后,從master分支創(chuàng)建新分支须教,切換到新分支進(jìn)行相應(yīng)功能開發(fā)皿渗;
- 新功能調(diào)試完成沒有問題后,寫下對(duì)應(yīng)的測(cè)試用例保證功能轻腺,在全部測(cè)試用例通過(guò)后乐疆;
- 更新master分支代碼,在新分支下merge master分支贬养,解決可能的沖突;
- 切換到master分支诀拭,在master分支下merge新分支;
- 最后將master分支push煤蚌。
開發(fā)服務(wù)器上跑著master分支的穩(wěn)定代碼耕挨,可供所有人調(diào)用和調(diào)試,可以自動(dòng)化來(lái)部署:
- Coding上設(shè)置每次push都給開發(fā)服務(wù)器某端口發(fā)送消息尉桩;
- 在開發(fā)服務(wù)器上開個(gè)服務(wù)監(jiān)聽該端口筒占,收到push的消息就執(zhí)行一次update_and_run.sh腳本。腳本在服務(wù)器上自動(dòng)pull最新代碼蜘犁,然后安裝依賴插件翰苫,再重新運(yùn)行后臺(tái)。
總結(jié)
- 因?yàn)楦鞣N原因項(xiàng)目方有點(diǎn)坑这橙,后臺(tái)現(xiàn)在并沒有按上面的設(shè)計(jì)來(lái)搞奏窑,所以當(dāng)前就是在一臺(tái)云主機(jī)上部署了所有。Nginx做反向代理到本地端口屈扎,Gunicorn在本地端口監(jiān)聽埃唯,mysql和redis都在本地。不過(guò)對(duì)于自用或者使用者不多的系統(tǒng)來(lái)說(shuō)鹰晨,也夠了墨叛。
- 設(shè)計(jì)和實(shí)現(xiàn)了一把后臺(tái)止毕。在構(gòu)思整個(gè)后臺(tái)功能階段,其實(shí)就是設(shè)計(jì)整個(gè)app的功能時(shí)漠趁,體會(huì)到了做產(chǎn)品經(jīng)理的不容易扁凛。包括數(shù)據(jù)庫(kù)設(shè)計(jì)這塊,需要什么樣的數(shù)據(jù)闯传?怎么存谨朝?怎么操作?怎么來(lái)實(shí)現(xiàn)后臺(tái)邏輯和功能甥绿?這一系列問題都需要一定的計(jì)算機(jī)基礎(chǔ)和整體把控字币,還是蠻有趣蠻有挑戰(zhàn)性的,每一次都能學(xué)到很多新東西妹窖。
- 對(duì)后臺(tái)開發(fā)這塊這次負(fù)責(zé)的比較多,還是挺有意思的收叶。后臺(tái)看不見骄呼,沒前端那么風(fēng)騷,但承載了所有功能判没,是整個(gè)軟件的核心蜓萄。而且后臺(tái)想要做得好,還是蠻難的澄峰。各種并發(fā)嫉沽、重復(fù)請(qǐng)求處理、安全性處理俏竞、邏輯判斷绸硕、數(shù)據(jù)庫(kù)操作優(yōu)化等等,需要耗費(fèi)大量的精力去做魂毁。特別是高并發(fā)這塊玻佩,好的系統(tǒng)怎么設(shè)計(jì)和優(yōu)化?像微信這樣的隨便就是上億席楚、上千萬(wàn)的請(qǐng)求咬崔,可不是加點(diǎn)服務(wù)器就能解決得好的,覺得好牛逼烦秩。剛好微信也在廣州垮斯,希望自己以后多看多學(xué)吧,加油~