一、導語
Redis(Remote Dictionary Server ),即遠端字典服務,是一個開源的使用ANSI C語言編寫、支援網路、可基於記憶體亦可持久化的日誌類型、Key-Value資料庫,並提供多種語言的API。
(學習影片分享:redis影片教學)
由於其上手快,執行效率高,擁有多種資料結構,支援持久化以及叢集等功能和特點被眾多互聯網公司所使用。但是,如果使用和操作不當,會造成記憶體浪費,甚至係統宕機等嚴重後果。
二、要點分析
2.1 使用正確的資料型別
在 Redis 5 種資料型別中,string 類型最為常用,也最為簡單。但是,能解決問題不代表使用了正確的資料類型。
例如,將一個使用者(name,age,city)資訊儲存到Redis 中,下邊有三種方案:
方案1:使用string 類型,每個屬性當作一個key
set user:1:name laowang set user:1:age 40 set user:1:city shanghai
優點:簡單直觀,每個屬性支援更新操作
缺點:使用過多的key,佔用的記憶體較大,同時使用者資訊的聚合性較差,管理和維護麻煩
方案2:使用string 類型,將使用者資訊序列化成字串保存
// 序列化用户信息 String userInfo = serialize(user) set user:1 userInfo
優點:簡化儲存步驟
缺點:序列化和反序列化存在一定開銷
方案3:使用hash 類型,每個屬性使用一對field-value,但只用一個key
hmset user:1 name laowang age 40 city shanghai
優點:簡單直觀,合理使用可以減少記憶體空間
總結:盡量減少Redis 中的key。
2.2 警惕Big Key
big key 一般指的是字串型別value 值非常大(大於10KB),或雜湊、列表、集合、有序集合元素個數多(大於5000個)的key。
big key 會對Redis 造成很多負面影響:
記憶體不均:在叢集環境下,big key 被分配到某個節點機器中,由於不知道被分配到哪個節點上且該節點記憶體佔用大,不利於叢集環境下記憶體的統一管理
#逾時阻塞:由於Redis 是單執行緒操作,操作big key 比較耗時,容易造成阻塞
##過期刪除:big key 不單讀寫慢,刪除也慢,刪除過期big key 也比較耗時#遷移困難:由於資料龐大,備份和還原也容易造成阻塞,操作失敗
##由於定時刪除需要建立定時器,會佔用的大量內存,同時精準刪除大量key 也會消耗大量CPU 資源,因此Redis 同時採用的是惰性刪除和定時刪除兩種策略。如果客戶端沒有要求過期的 key 或定期刪除線程沒有掃描到並清除這個 key,該 key 就會一直佔用內存,導致內存浪費。
知道了記憶體消耗的原因後,我們可以很快地想出優化方案:手動刪除。
當使用完快取後,快取即使設定了過期時間,我們也要手動呼叫 del 方法/指令刪除。如果無法當場刪除,我們也可以在程式碼中開啟計時器定期刪除這些過期的 key,相比較 Redis 的兩種刪除策略,手動清除資料要及時很多。
情況 3 的問題不算大,針對其最佳化的手段,我們可以調整 Redis 的淘汰策略。
2.4 多重命令的執行
Redis 是基於一個 request, 一個 response 的同步請求服務。即當多個客戶端向Redis 服務端發送命令時,Redis 服務端只能接收和處理其中的一個客戶端的命令,其他客戶端只能等待Redis 服務端處理好當前的命令並作出響應後才會繼續接收和處理其他命令請求。
Redis 處理指令分 3 個程序:接收指令,處理指令,回傳結果。由於處理的資料都是在記憶體中的,因此處理時長通常都是奈秒級別,非常快(big key 除外)。因此,大部分耗時的情況都發生在接受命令和返回結果上。當客戶端發送多個命令給 Redis 伺服器時,如果有一條命令處理時長很久,其他命令只能等待著,從而影響整體效能。
為了解決這類問題,Redis 提供了pipeline(管道),客戶端可以將多個命令放入pipeline 中,然後一次將pipeline 的命令發給Redis 服務端處理,當Redis 服務端處理完畢後再一次將結果傳回給客戶端。這樣處理減少了客戶端與 Redis 服務端的互動次數,從而減少了往返時間,提升了效能。
補充:
Redis pipeline 與原生批次指令比較:
原生批次指令是原子性,pipeline 是非原子性
原生批次指令一次只能執行一種命令,pipeline 支援執行多種命令
原生批次命令是服務端實現,pipeline 需要服務端和客戶端實現
使用Redis pipeline 的注意事項:
使用pipeline 裝載的命令數量不能太多
pipeline 中的命令會按照緩衝的順序執行,但是可能會穿插其他客戶端發來的命令,即不保證時序性
pipeline 執行中間某一指令出現異常,會繼續執行後續的指令,即不保證原子性
2.5 快取穿透
在專案中運用緩存,我們通常的設計思路如下圖:
發送請求查詢數據,查詢規則是先查緩存,如果緩存沒有數據再查詢資料庫,將查到的數據放入緩存最後返回資料給客戶端。如果請求的資料是不存在的,最終每次請求都會請求到資料庫中,這就是快取穿透。
快取穿透存到很大的安全隱患,如果有人使用工具發送大量請求,請求一個不存在的數據,大量請求會流入到資料庫上,導致資料庫壓力增大,可能會導致資料庫宕機,進而影響整個應用的正常運行,導致系統癱瘓。
解決這類問題,重點在於減少對資料庫的訪問,通常有以下幾種方案:
快取預熱:系統發佈上線後,提前把相關的資料直接載入到緩存系統中
設定預設值:如果請求最終落在資料庫中,資料庫也查不出數據,給快取key 設定一個預設值,放入快取中,注意:由於這個預設值是無意義的,因此我們需要設定過期時間,減少記憶體佔用
布隆過濾器:將所有可能存在的資料哈希到一個足夠大的bitmap 中,一個不存在的資料肯定會被bitmap 攔截掉
2.6 快取雪崩
快取雪崩: 簡單來說是指大量請求存取快取資料但無法查詢到,進而去請求資料庫,導致資料庫壓力增大,效能下降,不堪重負宕機,從而影響整個系統正常運行,甚至系統癱瘓的現象。
例如,一個完整的系統由系統A,系統B,系統C 三個子系統組成,它們的資料請求鍊是系統A -> 系統B -> 系統C -> 資料庫。如果快取中沒有數據,資料庫當機,系統C不能查詢資料回應,只能處在重試等待的階段,從而影響了系統B 和系統A。一處節點發生異常導致一連串的問題就像雪山的一陣風吹過引起雪崩的現象。
看到這裡,可能有讀者會疑惑,快取穿透和快取雪崩有什麼差別呢?
快取穿透側重於請求的資料不在快取中,從而去請求資料庫,就好像直接透過快取直接請求資料庫。
快取雪崩著重於大請求由於在快取中查詢不出數據,從而存取資料庫導致資料庫壓力增大引起一系列異常。
要解決快取雪崩問題,還是得先知道導致問題的原因:
Redis 自身出現問題
熱點資料集中失效
針對原因1,我們可以做主從,集群,盡量讓請求都在緩存中查到數據,減少對數據庫的訪問
針對原因2,給緩存設置過期時間時,錯開過期時間(如在基礎時間上在增減一個隨機值),避免快取集中失效。同時,我們也可以設定本地快取(如 ehcache),對介面進行限流或服務降級,也可以減少資料庫的存取壓力。
三、參考資料
相關推薦:redis資料庫教學
以上是redis要點分析的詳細內容。更多資訊請關注PHP中文網其他相關文章!