分散式快取方案

女友在高考發表於2021-08-22

一、從資料說起

我們再做快取之前需要把資料先分好類

按變化頻率:

  • 靜態資料:一般不變的,類似於字典表
  • 準靜態資料:變化頻率很低,部門結構設定,全國行政區劃資料
  • 中間狀態資料:一些計算的可複用中間資料,變數副本,配置中心的本地副本

按使用頻率:

  • 熱資料:使用頻率高的
  • 讀寫比大的:讀的頻率遠大於寫的頻率

這些資料就比較適合使用快取。

快取無處不在。記憶體可以看作是cpu和磁碟之間的快取。cpu與記憶體的處理速度也不一致,所以出現了L1&L2 Cache

快取的本質:系統各級之間處理速度不匹配,利用空間換時間。

快取載入時間

1. 啟動時全量載入

2. 懶載入

2.1. 同步使用載入

先看快取裡是否有資料,沒有的話從資料庫讀取。讀取的資料,先放到記憶體,然後返回給呼叫方。

2.2. 延遲非同步載入

從快取裡獲取資料,不管有沒有都直接返回。

策略1:如果快取為空的話,則發起一個非同步執行緒負責載入。

策略2:非同步執行緒負責維護快取的資料,定期或根據條件觸發更新。

快取過期策略

  • 按FIFO或LRU
  • 固定時間過期
  • 根據業務進行時間的加權。

二、本地快取

  1. Map 快取
public static final Map<String,Object> CACHE=new HashMap();
CACHE.put("key","value");
  1. Guava快取
Cache<String,String> cache = CacheBuilder.newBuilder() .maximumSize(1024) .expireAfterWrite(60,TimeUnit.SECONDS) .weakValues() .build();
cache.put("word","Hello Guava Cache");
System.out.println(cache.getIfPresent("word"));
  1. Spring Cache
  • 基於註解和AOP,使用方便
  • 可以配置Condition和SPEL,非常靈活
  • 需要注意:繞過Spring的話,註解無效

核心功能:@Cacheable、@CachePut、@CacheEvict

本地快取的缺點:

  • 在叢集環境中,如果每個節點都儲存一份快取,導致佔用記憶體變大
  • 在JVM中長期存在,會影響GC
  • 快取資料的排程處理,影響業務執行緒,爭奪資源

三、遠端快取

  1. Redis

Redis是一個開源的使用ANSI C語言編寫的,基於記憶體也可以持久化的key-value資料庫,並提供多種語言的API
2. Memcached

memcached是一套分散式的快取記憶體系統,由LiveJournal的Brad Fitzpatrick開發,但被許多網站使用。這是一套開放原始碼軟體,以BSD license授權釋出。

四、記憶體網格

  1. Hazelcast
  2. lgnite

五、快取常見問題

1. 快取穿透

問題描述:大量併發查詢不存在的KEY,導致都直接把壓力透傳到資料庫上。

分析:因為資料庫裡沒有值,所以沒有建立快取,導致一直打到資料庫上。

解決辦法:

  1. 快取空值的KEY
  2. Bloom過濾或RoaringBitmap判斷KEY是否存在
  3. 完全以快取為準,使用延遲非同步載入的方式去載入資料庫資料到快取。

Bloom過濾器示例:
(引入guava依賴)

 public static void main(String[] args) {
        BloomFilter<CharSequence> filter = BloomFilter.create(
                Funnels.stringFunnel(Charsets.UTF_8),//Funnels.integerFunnel(), //資料格式
                1000000,//預計存入資料量
                0.01);//誤判率

        System.out.println(filter.mightContain("abcdefg"));
        filter.put("abcdefg");
        System.out.println(filter.mightContain("abcdefg"));
    }

RoaringBitmap示例:
引入依賴:

<dependency>
			<groupId>org.roaringbitmap</groupId>
			<artifactId>RoaringBitmap</artifactId>
			<version>0.8.1</version>
		</dependency>
 public static void test3(){
        Roaring64NavigableMap roaring64NavigableMap = Roaring64NavigableMap.bitmapOf(3, 4, 5, 90);
        //是否包含
        boolean contains = roaring64NavigableMap.contains(3);
        long l = roaring64NavigableMap.rankLong(3);
        System.out.println(l);
        System.out.println(contains);
    }

2. 快取擊穿

問題:當某個KEY失效的時候,正好有大量併發請求訪問這個KEY

分析:跟快取穿透比較像,這個是屬於偶然的

解決辦法:

  1. KEY的更新的時候新增全域性互斥鎖
  2. 完全以快取為準,使用延遲非同步載入的策略

3. 快取雪崩

問題:當某一個時刻發生大規模的快取失效的情況,會有大量請求打到資料庫,導致資料庫壓力過大而當機

分析:一般來說,由於更新策略、或者資料熱點、快取服務當機等原因,導致快取資料同時大規模不可以。

解決辦法:

  1. 快取更新、失效策略在時間上做到比較均勻
  2. 使用的熱資料儘量分散到不同機器上
  3. 多臺機器做主從複製,實現高可用
  4. 實現熔斷限流機制,對系統進行負載能力控制
  5. 使用本地快取兜底

番外:

布隆過濾器:

目標就是要基於過濾器已儲存生成的原始後設資料,進行比較過濾,如果是在原始後設資料集合裡面的,一定會被發現。也有可能不是裡面的被誤殺。

BloomFilter 會開闢一個m位的bitArray(位陣列),開始所有資料都部署為0,當一個元素過來的時候,通過多個hash函式計算出不同的值,然後根據hash值找到對應的下標處,將裡面的值改為1.

優點:使用計算,節省儲存空間。

缺點:有失誤率。不是在過濾器原始表裡的資料也會被誤算進去。

使用場景:目標就是要基於過濾器已儲存生成的原始後設資料,進行比較過濾,如果是在原始後設資料集合裡面的,一定會被發現。布隆過濾器核心正確的使用就是進行過濾禁止,進行正確的否定。

舉例:如我們有100萬個黑名單的url地址,過來一個地址我們算出來不在裡面,那就肯定可以放行。

BitMap:

BitMap的基本思想是用一個bit位來標記某個元素對應的值,這樣就可以大大節省空間。

在Java中一個int佔4個位元組,也就是32bit。按int儲存和按位儲存的大小差距是32倍。

那麼怎麼表示一個數呢?可以使用1表示存在,0表示不存在。

如下面:表示{2,6}

一個byte只有8個位置,如果想表示13怎麼辦呢?只能再用一個byte了,就成了一個二維陣列了

1個int佔32位,那麼我們只需要申請一個int陣列長度為 int tmp[1+N/32] 即可儲存,其中N表示要儲存的這些數中的最大值

使用場景:

  1. 快速排序

把數放進去之後,遍歷一遍,把值是1的都取出來就排好序了。

  1. 快速去重

20億個整數中找出不重複的整數的個數?

記憶體不足以容納這20億個整數。我們怎麼表示數字的狀態呢?一個數的狀態可以分為3種,不存在、存在一次、存在兩次及以上。這就需要兩個bit來表示。00代表不存在,01代表一次,11代表兩次及以上。

接下來我們就把這20億個整數放進去,如果狀態為00,就改為01,如果狀態為01就改為11.如果狀態為11,就不動了。都放完後,遍歷取出值為01的,就是不重複的資料的個數。。
3. 快速查詢

給定一個整數M,M/32就能得到int陣列的下標,M%32就知道在這個下標裡面的具體位置。

如13,就能算出在int[0]裡面的第13個

相關文章