如何通過緩存來提升系統性能

緩存

在系統中最消耗性能的地方就是對數據庫的訪問了,一般來說,增、刪、改操作不會出現什麼性能問題,除非索引太多,並且數據量有十分龐大的情況下,這三個操作纔會導致性能問題。一般可以限制單表索引的數量來提升性能,比如單表的索引數量不能超過 5 個。

絕大多數情況下,性能問題都出在查詢上,select 操作提供了非常豐富的語法,這些語法包括函數,子查詢,like 子句,where 子句等,這些查詢都是非常消耗性能的。大部分應用都是讀多寫少的應用,所以查詢慢的問題會被放大了,導致慢查詢成爲了系統性能的瓶頸,這是就需要用到緩存來提高系統的性能。

緩存爲什麼能提供系統性能?

緩存通過減少系統對數據庫的訪問量來提高系統性能。試想一下,如果有 100 個請求同時請求同一個數據,沒有加緩存之前,需要訪問 100 次數據庫,而加了緩存之後,可能只需訪問 1 次數據庫,剩下的 99 個請求的數據從緩存中取,大大的較少了數據庫的訪問量,雖然同樣需要訪問 100 次,但數據庫的讀取性能和緩存的讀取性能不在一個級別上,所以對系統性能提升顯著。爲什麼說可能需要訪問 1 次數據庫呢,這個和過期有關,後面會講。

更新模式

既然知道了緩存對系統性能提升顯著,那下面先來了解一下緩存如何更新吧。

Cache Aside(推薦)

這應該是最常用的更新模式了,這種模式大致流程如下:

讀取

更新

爲什麼是讓緩存失效而不是更新緩存呢?

主要是因爲兩個併發寫操作導致髒數據。試想一下,有兩個線程 A 和 B,分別要將資源 A 的值修改爲 1 和 2,線程 A 先到達數據庫把數據更新爲 1,但還沒更新緩存,由於時間片用完了,此時線程 B 獲得了 CPU,並在這期間把數據庫資源 A 的值和緩存的值都更新爲 2,線程 B 結束後,線程 A 重新獲得 CPU,執行更新緩存,把資源 A 的值改爲 1,線程 A 結束。此時,數據庫中 A 的值爲 2,而緩存中 A 的值爲 1,數據不一致。所以讓緩存失效就不會有這個問題,保證緩存中的數據和數據庫的保持一致。

那是不是 Cache Aside 模式就不會有併發問題了呢?

不是的。比如,一個讀操作,沒有命中緩存,就去數據的讀取數據(A=1),此時一個寫操作,更新數據庫數據(A=2)並讓緩存失效,此時讀操作把讀取到的數據(A=1)寫到緩存中,導致髒數據。

這種情況理論上會出現,但現實情況中出現的幾率極低。要這種情況出現必須在一個讀操作發生時,有一個併發寫操作,並且既要讀操作要於寫操作寫入前讀取,又要後於寫操作寫入緩存。滿足這種條件的概率並不大。

基於出現上面所描述的問題,目前有兩種比較合理的解決方案:

Read/Write Through

在這種模式下,對於應用程序來說,所有的讀寫請求都是直接和緩存打交道,關於數據庫的數據完全由緩存服務來更新(更新同步爲同步操作)。

這種模式下流程就相當簡單了,完全就是對緩存的讀寫。

缺點:這種模式對緩存服務有強依賴性,要求緩存具備高可用性。所以應有沒有上一種普遍。

Write Behind Caching

其實這個模式就是 Read/Write Through 的一個變種,區別就在於前者是異步更新,後者是同步更新。

既然是異步更新數據庫,他的相應速度比 Read/Write Through 還要高,並且還能合併對同一個數據的多次操作。這個有點像 MySQL 的 buffer pool 的刷盤操作。

缺點:異步就代表數據不是強一致性的,還存在數據丟失的風險,實現邏輯也較爲複雜。

設計思路

無狀態的服務

在分佈式系統中,無狀態的服務有利於橫向擴展,所以緩存也應該獨立於業務服務在外,設計成一個獨立的服務,使業務服務變成無狀態的。很多公司都選擇是用 Redis 來搭建他們的緩存系統,取決於其高速的讀寫性能。

命中率

一個緩存服務的好壞主要看命中率,一般來說,命中率保存在 80% 以上已經算很高了,但我們不能爲了提高命中率而把數據庫的全部數據的寫到緩存了,這個是不符合緩存的設計理念,而且需要極大的內存空間。通常來說,應該只有小部分熱點數據寫到緩存。 緩存是通過犧牲強一致性來換取性能的,並不是所有的業務的適合使用緩存。

有效時間

緩存數據的有效時間不易過短,不易過長,不易過於集中。

淘汰策略

當內存不足時,緩存系統就要按照淘汰策略,把不適合留在緩存的數據淘汰掉,騰出位置給新數據。下面就以 Redis 爲例,給出了淘汰策略:

可根據項目實際情況進行選擇。

小結

緩存是爲了加速數據的訪問,在數據庫之上的一直機制,並非所有業務都適合使用緩存,要根據具體情況選擇更新策略和淘汰策略。

本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源https://mp.weixin.qq.com/s/HkJPweCzaQGMHfqkqJgotg