天天看點

[億級流量網站架構讀後記錄二、緩存篇][億級流量網站架構讀後記錄二、緩存篇]

[億級流量網站架構讀後記錄二、緩存篇]

高并發

緩存

​ 作用即讓資料更接近于使用者, 目的是讓通路速度更快.

緩存命中率

​ 從緩存中讀取資料的次數與總讀取次數的比率.

緩存回收政策

  • 基于空間, 空間達到上限按照政策回收.
  • 基于容量, 緩存條目數量達到上限…
  • 基于時間, TTL(Time To Live), 存活達到一定時間…; TTI(Time To Idle), 空閑達到一定時間…
  • 基于java對象引用, 比如軟弱引用.
  • 回收算法, 使用基于空間和容量的緩存會使用一定的政策移除舊資料, 常見如下:
    • FIFO(First In First Out): 先進先出算法, 即先放入緩存的先被移除.
    • LRU(Least Recently Used): 最近最少使用算法, 使用時間距離現在最久的那個被删除
    • LFU(Least Frequently Used): 最不常用算法, 一定時間段内使用頻率最少的被移除
    實際應用中基于LRU的緩存居多, 如Guava Cache, Ehcache 支援 LRU.

java 緩存類型

  • 堆記憶體: 使用java堆記憶體來存儲對象. 好處是不需要序列化和反序列化, 是最快的緩存.缺點就是當緩存資料量很大時, GC暫停時間會很長, 存儲容量受限于堆空間大小. 一般使用軟/弱引用來存儲. 如Guava cache, Ehcache 3.x, MapDB實作.
  • 堆外記憶體: 即緩存資料存儲在堆外記憶體, 可減少GC時間, 可以支援更大的緩存空間. 但是需要序列化.是以會比堆緩存慢得多.
  • 磁盤緩存: 即緩存資料存儲在磁盤上, 當JVM重新開機資料還是存在的, 而堆記憶體和堆外緩存資料會丢失, 需要重新加載. 可以使用Ehcache 3.x, MapDB實作.
  • 分布式緩存: 上邊的緩存是程序内緩存和磁盤緩存, 在多JVM執行個體下, 會存在兩個問題: 1.單機容量問題; 2.資料一緻性問題(多台JVM執行個體的緩存資料不一緻怎麼辦? 可以設定資料的過期時間定時更新資料); 3.緩存不命中時, 需要回溯到DB/服務請求多變問題, 每個執行個體在緩存不命中的情況下都會回溯到DB加載資料, 是以整體對DB的通路就變多了, 解決辦法是使用一緻性哈希分片算法. 是以, 要考慮使用分布式緩存.

兩種模式如下:

  • 單機時: 存儲最熱的資料到堆緩存, 相對熱的資料到堆外緩存, 不熱的資料到磁盤緩存.
  • 叢集時: 存儲最熱的資料到堆緩存, 相對熱的資料到堆外緩存, 全量資料到分布式緩存.

技術舉例:

​ Guava Cache 隻提供堆緩存, 小巧靈活, 性能最好, 如果隻使用堆緩存, 就它了.

​ Ehcache 3.x 提供了堆緩存, 堆外緩存, 磁盤緩存, 分布式緩存. 但是, 這個版本代碼注釋比較少, API 功能還不完善. 如果需要穩定的API和功能, 考慮使用2.x.

​ MapDB 是一款嵌入式Java資料庫引擎和集合架構. 提供了Maps, Sets, Lists, Queues, Bitmaps的支援, 還支援ACID事務, 增量備份. 支援堆緩存, 堆外緩存, 磁盤緩存.

應用級緩存示例

  • 多級緩存API封裝
    • 本地緩存初始化: 本地緩存過期時間使用分布式緩存過期時間的一半, 防止本地緩存資料緩存時間太長造成多執行個體間的資料不一緻; 另外, 将緩存key字首與本地緩存關聯, 進而比對緩存key字首, 就可以找到相關聯的本地的緩存.
    • 寫緩存: 先寫本地緩存, 如果需要寫分布式緩存, 則通過異步更新分布式緩存.
    • 讀緩存: 先讀本地緩存, 本地不命中再批量查詢分布式緩存, 在查詢分布式緩存時通過分區批量查詢(即将key分頁查詢).
  • NULL Cache: 當DB沒有資料時, 寫入NULL對象到緩存. 讀取資料時, 如果發現NULL對象, 則傳回null, 而不是回源到DB. 通過這種方式可防止當key對應的資料在DB中不存在時頻繁查詢DB的情況.
  • 強制擷取最新資料: 可通過ThreadLocal開關來決定是否強制重新整理緩存.
  • 失敗統計
  • 延遲報警(不能頻繁報警, 可考慮N久報警了M次)
  • 緩存使用模式實踐

    ​ 前人總結好的模式, 主要分為兩大類: Cache-Aside和Cache-As-SoR(Read-through、Write-through、Write-behind)。

    ​ SoR(system-of-record):記錄系統,或者可以叫做資料源。

    ​ Cache: 緩存,是SoR的快照資料,Cache的通路速度比SoR要快,放入Cache的目的是提升通路速度,減少回源到SoR的次數。

    ​ 回源:即回到資料源頭擷取資料。

    ​ Cache-Aside:即業務代碼圍繞Cache寫,比如讀取緩存,不存在則回源。适合AOP實作。可能存在并發更新的情況:

    • 如果是使用者次元的資料,這種幾率非常小,可以不考慮,加上過期時間來解決即可。
    • 對于如商品這種基礎資料,可考慮使用cannal訂閱binlog,來進行增量更新分布式緩存,這樣不會存在緩存資料不一緻的情況。但是,緩存更新會存在延遲。而本地緩存可根據不一緻容忍度設定合理的過期時間。
    • 讀服務場景,可以考慮使用一緻性哈希,将相同的操作負載均衡到同一個執行個體,進而減少并發幾率。或者設定比較短的過期時間。

    ​ Cache-As-SoR:即把Cache看作SoR,所有操作針對Cache進行,然後Cache再委托給SoR進行真實的讀寫。即業務代碼中隻看到Cache的操作。看不到SoR的操作。有三種實作:read-through、write-through、write-behind。

    ​ Read-through:業務代碼首先調用Cache,如果Cache不命中由Cache回源到SoR,而不是業務代碼。Guava cache和Ehcache 3.x都支援該模式。好處是,應用業務代碼更簡潔了,沒有重複代碼;解決Dog-pile effect,即當某個緩存失效時,又有大量相同的請求沒命中緩存,進而使請求同時到後端,導緻後端壓力太大,此時限定一個請求去拿即可。

    ​ Write-Through:稱為穿透寫模式/直寫模式–業務代碼首先調用Cache寫(新增/修改),然後由Cache負責寫緩存和寫SoR。目前隻有Ehcache 3.x支援。

    ​ Write-Behind:也叫Wrtie-Back,即回寫模式。不同于Write-Through是同步寫SoR和Cache,Write-Behind是異步寫。異步寫可實作批量寫,合并寫,延時和限流。

  • Copy Pattern

    ​ 有兩種Copy Pattern,Copy-On-Read(在讀時複制)和Copy-On-Write(在寫時複制),在Guava Cache和Ehcache中堆緩存都是基于引用的,這樣如果有人拿到緩存資料并修改,則發生不可預測的問題。Ehcache 3.x提供了支援。

  • 性能測試,可使用JMH1.4進行基準性能測試。