分布式專題——6 Redis緩存設計與性能優化

1 多級緩存架構

在這里插入圖片描述

2 緩存設計

2.1 緩存穿透

2.1.1 簡介

  • 緩存穿透是什么?當查詢一個根本不存在的數據時,緩存層和存儲層都不會命中。正常邏輯下,存儲層查不到數據就不會寫入緩存層。這會導致:每次請求這個不存在的數據,都要去存儲層查詢,緩存就失去了保護后端存儲的意義,存儲層可能被大量無效請求壓垮;

  • 出現緩存穿透的原因一般有以下兩個:

    • 自身業務代碼或數據有問題,導致查詢了本不該存在的數據;

    • 遭遇惡意攻擊、爬蟲等,它們會發起大量查詢不存在數據的請求。

2.1.2 緩存空對象

String get(String key) {// 從緩存中獲取數據String cacheValue = cache.get(key);// 如果緩存為空(包括空字符串或null值)if (StringUtils.isBlank(cacheValue)) {// 緩存未命中,從持久存儲層(如數據庫)中獲取數據String storageValue = storage.get(key);// 將從存儲層獲取的值寫入緩存,以便后續請求可以直接從緩存中獲取cache.set(key, storageValue);// 如果存儲層中也不存在該鍵對應的值(即值為null)if (storageValue == null) {// 將"空值"(null)也存入緩存,并設置一個較短的過期時間(5分鐘)// 這可以防止緩存穿透(大量請求查詢不存在的key)cache.expire(key, 60 * 5);}// 返回從存儲層獲取的值(可能為null)return storageValue;} else {// 緩存命中,直接返回緩存中的值return cacheValue;}
}

