Redis學習筆記2—緩存、集羣、一致性等

緩存淘汰策略

爲了保證高性能,緩存都保存在內存中,當內存滿了以後,須要經過適當的策略淘汰老數據,以便騰出空間存儲新數據。數據的淘汰策略,典型的包括FIFO(先進先出,淘汰最老數據),LRU(淘汰最近最少使用的),LFU(淘汰使用頻率最低的)。redis

FIFO很簡單就不展開了,主要說下LRU和LFU的區別,詳細區別參考這裏算法

  1. LRU(Least Recently Used),首先淘汰最長時間未被使用的數據。實現方法是每次訪問數據後把數據移到隊頭,刪除時從隊尾開始刪除。
  2. LFU(Least Frequently Used),首先淘汰必定時期內被訪問次數最少的數據。實現方法是記錄數據在必定時段內的訪問評率,刪除訪問頻率最低的數據。此算法須要額外維護每一個數據的訪問量,並排序,實現比較複雜。

Java的LinkedHashMap已經實現了LRU算法,具體實現請查看JDK源碼,使用方法請仔細閱讀LinkedHashMap如下兩個方法的JavaDoc(我貼出來了,註釋有點多,有刪減)。數據庫

/**
     * Constructs an empty <tt>LinkedHashMap</tt> instance with the
     * specified initial capacity, load factor and ordering mode.
     *
     * @param  initialCapacity the initial capacity
     * @param  loadFactor      the load factor
     * @param  accessOrder     the ordering mode - <tt>true</tt> for
     *         access-order, <tt>false</tt> for insertion-order
     * @throws IllegalArgumentException if the initial capacity is negative
     *         or the load factor is nonpositive
     */
    public LinkedHashMap(int initialCapacity,
                         float loadFactor,
                         boolean accessOrder) {
        super(initialCapacity, loadFactor);
        this.accessOrder = accessOrder;
    }
/**
     * Returns <tt>true</tt> if this map should remove its eldest entry.
     *
     * <p>This implementation merely returns <tt>false</tt> (so that this
     * map acts like a normal map - the eldest element is never removed).
     *
     * @param    eldest The least recently inserted entry in the map, or if
     *           this is an access-ordered map, the least recently accessed
     *           entry.  
     * @return   <tt>true</tt> if the eldest entry should be removed
     *           from the map; <tt>false</tt> if it should be retained.
     */
    protected boolean removeEldestEntry(Map.Entry<K,V> eldest) {
        return false;
    }

Redis提供的淘汰策略包括以下幾種:segmentfault

  1. noeviction(內存滿後不主動回收,沒法寫入新數據)
  2. allkeys-lru(最近最少使用的Key)
  3. allkeys-random(隨機回收)
  4. volatile-lru(過時集合中最近最少使用的Key)
  5. volatile-random(過時隨機回收)
  6. volatile-ttl(過時最短存活)

常見緩存性能問題

緩存穿透

緩存穿透是指去獲取一個Redis和DB都不存在的數據,因爲Redis中不存在,致使流量透傳到DB,而DB中無相關數據,查詢後不會緩存結果到Redis。若是大量此類查詢,會給數據庫帶來性能風險,此問題可被攻擊者利用。設計模式

避免方法:緩存

  1. 對於DB中查詢不到的數據,也在Redis中進行短時間緩存,避免反覆查詢DB。
  2. 使用互斥鎖(mutex key):到緩存沒命中時,不是當即去查詢DB,而是先獲取一個互斥鎖(SETNX命令),獲取到鎖成功後再去查詢DB。
  3. 業務層增長校驗,過濾非法數據。
public String get(key) {  
    String value = redis.get(key);  
    if (value == null) { //表明緩存值過時  
       //設置超時,防止del失敗時死鎖  
       if (redis.setnx(key_mutex, 1, 60) == 1) { //表明設置成功  
           value = db.get(key);  
           redis.set(key, value, expire_secs);  
           redis.del(key_mutex);  
        } else {  //沒獲取到鎖,代表其餘線程獲取了,等待一段時間後重試查詢緩存  
           sleep(50);  
           get(key); //重試  
        }  
    } else {  
        return value;  
  }  
}

以上代碼參考自Redis 的key設計技巧&&緩存問題服務器

緩存擊穿

緩存擊穿是指在某個熱點Key過時的時候,客戶端產生大量的狀況,致使請求擊穿緩存直接到達DB,給DB帶來巨大壓力,避免方法請參考上述緩存穿透的互斥鎖數據結構

緩存雪崩

緩存雪崩是指緩存服務器重啓時或者大量緩存在短期內集中過時時,剛好此時大量客戶端執行併發操做,緩存命中失敗致使給DB帶來巨大壓力。多線程

