引言
最近有個同事反映某個老舊項目總是有請求失敗的情況,早期項目為了快速上線完成任務(wù)钟病,很多代碼基本是怎么方便怎么寫蟹地,這樣就留下了很多隱患,于是找到服務(wù)器查看日志果然發(fā)現(xiàn)了大量的502去扣、499錯誤,然后又跟那個同事大致了解了下情況樊破,原來是客戶端最近增加了不少愉棱,并且有集中訪問的情況,根據(jù)情況自己寫了個測試腳本模擬真實請求開始測試哲戚,發(fā)現(xiàn)只要到大概200的并發(fā)奔滑,就會出現(xiàn)上述的錯誤(好吧,確實有點弱╮(╯╰)╭)
目標(biāo)
以目前的訪問量在不更換框架的情況下盡量把并發(fā)提高到1000以上
不影響現(xiàn)在的業(yè)務(wù)
服務(wù)器配置
硬件:16核CPU+32G內(nèi)存
軟件:gunicorn+django+postgresql
其他:gunicorn開了50個work
優(yōu)化流程
先從軟件配置開始查找問題顺少,發(fā)現(xiàn)gunicorn沒用gevent朋其,于是增加配置如下:
#gun.conf
worker_class = "gevent"
worker_connections = 2048
workers = 33 #官方建議(2 x $num_cores) + 1
backlog = 4096
#wsgi.py(別忘了打monkey patch)
import gevent.monkey
gevent.monkey.patch_all()
測試并發(fā)達(dá)到了300王浴,并且沒有錯誤,這個量還遠(yuǎn)遠(yuǎn)不夠令宿,繼續(xù)查找問題叼耙,發(fā)現(xiàn)每次測試的時候,數(shù)據(jù)庫服務(wù)器負(fù)載非常之高粒没,cpu使用率非常大筛婉,查下當(dāng)前慢查詢
select * from pg_stat_activity where state<>'idle' and now()-query_start > interval '1 s' order by query_start ;
果然有條很簡單的查詢很耗時,居然可以用12s癞松,這能忍么爽撒,原因有張百萬數(shù)據(jù)的表居然在查詢條件那沒建索引ㄟ( ▔, ▔ )ㄏ,建了索引之后响蓉,發(fā)現(xiàn)果然快了好多硕勿,至少大于1s的查詢沒有了,感覺正個數(shù)據(jù)庫都輕松了好多枫甲,又繼續(xù)300個并發(fā)走起源武,果然負(fù)載下來了很多,是以前的1/10想幻,繼續(xù)加大并發(fā)測試粱栖,發(fā)現(xiàn)大概到600的時候就開始出現(xiàn)錯誤了,同時數(shù)據(jù)庫的壓力也上來了脏毯,索性加個慢查詢?nèi)罩景赡志浚^續(xù)看看:
log_min_duration_statement = 100ms
log_destination = 'csvlog'
發(fā)現(xiàn)了大量的耗時的sql類似這種:
select * from tablename where upper(columnname) = upper(aaa);
之后去代碼里看了下,有幾條django orm 語句類似這樣的食店,忽略大小寫:
XXX.objects.filter(xxx__iexact='aaa',xxx__iexact='bbb')
用了這個__iexact之后渣淤,數(shù)據(jù)庫就會進(jìn)行upper操作,django源碼里的一段代碼:
operators = {
'exact': '= %s',
'iexact': '= UPPER(%s)',
'contains': 'LIKE %s',
'icontains': 'LIKE UPPER(%s)',
'regex': '~ %s',
'iregex': '~* %s',
'gt': '> %s',
'gte': '>= %s',
'lt': '< %s',
'lte': '<= %s',
'startswith': 'LIKE %s',
'endswith': 'LIKE %s',
'istartswith': 'LIKE UPPER(%s)',
'iendswith': 'LIKE UPPER(%s)',
}
在stackoverflow上查到的一條建議:
I'd say your best bet is to simply create an index with upper(column) as well or instead of, and go have a drink
跟業(yè)務(wù)那邊溝通了下,數(shù)據(jù)都是非常標(biāo)準(zhǔn)的不會出現(xiàn)大小寫的問題吉嫩,所以改用exact价认,再次測試600并發(fā),果然負(fù)載小了很多自娩,直到并發(fā)達(dá)到1000開始出錯刻伊,也算有點進(jìn)步了~
進(jìn)一步優(yōu)化,由于這個服務(wù)每次請求都會往數(shù)據(jù)庫寫一條數(shù)據(jù)椒功,想了下能不能改成異步的,查找官網(wǎng)資料智什,直到如下配置:
#postgresql.conf
synchronous_commit = off
這個配置安全性還是很高的动漾,幾乎不會造成數(shù)據(jù)丟失參考鏈接
到目前為止并發(fā)已經(jīng)達(dá)到1000沒有報任何錯誤了,因為這是線上實時跑的業(yè)務(wù)荠锭,還沒想到更換框架