1. 緩存設計
在使用Redis場景中,最常見的問題就是緩存雪崩、緩存穿透和緩存擊穿,后果都是由于各種情況導致大量請求直接訪問DB,使得DB壓力巨增,從而DB服務癱瘓,導致整體服務有問題。(參見新浪微博明星、大V的推送)
1.1 緩存雪崩
緩存雪崩指的是大批量緩存在同一時間失效或者是緩存層支撐不住宕機,導致流量直接涌入數(shù)據(jù)庫中,會造成數(shù)據(jù)庫壓力過大甚至掛掉。
1.1.1 解決方案
- 把每個key的失效時間都加個隨機值,保證數(shù)據(jù)不在同一時間大面積失效。
- 保證緩存層服務高可用
- 依靠隔離組件為后端限流熔斷并降級
- 提前演練緩存層宕機后,應用以及后端的負載情況以及可能出現(xiàn)的問題,在此基礎上做一些預案設定
1.2 緩存穿透
緩存穿透是指查詢一個根本不存在的數(shù)據(jù), 緩存層和存儲層都不會命中, 通常出于容錯的考慮,如果從存儲層查不到數(shù)據(jù)則不寫入緩存層。緩存穿透將導致不存在的數(shù)據(jù)每次請求都要到存儲層去查詢, 失去了緩存保護后端存儲的意義。
1.2.1 解決方案
- 緩存空對象
- 接口增加校驗,避免不合法的參數(shù)傳遞進來
- 采用布隆過濾器
1.3 緩存擊穿
緩存擊穿是指一個Key非常熱點,在不停的扛著大并發(fā),大并發(fā)集中對這一個點進行訪問,當這個Key在失效的瞬間,持續(xù)的大并發(fā)就穿破緩存,直接請求數(shù)據(jù)庫,就像在一個完好無損的桶上鑿開了一個洞。
1.3.1 解決方案
- 熱點數(shù)據(jù)永不過期
- 重建熱點key時,加互斥鎖
2. 性能優(yōu)化
2.1 緩存與數(shù)據(jù)庫雙寫不一致
解決方案如下:
- 對于并發(fā)幾率很小的數(shù)據(jù)(如個人維度的訂單數(shù)據(jù)、用戶數(shù)據(jù)等),這種幾乎不用考慮這個問題,很少會發(fā)生緩存不一致,可以給緩存數(shù)據(jù)加上過期時間,每隔一段時間觸發(fā)讀的主動更新即可。
- 就算并發(fā)很高,如果業(yè)務上能容忍短時間的緩存數(shù)據(jù)不一致(如商品名稱,商品分類菜單等),緩存加上過期 時間依然可以解決大部分業(yè)務對于緩存的要求。
- 如果不能容忍緩存數(shù)據(jù)不一致,可以通過加讀寫鎖保證并發(fā)讀寫或寫寫的時候按順序排好隊,讀讀的時候相 當于無鎖。
- 也可以用阿里開源的canal通過監(jiān)聽數(shù)據(jù)庫的binlog日志及時的去修改緩存,但是引入了新的中間件,增加 了系統(tǒng)的復雜度。
2.2 鍵值設計
2.2.1 key名設計
- 【建議】: 可讀性和可管理性,以業(yè)務名(或數(shù)據(jù)庫名)為前綴(防止key沖突),用冒號分隔,比如業(yè)務名:表名:id
trade:order:1
- 【建議】:簡潔性,保證語義的前提下,控制key的長度,當key較多時,內存占用也不容忽視,例如:
user:{uid}:friends:messages:{mid} 簡化為 u:{uid}:fr:m:{mid}
- 【強制】:不要包含特殊字符
2.2.2 value設計
- 【推薦】:選擇適合的數(shù)據(jù)類型,例如:實體類型(要合理控制和使用數(shù)據(jù)結構,但也要注意節(jié)省內存和性能之間的平衡)
- 控制key的生命周期,redis不是垃圾桶。建議使用expire設置過期時間(條件允許可以打散過期時間,防止集中過期)。
- 【強制】:拒絕bigkey(防止網(wǎng)卡流量、慢查詢)
2.2.2.1 bigkey
在Redis中,一個字符串最大512MB,一個二級數(shù)據(jù)結構(例如hash、list、set、zset)可以存 儲大約40億個(2^32-1)個元素,但實際中如果下面兩種情況,我就會認為它是bigkey。
- 字符串類型:它的big體現(xiàn)在單個value值很大,一般認為超過10KB就是bigkey。
- 非字符串類型:哈希、列表、集合、有序集合,它們的big體現(xiàn)在元素個數(shù)太多。 一般來說,string類型控制在10KB以內,hash、list、set、zset元素個數(shù)不要超過5000。
bigkey的危害:
- 導致Redis阻塞
- 網(wǎng)絡阻塞
- 過期刪除有可能阻塞Redis
bigkey的產生:
一般來說,bigkey的產生都是由于程序設計不當,或者對于數(shù)據(jù)規(guī)模預料不清楚造成的,來看幾個例子:
(1) 社交類:粉絲列表,如果某些明星或者大v不精心設計下,必是bigkey。
(2) 統(tǒng)計類:例如按天存儲某項功能或者網(wǎng)站的用戶集合,除非沒幾個人用,否則必是bigkey。
(3) 緩存類:將數(shù)據(jù)從數(shù)據(jù)庫load出來序列化放到Redis里,這個方式非常常用,但有兩個地方需 要注意,第一,是不是有必要把所有字段都緩存;第二,有沒有相關關聯(lián)的數(shù)據(jù),有的同學為了 圖方便把相關數(shù)據(jù)都存一個key下,產生bigkey。
如何優(yōu)化bigkey:
- 拆,big list: list1、list2、...listN
big hash:可以講數(shù)據(jù)分段存儲,比如一個大的key,假設存了1百萬的用戶數(shù)據(jù),可以拆分成 200個key,每個key下面存放5000個用戶數(shù)據(jù) - 如果bigkey不可避免,也要思考一下要不要每次把所有元素都取出來(例如有時候僅僅需要 hmget,而不是hgetall),刪除也是一樣,盡量使用優(yōu)雅的方式來處理。
2.2.3 命令使用
- 【推薦】 O(N)命令關注N的數(shù)量,例如hgetall、lrange、smembers、zrange、sinter等并非不能使用,但是需要明確N的值。有 遍歷的需求可以使用hscan、sscan、zscan代替。
- 【推薦】:禁用命令,禁止線上使用keys、flushall、flushdb等,通過redis的rename機制禁掉命令,或者使用scan的 方式漸進式處理。
- 【推薦】合理使用select,redis的多數(shù)據(jù)庫較弱,使用數(shù)字進行區(qū)分,很多客戶端支持較差,同時多業(yè)務用多數(shù)據(jù)庫實際還 是單線程處理,會有干擾。
- 【推薦】使用批量操作提高效率,但要注意控制一次批量操作的元素個數(shù)(例如500以內,實際也和元素字節(jié)數(shù)有關)。
- 【建議】Redis事務功能較弱,不建議過多使用,可以用lua替代
2.2.4 客戶端使用
- 【推薦】避免多個應用使用一個Redis實例,正例:不相干的業(yè)務拆分,公共數(shù)據(jù)做服務化。
- 【推薦】使用帶有連接池的數(shù)據(jù)庫,可以有效控制連接,同時提高效率。
- 【建議】高并發(fā)下建議客戶端添加熔斷功能(例如sentinel、hystrix)
- 【推薦】設置合理的密碼,如有必要可以使用SSL加密訪問
JedisPool連接池的優(yōu)化建議:
- maxTotal:最大連接數(shù),早期的版本叫maxActive 實際上這個是一個很難回答的問題,考慮的因素比較多:
- 業(yè)務希望Redis并發(fā)量
- 客戶端執(zhí)行命令時間
- Redis資源:例如 nodes(例如應用個數(shù)) * maxTotal 是不能超過redis的最大連接數(shù) maxclients。
- 資源開銷:例如雖然希望控制空閑連接(連接池此刻可馬上使用的連接),但是不希望因 為連接池的頻繁釋放創(chuàng)建連接造成不必靠開銷。
以一個例子說明,假設:
- 一次命令時間(borrow|return resource + Jedis執(zhí)行命令(含網(wǎng)絡) )的平均耗時約為 1ms,一個連接的QPS大約是1000
- 業(yè)務期望的QPS是50000 那么理論上需要的資源池大小是50000 / 1000 = 50個。但事實上這是個理論值,還要考慮到要 比理論值預留一些資源,通常來講maxTotal可以比理論值大一些。
- maxIdle和minIdle
maxIdle實際上才是業(yè)務需要的最大連接數(shù),maxTotal是為了給出余量,所以maxIdle不要設置 過小,否則會有new Jedis(新連接)開銷。
連接池的最佳性能是maxTotal = maxIdle,這樣就避免連接池伸縮帶來的性能干擾。但是如果 并發(fā)量不大或者maxTotal設置過高,會導致不必要的連接資源浪費。一般推薦maxIdle可以設置 為按上面的業(yè)務期望QPS計算出來的理論連接數(shù),maxTotal可以再放大一倍。 minIdle(最小空閑連接數(shù)),與其說是最小空閑連接數(shù),不如說是"至少需要保持的空閑連接 數(shù)",在使用連接的過程中,如果連接數(shù)超過了minIdle,那么繼續(xù)建立連接,如果超過了maxIdle,當超過的連接執(zhí)行完業(yè)務后會慢慢被移出連接池釋放掉。 如果系統(tǒng)啟動完馬上就會有很多的請求過來,那么可以給redis連接池做預熱,比如快速的創(chuàng)建一 些redis連接,執(zhí)行簡單命令,類似ping(),快速的將連接池里的空閑連接提升到minIdle的數(shù) 量。