最近一個(gè)多月一直在做服務(wù)器的性能優(yōu)化浮声,老大的要求是要做到300個(gè)并發(fā)睡互,控制在200毫秒以內(nèi)丛楚,就說說我最近做的內(nèi)容吧截碴。
從30個(gè)并發(fā)平均每個(gè)2000毫秒 到 300個(gè)并發(fā)平均每個(gè)178毫秒
簡單介紹一下做了那些優(yōu)化:
01、減少log日志的打印
02尺上、減少redis的交互
03材蛛、耗時(shí)操作的處理
04、大文件信息的存儲(chǔ)
05怎抛、python的緩存機(jī)制
06卑吭、異步處理非返回操作
一、定位耗時(shí)操作 -- 唯一變量法
由于之前一直定位錯(cuò)誤马绝,導(dǎo)致了很多彎路豆赏,現(xiàn)發(fā)現(xiàn)一個(gè)比較好的定位方法 --- 逐個(gè)函數(shù)定位
需要借用壓測(cè)工具:wrk 或 jmeter
測(cè)試性能順序:純服務(wù)器性能 -- 加上參數(shù)處理時(shí)的性能 -- 加上第一個(gè)函數(shù)時(shí)的性能 -- 加上第二個(gè)函數(shù)時(shí)的性能 等等
如果在添加某一個(gè)步驟時(shí)性能變差很多,說明里有問題,需要仔細(xì)排查
簡單放兩個(gè)對(duì)比圖
純服務(wù)器性能 + 參數(shù)處理時(shí)的性能
純服務(wù)器性能 + 參數(shù)處理時(shí)的性能 + 讀取音頻
我們發(fā)現(xiàn)兩個(gè)性能測(cè)試只差一個(gè)讀取音頻函數(shù) 但形成卻相差很多河绽,說明在讀取音頻這里是一個(gè)巨大的耗時(shí)己单,那么就要想辦法處理掉
二、性能處理
01耙饰、我們可能會(huì)感覺打印一個(gè)log不會(huì)是耗時(shí)操作纹笼,但通過唯一變量法發(fā)現(xiàn) 打印log也是耗時(shí)的,因?yàn)橐刂圃?00ms以內(nèi)苟跪,那就是任何耗時(shí)的都要深思熟慮廷痘,于是減少log的打印
02、當(dāng)對(duì)redis做讀取操作時(shí)件已,每次讀取都要花費(fèi)幾毫秒笋额,那就想辦法優(yōu)化甚至怎么減少redis的讀取:
優(yōu)化:
a篷扩、當(dāng)能確認(rèn)并必確認(rèn)這是第一個(gè)存儲(chǔ)并不需要獲取時(shí)兄猩,就可以減少一次獲取,直接存儲(chǔ)鉴未。
b枢冤、要使用redis的緩存池
c、使用redis的通道法
減少判斷:
先假想代碼處理流程铜秆,中間用到了幾次redis的讀取淹真,然后通過redis的INFO commandstats 命令,定位redis的耗時(shí)连茧,以及有沒有多余的操作
數(shù)據(jù)信息為:操作次數(shù) – 總耗時(shí) – 平均耗時(shí)
這樣我們就能清清楚楚的看到用到了幾次讀寫操作核蘸,分別耗時(shí)多少
具體詳見:
redis的使用,以及耗時(shí)定位
03+04+05啸驯、耗時(shí)操作的處理
一般指:mysql的讀取 -- I/O操作
當(dāng)頻繁性的使用一個(gè)數(shù)據(jù)時(shí)就要想著做緩存處理客扎,緩存也會(huì)考慮處理時(shí)間,個(gè)人感覺處理時(shí)間(如有不對(duì)坯汤,請(qǐng)斧正):
本地磁盤 > redis > 內(nèi)存 > 機(jī)制化內(nèi)存
通過”定位耗時(shí)操作 -- 唯一變量法“ 得知虐唠,音頻文件的讀取是一個(gè)很耗時(shí)的操作,那么就做緩存處理惰聂。
方法一:redis緩存
說到緩存數(shù)據(jù),首先想到了內(nèi)存性數(shù)據(jù)庫redis咱筛,于是想辦法將音頻存至redis中搓幌,操作很簡單,以音頻名稱為key值 -- 讀取的信息為value進(jìn)行存儲(chǔ)(注意類型為bytes類型) + 過期時(shí)間(redis的存儲(chǔ)大小為512M)
很快代碼寫完了迅箩,那就測(cè)測(cè)效果吧溉愁,一次效果還不錯(cuò),提升了不少饲趋,但還是很耗時(shí)拐揭,而且與想象的相差很多撤蟆,預(yù)想存儲(chǔ)redis,讀取都是幾毫秒 最多也就10+毫秒的時(shí)間堂污,為什么測(cè)試結(jié)果與預(yù)想結(jié)果查那么多家肯,在redis讀取那里加上時(shí)間,測(cè)一下讀取時(shí)間盟猖,一看打印時(shí)間都在80+以上有的甚至到達(dá)150+讨衣,后來發(fā)現(xiàn)原因:數(shù)據(jù)過大,讀取緩慢
方法二:cacheout緩存
于是將音頻的數(shù)據(jù)存至內(nèi)存中式镐,發(fā)小效果不錯(cuò)反镇,幾乎不耗時(shí),達(dá)到了理想狀態(tài)娘汞。
轉(zhuǎn)念一想歹茶,數(shù)據(jù)會(huì)一直累加與服務(wù)器內(nèi)存,導(dǎo)致整個(gè)服務(wù)器增加你弦,于是查找有效的緩存機(jī)制辆亏,就找到了cacheout緩存
它可以設(shè)置同時(shí)設(shè)置多個(gè)緩存,并且可以設(shè)置緩存機(jī)制(優(yōu)先策略)鳖目,設(shè)置有效條目數(shù) 以及 設(shè)置有效時(shí)間扮叨, 大多數(shù)操作基本和redis一樣,簡單易懂
# 判斷是否存在
cache["voice_store"].has(voice_id)
# 根據(jù)key獲取
cache["voice_store"].get(voice_id)
# 存儲(chǔ)
cache["voice_store"].set(voice_store_key, voice_body)
方法三:redis + cacheout
(主要考慮到負(fù)載均衡领迈,可能會(huì)有多個(gè)服務(wù)彻磁,但會(huì)公用一個(gè)redis)
按理說現(xiàn)在已經(jīng)完全達(dá)到了要求,對(duì)音頻(根據(jù)url下載的)的處理已經(jīng)最優(yōu)化了狸捅,但有一個(gè)問題是音頻文件還一直存在于服務(wù)器內(nèi)衷蜓,增加內(nèi)存,那就想辦法移除尘喝。
于是就有了這個(gè)redis + cacheout的想法磁浇。
存儲(chǔ):
URL下載音頻 -- 讀取音頻 -- 將音頻信息存至redis 和 緩存中 -- 刪除音頻文件
讀取:
獲取音頻名稱 -- 緩存查找 -- redis查找 -- URL下載存儲(chǔ)
06朽褪、異步處理非返回操作
一次請(qǐng)求處理中置吓,打印log和發(fā)送日志,以及一些I/O是避免不了的缔赠,所以我們會(huì)用到子線程異步存儲(chǔ)衍锚,讓這些耗時(shí)的去一邊處理,不影響主線的處理嗤堰。
另加一個(gè)小點(diǎn)-如果你的用戶請(qǐng)求是有順序的戴质,那么在存儲(chǔ)redis時(shí)也可以用一下時(shí)間差,但一定要把握好!
我這里用的是twisted的threads
threads.deferToThread(save_user_info, "voice_body", voice, 1)
附送:使同步阻塞函數(shù)秒表非阻塞異步并發(fā)函數(shù)--twisted框架
一告匠、使同步函數(shù)秒變異步并發(fā)函數(shù)
如果需要返回值戈抄, 如run2()函數(shù)
給請(qǐng)求函數(shù)添加裝飾器@inlineCallbacks
并使用yield進(jìn)行接收返回值
@inlineCallbacks
def run2():
"""
主函數(shù)
"""
# 將耗時(shí)函數(shù)放入另一個(gè)線程執(zhí)行,返回一個(gè)deferred對(duì)象
d = yield (threads.deferToThread(largeFibonnaciNumber))
# 等待返回的結(jié)果 再做處理
print(d)
二后专、如果不需要返回值可以使用addCallback回調(diào)函數(shù) 如run()函數(shù)
def run():
"""
主函數(shù)
"""
# 將耗時(shí)函數(shù)放入另一個(gè)線程執(zhí)行划鸽,返回一個(gè)deferred對(duì)象
d = threads.deferToThread(largeFibonnaciNumber)
# 添加回調(diào)函數(shù)
d.addCallback(fibonacciCallback)
具體代碼見項(xiàng)目中的other目錄
Git代碼: 公眾號(hào)后臺(tái)回復(fù) python_sanic