實踐系列:高併發的緩存實踐

在溝通「商品首頁」展示時,如何保證高性能、高可用,具體來說,3 個方面需要注意:

  1. 本地緩存
  2. 分佈式緩存
  3. 冷數據存儲方案:未命中緩存的冷數據,數據庫併發壓力

具體來說,圍繞下面 3 項,逐個討論:

  1. 本地緩存
  2. 分佈式緩存
  3. 冷數據存儲方案:未命中緩存的冷數據,數據庫併發壓力

本地緩存,一般採用 Guava,此時,需要考慮 2 個問題:

  1. 數據一致性:本地緩存,會導致數據一致性減弱
  2. 線上服務的影響:大量本地緩存,會導致「堆內存佔用」過多,並且導致 gc,會影響線上服務的執行

業界解決方案:

  • TODO

Think:

  1. JVM 上,本地內存的緩存,分級?
  2. JVM 上,如何使用「直接內存」?直接內存是否存在安全性問題?(多進程都訪問)

關於堆外內存:

  1. JDK 1.8 默認的「堆外內存大小」跟「堆大小」一致,如果沒設置-XX:MaxDirectMemorySize,則默認與-Xmx參數值相同

  2. 堆外內存:

    1. 是什麼?JVM 進程佔用的「直接內存」,非堆內存
    2. 疑問:這個內存,是 JVM 進程獨佔的嗎?是否有其他進程訪問的風險?
    3. 確定:這個內存本質是受 OS 管理的,其他進程可能會訪問到
    4. 「堆外內存」:JVM 在直接內存中,開啓的一塊內存區域
      1. 利用 unsafe 包內工具,直接對物理內存進行的讀寫
      2. 是「byte 數組」
      3. Java 對象不能直接保存在裏面,需要經過「序列化/反序列化」實現存取
    5. 有什麼用?
      1. 減少 GC 次數:「堆外內存」的佔用,不需要
      2. 減少複製次數:「堆內數據」發送到遠端時(網絡 IO or 文件 IO),會先複製到「直接內存」再發送,使用「堆外內存」節省了這一步
    6. 適用場景
      1. 長期存在和能複用的場景:「堆外內存」的分配和回收,也是需要成本的
      2. 注重穩定性的場景:「堆外內存」能有效避免因 GC 導致的暫停
      3. 適合簡單對象存儲:內部存儲的都是「byte 數組」,Java 對象的讀寫,需要經過「序列化/反序列化」,複雜對象的「序列化/反序列化」需要特別注意
      4. 適合注重 IO 效率的場景:用「堆外內存」讀寫文件,效率高
    7. 有什麼問題?
      1. 內存泄露:對外內存如果泄露,很難排查
      2. 不適合存儲複雜對象:內部存儲的都是「byte 數組」,Java 對象的讀寫,需要經過「序列化/反序列化」,複雜對象的「序列化/反序列化」需要特別注意
    8. 如何分配對外內存?
      1. 分配:本質 unsafe 包,直接操作物理內存,都要轉換爲「字節數組」
      2. 複雜的 Java 對象,都需要手動進行「序列化/反序列化」,因此,複雜對象不建議存儲在「對外內存」
    9. 如何實現「堆外內存」的讀寫?
      1. 有開源方案,實現「堆外內存」的讀寫

分佈式緩存,存在幾個問題:

  1. 分佈式緩存存儲不足時,如何處理?只有 LRU 清理緩存這一種策略?
  2. 分佈式緩存,Redis 單實例,能支持最大多少併發連接數?QPS 又是多大?

實踐中,典型問題

  • Redis 單實例的併發連接數,以及如何設置?默認 1w,可以設置爲 1~10w,底層使用 IO 多路複用
  • QPS 如何衡量,跟「併發連接數」之間,是什麼關係?1kw

關聯資料

分佈式緩存的:雪崩、擊穿、併發(併發控制)

Note:

  • 併發:大量請求,集中到達,讀取同一個 key,都未命中緩存,都去 DB 中查詢,導致 DB 壓力激增
  • 併發控制:本質就是「互斥鎖」例如 Redis 的 setnx,獲取互斥鎖的,其他請求會「本地自旋」查詢「緩存」
  • 謹慎使用:針對「熱點數據」需要精細分析業務,然後,謹慎使用「緩存的併發控制」

採用 MySQL 存儲數據時,在請求量過大,並且緩存空間有限的情況下,如何考慮「冷數據」的處理:

  • 假設首頁的 QPS 20w,每次 50 個商品,緩存命中率 90%

  • 在不使用 redis 的 mget(multiGet)

可以使用 NoSQL 方案,例如阿里的 Tair,內存存儲,併發效率更高,具體還需要進一步實踐。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章