Redis 很屌,不懂使用規範就糟蹋了
這可能是最中肯的 Redis 使用規範了
碼哥,昨天我被公司 Leader 批評了。
我在單身紅娘婚戀型別網際網路公司工作,在雙十一推出下單就送女朋友的活動。
誰曾想,凌晨 12 點之後,使用者量暴增,出現了一個技術故障,使用者無法下單,當時老大火冒三丈!
經過查詢發現 Redis 報 Could not get a resource from the pool
。
獲取不到連線資源,並且叢集中的單臺 Redis 連線量很高。
於是各種更改最大連線數、連線等待數,雖然報錯資訊頻率有所緩解,但還是持續報錯。
後來經過線下測試,發現存放 Redis 中的字元資料很大,平均 1s 返回資料。
碼哥,可以分享下使用 Redis 的規範麼?我想做一個唯快不破的真男人!
通過 Redis 為什麼這麼快?這篇文章我們知道 Redis 為了高效能和節省記憶體費勁心思。
所以,只有規範的使用 Redis,才能實現高效能和節省記憶體,否則再屌的 Redis 也禁不起我們瞎折騰。
Redis 使用規範圍繞如下幾個緯度展開:
- 鍵值對使用規範;
- 命令使用規範;
- 資料儲存規範;
- 運維規範。
鍵值對使用規範
有兩點需要注意:
- 好的
key
命名,才能提供可讀性強、可維護性高的 key,便於定位問題和尋找資料。 value
要避免出現bigkey
、選擇高效的序列化和壓縮、使用物件共享池、選擇高效恰當的資料型別(可參考《Redis 實戰篇:巧用資料型別實現億級資料統計》)。
key 命名規範
規範的 key
命名,在遇到問題的時候能夠方便定位。Redis 屬於 沒有 Scheme
的 NoSQL
資料庫。
所以要靠規範來建立其 Scheme
語意,就好比根據不同的場景我們建立不同的資料庫。
敲黑板
把「業務模組名」作為字首(好比資料庫 Scheme
),通過「冒號」分隔,再加上「具體業務名」。
這樣我們就可以通過 key
字首來區分不同的業務資料,清晰明瞭。
總結起來就是:「業務名:表名:id」
比如我們要統計公眾號屬於技術型別的博主「碼哥位元組」的粉絲數。
set 公眾號:技術類:碼哥位元組 100000
碼哥,key 太長的話有什麼問題麼?
key 是字串,底層的資料結構是 SDS
,SDS 結構中會包含字串長度、分配空間大小等元資料資訊。
字串長度增加,SDS 的元資料也會佔用更多的記憶體空間。
所以當字串太長的時候,我們可以採用適當縮寫的形式。
不要使用 bigkey
碼哥,我就中招了,導致報錯獲取不到連線。
因為 Redis 是單執行緒執行讀寫指令,如果出現bigkey
的讀寫操作就會阻塞執行緒,降低 Redis 的處理效率。
bigkey
包含兩種情況:
- 鍵值對的
value
很大,比如value
儲存了2MB
的String
資料; - 鍵值對的
value
是集合型別,元素很多,比如儲存了 5 萬個元素的List
集合。
雖然 Redis 官方說明了 key
和string
型別 value
限制均為512MB
。
防止網絡卡流量、慢查詢,string
型別控制在10KB
以內,hash、list、set、zset
元素個數不要超過 5000。
碼哥,如果業務資料就是這麼大咋辦?比如儲存的是《金ping梅》這個大作。
我們還可以通過 gzip
資料壓縮來減小資料大小:
```java /* * 使用gzip壓縮字串 / public static String compress(String str) { if (str == null || str.length() == 0) { return str; }
try (ByteArrayOutputStream out = new ByteArrayOutputStream();
GZIPOutputStream gzip = new GZIPOutputStream(out)) {
gzip.write(str.getBytes());
} catch (IOException e) {
e.printStackTrace();
}
return new sun.misc.BASE64Encoder().encode(out.toByteArray());
}
/* * 使用gzip解壓縮 / public static String uncompress(String compressedStr) { if (compressedStr == null || compressedStr.length() == 0) { return compressedStr; } byte[] compressed = new sun.misc.BASE64Decoder().decodeBuffer(compressedStr);; String decompressed = null; try (ByteArrayOutputStream out = new ByteArrayOutputStream(); ByteArrayInputStream in = new ByteArrayInputStream(compressed); GZIPInputStream ginzip = new GZIPInputStream(in);) { byte[] buffer = new byte[1024]; int offset = -1; while ((offset = ginzip.read(buffer)) != -1) { out.write(buffer, 0, offset); } decompressed = out.toString(); } catch (IOException e) { e.printStackTrace(); } return decompressed; }
```
集合型別
如果集合型別的元素的確很多,我們可以將一個大集合拆分成多個小集合來儲存。
使用高效序列化和壓縮方法
為了節省記憶體,我們可以使用高效的序列化方法和壓縮方法去減少 value
的大小。
protostuff
和 kryo
這兩種序列化方法,就要比 Java
內建的序列化方法效率更高。
上述的兩種序列化方式雖然省記憶體,但是序列化後都是二進位制資料,可讀性太差。
通常我們會序列化成 JSON
或者 XML
,為了避免資料佔用空間大,我們可以使用壓縮工具(snappy、 gzip)將資料壓縮再存到 Redis 中。
使用整數物件共享池
Redis 內部維護了 0 到 9999 這 1 萬個整數物件,並把這些整數作為一個共享池使用。
即使大量鍵值對儲存了 0 到 9999 範圍內的整數,在 Redis 例項中,其實只儲存了一份整數物件,可以節省記憶體空間。
需要注意的是,有兩種情況是不生效的:
- Redis 中設定了
maxmemory
,而且啟用了LRU
策略(allkeys-lru 或 volatile-lru 策略
),那麼,整數物件共享池就無法使用了。
這是因為 LRU 需要統計每個鍵值對的使用時間,如果不同的鍵值對都複用一個整數物件就無法統計了。
- 如果集合型別資料採用 ziplist 編碼,而集合元素是整數,這個時候,也不能使用共享池。
因為 ziplist 使用了緊湊型記憶體結構,判斷整數物件的共享情況效率低。
命令使用規範
有的命令的執行會造成很大的效能問題,我們需要格外注意。
生產禁用的指令
Redis 是單執行緒處理請求操作,如果我們執行一些涉及大量操作、耗時長的命令,就會嚴重阻塞主執行緒,導致其它請求無法得到正常處理。
- KEYS:該命令需要對 Redis 的全域性雜湊表進行全表掃描,嚴重阻塞 Redis 主執行緒;
應該使用 SCAN 來代替,分批返回符合條件的鍵值對,避免主執行緒阻塞。
-
FLUSHALL:刪除 Redis 例項上的所有資料,如果資料量很大,會嚴重阻塞 Redis 主執行緒;
-
FLUSHDB,刪除當前資料庫中的資料,如果資料量很大,同樣會阻塞 Redis 主執行緒。
加上 ASYNC 選項,讓 FLUSHALL,FLUSHDB 非同步執行。
我們也可以直接禁用,用rename-command
命令在配置檔案中對這些命令進行重新命名,讓客戶端無法使用這些命令。
慎用 MONITOR 命令
MONITOR 命令會把監控到的內容持續寫入輸出緩衝區。
如果線上命令的操作很多,輸出緩衝區很快就會溢位了,這就會對 Redis 效能造成影響,甚至引起服務崩潰。
所以,除非十分需要監測某些命令的執行(例如,Redis 效能突然變慢,我們想檢視下客戶端執行了哪些命令)我們才使用。
慎用全量操作命令
比如獲取集合中的所有元素(HASH 型別的 hgetall、List 型別的 lrange、Set 型別的 smembers、zrange 等命令)。
這些操作會對整個底層資料結構進行全量掃描 ,導致阻塞 Redis 主執行緒。
碼哥,如果業務場景就是需要獲取全量資料咋辦?
有兩個方式可以解決:
- 使用
SSCAN、HSCAN
等命令分批返回集合資料; - 把大集合拆成小集合,比如按照時間、區域等劃分。
資料儲存規範
冷熱資料分離
雖然 Redis 支援使用 RDB 快照和 AOF 日誌持久化儲存資料,但是,這兩個機制都是用來提供資料可靠性保證的,並不是用來擴充資料容量的。
不要什麼資料都存在 Redis,應該作為快取儲存熱資料,這樣既可以充分利用 Redis 的高效能特性,還可以把寶貴的記憶體資源用在服務熱資料上。
業務資料隔離
不要將不相關的資料業務都放到一個 Redis 中。一方面避免業務相互影響,另一方面避免單例項膨脹,並能在故障時降低影響面,快速恢復。
設定過期時間
在資料儲存時,我建議你根據業務使用資料的時長,設定資料的過期時間。
寫入 Redis 的資料會一直佔用記憶體,如果資料持續增多,就可能達到機器的記憶體上限,造成記憶體溢位,導致服務崩潰。
控制單例項的記憶體容量
建議設定在 2~6 GB 。這樣一來,無論是 RDB 快照,還是主從叢集進行資料同步,都能很快完成,不會阻塞正常請求的處理。
防止快取雪崩
避免集中過期 key 導致快取雪崩。
碼哥,什麼是快取雪崩?
當某一個時刻出現大規模的快取失效的情況,那麼就會導致大量的請求直接打在資料庫上面,導致資料庫壓力巨大,如果在高併發的情況下,可能瞬間就會導致資料庫宕機。
運維規範
- 使用 Cluster 叢集或者哨兵叢集,做到高可用;
- 例項設定最大連線數,防止過多客戶端連線導致例項負載過高,影響效能。
- 不開啟 AOF 或開啟 AOF 配置為每秒刷盤,避免磁碟 IO 拖慢 Redis 效能。
- 設定合理的 repl-backlog,降低主從全量同步的概率
- 設定合理的 slave client-output-buffer-limit,避免主從複製中斷情況發生。
- 根據實際場景設定合適的記憶體淘汰策略。
- 使用連線池操作 Redis。
最後,歡迎在留言區分享一下你常用的使用規範,我們一起交流討論。
好文推薦
Redis 持久化篇:AOF 與 RDB 如何保證資料高可用
- Redis的資料被刪除,佔用記憶體咋還那麼大?
- Redis 很屌,不懂使用規範就糟蹋了
- Redis進階篇:釋出訂閱模式原理與運用
- Redis 6.0 絕絕子新特性:客戶端快取讓效能更上一層樓
- Redis 記憶體優化神技,小記憶體儲存大資料
- Redis 記憶體優化神技:小記憶體儲存大資料
- SpringBoot 整合快取效能之王 Caffeine
- 掘地三尺搞定 Redis 與 MySQL 資料一致性問題
- 掘地三尺搞定 Redis 與 MySQL 資料一致性問題
- Redis 的資料過期了就會馬上刪除麼?
- Redis 為何使用近似 LRU 演算法淘汰資料,而不是真實 LRU?
- Redis 為何使用近似 LRU 演算法淘汰資料,而不是真實 LRU?
- Redis 記憶體滿了怎麼辦?這樣設定才正確!
- Redis HyperLogLog 是什麼?這些場景使用它,讓我槍出如龍,一笑破蒼穹
- 硬核 | Redis 布隆(Bloom Filter)過濾器原理與實戰
- Redis 快取擊穿(失效)、快取穿透、快取雪崩怎麼解決?
- 別再用 Redis List 實現訊息隊列了,Stream 專為佇列而生
- 別再用 Redis List 實現訊息隊列了,Stream 專為佇列而生
- Redis 忽然變慢了如何排查並解決?
- 為什麼你辛苦肝的部落格沒人看?搭框架、排版、畫圖技巧這些你真的懂麼?