redis緩存失效問題

redis數據失效導致的雪崩

因爲緩存失效,從而導致大量的請求沒有命中緩存,導致請求全部打到數據庫。

1.大量請求,導致數據庫處理不過來,整個系統依賴數據庫的功能全部崩潰。

2.單系統掛掉,其它依賴於該系統的應用也會出現不穩定甚至崩潰。

redis數據失效的場景

1.因爲打到內存閥值,採用數據淘汰策略(LRU/LFU)導致數據失效。

2.數據設置了過期時間,達到過期時間後,數據失效。

3.因爲故障或者宕機/升級,導致服務重啓,數據失效。

防止redis數據失效方法

1.保證內存充足,淘汰的數據都是非熱點的數據,不會導致系統崩潰;可以採用redis集羣分片部署。

2.對於大量的過期數據,設置過期錯開,不要設置同一個過期時間,導致某一時刻,大量數據不命中。

3.做好數據備份,做好主從複製。

緩存雪崩解決方案

1.對數據庫訪問進行限流,保證不會因爲緩存雪崩導致,數據庫故障——使用信號量控制併發。

2.容錯降級——返回指定的異常碼。

代碼實例:

 // 數據庫限流,根據數據庫連接大小定義
    Semaphore semaphore = new Semaphore(30);
 
    public Object queryStock(String goodsId) {
        String cacheKey = "goodsStock-" + goodsId;
        // 1.先從Redis裏面獲取數據
        String value = mainRedisTemplate.opsForValue().get(cacheKey);
        // 2.緩存裏面沒有數據,從數據庫取
        if (value != null) {
            logger.warn(Thread.currentThread().getName() + "緩存中獲得數據+++++++++++++++++++++++++++");
            return value;
        }
        // 3.去請求數據庫,需要進行控制,根據連接數進行控制
        try {
            // 同一時間,只能有30個請求去數據庫獲取數據,並且重構緩存
            boolean acquire = semaphore.tryAcquire(5, TimeUnit.SECONDS);
            if (acquire) {
                // 再次從Reids中獲取數據
                value = mainRedisTemplate.opsForValue().get(cacheKey);
                if (value != null) {
                    logger.warn(" 緩存中獲得數據+++++++++++++++++++++++++++");
                    return value;
                }
                value = databaseService.queryFromDatabase(goodsId);
                System.err.println(" 數據庫中獲得數據==============================");
                // 3.塞到緩存,過期時間
                String v = value;
                mainRedisTemplate.execute((RedisCallback<Boolean>) conn ->{
                    return conn.setEx(cacheKey.getBytes(), 120, v.getBytes());
                });
            } else {
                // 等待時間
                // 定義錯誤信息返回指定錯誤碼
            }
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            // 釋放信號量
            semaphore.release();
        }
        return value;
    }

布隆過濾器——過濾不必要的查詢

對於有些場景,當訪問一個本就不存的數據時,這時我們不需要去走redis和數據庫查詢,應該直接返回一個錯誤的提示,這樣可以防止惡意訪問,導致服務器癱瘓。

布隆過濾器(Bloom Filter):是1970年布隆提出的,它實際上是一個很長的二進制數組和一系列hash函數,布隆過濾器可以用於檢索一個元素是否在一個集合中;它的優點是空間效率和查詢時間都比一般的算法要好的多,缺點是有一定的誤識別率和刪除困難。

布隆過濾器的構建過程:

1.加載符合條件的記錄

2.計算每條元素的hash值

3.計算hash值對應二進制數據的位置

4.將對應位置的值改爲1

查找元素是否存在的過程:

1.計算元素的hash值

2.計算hash值對應二進制數組的位置

3.找到數組中對應的位置的值,0代表不存在,1代表存在。

代碼實例:

@Service
// 構建一個布隆過濾器
public class RedisBloomFilter {
  // redis連接信息
  @Value("${spring.redis.main.hostName}")
  private String redisHost;
  
  @Value("${spring.redis.main.port}")
  private int redisPort;
  
  // bloomfilter 客戶端
  private Client client;
  
  @PostConstruct
  public void init() {
    // bloomfilter 客戶端
    client = new Client(redisHost, redisPort);
  }
  
  /**
   * 創建一個自定義的過濾器
   * @param filterName
   */
  public void createFilter(String filterName) {
    // 創建一個容量10萬,誤判率0.01%的布隆過濾器
    client.createFilter(filterName, 1000, 0.1);
  }
  
  /**
   * 添加元素
   * @param filterName
   * @param value
   * @return
   */
  public boolean addElement(String filterName, String value) {
    return client.add(filterName, value);
  }
 
  /**
   * 判斷元素是否存在
   * @param filterName
   * @param value
   * @return
   */
  public boolean exists(String filterName, String value) {
    return client.exists(filterName, value);
  }
}
public Object queryStock(final String goodsId) {
      String cacheKey = "goodsStock-"+ goodsId;
      // 增加一個布隆過濾器的篩選
      boolean exists = filter.exists("goodsBloomFilter", cacheKey);
      if(!exists) {
        logger.warn(Thread.currentThread().getName()+" 您需要的商品不存在+++++++++++++++++++++++++++");
        return "您需要的商品不存在";
      }
      
      public Object queryStock(String goodsId) {
        String cacheKey = "goodsStock-" + goodsId;
        // 1.先從Redis裏面獲取數據
        String value = mainRedisTemplate.opsForValue().get(cacheKey);
        // 2.緩存裏面沒有數據,從數據庫取
        if (value != null) {
            logger.warn(Thread.currentThread().getName() + "緩存中獲得數據+++++++++++++++++++++++++++");
            return value;
        }
        // 3.去請求數據庫,需要進行控制,根據連接數進行控制
        try {
            // 同一時間,只能有30個請求去數據庫獲取數據,並且重構緩存
            boolean acquire = semaphore.tryAcquire(5, TimeUnit.SECONDS);
            if (acquire) {
                // 再次從Reids中獲取數據
                value = mainRedisTemplate.opsForValue().get(cacheKey);
                if (value != null) {
                    logger.warn(" 緩存中獲得數據+++++++++++++++++++++++++++");
                    return value;
                }
                value = databaseService.queryFromDatabase(goodsId);
                System.err.println(" 數據庫中獲得數據==============================");
                // 3.塞到緩存,過期時間
                String v = value;
                mainRedisTemplate.execute((RedisCallback<Boolean>) conn ->{
                    return conn.setEx(cacheKey.getBytes(), 120, v.getBytes());
                });
            } else {
                // 等待時間
                // 定義錯誤信息返回指定錯誤碼
            }
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            // 釋放信號量
            semaphore.release();
        }
        return value;
    }

 

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