Redis
-
Redis數據結構
String字符串,list鏈表,hash鍵值對,set集合,sortedset有序集合,BloomFilter布隆過濾器
布隆過濾器原理:當一個元素被加入到集合中時,通過K個散列函數將元素分布到一個位數組上的K個點,查詢該元素的時候,如果hash出來的這個K個點都為1,則說明元素可能存在,如果有一個為0,則說明元素一定不存在。優點:可用於防止緩存穿透,數據庫的元素先散列到布隆數組中,查詢時先通過布隆過濾器判斷是否存在再查詢。缺點:犧牲了准確性和便利性,查詢出來位數組上的點全為1也不一定存在。刪除時也不能修改位數組,否則容易影響其他數據的查詢
-
Redis數據結構的底層實現
- String:
struct sdshdr{ //記錄buf數組中已使用字節的數量 //等於 SDS 保存字符串的長度 int len; //記錄 buf 數組中未使用字節的數量 int free; //字節數組,用於保存字符串 char buf[]; }
優點:不會有字符串變更造成的內存溢出問題;獲取字符串長度的時間復雜度為1;空間預分配,惰性空間釋放free字段,會默認留一定的空間放置多次重分配內存
- Hash:數組+鏈表的基礎上,進行了一些rehash的優化,
- Redis的hash采用鏈地址法來處理沖突,沒有使用紅黑樹
- hash表節點采用單鏈表結構
- rehash優化:分治思想,將龐大的遷移工作量分到每一次curd中,避免服務繁忙
- List:雙向鏈表
- set:內部實現是value為null的Hashmap,通過計算hash的方式來排重。
- zset:使用hashmap和跳躍表(skiplist)來保證數據的存儲和有序,Hashmap存放的是成員到score的映射,跳躍表中存放的是所有成員,排序依據是score,使用跳躍表機構可以獲得比較高的查詢效率,並且實現上簡單。跳躍表:每個節點維持多個指向其他節點的指針,從而達到快速訪問節點的目的
- String:
-
Redis有事務嗎?
- multi開啟事務
- exec執行事務內命令
- discard取消事務
- watch監視一個或多個key,key被改動時事務將會被打斷
-
Redis單線程為什么執行這么快
- 純內存操作,讀寫的時候不會受到磁盤IO的限制
- 單線程操作,避免了切換上下文和競爭條件,也避免了多進程或線程切換的CPU損耗,也不存在死鎖的問題
- 采用了IO多路復用機制
-
緩存問題:雪崩,穿透,擊穿
雪崩:同一時間內大量的緩存失效導致DB壓力過大。
這種情況可以對緩存的key在默認值上加上一些隨機時間,讓緩存失效的時間分散開來
穿透:指緩存和數據庫中都沒有的數據,而用戶不斷發起請求
這種情況可以對一些查詢出來不存在的數據緩存一個null,失效時間設置短一些防止數據庫新增了這個元素
還可以使用布隆過濾器,在查詢之前先通過布隆過濾器判斷是否存在
擊穿:這個和穿透有點像,是指某個熱點key在大量訪問的時候突然失效了,導致請求直接來到數據庫。
熱點key設置為永不過期,或者定時對這個key進行更新
-
Redis出現熱點key造成集群訪問量傾斜的解決辦法?
- 使用本地緩存
- 利用分片算法的特性,對key進行打散處理,給hotkey加上前綴或后綴,把一個hotkey的數量變成redis實例個數的倍數
-
Redis里面有1億個key,其中有10w個key是以某個固定的已知的前綴開頭的,如何將它們全部找出來
redis中有個指令是keys,可以指定模式查詢對應的key列表
----如果這個redis正在給線上的業務提供服務,那使用keys指令會有什么問題?
redis是單線程的,keys指令會阻塞一段時間,查詢完成才能恢復。可以使用scan指令替代,scan是無阻塞的指令,不過scan可能會查出重復的數據,這個可以在客戶端做一些去重。
-
有使用過redis做異步隊列嗎?
redis的list就可以做消息隊列,通過rpush插入,lpop彈出。
----能否生產一次,消費多次?
redis有pub/sub模型,可以實現一對多的消費,不過在消費者掉線的時候會有消息丟失的問題,這類情況還是使用專業的消息中間件比較好
----那有用過redis做延時隊列嗎?
可以使用sortedset,拿時間戳作為score,消息內容作為key調用zadd來生產消息,消費者用zrangebyscore指令獲取N秒之前的數據輪詢進行處理。
-
redis怎么做持久化的?
redis有兩種持久化的方式,AOF和RDB,其中AOF是增量持久化,RDB是全量持久化,所有一般RDB會比較耗時,需要配合AOF使用,一般redis重啟時,會通過RDB文件重新構建內存,然后再使用AOF重新對近期數據做處理進行完整恢復,不過一般redis在AOF開啟時,是優先加載AOF,AOF文件找不到才使用RDB,AOF是日志型文件,每個指令都會存儲,而RDB會優化指令,保證數據完整即可。
-
機器突然斷電,丟失的數據怎么辦?
開啟了AOF之后,取決於AOF的sync設置,如果設置1s一次的話,會丟失1s內的數據,也可以設置成每條指令都sync一下,不過會導致磁盤io增多性能降低。
-
RDB的原理?
redis在開啟RDB時,會定時fork一個子進程,子進程會根據父進程內存生成臨時快照文件,完成后對RDB文件進行替換。
RDB相當於是redis某個時間段的內存快照,是十分緊湊的二進制文件,加載RDB文件比AOF文件快
AOF相當於對redis的操作指令做復制,所有的寫入命令會追加到aof_buf(緩沖區)中,根據對應的策略向硬盤做同步操作,然后定期對AOF文件進行重寫,此外,AOF會對命令進行優化,簡化一些操作命令
-
redis集群的同步原理?
redis的主從同步分為全同步和修改同步,全同步一般是在啟動的時候通過主機的bgsave生成RDB文件,從機通過RDB文件進行恢復,后續的同步都是修改同步,修改同步是通過propagate函數完成將一個操作記錄到aof文件中或者擴散到其他slave中,在該函數中通過調用feedAppendOnlyFile()將操作記錄到aof中,通過調用replicationFeedSlaves()將操作擴散到各slave中
-
redis集群模式的性能優化該怎么做?
- Master不做任何持久化操作
- 數據如果比較重要,某個slave開啟aof備份,策略為每秒一次
- master和slave在同一個局域網內
- 避免在壓力很大的主庫上增加從庫
-
redis怎么保證高可用
redis中有哨兵模式,在主機宕機的時候,通過哨兵選舉出新的master,哨兵必須存在三個以上,否則無法選舉
1. 領導者選舉
作用:選舉出一個sentenel節點作為領導者去進行故障轉移操作。
過程:
1). 每個做主觀下線的sentinel節點向其他sentinel節點發送vote命令,要求將它設置為領導者。
2). 收到命令的sentinel節點如果還沒有同意過其他的sentinel發送的命令(還未投過票),那么就會同意,否則拒絕。
3). 如果該sentinel節點發現自己的票數已經過半且達到了quorum的值,就會成為領導者。
4). 如果這個過程出現多個sentinel成為領導者,則會等待一段時間重新選舉。
2. 選出新的master節點
redis sentinel會選一個合適的slave來升級為master,那么,如何選擇一個合適的slave呢?順序如下:
1). 選擇slave-priority最高的slave節點(默認是相同)。
2). 選擇復制偏移量最大的節點。
3). 如果以上兩個條件都不滿足,選runId最小的(啟動最早的)。
3. 更改slave節點的master節點
當選舉出新的master節點后,會將其余的節點變更為新的master節點的slave節點,如果原有的master節點重新上線,成為新的master節點的slave節點
-
redis的哨兵sentinel怎么保證自身的高可用呢?使用了什么協議?
使用了Raft協議來保證高可用
Raft協議中的相關概念:
1、角色定義
Leader:
主節點,整個集群只有一個Leader,所有的寫請求都通過Leader發送給Follower;
Follower:
從節點(服從角色);
Candidate:
在Leader消息發送失敗或宕機,整集群沒有Leader時,此時Follower接收Leader的心跳包失敗,則Follwer開始競選Leader時,它們的身份是Candidate。Candidate只是個中間狀態,不會長期存在。
2、Term(任期)定義
在每一個Leader的任期期間,都有唯一表示該任期的一個Term;
3、基本操作
1、Raft將state machine replication划分為三個子問題
1、Leader Election
2、Log Replication
3、Safety
2、Leader Election步驟
集群啟動或Leader的心跳包消息無法發送給Follower時,觸發 Leader Election——選主 操作。
3、Log Replication步驟
1、所有的寫請求都要經過Leader;
2、Leader將寫請求攜帶在心跳包中發送給Follower;
3、當Leader收到多數派回復的消息后,則先自己提交寫操作,同時發送Commit請求給Follower;
4、Safety保證
1、Leader宕機感知:
a、Raft通過TimeOut來保證Follower能正確感知Leader宕機或消息丟失的事件,並觸發Follower競選Leader;
b、Leader需要給Follower發送心跳包(heartbeats),數據也是攜帶在心跳包中發送給Follower的;
2、選主平票情況
Leader Election時平票情況下,則兩個Candidates會產生一個隨機的Timewait,繼續發送下一個競選消息。
3、腦裂(大小集群)情況:
小集群由於沒有得到多數派的回復,寫操作失敗;
大集群會發生重新選主的過程,且新Leader擁有自己新的Term(任期),寫操作成功;
當小集群回到大集群時,由於小集群的Term小於新集群的Term,則同步新集群的信息。
-
有沒有考慮過,如果多個系統同時操作(並發)Redis帶來的數據問題
可以基於分布式鎖操作。每個系統通過 Redis獲取分布式鎖,確保同一時間,只能有一個系統實例在操作某個 Key,別人都不允許讀和寫。
你要寫入緩存的數據,都是從 MySQL 里查出來的,都得寫入 MySQL 中,寫入 MySQL 中的時候必須保存一個時間戳,從 MySQL 查出來的時候,時間戳也查出來。
每次要寫之前,先判斷一下當前這個 Value 的時間戳是否比緩存里的 Value 的時間戳要新。如果是的話,那么可以寫,否則,就不能用舊的數據覆蓋新的數據。
-
如何保證緩存與數據庫的雙寫一致性?
先更新數據庫,再刪除緩存,將刪除緩存的操作放在更新數據庫的同一個事務中,如果刪除緩存失敗,拋出異常使當前事務回滾
-
redis線程模型了解嗎?
Redis 基於 Reactor 模式開發了自己的網絡事件處理器: 這個處理器被稱為文件事件處理器(file event handler)。文件事件處理器使用 I/O 多路復用(multiplexing)程序來同時監聽多個套接字, 並根據套接字目前執行的任務來為套接字關聯不同的事件處理器。文件處理器包括連接處理器,命令請求處理器,命令回復處理器,文件事件處理器以單線程方式運行, 但通過使用 I/O 多路復用程序來監聽多個套接字, 文件事件處理器既實現了高性能的網絡通信模型, 又可以很好地與 redis 服務器中其他同樣以單線程方式運行的模塊進行對接, 這保持了 Redis 內部單線程設計的簡單性
-
如何實現分布式鎖?
兩種實現方式,redis和zookeeper,其中redis是通過lua表達式實現
-
說到zookeeper,了解ZAB協議嗎?
zookeeper依賴ZAB協議來實現分布式數據一致性,ZAB協議全稱為原子消息廣播協議,ZAB協議可以說是在Paxos算法基礎上進行了擴展和改造的。
在ZooKeeper中所有的事務請求都由一個主服務器也就是Leader來處理,其他服務器為Follower,Leader將客戶端的事務請求轉換為事務Proposal,並且將Proposal分發給集群中其他所有的Follower,然 后Leader等待Follwer反饋,當有 過半數(>=N/2+1) 的Follower反饋信息后,Leader將再次向集群內Follower廣播Commit信息,Commit為將之前的Proposal提交;
ZAB協議中節點存在三種狀態:
Looking :系統剛啟動時或者Leader崩潰后正處於選舉狀態
Following :Follower節點所處的狀態,Follower與Leader處於數據同步階段;
Leading :Leader所處狀態,當前集群中有一個Leader為主進程;
Zookeeper剛啟動時,所有節點都處於Looking狀態,這時集群會選舉出一個Leader,Leader會處於Leading狀態,其余節點發現存在主節點后,處於Following狀態,並且與Leader保持同步,當從節點發現 Leader失去聯系后,會開始新一輪選舉。Zookeeper整個生命周期內都處於這樣的循環
ZAB協議定義了選舉(election)、發現(discovery)、同步(sync)、廣播(Broadcast)四個階段。
選舉時,節點都會發送(myID,ZXID)到其余節點,通過判斷ZXID(事務ID)的大小來決定哪個節點為leader,如果有多個節點都有LastZXID,則由myId決定,id小的為leader,id小表明該節點先加入集群。當投票數過半時,leader就產生了。然后Leader會以自身的lastZXID為基准,將follower同步。
客戶端提交事務請求時Leader節點為每一個請求生成一個事務Proposal,將其發送給集群中所有的Follower節點,收到過半Follower的反饋后開始對事務進行提交,ZAB協議使用了原子廣播協議;在ZAB協議中只需要得到過半的Follower節點反饋Ack就可以對事務進行提交,這也導致了Leader幾點崩潰后可能會出現數據不一致的情況,ZAB使用了崩潰恢復來處理數字不一致問題;消息廣播使用了TCP協議進行通訊所有保證了接受和發送事務的順序性。廣播消息時Leader節點為每個事務Proposal分配一個全局遞增的ZXID(事務ID),每個事務Proposal都按照ZXID順序來處理;
Leader節點為每一個Follower節點分配一個隊列按事務ZXID順序放入到隊列中,且根據隊列的規則FIFO來進行事務的發送。Follower節點收到事務Proposal后會將該事務以事務日志方式寫入到本地磁盤中,成功后反饋Ack消息給Leader節點,Leader在接收到過半Follower節點的Ack反饋后就會進行事務的提交,以此同時向所有的Follower節點廣播Commit消息,Follower節點收到Commit后開始對事務進行提交;
-
一致性算法除了ZAB還有哪些?
Base-Paxos
paxos——Chubby(Google首次運用Multi Paxos算法到工程領域)
Multi-Paxos
zab協議——Zookeeper,raft協議——redis哨兵
Paxos中的角色分類
- Proposer 提案者:可以有多個,用於發起一個議案(由client向Proposer發出),議案包括議案編號、議案內容
- Acceptor 接收者、批准者:用於接收Proposer提出的議案,並決定采納哪一個議案
- Learner 學習者、記錄者:在最終決定采納某個議案后,進行記錄,不做其他事
其中Proposer在Base-Paxos中可以存在多個,而Multi-Paxos中僅有一個,別名leader。因此,Base-paxos存在活鎖問題:
三種算法的區別:
Raft:
追加日志的操作必須是連續的(Multi-Paxos中是並發)
只有擁有最新、最全日志的節點才能夠當選Leader(Multi-Paxos中任意節點都可以寫日志,無限制)
zab:
Raft每一次leader的任期叫做term,ZAB中叫做epoch
Raft由leader向follower發送心跳,ZAB相反
選主投票方式不同
事務操作方式不同
-
一致性就代表了正確性嗎?
一致性並不代表完全正確性,一致性需要客戶端和共識算法(Consensus)來共同保證。
Client Request操作的三個可能結果:成功、失敗、unknown(Timeout)
理解unknown(Timeout)
場景:Client寫請求,Leader向Follower同步日志,此時集群中有3個節點失敗,2個節點存活,結果是? 假設節點為:S1、S2、S3、S4、S5(Leader)
假設S5和S4存活,Client發起 第N次寫請求 為 操作I 時,由於Leader沒有得到多數派的回復,操作I只被發送到了S4中,此時Leader即會返回Client unknown,因為Leader不知道后面會不會成功將該條日志寫入多數派中。
結果1:假設Leader在返回客戶端后,宕機的Follower:S1、S2、S3恢復正常,Leader再次發送 第N次寫請求——操作I,且得到了多數派的回復,則提交日志,寫操作最終結果為成功;
結果2:假設Leader在返回客戶端后,此時S5和S4宕機,且S1、S2、S3恢復正常,此時S1、S2、S3觸發選主操作,且集群恢復可用,如果此時Client發起 第N+1次請求 為 操作I+1 ,且Client操作成功后 S5、S4恢復正常,則保存在S5、S4中的 操作I 會被刪除,S5、S4同步最新的 操作I+1 到本地。則 第N次寫請求—操作I 失敗;