分庫分表實戰(zhàn):激流勇進-千萬級數(shù)據(jù)優(yōu)化之加緩存
前 言
經(jīng)過前面索引和sql的優(yōu)化后,現(xiàn)在查詢速度快的飛起,然后,我們繼續(xù)回歸到了日常需求的開發(fā)中。
3個月過后,訂單表的數(shù)據(jù)已經(jīng)達(dá)到5000萬了,不過sql一次查詢的時間,基本穩(wěn)定在300ms以下。
但是某個周一,leader剛開完周會就直接來找你了,直接說:“哎呀,周會上DBA找我了,說咱們訂單組的sql偶爾會超過2s,DBA現(xiàn)在要求優(yōu)化,平均時間要優(yōu)化到300ms以下,不過,優(yōu)化前你要先查下,為什么sql的查詢時間會偶爾突增?!?/p>
問題排查
然后我們就接下了這個任務(wù),接著,我們就根據(jù)DBA給的慢sql,去查這條sql的相關(guān)日志,然后結(jié)合著監(jiān)控,最后發(fā)現(xiàn)這條sql平常一直很穩(wěn)定,但是在高峰期的時候,這條sql偶爾花費的時間會超過2s。
此時,我們又查看了一下訂單數(shù)據(jù)庫所在物理機的資源占用情況,發(fā)現(xiàn)高峰期時,這臺物理機的資源占用非常高,CPU和內(nèi)存占用率都很高,這下基本就確定原因了。
說白了,就是一到高峰期,大量請求跑到MySQL這查詢數(shù)據(jù),此時就會有大量請求密集請求數(shù)據(jù)庫,然后就會導(dǎo)致數(shù)據(jù)庫所在機器的CPU和內(nèi)存占用率都飆升,最終就會導(dǎo)致MySQL查詢效率極速降低。
leader了解情況后說:“其實數(shù)據(jù)庫查詢慢,不一定就是MySQL數(shù)據(jù)量大導(dǎo)致的,比如當(dāng)前這個情況,明顯是大量請求密集請求數(shù)據(jù)庫,造成數(shù)據(jù)庫負(fù)載變大,從而大大降低了數(shù)據(jù)庫的查詢效率,這個時候,其實我們就需要在MySQL的前邊,加上一層緩存,來進行流量削峰,以保證MySQL能穩(wěn)定的完成查詢”
經(jīng)過leader一點撥,我們恍然大悟,原來是這樣,說白了,這個時候我們可以加一些緩存,來為MySQL進行流量削峰,添加了緩存后的運行流程,大概是這樣的:
就是說,按照標(biāo)準(zhǔn)的請求流程,用戶的請求是會打到數(shù)據(jù)庫上的,但是加了緩存之后就不是這種流程了。這個時候請求可以直接從緩存中獲取到數(shù)據(jù)并返回,此時就會減少后續(xù)流程的處理,比如查詢數(shù)據(jù)庫的操作,這樣就有效降低了數(shù)據(jù)庫的負(fù)載。
說白了,就是使用緩存來承接大多數(shù)的查詢請求,達(dá)到流量削峰的效果,從而降低數(shù)據(jù)庫的負(fù)載,以保證MySQL能穩(wěn)定高效的完成查詢,這樣MySQL在高峰期查詢時間突增的問題就可以完美解決了。
雖然緩存非常好用,但是使用緩存的過程中,我們要關(guān)注緩沖的命中率,命中率=返回正確結(jié)果數(shù)/請求緩存次數(shù),命中率是衡量緩存有效性的重要指標(biāo),命中率越高,說明緩存的使用率越高。
除了要關(guān)注緩存命中率,我們還要了解緩存的清空策略,比如 先進先出策略FIFO(first in first out)、最少使用策略LFU(less frequently used) 和最近最少使用策略LRU(least recently used)。
如何提高緩存命中率
剛才我們也說了,命中率是衡量緩存有效性的重要指標(biāo),那么怎么才能提高緩存命中率呢?
其實要想提高緩存命中率,需要考慮的點有很多,大概有以下幾點:
1.選擇合適的業(yè)務(wù)場景
首先,緩存適合讀多寫少的場景,最好還是高頻訪問的場景,因為訪問頻率越高,命中率也就越高。
2.合理設(shè)置緩存容量
緩存容量如果太小的話,會觸發(fā)Redis的內(nèi)存淘汰機制,這樣就會導(dǎo)致一些緩存key被刪除,就會降低緩存命中率,所以,合理設(shè)置緩存容量是非常有必要的。
3.控制好緩存粒度
緩存的粒度越小,緩存命中率越高,因為單個key的數(shù)據(jù)單位越小的話,這個緩存就越不容易發(fā)生更改。
4.靈活設(shè)置緩存key的過期時間
這里說的是,要盡量避免緩存同時過期,如果緩存同時過期的話,假如此時有多個查詢請求,那么這些請求就都會打到數(shù)據(jù)庫上去。這種情況叫做緩存擊穿,這會導(dǎo)致數(shù)據(jù)庫的壓力很大。
5.避免緩存穿透
先來了解下緩存命中率,比如當(dāng)請求過來查詢一條數(shù)據(jù)時,如果在緩存中沒有查到這條數(shù)據(jù),此時,我們可以說沒有命中緩存,如果大量查詢請求在緩存中都很少能查到數(shù)據(jù),我們就可以說緩存命中率很低。
當(dāng)緩存命中率很低時,因為在緩存中查不到數(shù)據(jù),這個時候請求就會打到數(shù)據(jù)中,去數(shù)據(jù)庫中查詢數(shù)據(jù),如果數(shù)據(jù)庫中依然沒有查到數(shù)據(jù),說明這個請求已經(jīng)穿透緩存了。
一旦緩存穿透了,當(dāng)海量的請求涌來時,如果一直命中不了緩存,海量的請求就會轉(zhuǎn)而涌向數(shù)據(jù)庫,而數(shù)據(jù)庫處理請求的能力是有限的,此時數(shù)據(jù)庫可能因為請求量暴增壓力過大而宕機,數(shù)據(jù)庫一旦宕機,就很有可能演化成緩存雪崩,導(dǎo)致整個系統(tǒng)大面積的陷入癱瘓,這是非??植赖?。
所以,我們需要提前做好兜底方案,以此來避免緩存穿透的發(fā)生,比如當(dāng)一個查詢請求過來時,如果緩存中沒有查詢到數(shù)據(jù),數(shù)據(jù)庫中也還是沒有查詢到數(shù)據(jù),此時,我們可以在緩存中,給這個查詢請求設(shè)置一個空對象,然后請求拿著這個空對象返回。
同樣的查詢請求下一次再過來時,直接就可以在緩存中命中這個空對象了,請求就不需要涌向數(shù)據(jù)庫了,這樣就算海量請求涌來時,也可以做到緩存命中率很高,緩存穿透的問題也就解決了。
6.做好緩存預(yù)熱
一般來說,第一次查詢的請求都會打到數(shù)據(jù)庫上去,所以,我們可以提前將數(shù)據(jù)庫的數(shù)據(jù)加載到緩存中,也就是緩存預(yù)熱,這樣的話第一次查詢請求也可以直接走緩存了。
以上幾點都做好的話,那么緩存命中率自然就提高了,好了,接下來廢話也不多說了,我們一起來搞一把緩存實戰(zhàn),來切身感受下加了緩存后的查詢效果。
緩存實戰(zhàn)
場景介紹:歷史訂單查詢
由于已完成的訂單狀態(tài)不會再發(fā)生變化,因此再進行歷史訂單查詢時會將查詢結(jié)果緩存進redis,并設(shè)置失效時間為一小時,因此在緩存失效前,用戶再次查詢歷史訂單時則會直接請求redis,減小數(shù)據(jù)庫壓力
未添加緩存的查詢時間
Redis優(yōu)化思路
查詢歷史訂單時會先查詢redis中是否有緩存,如有則直接返回redis中數(shù)據(jù),如無則會查詢MySQL,然后將查詢數(shù)據(jù)返回,同時將查詢結(jié)果設(shè)置到緩存中,以便下一次查詢可以走緩存。
緩存Key的生成規(guī)則
用戶id+頁碼+頁數(shù)生成redis Key
緩存核心代碼
緩存優(yōu)化后的效果
加緩存后可以看到第二次請求時走了redis緩存查詢,效率有了極大的提升。
然后,你加了緩存之后,發(fā)現(xiàn)效果確實不錯,大量請求打到了緩存上,數(shù)據(jù)庫的資源占用率也維持在一個合理的范圍,sql查詢時間也都穩(wěn)定在了300ms以下。