Redis 四連發(fā):緩存雪崩、穿透、預(yù)熱、降級

一.Redis緩存雪崩

Redis緩存雪崩和穿透乍一看好像差不多,概念容易混淆.
緩存雪崩是指在我們設(shè)置緩存失效時間上時采用了相同的過期時間,導(dǎo)致緩存在某一時刻同時失效,請求全部打到后端數(shù)據(jù)庫,數(shù)據(jù)庫一時請求過大,數(shù)據(jù)庫cpu和IO一時負載過大,造成雪崩。如果不能理解的話,閉上眼睛想象一下,雪山崩塌的場景,還不能理解的話,那就算了吧.

用戶請求.jpg

這塊其實業(yè)界有很多解決方案,但是沒有哪一種方案說是完美的,需要結(jié)合實際并發(fā)量.第一種就是加鎖排隊,串行的去執(zhí)行,但是這本質(zhì)上是一種緩解,允許等待,從用戶角度來說不是很好.第二種比較多的就是mutex互斥鎖,比如Redis分布式鎖SetNX,如果緩存里面沒有并不是去加載數(shù)據(jù)庫.第三種限流:比如用滑動窗口控制,在大流量來的時候縮緊;或者通過令牌桶、漏桶算法等.第四種就是做二級緩存,或者雙緩存策略。比如B為原始緩存,B2為拷貝緩存,B失效時,可以訪問B2,B1緩存失效時間設(shè)置為短期,B2設(shè)置為長期。這塊有點master-slave,主從的意思.
這些技術(shù)手段,想達到的終極目標(biāo)無外乎就是讓失效時間達到均勻的狀態(tài).

二.Redis穿透
Redis穿透是大量的請求在緩存中沒有命中,導(dǎo)致每次都要到數(shù)據(jù)庫里面去查詢,這樣導(dǎo)致緩存被穿透.
解決方案:
1.Bloom filter
布隆過濾器背景:(Bloom Filter)是由伯頓·布隆(Burton Bloom)于1970年提出來的,它實際上是一個很長的二進制向量和一系列隨機映射函數(shù)。
我們知道Redis之所以快除了單線程避免了CPU上下文切換,采用了epoll機制,還有一個重要的問題是他的存儲結(jié)構(gòu),時間復(fù)雜度是o(1),但是redis的存儲空間是很珍貴的,過多的key對于redis來說是一件麻煩的事情,比如你有幾億個key,這種一股腦的丟到Redis,很不合理.Bloom Filter或許就是一種改善的解決方案,極大的減小了存儲空間.布隆過濾器是一個位向量或者說是位數(shù)組.

但是對于布隆過濾器的使用一定要謹慎,Bloom過濾器比較雞肋的地方是它存在一定的概率的誤判,我們在學(xué)術(shù)上稱他為假陽性,而且隨著元素的增加,這種誤判的機率會隨著增加,但是誤判的概率幾乎可以忽略,影響不到,一般key不多的情況,用散列表就可以了,唯一的好處就是節(jié)省空間.目前它只支持add和isExist操作,不支持delete操作,這個理解它的原理的很容易明白,因為你刪除更新那一位1,正好可能也是別的key的entry.下面看一下代碼吧:

布隆過濾器在Guava中的實現(xiàn):

創(chuàng)建:

static <T> BloomFilter<T> create(
      Funnel<? super T> funnel, long expectedInsertions, double fpp, Strategy strategy) {
    checkNotNull(funnel);
    checkArgument(
        expectedInsertions >= 0, "Expected insertions (%s) must be >= 0", expectedInsertions);
    checkArgument(fpp > 0.0, "False positive probability (%s) must be > 0.0", fpp);
    checkArgument(fpp < 1.0, "False positive probability (%s) must be < 1.0", fpp);
    checkNotNull(strategy);

    if (expectedInsertions == 0) {
      expectedInsertions = 1;
    }
    /*
     * TODO(user): Put a warning in the javadoc about tiny fpp values, since the resulting size
     * is proportional to -log(p), but there is not much of a point after all, e.g.
     * optimalM(1000, 0.0000000000000001) = 76680 which is less than 10kb. Who cares!
     */
    long numBits = optimalNumOfBits(expectedInsertions, fpp);
    int numHashFunctions = optimalNumOfHashFunctions(expectedInsertions, numBits);
    try {
      return new BloomFilter<T>(new BitArray(numBits), numHashFunctions, funnel, strategy);
    } catch (IllegalArgumentException e) {
      throw new IllegalArgumentException("Could not create BloomFilter of " + numBits + " bits", e);
    }
  }

