農林漁牧網

您現在的位置是:首頁 > 農業

Redis 很屌,不懂使用規範就糟蹋了

2022-04-12由 程式那點事 發表于 農業

SDS是什麼的縮寫

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」

比如我們要統計公眾號屬於技術型別的博主 xx 的粉絲數。

set xx 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。

碼哥,如果業務資料就是這麼大咋辦?比如儲存的是《金瓶梅》這個大作。

我們還可以透過

gzip

資料壓縮來減小資料大小:

/** * 使用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。