2.1.3 布隆過濾器

  • 針對惡意攻擊等導致的“大量請求不存在數據”的緩存穿透場景,可以用布隆過濾器先做一次過濾,其判定邏輯是:

    • 如果布隆過濾器說“某個值不存在”,那這個值肯定不存在
    • 如果它說“某個值存在”,這個值可能不存在(有誤判概率);
  • 原理

    在這里插入圖片描述

    • 它由大型位數組多個無偏 hash 函數(“無偏”指能把元素的 hash 值分布得比較均勻)組成;

    • 添加元素(如 key):用多個 hash 函數對 key 做 hash,得到整數索引值,其再對位數組長度取模,得到具體位置(每個 hash 函數都會算得一個不同的位置),最后把這些位置都置為 1,就完成了添加;

    • 查詢元素是否存在:同樣用多個 hash 函數算出 key 對應的位置,看位數組中這些位置是否都為 1。只要有一個位為 0,說明 key 不存在;如果都為 1,只能說“很可能存在”(因為其他 key 也可能把這些位置置為 1,導致誤判);

    • 位數組越稀疏(空閑位多),誤判概率越大;越擁擠(1 多),誤判概率越低;

  • 適用場景:數據命中不高(很多請求查的是不存在數據)、數據相對固定(新增/變更不頻繁)、實時性低(能接受一定延遲或誤判)且數據集較大的場景。

  • 它的優勢是緩存空間占用極少,但代碼維護相對復雜;

  • 注意:布隆過濾器不能刪除數據,如果要刪除元素,得重新初始化所有數據(因為位數組的位一旦置為 1,無法精準回退,刪除會破壞現有判斷邏輯);

  • 可以用 Redisson 實現布隆過濾器,引入依賴:

    <dependency><groupId>org.redisson</groupId><artifactId>redisson</artifactId><version>3.6.5</version>
    </dependency>
    
  • 示例偽代碼:初始化與使用

    package com.redisson;import org.redisson.Redisson;
    import org.redisson.api.RBloomFilter;
    import org.redisson.api.RedissonClient;
    import org.redisson.config.Config;public class RedissonBloomFilter {public static void main(String[] args) {// 創建Redisson配置對象Config config = new Config();// 配置單機Redis服務器地址config.useSingleServer().setAddress("redis://localhost:6379");// 根據配置創建Redisson客戶端實例RedissonClient redisson = Redisson.create(config);// 從Redisson客戶端獲取或創建一個名為"nameList"的布隆過濾器RBloomFilter<String> bloomFilter = redisson.getBloomFilter("nameList");// 初始化布隆過濾器參數:// - 預計要插入的元素數量:100,000,000個// - 期望的誤判率:3%(即允許有3%的概率將不存在的元素誤判為存在)// 底層會根據這兩個參數自動計算所需的bit數組大小和哈希函數數量bloomFilter.tryInit(100000000L, 0.03);// 向布隆過濾器中插入元素"shisan"bloomFilter.add("shisan");// 檢查元素是否可能存在于布隆過濾器中System.out.println(bloomFilter.contains("guojia")); // falseSystem.out.println(bloomFilter.contains("jiating")); // falseSystem.out.println(bloomFilter.contains("shisan")); // true// 注意:布隆過濾器的特性:// 1. 如果contains()返回false,則該元素一定不存在// 2. 如果contains()返回true,則該元素可能存在(可能有誤判)// 3. 布隆過濾器不支持元素刪除操作}
    }
    
  • 使用布隆過濾器需要把所有數據提前放入布隆過濾器,并且在后續新增數據時也要記得往布隆過濾器里放,布隆過濾器緩存過濾偽代碼:

    RBloomFilter<String> bloomFilter = redisson.getBloomFilter("nameList");
    bloomFilter.tryInit(100000000L,0.03);// 初始化方法:將所有數據預先加載到布隆過濾器中
    // 這通常在系統啟動時執行一次,用于構建完整的布隆過濾器
    void init(){// 遍歷所有鍵,將其添加到布隆過濾器中for (String key: keys) {// 將鍵放入布隆過濾器,建立快速查找的索引bloomFilter.put(key);}
    }// 根據鍵獲取值的業務方法,集成了布隆過濾器優化查詢
    String get(String key) {// 首先通過布隆過濾器快速判斷key是否可能存在Boolean exist = bloomFilter.contains(key);// 如果布隆過濾器確認key不存在,直接返回空字符串,避免后續不必要的緩存和存儲查詢if(!exist){return "";}// 布隆過濾器判斷key可能存在,繼續從Redis緩存中獲取數據String cacheValue = cache.get(key);if (StringUtils.isBlank(cacheValue)) {String storageValue = storage.get(key);cache.set(key, storageValue);if (storageValue == null) {cache.expire(key, 60 * 5);}return storageValue;} else {return cacheValue;}
    }
    

2.2 緩存擊穿(失效)

  • 緩存擊穿(失效)是什么?大批量緩存數據在同一時間過期失效時,會導致大量請求同時無法從緩存中獲取數據,只能直接穿透到數據庫查詢。這可能瞬間給數據庫帶來巨大壓力,甚至導致數據庫崩潰;

  • 這種情況通常發生在:

    • 系統初始化時批量加載了一批數據到緩存,且設置了相同的過期時間;

    • 某類熱點數據集中過期(比如電商促銷活動結束時間統一);

  • 解決方案:錯開緩存過期時間,即在批量設置緩存時,將過期時間分散在一個時間區間內,而不是使用固定值。這樣可以避免大量緩存同時失效,將數據庫壓力分散到不同時間點;

    String get(String key) {String cacheValue = cache.get(key);if (StringUtils.isBlank(cacheValue)) {String storageValue = storage.get(key);cache.set(key, storageValue);// 生成一個300到600秒之間的隨機過期時間(5到10分鐘)// 這種隨機化策略可以有效防止緩存雪崩(大量緩存同時失效導致請求直接打到數據庫)int expireTime = new Random().nextInt(300) + 300;if (storageValue == null) {// 為不存在的鍵設置隨機過期時間// 這樣即使大量不存在的鍵同時被緩存,它們也會在不同的時間點過期cache.expire(key, expireTime);}return storageValue;} else {return cacheValue;}
    }
    

2.3 緩存雪崩

  • **緩存雪崩是什么?**當緩存層因為各種原因(像遭遇超大并發請求但是系統扛不住、緩存設計不佳,比如大量請求訪問“大 key”導致緩存能支撐的并發量急劇下降等),無法正常提供服務甚至宕機時,原本由緩存層承接的大量請求,會像失控的野牛一樣,全部涌向后端存儲層。由于存儲層原本依賴緩存層分擔壓力,突然面臨這么多請求,調用量暴增,很可能也會被壓垮,進而引發級聯宕機的嚴重情況;

  • 如何預防和解決緩存雪崩?

    • 保證緩存層服務高可用性:可以采用像 Redis Sentinel(哨兵模式)或者 Redis Cluster(集群模式)這樣的方案。Redis Sentinel 能對 Redis 進行監控、提醒和自動故障轉移,確保緩存服務的可用性;Redis Cluster 則通過將數據分布在多個節點上,實現數據的高可用和高并發訪問;

    • 依賴隔離組件進行后端限流、熔斷與降級:可以使用 Sentinel 或者 Hystrix 這類限流降級組件

      • 服務降級方面,能針對不同數據采取不同處理方式;
      • 比如對于非核心數據(像電商商品屬性、用戶信息等),當緩存層出問題時,暫時停止從緩存查詢這些數據,直接返回預先定義好的默認降級信息、空值或者錯誤提示;
      • 而對于核心數據(像電商商品庫存),仍然允許查詢緩存,若緩存里沒有,還能從數據庫讀取;
      • 這樣既保障核心業務不受太大影響,又減輕了存儲層壓力;
    • 提前演練:在項目上線之前,模擬緩存層宕機的情況,演練應用以及后端的負載情況和可能出現的問題,然后基于演練結果制定相應的預案,以便在實際出現緩存雪崩時能快速應對。

2.4 熱點緩存 key 重建優化

  • 當同時滿足以下兩個條件時,可能對應用造成致命危害:

    • 熱點key:某個緩存key訪問量極大(比如熱門新聞、爆款商品)

    • 緩存重建耗時:從數據源(如數據庫)重新加載并計算該 key 對應的緩存數據需要很長時間(可能涉及復雜SQL、多次IO操作或多個依賴服務調用)

    • 此時,當這個熱點 key 的緩存過期失效瞬間,會有大量并發線程同時發現緩存缺失,然后同時去重建緩存,這會導致后端數據源壓力驟增,甚至可能讓應用崩潰;

  • 解決方案:互斥鎖機制,即只允許一個線程負責重建緩存,其他線程等待重建完成后再從緩存獲取數據,避免大量線程同時沖擊數據源

    String get(String key) {// 從Redis緩存中獲取指定key的數據String value = redis.get(key);// 如果緩存中不存在該key的值(緩存未命中)if (value == null) {// 創建互斥鎖的key,格式為"mutext:key:原key",用于防止緩存擊穿String mutexKey = "mutext:key:" + key;// 嘗試獲取分布式鎖:設置一個值為"1"的鎖,過期時間為180秒,只有在key不存在時才能設置成功(NX選項)if (redis.set(mutexKey, "1", "ex 180", "nx")) {try {// 成功獲取到鎖的線程,從數據庫(或其他數據源)獲取真實數據value = db.get(key);// 將獲取到的數據寫入Redis緩存,并設置正常的過期時間redis.setex(key, timeout, value);} finally {// 無論是否成功獲取數據,都釋放分布式鎖redis.delete(mutexKey);}} else {// 未獲取到鎖的線程(其他線程正在重構緩存),等待50毫秒,讓持有鎖的線程完成緩存重構Thread.sleep(50);// 遞歸調用自身,重新嘗試從緩存獲取數據(此時可能已經重構完成)return get(key);}}// 返回獲取到的值(可能來自緩存,也可能是剛重構的數據)return value;
    }
    

2.5 緩存與數據庫雙寫不一致

  • 在大并發下,同時操作緩存與數據庫會存在數據不一致性的問題;

    • 雙寫不一致:多線程并發寫數據庫和更新緩存時,因執行順序差異,可能導致緩存與數據庫數據不匹配

      在這里插入圖片描述

      • 比如線程1先寫數據庫后更新緩存,線程2寫數據庫后更新緩存,若線程1的緩存更新動作滯后,就會使緩存數據不符合最終數據庫狀態;
    • 讀寫并發不一致:讀寫操作并發時,也易引發數據不一致

      在這里插入圖片描述

      • 像線程1寫數據庫后刪除緩存,線程2寫數據庫后刪除緩存,線程3查緩存(為空)后查數據庫(取到線程1寫入的舊數據)并更新緩存,最終緩存會留存舊數據,與數據庫最新數據(線程2寫入的)不符;
  • 解決方案

    • 對于并發概率小的數據(如個人訂單、用戶數據),因本身并發沖突少,很少出現緩存不一致,可給緩存設過期時間,通過定時讀操作主動更新緩存;

    • 若業務能容忍短時緩存不一致(如商品名稱、分類菜單),給緩存加過期時間,也能滿足大部分業務對緩存的需求,過期后緩存會重新從數據庫加載最新數據;

    • 若有強一致性要求

      • 加分布式讀寫鎖,保證并發讀寫或寫寫操作有序進行,讀操作間無鎖,既保障數據一致性,又盡可能減少對讀性能的影響;

      • 用阿里開源的 Canal,監聽數據庫 binlog 日志,實時修改緩存。當數據庫數據變更,binlog 記錄變更,Canal 監聽到后同步更新緩存,能實時保證緩存與數據庫一致,但引入了新中間件,增加了系統復雜度;

        在這里插入圖片描述

  • 總結:

    • 緩存主要用于讀多寫少場景以提升性能,若寫多讀多且不能容忍緩存不一致,直接操作數據庫更合適;若數據庫壓力大,也可將緩存作為主存儲,異步同步數據到數據庫,數據庫作為備份;

    • 放入緩存的數據應是對實時性、一致性要求不高的,不要為追求緩存絕對一致做過度設計,否則會徒增系統復雜度。

3 開發規范與性能優化

3.1 鍵與值的設計

3.1.1 key 的設計

  • 可讀性和可管理性

    • 避免不同業務 / 模塊的 key 沖突,同時讓 key 更容易理解和維護;

    • 業務名(或數據庫名)作為前綴,用**冒號(:)**分隔不同層級的信息;

    • 例:trade:order:1,能清晰看出這是“交易(trade)”業務下,“訂單(order)”模塊中 ID 為 1 的訂單數據對應的 key;

  • 簡潔性

    • 減少 key 的長度,降低內存占用(當 key 數量極多時,長 key 會累積占用較多內存);

    • 保證語義清晰的前提下,對 key 進行簡化縮寫;

    • 例:把 user:{uid}:friends:messages:{mid} 簡化為 u:{uid}:fr:m:{mid},通過縮寫(userufriendsfrmessagesm)縮短 key 長度,同時仍能體現“用戶 - 好友 - 消息”的層級關系;

  • 不要包含特殊字符

    • 避免特殊字符導致 key 解析、存儲或訪問時出現異常(如語法錯誤、轉義問題等);

    • key 中不能包含空格、換行、單雙引號以及其他轉義字符(如 \ 等);

    • 原因:這些特殊字符可能會干擾緩存系統對 key 的識別,甚至引發程序報錯,影響緩存的正常使用。

3.1.2 value 的設計

  • 在 Redis 中,一個字符串最大 512 MB,一個二級數據結構(例如 Hash、List、Set、Zset)可以存儲大約 40 億(2^32-1)個元素,但實際上如果出現下面兩種情況,就認為它是 bigkey

    • 字符串類型:它的“big”體現在單個 value 值很大,一般認為超過 10KB 就是 bigkey;
    • 非字符串類型:哈希、列表、集合、有序集合,它們的 big 體現在元素個數太多(建議不超過5000個);
  • bigkey 的危害

    • Redis阻塞:操作 bigkey 需要消耗更多 CPU 和內存資源,可能導致 Redis 服務阻塞;

    • 網絡擁塞:bigkey 會產生大量網絡流量,例如 1MB 的 bigkey 每秒被訪問 1000 次,會產生 1000MB/s 的流量,遠超普通千兆網卡的承載能力;

    • 過期刪除問題:bigkey 過期時,如果沒有啟用 Redis 4.0 的異步刪除功能,刪除操作可能阻塞 Redis;

  • bigkey 的產生原因:程序設計不當、對數據規模預估不足。例:

    • 社交類:粉絲列表,對于某些明星或者大v的粉絲列表,如果不精心設計一下,必是 bigkey;
    • 統計類:例如按天存儲某項功能或者網站的用戶集合,除非沒幾個人用,否則必是 bigkey;
    • 緩存類:將數據從數據庫 load 出來序列化放到 Redis 時,把所有字段或關聯數據都緩存,造成 bigkey;
  • bigkey 的優化方案

    • 拆分

      • 對于 List:將一個大 List 拆分為多個小 List;
      • 對于Hash:將大 hash 按分段存儲(如將100萬用戶數據拆分為200個key,每個存儲5000個用戶);
    • 如果有 bigkey,對其的操作:避免一次性獲取或刪除所有元素(會阻塞),使用 hmget 而非 hgetall,采用 hscan、sscan、zscan 等漸進式刪除方法;

    • 選擇適合的數據類型

      • 反例:將一個實體的不同屬性用多個 string 存儲(如set user:1:nameset user:1:age等);
      • 正例:使用 Hash 存儲實體數據(如hmset user:1 name tom age 19 favor football),更節省內存且操作更高效;
    • 控制 key 的生命周期

      • Redis 通常不是用于持久存儲,應給 key 設置合理的過期時間;
      • 條件允許時,應打散過期時間,避免大量 key 集中過期導致緩存擊穿問題。

3.2 命令使用

  • 關注O(N)命令的N值大小

    • hgetalllrangesmemberszrangesinter等命令的時間復雜度是O(N),執行效率與數據量N直接相關;

      hgetall:獲取哈希表中所有字段和值,適用于小型哈希但可能阻塞Redis服務
      lrange:獲取列表指定范圍內的元素,支持分頁查詢列表數據
      smembers:返回集合中的所有成員,當集合很大時會導致Redis阻塞
      zrange:返回有序集合中指定排名范圍的成員(可帶分數),支持按排名范圍查詢
      sinter:計算多個集合的交集,返回所有給定集合中都存在的成員

    • 并非不能使用這些命令,但必須清楚N的具體數量,避免在大數據量上(比如 bigkey)執行;

    • 有遍歷需求時,推薦使用hscansscanzscan等漸進式遍歷命令,它們可以分批獲取數據,避免一次性處理大量數據導致 Redis 阻塞;

      hscan:增量迭代哈希表中的鍵值對,避免一次性獲取大哈希造成的阻塞
      sscan:增量迭代集合中的元素,安全遍歷大集合的解決方案
      zscan:增量迭代有序集合中的元素和分數,用于處理大型有序集合

  • 禁用危險命令

    • 禁止在線上環境使用keysflushallflushdb等命令:

      • keys命令會遍歷整個數據庫,在數據量大時會嚴重阻塞Redis
      • flushallflushdb會清空數據庫,風險極高
    • 可以通過Redis的rename機制禁用這些命令,或用scan命令替代keys進行漸進式處理

  • 合理使用select命令(多數據庫)

    • Redis 的多數據庫功能較弱,其通過數字(0-15)區分不同數據庫

    • 很多客戶端對多數據庫支持不好,且多業務共用同一 Redis 實例的不同數據庫時,仍然是單線程處理,會相互干擾

    • 建議謹慎使用多數據庫,更好的做法是按業務拆分不同的 Redis 實例

  • 使用批量操作提高效率

    • 原生命令:如mgetmset,可以一次性操作多個key,減少網絡往返

    • pipeline:非原生命令的批量處理方式,能打包多個命令一次性發送

    • 注意事項:

      • 控制批量操作的元素個數(建議500以內,具體與元素大小有關),避免單次操作過大
      • 原生命令是原子操作,pipeline 是非原子操作
      • pipeline 可以打包不同命令,原生命令只能處理同類型命令
      • pipeline 需要客戶端和服務端同時支持
  • 謹慎使用事務功能

    • Redis 的事務功能相對較弱,不建議過多依賴

    • 可以使用 Lua 腳本替代事務,Lua 腳本在 Redis 中是原子執行的,能保證復雜操作的原子性

3.3 客戶端使用

3.3.1 連接池

  • 客戶端實例使用建議:避免多個應用共用一個 Redis 實例,建議不同業務拆分 Redis 實例,可防止公共數據服務劣化,讓各業務數據訪問更獨立、穩定;

  • 推薦使用連接池:能有效控制連接數,提升效率;

    // 通過JedisPoolConfig配置連接池參數
    JedisPoolConfig jedisPoolConfig = new JedisPoolConfig();
    jedisPoolConfig.setMaxTotal(5); // 最大連接數
    jedisPoolConfig.setMaxIdle(2); // 最大空閑連接數
    jedisPoolConfig.setTestOnBorrow(true); // 向資源池借用連接時是否做連接有效性檢測(ping)// 基于配置創建JedisPool獲取連接
    JedisPool jedisPool = new JedisPool(jedisPoolConfig, "192.168.0.60", 6379, 3000, null);Jedis jedis = null;
    try {jedis = jedisPool.getResource();//具體的命令jedis.executeCommand()
    } catch (Exception e) {logger.error("op key {} error: " + e.getMessage(), key, e);
    } finally {//注意這里不是關閉連接,在JedisPool模式下,Jedis會被歸還給資源池if (jedis != null) jedis.close();
    }
    

3.3.2 連接池參數含義與優化建議(連接池預熱)

  • maxTotal:最大連接數(早期版本叫maxActive),需結合業務 Redis 并發量、客戶端執行命令耗時、Redis 資源等因素確定,通常比理論計算值略大,同時要注意不能超過 Redis 最大連接數(maxclients)限制;

    以一個例子說明,假設:

    • 單連接處理能力:每個Redis連接完成一次命令操作(獲取連接+執行命令+歸還連接)平均耗時1ms
    • 單連接QPS:1000(因為1秒/1ms = 1000)
    • 目標業務QPS:50000

    理論最小連接數 = 目標QPS / 單連接QPS = 50000 / 1000 = 50個連接

    實際配置考慮:

    • 需要預留緩沖資源:實際mxTotal應該略大于理論值(如55-60),以應對流量波動和突發請求
    • 連接數不是越多越好:過多連接會消耗客戶端和服務端資源,增加管理開銷
    • 性能瓶頸分析:Redis是單線程模型,如果遇到大命令阻塞(如keys*、大型集合操作),即使增加再多客戶端連接也無法提高吞吐量,因為服務端處理能力已成為瓶頸

    所以:連接池大小設置需要平衡理論計算、資源消耗和實際業務特性,同時要認識到連接數不能解決 Redis 單線程架構下的命令阻塞問題

  • maxIdle:最大空閑連接數,建議設為業務所需最大連接數,maxTotal為其留出余量,且maxIdle不超過maxTotal,避免資源浪費;

  • minIdle:最小空閑連接數,即至少需要保持的空閑連接數,用于維持連接池基本空閑連接,若連接數超過minIdle,多余連接會在完成業務后被移出連接池釋放;

    如果系統啟動完馬上就會有很多的請求過來,那么可以給 Redis 連接池做預熱

    比如快速地創建一些 Redis 連接,執行一些簡單命令(比如ping()),快速地將連接池里的空閑連接提升到minIdle的數量;

    連接池預熱示例代碼:

    // 創建一個ArrayList用于存儲預熱的Jedis連接,初始容量設置為連接池的最小空閑連接數
    List<Jedis> minIdleJedisList = new ArrayList<Jedis>(jedisPoolConfig.getMinIdle());// 第一階段:預熱連接池,創建最小空閑連接數指定的連接數量
    for (int i = 0; i < jedisPoolConfig.getMinIdle(); i++) {Jedis jedis = null;try {// 從連接池獲取一個Jedis連接實例jedis = pool.getResource();// 將獲取的連接添加到預熱列表中暫存minIdleJedisList.add(jedis);// 執行ping命令測試連接有效性,確保連接是可用的jedis.ping();} catch (Exception e) {// 記錄連接預熱過程中的異常信息logger.error(e.getMessage(), e);} finally {// 此處不能立即歸還連接,否則連接池會重復使用同一個連接,目的是保持多個不同的連接實例在預熱列表中// jedis.close();}
    }// 第二階段:將所有預熱的連接統一歸還到連接池
    for (int i = 0; i < jedisPoolConfig.getMinIdle(); i++) {Jedis jedis = null;try {// 從預熱列表中獲取預先創建的連接jedis = minIdleJedisList.get(i);// 將連接歸還回連接池,此時連接池中就會有minIdle個已初始化的可用連接jedis.close();} catch (Exception e) {// 記錄連接歸還過程中的異常信息logger.error(e.getMessage(), e);} finally {// 可選的清理操作}
    }
    
  • blockWhenExhausted:當連接池用盡時,是否讓調用者等待,建議用默認值true

  • maxWaitMillis:調用者等待連接的最大時長,不建議用默認的“不超時”,避免長時間阻塞;

  • testOnBorrowtestOnReturn:分別在借連接和還連接時測試連接可用性(如ping),業務量大時建議開啟,確保連接有效;

3.3.3 高并發與安全建議

  • 高并發下,建議客戶端添加熔斷功能(如結合 Sentinel、Hystrix),增強系統穩定性;
  • 設置合理密碼,必要時用 SSL 加密訪問,保障 Redis 訪問安全。

3.3.4 Redis 的過期鍵清理策略

  • Redis 對于過期鍵有三種清除策略:

    • 被動刪除:當讀寫一個已過期的鍵時,會觸發惰性刪除策略刪除該鍵;

    • 主動刪除:由于惰性刪除策略無法保證冷數據被及時刪掉,所以 Redis 會定期(默認100ms)主動淘汰一部分已過期鍵;

      • 注意:這里淘汰的是一部分,所以可能會出現部分 key 已經過期但還沒有被清理掉的情況;
    • 內存超限觸發:當內存超過maxmemory限制時,觸發主動清理策略;

  • 主動清理策略在 Redis 4.0 之前一共實現了 6 種內存淘汰策略,在 4.0 之后,又增加了 2 種策略,總共8種:

    • 針對設置了過期時間的 key 做處理:

      • volatile-ttl:按過期時間先后刪除

      • volatile-random:隨機刪除

      • volatile-lru:用 LRU 算法刪除

        LRU 算法(Least Recently Used,最近最少使用):淘汰很久沒被訪問過的數據,以最近一次訪問時間作為參考;

        存在熱點數據時 LRU 效率好;

      • volatile-lfu:用 LFU 算法刪除

        LFU 算法(Least Frequently Used,最不經常使用):淘汰最近一段時間被訪問次數最少的數據,以次數作為參考;

        但若有偶發、周期性批量操作,LRU 命中率會下降,緩存污染嚴重,此時 LFU 更優;

    • 針對所有的key做處理:

      • allkeys-random:隨機刪除
      • allkeys-lru:用 LRU 算法刪除
      • allkeys-lfu:用 LFU 算法刪除
    • noeviction:不刪除任何數據,拒絕所有寫入操作并返回客戶端錯誤信息(error) OOM command not allowed when used memory,此時 Redis 只響應讀操作;

  • 推薦配置maxmemory-policy(默認是noeviction)為volatile-lru,且要設置最大內存,否則 Redis 內存超出物理內存限制時會頻繁換頁(Swap),導致性能急劇下降。

  • 當 Redis 運行在主從模式時,只有主節點才會執行過期刪除策略,再將刪除操作del key同步到從節點刪除數據。

4 系統內核參數優化

4.1 虛擬內存交換(vm.swappiness

  • 作用

    • 當物理內存不足時,系統會把部分內存頁(page)交換到磁盤的 swap 分區,暫時緩解內存壓力。但 swap 依賴磁盤 IO,高并發場景下磁盤 IO 會成為系統瓶頸;
    • 在 Linux 中,并不是要等到所有物理內存都使用完才會使用到 swap,系統參數 swppiness 會決定操作系統使用 swap 的傾向程度;
  • swappiness 取值范圍是 0 - 100,值越大,系統越傾向用 swap;值越小,越傾向用物理內存;

  • Redis 優化建議

    • 若 Linux 內核版本 < 3.5,設 swappiness = 0,減少 swap 使用,避免觸發 OOM killer(系統內存不足時強制殺用戶進程);

    • 若內核版本 >= 3.5,設 swappiness = 1,同樣減少 swap 依賴,降低 OOM killer 風險;

    • 操作示例:

      cat /proc/version  # 查看Linux內核版本
      echo 1 > /proc/sys/vm/swappiness # 臨時寫入
      echo vm.swapiness=1 >> /etc/sysctl.conf # 寫入配置文件,永久生效
      

4.2 內存超額提交(vm.overcommit_memory

  • 參數含義:控制內核是否允許“超額”分配物理內存

    • 0:檢查可用物理內存,足夠才允許內存申請,否則申請失敗;
    • 1:內核允許分配所有物理內存,不管當前內存狀態;
  • Redis 優化建議:設為 1,確保 fork 操作(Redis 持久化等場景會用到)能在內存不足時也成功執行,避免因內存申請失敗導致 fork 等操作異常;

  • 操作示例

    cat /proc/sys/vm/overcommit_memory
    echo "vm.overcommit_memory=1" >> /etc/sysctl.conf
    sysctl vm.overcommit_memory=1 # 使其立即生效
    

4.3 文件句柄數

  • 問題場景:系統進程打開文件(或者稱作句柄)的數量達到上限時,會報 Too many open files 錯誤,影響 Redis 等服務的文件操作(如網絡連接、日志文件等);
  • 優化操作
    • ulimit -a 查看系統文件句柄數限制;
    • ulimit -n 65535 臨時提高文件句柄數(此處設為 65535),也可通過系統配置永久調整,讓 Redis 能支持更多并發連接等文件操作。

4.4 慢查詢日志(slowlog

  • 作用:記錄 Redis 中執行耗時超閾值的命令,用于排查性能問題(如慢查詢導致的 Redis 卡頓);
  • 關鍵配置與命令
    • config get slowlog-*:查看慢查詢日志相關配置;
    • config set slowlog-log-slower-than 1000:設置慢查詢閾值(單位微秒,示例中設為 1000 微秒即 1 毫秒,超過該耗時的命令會被記錄)。若要更高并發場景下的細粒度監控,可設為 500 微秒;
    • config set slowlog-max-len 1024:設置慢查詢日志最大保存條數,滿了會刪除最早的記錄,保留最新的;
    • config rewrite:將當前生效的配置持久化到 redis.conf
    • slowlog len:查看慢查詢日志當前長度;
    • slowlog get 5:獲取最新 5 條慢查詢日志,每條包含標識 ID、發生時間、命令耗時、命令及參數;
    • slowlog reset:重置慢查詢日志。

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/news/921885.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/921885.shtml
英文地址,請注明出處:http://en.pswp.cn/news/921885.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

一文了解大模型壓縮與部署

一文了解大模型壓縮與部署&#xff1a;從 INT4 量化到 MoE&#xff0c;讓大模型跑在手機、邊緣設備和云端&#x1f3af; 為什么需要模型壓縮與部署&#xff1f;你訓練了一個強大的大模型&#xff08;如 Qwen-72B、LLaMA-3-70B&#xff09;&#xff0c;但在部署時發現&#xff1…

新手向:中文語言識別的進化之路

自然語言處理&#xff08;NLP&#xff09;技術正在以前所未有的速度改變我們與機器的交互方式。根據Gartner最新報告顯示&#xff0c;全球NLP市場規模預計在2025年將達到430億美元&#xff0c;年復合增長率高達21%。而中文作為世界上使用人數最多的語言&#xff08;全球約15億使…

LeetCode100-206反轉鏈表

本文基于各個大佬的文章上點關注下點贊&#xff0c;明天一定更燦爛&#xff01;前言Python基礎好像會了又好像沒會&#xff0c;所有我直接開始刷leetcode一邊抄樣例代碼一邊學習吧。本系列文章用來記錄學習中的思考&#xff0c;寫給自己看的&#xff0c;也歡迎大家在評論區指導…

uniapp開源多商戶小程序商城平臺源碼 支持二次開發+永久免費升級

在電商行業競爭日益激烈的今天&#xff0c;擁有一個功能強大、靈活可拓展的多商戶小程序商城至關重要。今天給大家分享一款 uniapp 開源多商戶小程序商城平臺源碼&#xff0c;它不僅具備豐富的基礎功能&#xff0c;還支持二次開發&#xff0c;更能享受永久免費升級服務&#xf…

使用腳本一鍵更新NTP服務器地址為自定義地址

【使用場景】 在銀河麒麟桌面操作系統V10SP1-2303版本中使用腳本一鍵修改NTP服務器地址為自定義地址。 【操作步驟】 步驟1. 編寫shell腳本 ```bash desktop2303@desktop2303-pc:~$ vim setntptimeserver.sh #!/bin/bashfunction modifykylinconf() { # 檢查是否已存在目標配置…

linux內核 - 內核架構概覽

當 Linux 系統啟動時,內核會在啟動過程的早期階段接管控制——緊跟在固件(BIOS 或 UEFI)和引導加載程序完成任務之后。此時,壓縮的 Linux 內核鏡像會被加載到內存中,通常會附帶一個稱為 initramfs 的最小臨時根文件系統,它用于在切換到真實根文件系統并繼續系統初始化之前…

[react] react-router-dom是啥?

頁面路由&#xff0c;注意頁面路由不是路由器&#xff0c;因為我之前總是把路由和路由器搞混。而且我總是把前端頁面的路由和路由器的路由搞混。那么這里一定要明白&#xff0c;這里我所說的頁面路由就是指在瀏覽器里面的導航路由。 npm create vitelatest my-react-app – --t…

HTTP簡易客戶端實現

&#x1f310; HTTP簡易客戶端實現 流程圖&#xff1a; 引用&#xff1a; chnroutes2.cpp#L474 chnroutes2_getiplist() chnroutes2.cpp#L443 http_easy_get(…) &#x1f552; 1. 超時管理機制 (http_easy_timeout) &#x1f539; 核心功能&#xff1a;創建定時器自動關…

建筑面LAS點云高度計算工具

效果 例如中位數,計算后,在shp建筑面中添加一個字段meidian_hei 準備數據 1、建筑矢量面.shp 2、點云.las 界面 腳本 import laspy import shapefile # pyshp庫,處理POLYGONZ坐標格式異常 import pandas as pd import numpy as np import os import traceback # 打印…

java day18

繼續學習&#xff0c;學習sringboot案例&#xff1b;熟悉的三件套&#xff1b;比如做一個表&#xff0c;前端搭建好框架&#xff0c;然后返回給后端一個請求&#xff0c;說要這個表的數據吧&#xff1b;然后通過請求和規定的格式返回給后端之后&#xff0c;我們后端進行接收處理…

并發編程原理與實戰(二十八)深入無鎖并發演進,AtomicInteger核心API詳解與典型場景舉例

無鎖并發演進背景 隨著系統高并發的壓力越來越大&#xff0c;傳統同步機制在高并發場景下的性能瓶頸和缺點可能會逐漸顯露&#xff1a; &#xff08;1&#xff09;性能損耗&#xff1a;synchronized等鎖機制會導致線程阻塞和上下文切換&#xff0c;在高并發場景下性能損耗顯著。…

整體設計 之 緒 思維導圖引擎 之 引 認知系統 之 引 認知系統 之 序 認知元架構 之5 : Class 的uml profile(豆包助手 之7)

摘要&#xff08;AI生成&#xff09;三層中間件架構的約束邏輯體系1. 架構定位與功能分工三個中間層&#xff08;隔離層/隱藏層/防腐層&#xff09;構成數據處理管道&#xff0c;分別承擔&#xff1a;隔離層&#xff1a;跨系統數據轉換處理對象&#xff1a;異構數據&#xff08…

iframe引入界面有el-date-picker日期框,點擊出現閃退問題處理

前言&#xff1a;iframe引入界面有el-date-picker日期框&#xff0c;點擊出現閃退問題處理。問題情況&#xff1a;點擊開始日期的輸入部分&#xff0c;會出現閃退情況&#xff0c;該組件是iframe調用的內容問題分析&#xff1a;事件冒泡&#xff0c;點擊與聚焦的時候&#xff0…

docker 拉取本地鏡像

要在Docker中拉取本地鏡像&#xff0c;通常有以下幾種實現方法&#xff1a; 使用docker pull命令&#xff1a;可以使用docker pull命令從本地鏡像倉庫拉取鏡像。例如&#xff0c;如果本地鏡像的名稱是my-image&#xff0c;則可以運行以下命令拉取鏡像&#xff1a; docker pull …

嘉立創EDA從原理圖框選住器件進行PCB布局

1、先選中需要布局的模塊的相關器件2、設計-》布局傳遞3、在PCB會選中模塊相關的元器件&#xff0c;拖動進行布局4、依次將每個模塊都分類出來5、板框設計&#xff1a;如果有要求大小&#xff0c;可以先將單位設置為mm&#xff0c;然后畫出來板框的尺寸

http接口冪等性

實現 HTTP 接口的冪等性是確保多次相同請求產生相同結果的重要設計原則&#xff0c;尤其在網絡不穩定或分布式系統中非常關鍵。以下是幾種常見的實現方式&#xff1a;1. 基于冪等性令牌&#xff08;Token&#xff09;的實現適合支付、訂單創建等場景&#xff0c;步驟如下&#…

【華為OD】貪吃的猴子

文章目錄【華為OD】貪吃的猴子題目描述輸入描述輸出描述示例示例一示例二解題思路解法一&#xff1a;前綴和枚舉法Java實現Python實現C實現解法二&#xff1a;滑動窗口法Java實現Python實現C實現解法三&#xff1a;優化的動態規劃法Java實現Python實現C實現算法復雜度分析解法一…

Flie ,IO流(一)

一.File&#xff0c;IO流概述二.File文件1.File文件對象的創建&#xff08;路徑&#xff1a;&#xff09;2.常用方法1:判斷文件類型、獲取文件信息&#xff08;注意&#xff1a;&#xff09;3.常用方法2:創建文件、刪除文件&#xff08;creatNewFile&#xff08;&#xff09;會…

第2講 機器學習 - 導論

我們正處在一個"數據時代"&#xff0c;更強的計算能力和更豐富的存儲資源使數據總量與日俱增。然而真正的挑戰在于如何從海量數據中提取價值。企業與組織正通過數據科學、數據挖掘和機器學習的技術體系構建智能系統應對這一挑戰。其中&#xff0c;機器學習已成為計算…

如何解決pip安裝報錯ModuleNotFoundError: No module named ‘python-dateutil’問題

【Python系列Bug修復PyCharm控制臺pip install報錯】如何解決pip安裝報錯ModuleNotFoundError: No module named ‘python-dateutil’問題 摘要 在日常 Python 開發過程中&#xff0c;我們經常會遇到各種 pip install 的報錯&#xff0c;尤其是在 PyCharm 2025 控制臺環境下&…