一、布隆過濾器
? ? ? ? 布隆過濾器由一個 BitMap 和若干 Hash 函數組成,可以用來快速判斷一個值是否存在后端存儲中。它是解決 Redis 緩存穿透問題的一個不錯的解決方案。
工作原理
步驟1:當 key-value 鍵值對存儲到 Redis 后,向布隆過濾器添加 key
步驟2:布隆過濾器會對 key 進行多個獨立的哈希運算,假設是3個Hash,則得到 hash1、hash2、hash3
步驟3:假設布隆過濾器的 BitMap 長度為 L,那么 BitMap 中的 hash1 % L、hash2 % L、hash3 % L 這三個位會被設置為1
步驟4:若要查詢某個 key 是否存在于 Redis 中,可以先查詢布隆過濾器。布隆過濾器會檢查 hash1 % L、hash2 % L、hash3 % L 這三個位置是否都為1
步驟4.1:若否,則 key 一定不存在于 Redis 中
步驟4.2:若是,由于可能出現 Hash 沖突,因此無法判斷 key 是否存在于 Redis 中
特點
1、布隆過濾器無法刪除數據,若想要刪除只能全量初始化
2、布隆過濾器采用 Bitmap 結構,十分節省空間(假如1億的key,一般需要10億長度的Bitmap,這樣的 Bitmap 只需要 120MB 左右的空間即可)
3、布隆過濾器適用于數據命中率不高、數據相對固定、數據量大的場景。配合“緩存空值”的策略來對抗緩存穿透時,可以大幅減少 Redis 中空值對應的 key 的數量,從而節約 Redis 空間
示例代碼
void init(){// 初始化布隆過濾器, 預計元素10萬, 誤差率 3%,// 根據這兩個參數會計算出底層 bitmap 的大小及 Hash 函數的數量RBloomFilter<String> bloomFilter = redisson.getBloomFilter("bloom1");bloomFilter.tryInit(100000, 0.03);// 將現有 key 全部初始化for(String key : allKeys){bloomFilter.put(key);}
}String get(String key){// 由布隆過濾器判斷是否存在if(!bloomFilter.contains(key)){return "";}// 布隆過濾器不能確定, 嘗試從緩存中獲取String value = cache.get(key);if(value != null){return value;}// 緩存中不存在, 嘗試從數據庫獲取, 并更新緩存value = db.get(key);if(value != null){cache.set(key, value, 600s)} else {// 緩存空值value = "";cache.set(key, value, 30s);}return value;
}void add(String key, String value){// 新增元素時,除了寫入數據庫,還需要將 key 寫入布隆過濾器db.add(key, value);bloomFilter.put(key);
}
二、bigkey 問題
定義
????????bigkey 是指單值類型(string)大小很大,二級數據結構的大小很大或元素過多。
????????一般地,超過 10KB 的 string,或者超過 5000 個元素的 hash/list/set/zset,可以認定為 bigkey。
危害
1、高并發請求大 key 時,容易導致 Redis 阻塞
2、高并發請求大 key 時,容易導致網絡擁塞
3、過期刪除大 key 時,存在阻塞 Redis 的可能性。(Redis 4.0 之后默認采用過期異步刪除,可以一定程度緩解,但是大 key 刪除仍有較大的性能開銷)
產生原因
? ? ? ? 程序設計不當,或對數據規模預估不足。比如:
1、粉絲列表,若沒有精心設計,大 V 的粉絲列表容易成為大 key;
2、為了方便,把關聯的數據全部放到一個 key 中存儲;
優化方法
1、數據分段存儲,比如一個大的 list,分成10個小的 list 存儲
2、如果 bigkey 不可避免,每次請求盡量取出少量數據,比如用 hmget 代替 hgetall
3、使用合適的數據結構,比如一個大的 JSON 存儲為 string 類型,容易成為大 key,使用 hash 可以將數據分攤到多個 field 中。
4、設置 key 的過期時間,避免 Redis 內存不斷膨脹
三、命令使用
1、O(N) 操作注意性能問題
????????對于 hgetall、smembers、zrange 等等 O(N) 操作,要關注 N 的值。有遍歷元素的需求時,可以使用 hscan、sccan、zscan 等代替,這些 scan 命令使用游標進行遍歷,一次只返回有限數量的元素以及下一次 scan 的游標,而不是一次性返回全量數據。
2、通過 rename 禁用 keys、flushall、flushdb 等危險命令
3、合理使用 select
????????Redis 多數據庫的功能比較弱,使用數字區分。多個業務分別使用同一個 Redis 實例的不同數據庫,Redis 實際上還是單線程處理。
4、使用批量操作提高效率
? ? ? ? 原生命令:例如用 mget/mset 代替多次 get/set;非原生命令:使用 pipeline 打包發送多個命令。
? ? ? ? 但也要注意批量操作規模不能太大。
四、客戶端連接池
? ? ? ? 應用程序使用 Redis 連接池,可以避免連接頻繁建立、釋放造成的性能損耗。
????????使用連接池后,由連接池維護若干連接,負責這些連接的建立、保活、銷毀。業務層需要使用連接的時候,從連接池取出直接使用,使用完畢后歸還給連接池。
? ? ? ? 連接池的關鍵參數:
1、maxTotal?最大連接數
????????maxTotal 的估算要考慮多方因素,比如:
? ? ? ? (1)一次“借出連接 -> 執行命令 -> 歸還連接"大約耗時 1ms,那么單個連接的 QPS 就是1000,如果業務要求的 QPS 是 50000,那么至少需要 50 個連接,此時 maxTotal 可以比 50 大一些
? ? ? ? (2)Redis 服務端的最大連接數是 maxClient,則應用節點數量 * maxTotal 不能超過 maxClient
? ? ? ? (3)從性能最優考慮,一般設置為和 maxIdle 相等,可以避免緩存池伸縮帶來性能開銷。如果考慮連接占用
? ? ? ? (4)maxTotal 不是越大越好,設置合理的值可以限制應用程序消耗 Redis 的資源,而且由于 Redis 工作線程為單線程,一個大命令阻塞的發生時,即使 maxTotal 設置再大也沒用
2、maxIdle?最大空閑連接數
????????超出 maxIdle 的連接在被歸還后會被緩慢釋放。
????????從性能最優考慮,一般設置為 maxIdle = maxIdle,可以避免緩存池伸縮帶來性能開銷。如果考慮連接占用問題,maxIdle 一般設為業務預期的最高并發數,maxTotal 再放大一倍。
3、minIdle 最少空閑連接數
????????注意,Redis 連接池是懶加載的,因此應用程序啟動時,可以用的連接數不會直接到達 minIdle,如果應用啟動后會有很多請求過來,那么我們提前執行代碼進行預熱,使得初始的可用連接數到達 minIdle 個。
4、blockWhenExhausted? 連接池沒有空閑連接時,調用方是否等待
5、maxWaitMillis??blockWhenExhausted=true 時,調用方等待的最長時間
6、testOnBorrow? 連接池是否在借出連接時,檢測連接的有效性
7、testOnReturn??連接池是否在歸還連接時,檢測連接的有效性
五、數據清除策略
? ? ? ? 在 Redis 中,maxmemory 參數規定了能夠使用的最大內存。
1、已使用內存 < maxmemory
? ? ? ? 此時,Redis 會對過期數據進行清除,方式如下:
(1)被動刪除:惰性刪除,當一個 key 到期時,Redis 不會立刻刪除。而是當客戶端讀/寫一個已經過期的 key 時,Redis 進行刪除
(2)主動刪除:惰性刪除無法確保冷數據被及時刪除,所以 Redis 會定期主動刪除一批已經過期的 key
2、已使用內存 > maxmemory
? ? ? ? 此時,Redis 會觸發主動清理策略。主動清理策略有 8 種:
(1)針對設置了有效期的 key 進行刪除
? ? ? ? a. volatile-ttl:越早過期的數據越先被刪除
? ? ? ? b. volatile-random:隨機刪除
? ? ? ? c. volatile-lru:使用 LRU 算法刪除
? ? ? ? d. volatile-lfu:使用 LFU 算法刪除
(2)針對所有的 key 進行刪除
? ? ? ? e. allkeys-random:隨機刪除
? ? ? ? f. allkeys-lru:使用 LRU 算法刪除
? ? ? ? g.?allkeys-lfu:使用 LFU 算法刪除
(3)不刪除
? ? ? ? h. noeviction:缺省值。不會刪除任何數據,并拒絕客戶端的所有寫操作。
3、配置建議
(1)Redis 默認不限制 maxmemory。生產環境建議配置,并且不要讓 maxmemory 超過物理內存大小,否則會觸發磁盤 swap 影響性能。
(2)一般推薦使用 volatile-lru;如果熱點數據比較多,則用 volatile-lfu