文章對Redis數據結構指令、高併發處理、持久化、集羣、複製、Redis 應用等問點作了解析。
java
用XMind畫了一張導圖記錄Redis的學習筆記和一些面試解析(源文件對部分節點有詳細備註和參考資料,歡迎關注個人公衆號:以Java架構贏天下 後臺發送【Redis】拿下載連接,已經完善更新):
面試
String字符串redis
格式:set key value
string類型是二進制安全的。意思是redis的string能夠包含任何數據。好比jpg圖片或者序列化的對象 。
string類型是Redis最基本的數據類型,一個鍵最大能存儲512MB。sql
Hash(哈希)數據庫
格式: hmset name key1 value1 key2 value2
Redis hash 是一個鍵值(key=>value)對集合。
Redis hash是一個string類型的field和value的映射表,hash特別適合用於存儲對象。後端
List(列表)緩存
Redis 列表是簡單的字符串列表,按照插入順序排序。你能夠添加一個元素到列表的頭部(左邊)或者尾部(右邊)
格式:lpush name value
在 key 對應 list 的頭部添加字符串元素
格式:rpush name value
在 key 對應 list 的尾部添加字符串元素
格式:lrem name index
key 對應 list 中刪除 count 個和 value 相同的元素
格式:llen name
返回 key 對應 list 的長度安全
Set(集合)服務器
格式:sadd name value
Redis的Set是string類型的無序集合。
集合是經過哈希表實現的,因此添加,刪除,查找的複雜度都是O(1)。數據結構
zset(sorted set:有序集合)
格式:zadd name score value
Redis zset 和 set 同樣也是string類型元素的集合,且不容許重複的成員。
不一樣的是每一個元素都會關聯一個double類型的分數。redis正是經過分數來爲集合中的成員進行從小到大的排序。
zset的成員是惟一的,但分數(score)卻能夠重複。
Redis 中的事務是一組命令的集合,是 Redis 的最小執行單位,一個事務要麼都執行,要麼都不執行。帶有如下三個重要的保證:
Redis 事務的原理是先將屬於一個事務的命令發送給 Redis,而後依次執行這些命令。
單個 Redis 命令的執行是原子性的,但 Redis 沒有在事務上增長任何維持原子性的機制,因此 Redis 事務的執行並非原子性的。
事務能夠理解爲一個打包的批量執行腳本,但批量指令並不是原子化的操做,中間某條指令的失敗不會致使前面已作指令的回滾,也不會形成後續的指令不作。
DISCARD:取消事務,放棄執行事務塊內的全部命令。
EXEC:執行全部事務塊內的命令。
MULTI:標記一個事務塊的開始。
WATCH:Redis Watch 命令用於監視一個(或多個) key ,若是在事務執行以前這個(或這些) key 被其餘命令所改動,那麼事務將被打斷
UNWATCH :取消 WATCH 命令對全部 key 的監視。
用一句話能夠將持久化歸納爲:將數據(如內存中的對象)保存到可永久保存的存儲設備中。
持久化的主要應用是將內存中的對象存儲在數據庫中,或者存儲在磁盤文件中、 XML 數據文件中等等。
也能夠從以下兩個層面來理解持久化:
應用層:若是關閉( Close )你的應用,而後從新啓動則先前的數據依然存在。
系統層:若是關閉( Shut Down )你的系統(電腦),而後從新啓動則先前的數據依然存在。
Redis 提供兩種方式進行持久化。
RDB 持久化:原理是將 Reids 在內存中的數據庫記錄定時 dump 到磁盤上的 RDB 持久化。
AOF(append only file)持久化:原理是將 Redis 的操做日誌以追加的方式寫入文件。
aof,rdb是兩種 redis持久化的機制。用於crash後,redis的恢復。
rdb的特性以下:
aof有以下特性:
兩種區別就是,一個是持續的用日誌記錄寫操做,crash後利用日誌恢復;一個是平時寫操做的時候不觸發寫,只有手動提交save命令,或者是關閉命令時,才觸發備份操做。
選擇的標準,就是看系統是願意犧牲一些性能,換取更高的緩存一致性(aof),仍是願意寫操做頻繁的時候,不啓用備份來換取更高的性能,待手動運行save的時候,再作備份(rdb)。rdb這個就更有些 eventually consistent的意思了。
RDB優勢
RDB缺點
AOF優勢
AOF缺點:
Redis的內存淘汰策略是指在Redis的用於緩存的內存不足時,怎麼處理須要新寫入且須要申請額外空間的數據。
Redis 的緩存淘汰策略有:
noeviction:當內存不足以容納新寫入數據時,新寫入操做會報錯。
allkeys-lru:當內存不足以容納新寫入數據時,在鍵空間中,移除最近最少使用的key。
allkeys-random:當內存不足以容納新寫入數據時,在鍵空間中,隨機移除某個key。
volatile-lru:當內存不足以容納新寫入數據時,在設置了過時時間的鍵空間中,移除最近最少使用的key。
volatile-random:當內存不足以容納新寫入數據時,在設置了過時時間的鍵空間中,隨機移除某個key。
定時過時策略
每一個設置過時時間的key都須要建立一個定時器,到過時時間就會當即清除。該策略能夠當即清除過時的數據,對內存很友好;可是會佔用大量的CPU資源去處理過時的數據,從而影響緩存的響應時間和吞吐量。
惰性過時策略
只有當訪問一個key時,纔會判斷該key是否已過時,過時則清除。該策略能夠最大化地節省CPU資源,卻對內存很是不友好。極端狀況可能出現大量的過時key沒有再次被訪問,從而不會被清除,佔用大量內存。
按期過時策略
每隔必定的時間,會掃描必定數量的數據庫的expires字典中必定數量的key,並清除其中已過時的key。該策略是前二者的一個折中方案。經過調整定時掃描的時間間隔和每次掃描的限定耗時,能夠在不一樣狀況下使得CPU和內存資源達到最優的平衡效果。
Redis中同時使用了惰性過時和按期過時兩種過時策略。
所謂按期刪除,指的是 redis 默認是每隔 100ms 就隨機抽取一些設置了過時時間的 key,檢查其是否過時,若是過時就刪除。
假設 redis 裏放了 10w 個 key,都設置了過時時間,你每隔幾百毫秒,就檢查 10w 個 key,那 redis 基本上就死了,cpu 負載會很高的,消耗在你的檢查過時 key 上了。注意,這裏可不是每隔 100ms 就遍歷全部的設置過時時間的 key,那樣就是一場性能上的災難。實際上 redis 是每隔 100ms 隨機抽取一些 key 來檢查和刪除的。
可是問題是,按期刪除可能會致使不少過時 key 到了時間並無被刪除掉,那咋整呢?因此就是惰性刪除了。這就是說,在你獲取某個 key 的時候,redis 會檢查一下 ,這個 key 若是設置了過時時間那麼是否過時了?若是過時了此時就會刪除,不會給你返回任何東西。
獲取 key 的時候,若是此時 key 已通過期,就刪除,不會返回任何東西。可是實際上這仍是有問題的,若是按期刪除漏掉了不少過時 key,而後你也沒及時去查,也就沒走惰性刪除,此時會怎麼樣?若是大量過時 key 堆積在內存裏,致使 redis 內存塊耗盡了,咋整?答案是:走內存淘汰機制。
1.一、什麼是緩存雪崩?
若是緩存集中在一段時間內失效,發生大量的緩存穿透,全部的查詢都落在數據庫上,形成了緩存雪崩因爲原有緩存失效,新緩存未到期間全部本來應該訪問緩存的請求都去查詢數據庫了,而對數據庫CPU和內存形成巨大壓力,嚴重的會形成數據庫宕機。
舉例來講, 咱們在準備一項搶購的促銷運營活動,活動期間將帶來大量的商品信息、庫存等相關信息的查詢。 爲了不商品數據庫的壓力,將商品數據放入緩存中存儲。 不巧的是,搶購活動期間,大量的熱門商品緩存同時失效過時了,致使很大的查詢流量落到了數據庫之上。對於數據庫來講形成很大的壓力。
1.二、有什麼解決方案來防止緩存雪崩?
一、加鎖排隊
mutex互斥鎖解決,Redis的SETNX去set一個mutex key,當操做返回成功時,再進行加載數據庫的操做並回設緩存,不然,就重試整個get緩存的方法
二、數據預熱
緩存預熱就是系統上線後,將相關的緩存數據直接加載到緩存系統。這樣就能夠避免在用戶請求的時候,先查詢數據庫,而後再將數據緩存的問題。用戶直接查詢事先被預熱的緩存數據。能夠經過緩存reload機制,預先去更新緩存,再即將發生大併發訪問前手動觸發加載緩存不一樣的key
三、雙層緩存策略
C1爲原始緩存,C2爲拷貝緩存,C1失效時,能夠訪問C2,C1緩存失效時間設置爲短時間,C2設置爲長期
四、定時更新緩存策略
實效性要求不高的緩存,容器啓動初始化加載,採用定時任務更新或移除緩存
五、設置不一樣的過時時間,讓緩存失效的時間點儘可能均勻
2.1.什麼是緩存預熱
緩存預熱就是系統上線後,將相關的緩存數據直接加載到緩存系統。這樣就能夠避免在用戶請求的時候,先查詢數據庫,而後再將數據緩存的問題。用戶直接查詢事先被預熱的緩存數據。如圖所示:
若是不進行預熱, 那麼 Redis 初識狀態數據爲空,系統上線初期,對於高併發的流量,都會訪問到數據庫中, 對數據庫形成流量的壓力。
2.2.有什麼解決方案?
3.一、什麼是緩存穿透?
緩存穿透是指用戶查詢數據,在數據庫沒有,天然在緩存中也不會有。這樣就致使用戶查詢的時候,在緩存中找不到對應key的value,每次都要去數據庫再查詢一遍,而後返回空(至關於進行了兩次無用的查詢)。這樣請求就繞過緩存直接查數據庫
3.二、有什麼解決方案來防止緩存穿透?
一、緩存空對象
簡單粗暴的方法,若是一個查詢返回的數據爲空(無論是數據不存在,仍是系統故障),咱們仍然把這個空結果進行緩存,但它的過時時間會很短,最長不超過五分鐘。
二、布隆過濾器
優點:佔用內存空間很小,位存儲;性能特別高,使用key的hash判斷key存不存在
將全部可能存在的數據哈希到一個足夠大的bitmap中,一個必定不存在的數據會被 這個bitmap攔截掉,從而避免了對底層存儲系統的查詢壓力
降級的狀況,就是緩存失效或者緩存服務掛掉的狀況下,咱們也不去訪問數據庫。咱們直接訪問內存部分數據緩存或者直接返回默認數據。
舉例來講:
對於應用的首頁,通常是訪問量很是大的地方,首頁裏面每每包含了部分推薦商品的展現信息。這些推薦商品都會放到緩存中進行存儲,同時咱們爲了不緩存的異常狀況,對熱點商品數據也存儲到了內存中。同時內存中還保留了一些默認的商品信息。以下圖所示:
降級通常是有損的操做,因此儘可能減小降級對於業務的影響程度。
5.一、什麼是緩存擊穿?
緩存擊穿是指緩存中沒有但數據庫中有的數據(通常是緩存時間到期),這時因爲併發用戶特別多,同時讀緩存沒讀到數據,又同時去數據庫去取數據,引發數據庫壓力瞬間增大,形成過大壓力
5.二、會帶來什麼問題
會形成某一時刻數據庫請求量過大,壓力劇增
5.三、如何解決
5.3.1.使用互斥鎖(mutex key)
這種解決方案思路比較簡單,就是隻讓一個線程構建緩存,其餘線程等待構建緩存的線程執行完,從新從緩存獲取數據就能夠了。 若是是單機,能夠用synchronized或者lock來處理,若是是分佈式環境能夠用分佈式鎖就能夠了(分佈式鎖,能夠用memcache的add, redis的setnx, zookeeper的添加節點操做)。
5.3.2."永遠不過時"
從功能上看,若是不過時,那不就成靜態的了嗎?因此咱們把過時時間存在key對應的value裏,若是發現要過時了,經過一個後臺的異步線程進行緩存的構建,也就是「邏輯」過時
5.3.3.緩存屏障
該方法相似於方法一:使用countDownLatch和atomicInteger.compareAndSet()方法實現輕量級鎖
class MyCache{
private ConcurrentHashMap<String, String> map;
private CountDownLatch countDownLatch;
private AtomicInteger atomicInteger;
public MyCache(ConcurrentHashMap<String, String> map, CountDownLatch countDownLatch, AtomicInteger atomicInteger) {
this.map = map;
this.countDownLatch = countDownLatch;
this.atomicInteger = atomicInteger;
}
public String get(String key){
String value = map.get(key);
if (value != null){
System.out.println(Thread.currentThread().getName()+"\t 線程獲取value值 value="+value);
return value;
}
// 若是沒獲取到值
// 首先嚐試獲取token,而後去查詢db,初始化化緩存;
// 若是沒有獲取到token,超時等待
if (atomicInteger.compareAndSet(0,1)){
System.out.println(Thread.currentThread().getName()+"\t 線程獲取token");
return null;
}
// 其餘線程超時等待
try {
System.out.println(Thread.currentThread().getName()+"\t 線程沒有獲取token,等待中。。。");
countDownLatch.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
// 初始化緩存成功,等待線程被喚醒
// 等待線程等待超時,自動喚醒
System.out.println(Thread.currentThread().getName()+"\t 線程被喚醒,獲取value ="+map.get("key"));
return map.get(key);
}
public void put(String key, String value){
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
map.put(key, value);
// 更新狀態
atomicInteger.compareAndSet(1, 2);
// 通知其餘線程
countDownLatch.countDown();
System.out.println();
System.out.println(Thread.currentThread().getName()+"\t 線程初始化緩存成功!value ="+map.get("key"));
}
}
class MyThread implements Runnable{
private MyCache myCache;
public MyThread(MyCache myCache) {
this.myCache = myCache;
}
@Override
public void run() {
String value = myCache.get("key");
if (value == null){
myCache.put("key","value");
}
}
}
public class CountDownLatchDemo {
public static void main(String[] args) {
MyCache myCache = new MyCache(new ConcurrentHashMap<>(), new CountDownLatch(1), new AtomicInteger(0));
MyThread myThread = new MyThread(myCache);
ExecutorService executorService = Executors.newFixedThreadPool(5);
for (int i = 0; i < 5; i++) {
executorService.execute(myThread);
}
}
}
複製代碼
1.1slaveof命令
1.2.redis.conf配置文件配置
主從結構一是能夠進行冗餘備份,二是能夠實現讀寫分離
主從複製
冗餘備份(還能夠稱爲:主從複製,數據冗餘,數據備份,能夠實現容災快速恢復)
持久化保證了即便redis服務重啓也會丟失數據,由於redis服務重啓後會將硬盤上持久化的數據恢復到內存中,可是當redis服務器的硬盤損壞了可能會致使數據丟失,若是經過redis的主從複製機制就能夠避免這種單點故障. 例如:咱們搭建一個主叫作redis0,兩個從,分別叫作redis1和redis2,即便一臺redis服務器宕機其它兩臺redis服務也能夠繼續提供服務。主redis中的數據和從redis上的數據保持實時同步,當主redis寫入數據時經過主從複製機制會複製到兩個從redis服務上。
1.一個Master能夠有多個Slave,不只主服務器能夠有從服務器,從服務器也能夠有本身的從服務器
2.複製在Master端是非阻塞模式的,這意味着即使是多個Slave執行首次同步時,Master依然能夠提供查詢服務;
3.複製在Slave端也是非阻塞模式的:若是你在redis.conf作了設置,Slave在執行首次同步的時候仍可使用舊數據集提供查詢;你也能夠配置爲當Master與Slave失去聯繫時,讓Slave返回客戶端一個錯誤提示;
4.當Slave要刪掉舊的數據集,並從新加載新版數據時,Slave會阻塞鏈接請求
讀寫分離
主從架構中,能夠考慮關閉主服務器的數據持久化功能,只讓從服務器進行持久化,這樣能夠提升主服務器的處理性能。從服務器一般被設置爲只讀模式,這樣能夠避免從服務器的數據被誤修改。
慢查詢就是指,系統執行命令以後,計算統計每條指令執行的時間,若是執行時間超過設置的閾值,就說明該命令爲慢指令。
Redis 慢查詢配置參數爲:
slowlog-log-slower-than:設置慢查詢定義閾值,超過這個閾值就是慢查詢。單位爲微妙,默認爲 10000。
slowlog-max-len:慢查詢的日誌列表的最大長度,當慢查詢日誌列表處於最大長度的時候,最先插入的一個命令將從列表中移除。
主從模式,通常是一個主節點,一或多個從節點,爲了保證咱們的主節點宕機後,數據不丟失,咱們將主節點的數據備份到從節點,從節點並不進行實際操做,只作實時同步操做,並不能起到高併發的目的。
哨兵模式,一個哨兵集羣和一組主從架構組成。比主從更好的是當咱們的主節點宕機之後,哨兵會主動選舉出一個主節點繼續向外提供服務。
集羣架構,由不少個小主從彙集在一塊兒,一塊兒向外提供服務的,將16384個卡槽切分存儲,並非一個強一致性的集羣架構,每個小主從節點內會存在選舉機制,保證對外的高可用架構。
有時候講出來的更通俗
2020年面試必備的Java後端進階面試題總結了一份將近500頁的pdf文檔,歡迎關注個人公衆號:以Java架構贏天下,回覆【2020】領取這些整理的資料!
喜歡文章記得關注我點個贊喲,感謝支持!