避免方法:併發

  1. 查詢緩存失敗後,查詢數據庫的代碼先加鎖再查詢數據庫,或者隊列執行,對於每一個Key,每一個進程同時只容許一個線程訪問數據庫,減輕數據庫壓力。
  2. 參考上述緩存穿透的互斥鎖
  3. 給每一個Key的過時時間後面加個隨機值,確保緩存不會在同一時刻大面積失效。
  4. 設置熱點數據永不過時,數據更新後,主動刷新緩存。

緩存和數據一致性

多服務寫Redis

首先,多個服務修改同一個Key是很差的設計模式,應該把維護同一個Key的操做集中到一個服務裏,好比更新訂單的狀態,應該都轉發給訂單服務來操做。當多寫狀況沒法避免時,應採起以下措施:

  1. 互斥寫,經過Redis的setex實現互斥說,來競爭對Key的寫入權限。
  2. 使用樂觀鎖,給數據添加版本號或時間戳,經過樂觀鎖判斷是否能夠寫入。

Redis & DB一致性

Redis做爲緩存使用的時候,通常都是DB數據的映像,兩套系統就會存在數據不一致的狀況,如何才能最大限度的下降數據不一致的影響呢?好比數據庫剛寫入一個更新,緩存更新命令還沒執行,這個時候來了個讀請求,從緩存中讀取的數據就不是最新的。

若是對於高一致性要求的場景,只能把讀寫操做串行執行,確保緩存和DB的一致性,但這樣會嚴重下降系統的吞吐量,甚至成爲系統瓶頸。

更通用的保存緩存和DB數據一致性的作法,是DB寫入數據庫後,刪除緩存數據。這樣系統下次讀取請求時,會從DB中讀取最新的數據並進行緩存。採用刪除而不是更新緩存,主要是基於性能的考慮,否則若是反覆更新數據場景下,反覆寫無人消費的緩存數據是一種浪費。

Redis集羣部署

Redis支持主從和分片兩種Cluster部署模式,提供高可用性。

主從

在主備模式下,Redis經過Sentinel哨兵來監控Master的狀態,當Master異常後,從從節點中選出新主節點,並調整其餘從節點的slaveof到新Master。
Sentinel經過部署多實例,實例間經過 Raft協議 實現自身的高可用,全部Sentinel須要部署 3個 節點才能保持自身的健壯性。

在一主多從模式下,爲了減輕Master的數據同步壓力,能夠把主從模式配置爲主從鏈模式,即A是B的主,B是C的主,從而減輕B和C都從A同步數據的壓力。

主從模式下,當有新節點加入時,流程以下:

  1. 新節點向Master發送psync命令
  2. Master執行bgsave,fork子進程,生成RDB數據,並緩存新數據
  3. Master把RDB發送給新節點恢復
  4. Master把緩存中的新數據發送給新節點
  5. 新節點初始化完成,後續經過AOF進行增量數據的同步

分片

分別模式先,Redis經過一致性Hash算法,在內部把數據切分爲16384個slot。經過對數據的Key進行Hash計算來數據保存的分片位置。

數據持久化機制和恢復

Redis支持RDB和AOF兩種持久化機制。
RDB是內存數據庫快照,Redis經過fork子進程把內存存儲(二進制壓縮)爲RDB文件。快照過程當中,新增數據使用COW(copy on write)的模式寫入。RDB適合作災備,可是因爲定時報錯,容易丟失部分數據。
AOF(append only file)是以文本日誌形式記錄Redis每一個寫入和刪除操做。AOF日誌寫入支持靈活的策略,如每秒刷盤,根據數據量刷盤等。
RDB是最新數據快照,文件小,AOF記錄操做過程,文件比較大。數據恢復時,通常採用RDB+AOF的模式來實現,RDB做爲基準數據,疊加快照以後的AOF數據,完成完整的數據恢復。

Keys和Scan方法

Keys會全表掃描,對於單線程的Redis,容易出現卡頓,影響性能。
Scan採用相似分頁獲取的方式,雖然實現代碼複雜一點,並且有可能數據重複,可是不會有性能問題。
通常生產庫,都會禁用keys命令。

Redis vs Memcached

特性 Redis Memcached
性能 單線程非阻塞異步IO,避免線程切換 多線程異步IO,可利用多核
持久化 支持,可做爲NoSQL數據庫 不支持,有效期最長30天
數據結構 支持5種 只支持簡單數據結構
限制 - Key 250字節,Value 1M,緩存30天
HA 主從、Cluster 不支持
相關文章
相關標籤/搜索