爲了保證高性能,緩存都保存在內存中,當內存滿了以後,須要經過適當的策略淘汰老數據,以便騰出空間存儲新數據。數據的淘汰策略,典型的包括FIFO(先進先出,淘汰最老數據),LRU(淘汰最近最少使用的),LFU(淘汰使用頻率最低的)。redis
FIFO很簡單就不展開了,主要說下LRU和LFU的區別,詳細區別參考這裏。算法
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
緩存穿透是指去獲取一個Redis和DB都不存在的數據,因爲Redis中不存在,致使流量透傳到DB,而DB中無相關數據,查詢後不會緩存結果到Redis。若是大量此類查詢,會給數據庫帶來性能風險,此問題可被攻擊者利用。設計模式
避免方法:緩存
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帶來巨大壓力。多線程
避免方法:併發
首先,多個服務修改同一個Key是很差的設計模式,應該把維護同一個Key的操做集中到一個服務裏,好比更新訂單的狀態,應該都轉發給訂單服務來操做。當多寫狀況沒法避免時,應採起以下措施:
Redis做爲緩存使用的時候,通常都是DB數據的映像,兩套系統就會存在數據不一致的狀況,如何才能最大限度的下降數據不一致的影響呢?好比數據庫剛寫入一個更新,緩存更新命令還沒執行,這個時候來了個讀請求,從緩存中讀取的數據就不是最新的。
若是對於高一致性要求的場景,只能把讀寫操做串行執行,確保緩存和DB的一致性,但這樣會嚴重下降系統的吞吐量,甚至成爲系統瓶頸。
更通用的保存緩存和DB數據一致性的作法,是DB寫入數據庫後,刪除緩存數據。這樣系統下次讀取請求時,會從DB中讀取最新的數據並進行緩存。採用刪除而不是更新緩存,主要是基於性能的考慮,否則若是反覆更新數據場景下,反覆寫無人消費的緩存數據是一種浪費。
Redis支持主從和分片兩種Cluster部署模式,提供高可用性。
在主備模式下,Redis經過Sentinel哨兵來監控Master的狀態,當Master異常後,從從節點中選出新主節點,並調整其餘從節點的slaveof到新Master。
Sentinel經過部署多實例,實例間經過 Raft協議 實現自身的高可用,全部Sentinel須要部署 3個 節點才能保持自身的健壯性。
在一主多從模式下,爲了減輕Master的數據同步壓力,能夠把主從模式配置爲主從鏈模式,即A是B的主,B是C的主,從而減輕B和C都從A同步數據的壓力。
主從模式下,當有新節點加入時,流程以下:
分別模式先,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會全表掃描,對於單線程的Redis,容易出現卡頓,影響性能。
Scan採用相似分頁獲取的方式,雖然實現代碼複雜一點,並且有可能數據重複,可是不會有性能問題。
通常生產庫,都會禁用keys命令。
特性 | Redis | Memcached |
---|---|---|
性能 | 單線程非阻塞異步IO,避免線程切換 | 多線程異步IO,可利用多核 |
持久化 | 支持,可做爲NoSQL數據庫 | 不支持,有效期最長30天 |
數據結構 | 支持5種 | 只支持簡單數據結構 |
限制 | - | Key 250字節,Value 1M,緩存30天 |
HA | 主從、Cluster | 不支持 |