目錄
[TOC]
基本特性以及優勢
==============
- 讀寫性能優異:全部在內存中計算,單線程,IO非阻塞。
- 支持數據持久化,支持AOF和RDB兩種持久化方式
- 數據結構豐富:基於KEY-VALUE除了支持string類型的value外還支持string、hash、set、sortedset、list等數據結構。
- 功能豐富:Redis還支持 publish/subscribe, 通知, key 過期等等特性;簡單的事務;pipeline批量命令執行。
- 多語言支持: Redis支持的客戶端操作語言非常豐富,基本的主流語言都支持。
- 可複製:支持主從複製。
- 高可用分佈式: redis sensinel,redis cluster。
各大類型的典型應用場景
================
String類型的典型應用場景—緩存
緩存一致性問題:
操作1 | 操作2 | 最終結果 |
---|---|---|
DB 更新成功 | 緩存更新失敗 | 數據不一致 |
緩存更新成功 | DB更新失敗 | 數據不一致 |
DB 更新成功 | 緩存淘汰失敗 | 數據不一致 |
緩存淘汰成功 | DB 更新成失敗 | 緩存未命中 |
更新緩存VS淘汰緩存
更新緩存:同事寫入數據庫和緩存
淘汰緩存:只寫入數據庫,刪除緩存; 比更新緩存多了一次miss,需要多查詢一次。
那麼如果選擇??
如果更新的值是直接查詢的,選擇更新; 如果是通過比較複雜的業務邏輯計算的,更新比淘汰緩存代價大,選擇淘汰緩存。
先操作數據庫 or 先操作緩存?
先操作緩存只會造成一次cache miss,不會出現數據不一致的情況, 所以一般選前者。
緩存常見問題以及解決思路:
緩存穿透問題:
緩存穿透是指查詢一個一定不存在的數據,由於緩存是不命中時被動寫的,並且出於容錯考慮,如果從存儲層查不到數據則不寫入緩存,這將導致這個不存在的數據每次請求都要到存儲層去查詢,失去了緩存的意義。在流量大時,可能DB就掛掉了,要是有人利用不存在的key頻繁攻擊我們的應用,這就是漏洞。
緩存穿透的解決方案:有很多種方法可以有效地解決緩存穿透問題,最常見的則是採用布隆過濾器,將所有可能存在的數據哈希到一個足夠大的bitmap中,一個一定不存在的數據會被 這個bitmap攔截掉,從而避免了對底層存儲系統的查詢壓力。另外也有一個更爲簡單粗暴的方法(我們採用的就是這種),如果一個查詢返回的數據爲空(不管是數 據不存在,還是系統故障),我們仍然把這個空結果進行緩存,但它的過期時間會很短,最長不超過五分鐘。
緩存雪崩:
緩存雪崩是指在我們設置緩存時採用了相同的過期時間,導致緩存在某一時刻同時失效,請求全部轉發到DB,DB瞬時壓力過重雪崩。
緩存雪崩的解決方案:
考慮用加鎖或者隊列的方式保證緩存的單線 程(進程)寫,從而避免失效時大量的併發請求落到底層存儲系統上。這裏分享一個簡單方案就時講緩存失效時間分散開,比如我們可以在原有的失效時間基礎上增加一個隨機值,比如1-5分鐘隨機,這樣每一個緩存的過期時間的重複率就會降低,就很難引發集體失效的事件。
緩存擊穿:
對於一些設置了過期時間的key,如果這些key可能會在某些時間點被超高併發地訪問,是一種非常“熱點”的數據。這個時候,需要考慮一個問題:緩存被“擊穿”的問題,這個和緩存雪崩的區別在於這裏針對某一key緩存,前者則是很多key。
緩存擊穿解決方案:
給查詢緩存加鎖,如果查詢時緩存不存在則枷鎖去數據庫查詢,結束後釋放該鎖。
String類型的典型應用場景—分佈式鎖
這裏簡單介紹一種單點悲觀鎖;還有比較靠譜的多節點分佈式鎖,或者樂觀鎖這裏就不做介紹。
單點分佈式鎖代碼示例:
一些人可能想直接用setNx命令來實現分佈式鎖,但是會有一個問題,setNx和EXPIRE命令是非原子性操作;如果兩步走會存在一定的問題。
加鎖
public boolean tryLock(String key, String value, long seconds) {
Jedis jedis = null;
String res = null;
try {
jedis = getJedis();
res = jedis.set(key, value, "NX", "EX", seconds);
} catch (Exception e) {
e.printStackTrace();
} finally {
if (jedis != null) {
jedis.close();
}
}
return "OK".equals(res) ? true : false;
}
解鎖:
public boolean unLock(String key, String value) {
String script = "if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1]) else return 0 end";
Boolean res = false;
Jedis jedis = null;
try {
jedis = getJedis();
Object result = jedis.eval(script, Collections.singletonList(key), Collections.singletonList(value));
if (result.equals(1L)) {
res = true;
}
} catch (Exception e) {
e.printStackTrace();
} finally {
if (jedis != null) {
jedis.close();
}
}
return res;
}