Redis 很屌,不懂使用規(guī)范就糟蹋了
昨天我被公司 Leader 批評了。
我在單身紅娘婚戀類型互聯(lián)網(wǎng)公司工作,在雙十一推出下單就送女朋友的活動。
誰曾想,凌晨 12 點(diǎn)之后,用戶量暴增,出現(xiàn)了一個技術(shù)故障,用戶無法下單,當(dāng)時老大火冒三丈!
經(jīng)過查找發(fā)現(xiàn) Redis 報 Could not get a resource from the pool。
獲取不到連接資源,并且集群中的單臺 Redis 連接量很高。
于是各種更改最大連接數(shù)、連接等待數(shù),雖然報錯信息頻率有所緩解,但還是持續(xù)報錯。
后來經(jīng)過線下測試,發(fā)現(xiàn)存放 Redis 中的字符數(shù)據(jù)很大,平均 1s 返回數(shù)據(jù)。
?可以分享下使用 Redis 的規(guī)范么?我想做一個唯快不破的真男人!
通過 Redis 為什么這么快?這篇文章我們知道 Redis 為了高性能和節(jié)省內(nèi)存費(fèi)勁心思。
所以,只有規(guī)范的使用 Redis,才能實現(xiàn)高性能和節(jié)省內(nèi)存,否則再屌的 Redis 也禁不起我們瞎折騰。
Redis 使用規(guī)范圍繞如下幾個緯度展開:
鍵值對使用規(guī)范;
命令使用規(guī)范;
數(shù)據(jù)保存規(guī)范;
運(yùn)維規(guī)范。
?
鍵值對使用規(guī)范
好的 key命名,才能提供可讀性強(qiáng)、可維護(hù)性高的 key,便于定位問題和尋找數(shù)據(jù)。value要避免出現(xiàn)bigkey、選擇高效的序列化和壓縮、使用對象共享池、選擇高效恰當(dāng)?shù)臄?shù)據(jù)類型(可參考《Redis 實戰(zhàn)篇:巧用數(shù)據(jù)類型實現(xiàn)億級數(shù)據(jù)統(tǒng)計》)。
key 命名規(guī)范
key命名,在遇到問題的時候能夠方便定位。Redis 屬于 沒有 Scheme的 NoSQL數(shù)據(jù)庫。Scheme 語意,就好比根據(jù)不同的場景我們建立不同的數(shù)據(jù)庫。Scheme),通過「冒號」分隔,再加上「具體業(yè)務(wù)名」。key 前綴來區(qū)分不同的業(yè)務(wù)數(shù)據(jù),清晰明了。set?公眾號:技術(shù)類:AAA?100000
SDS,SDS 結(jié)構(gòu)中會包含字符串長度、分配空間大小等元數(shù)據(jù)信息。不要使用 bigkey
bigkey 的讀寫操作就會阻塞線程,降低 Redis 的處理效率。bigkey包含兩種情況:鍵值對的 value很大,比如value保存了2MB的String數(shù)據(jù);鍵值對的 value是集合類型,元素很多,比如保存了 5 萬個元素的List集合。
key和string類型 value限制均為512MB。string類型控制在10KB以內(nèi),hash、list、set、zset元素個數(shù)不要超過 5000。gzip 數(shù)據(jù)壓縮來減小數(shù)據(jù)大小:/**
?*?使用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內(nèi)置的序列化方法效率更高。JSON或者 XML,為了避免數(shù)據(jù)占用空間大,我們可以使用壓縮工具(snappy、 gzip)將數(shù)據(jù)壓縮再存到 Redis 中。使用整數(shù)對象共享池
Redis 中設(shè)置了 maxmemory,而且啟用了LRU策略(allkeys-lru 或 volatile-lru 策略),那么,整數(shù)對象共享池就無法使用了。?這是因為 LRU 需要統(tǒng)計每個鍵值對的使用時間,如果不同的鍵值對都復(fù)用一個整數(shù)對象就無法統(tǒng)計了。 如果集合類型數(shù)據(jù)采用 ziplist 編碼,而集合元素是整數(shù),這個時候,也不能使用共享池。 ?因為 ziplist 使用了緊湊型內(nèi)存結(jié)構(gòu),判斷整數(shù)對象的共享情況效率低。
?
命令使用規(guī)范
生產(chǎn)禁用的指令
KEYS:該命令需要對 Redis 的全局哈希表進(jìn)行全表掃描,嚴(yán)重阻塞 Redis 主線程; ?應(yīng)該使用 SCAN 來代替,分批返回符合條件的鍵值對,避免主線程阻塞。 FLUSHALL:刪除 Redis 實例上的所有數(shù)據(jù),如果數(shù)據(jù)量很大,會嚴(yán)重阻塞 Redis 主線程; FLUSHDB,刪除當(dāng)前數(shù)據(jù)庫中的數(shù)據(jù),如果數(shù)據(jù)量很大,同樣會阻塞 Redis 主線程。 ?加上 ASYNC 選項,讓 FLUSHALL,F(xiàn)LUSHDB 異步執(zhí)行。
rename-command命令在配置文件中對這些命令進(jìn)行重命名,讓客戶端無法使用這些命令。慎用 MONITOR 命令
慎用全量操作命令
使用 SSCAN、HSCAN等命令分批返回集合數(shù)據(jù);把大集合拆成小集合,比如按照時間、區(qū)域等劃分。
?
數(shù)據(jù)保存規(guī)范
冷熱數(shù)據(jù)分離
業(yè)務(wù)數(shù)據(jù)隔離
設(shè)置過期時間
控制單實例的內(nèi)存容量
防止緩存雪崩
?
運(yùn)維規(guī)范
使用 Cluster 集群或者哨兵集群,做到高可用; 實例設(shè)置最大連接數(shù),防止過多客戶端連接導(dǎo)致實例負(fù)載過高,影響性能。 不開啟 AOF 或開啟 AOF 配置為每秒刷盤,避免磁盤 IO 拖慢 Redis 性能。 設(shè)置合理的 repl-backlog,降低主從全量同步的概率 設(shè)置合理的 slave client-output-buffer-limit,避免主從復(fù)制中斷情況發(fā)生。 根據(jù)實際場景設(shè)置合適的內(nèi)存淘汰策略。 使用連接池操作 Redis。
有道無術(shù),術(shù)可成;有術(shù)無道,止于術(shù)
歡迎大家關(guān)注Java之道公眾號
好文章,我在看??