create里面有4個參數(shù),funnel:輸入的數(shù)據(jù),expectedInsertions:預(yù)估計插入的元素總量,fpp:你自己想達到的誤判率,strategy:實現(xiàn)的實例

BloomFilterStrategies類:
MURMUR128_MITZ_64() {
    @Override
    public <T> boolean put(
        T object, Funnel<? super T> funnel, int numHashFunctions, BitArray bits) {
      long bitSize = bits.bitSize();
      byte[] bytes = Hashing.murmur3_128().hashObject(object, funnel).getBytesInternal();
      long hash1 = lowerEight(bytes);
      long hash2 = upperEight(bytes);

      boolean bitsChanged = false;
      long combinedHash = hash1;
      for (int i = 0; i < numHashFunctions; i++) {
        // Make the combined hash positive and indexable
        bitsChanged |= bits.set((combinedHash & Long.MAX_VALUE) % bitSize);
        combinedHash += hash2;
      }
      return bitsChanged;
    }

    @Override
    public <T> boolean mightContain(
        T object, Funnel<? super T> funnel, int numHashFunctions, BitArray bits) {
      long bitSize = bits.bitSize();
      byte[] bytes = Hashing.murmur3_128().hashObject(object, funnel).getBytesInternal();
      long hash1 = lowerEight(bytes);
      long hash2 = upperEight(bytes);

      long combinedHash = hash1;
      for (int i = 0; i < numHashFunctions; i++) {
        // Make the combined hash positive and indexable
        if (!bits.get((combinedHash & Long.MAX_VALUE) % bitSize)) {
          return false;
        }
        combinedHash += hash2;
      }
      return true;
    }

底層bit數(shù)組實現(xiàn):

 static final class BitArray {
    final long[] data;
    long bitCount;

    BitArray(long bits) {
      this(new long[Ints.checkedCast(LongMath.divide(bits, 64, RoundingMode.CEILING))]);
    }

    // Used by serialization
    BitArray(long[] data) {
      checkArgument(data.length > 0, "data length is zero!");
      this.data = data;
      long bitCount = 0;
      for (long value : data) {
        bitCount += Long.bitCount(value);
      }
      this.bitCount = bitCount;
    }

    /** Returns true if the bit changed value. */
    boolean set(long index) {
      if (!get(index)) {
        data[(int) (index >>> 6)] |= (1L << index);
        bitCount++;
        return true;
      }
      return false;
    }

    boolean get(long index) {
      return (data[(int) (index >>> 6)] & (1L << index)) != 0;
    }

    /** Number of bits */
    long bitSize() {
      return (long) data.length * Long.SIZE;
    }
...
}

2.緩存這些空對象,但是注意失效時間設(shè)置的小一點,防止Redis的key過多.

三.Redis預(yù)熱
緩存預(yù)熱就是系統(tǒng)發(fā)布之前,先把緩存數(shù)據(jù)加載到緩存系統(tǒng)里面,這樣避免了活動正式開始之前,首次沒有命中,要查數(shù)據(jù)庫的問題,另外一方面預(yù)熱也是提前對流量的一種預(yù)估方式,很多大型活動或者秒殺,都會提前來一波預(yù)熱.

四.降級
當(dāng)并發(fā)量大的時候,響應(yīng)很慢或者超時,影響到核心鏈路的業(yè)務(wù)處理(比如支付、訂單等),或者導(dǎo)致到上游的系統(tǒng)的扇出,這種情形我需要對服務(wù)進行降級,換句話說就是保帥丟兵的思想.

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容