1. 程式人生 > 實用技巧 >redis原理剖析

redis原理剖析

redis技術分享

大家知道redis的預設埠為什麼是6379嗎

而是由手機鍵盤字母「MERZ」的位置決定的。
「MERZ」在 Antirez 的朋友圈語言中是「愚蠢」的代名詞,它源於義大利廣告女郎
「Alessia Merz」在電視節目上說了一堆愚蠢的話 Antirez 今年已經四十歲了,依舊在孜孜不倦地寫程式碼,為 Redis 的開源事業持續貢獻力量

redis簡介

Redis採用的是基於記憶體的單程序單執行緒模型的KV資料庫,由C語言編寫,官方提供的資料是可以達到100000+的
QPS(每秒內查詢次數),他常被用作快取和訊息中介軟體

它支援多種型別的資料結構,如字串(String),雜湊(Hash),列表(List),集合(Set),
有序集合(SortedSet或者是ZSet),Bitmaps,Hyperloglogs。其中常見的資料結構型別有:String、
List、Set、Hash、ZSet這5種。

**橫軸為連線數,縱軸為響應次數

redis常用資料結構的底層實現

  • 有序集合(SortedSet)
跳躍表是有序集合(Sorted Set)的底層實現,  

redis為什麼不使用平衡樹或者B樹或者紅黑樹

  • Hyperloglogs
在 Redis 裡面,每個 HyperLogLog 鍵只需要花費 12 KB 記憶體,就可以計算接近 2^64 個不同元素的基 數,0.81%

hyperloglog的原理

redis持久化

為什麼需要持久化?
redis裡有10gb資料,突然停電或者意外宕機了,再啟動的時候10gb都沒了,所以需要
持久化,宕機後再通過持久化檔案將資料恢復。

redis的兩種持久化方式:RDB和AOF

  • RDB
  RDB有兩種持久化方式:SAVE,BGSAVE
  1.SAVE
  它是同步阻塞持久化的,致命的問題,持久化的時候redis服務阻塞(準確的說會
  阻塞當前執行save命令的執行緒,但是redis是單執行緒的,所以整個服務會阻塞),
  不能繼對外提供請求,GG!資料量小的話肯定影響不大,資料量大呢?每次複製需
  要1小時,那就相當於停機一小時。
  
  2.BGSAVE (background save)
  非同步非阻塞進行持久化的
  他可以一邊進行持久化,一邊對外提供讀寫服務,互不影響,新寫的資料對我持久化不會
  造成資料影響,你持久化的過程中報錯或者耗時太久都對我當前對外提供請求的服務不會
  產生任何影響。持久化完會將新的rdb檔案覆蓋之前的。
  
  BGSAVE原理:fork() + copyonwrite
  
  fork:用於建立一個子程序,注意是子程序,不是子執行緒。fork()出來的程序共享其
  父類的記憶體資料。僅僅是共享fork()出子程序的那一刻的記憶體資料,後期主程序修改資料
  對子程序不可見,同理,子程序修改的資料對主程序也不可見。
  
  copyonwrite:主程序fork()子程序之後,核心把主程序中所有的記憶體頁的許可權都設為read-
  only,然後子程序的地址空間指向主程序。這也就是共享了主程序的記憶體,當其中某個進
  程寫記憶體時(這裡肯定是主程序寫,因為子程序只負責rdb檔案持久化工作,不參與客戶端
  的請求),CPU硬體檢測到記憶體頁是read-only的,於是觸發頁異常中斷(page-fault),陷
  入核心的一箇中斷例程。
  • AOF
它也是Redis持久化的重要手段之一,全稱Append Only file 的縮寫,叫只追加檔案
也就是每次處理完請求命令後都會將此命令追加到aof檔案的末尾。而RDB是壓縮成二進位制等
時機開子程序去幹這件事。

原理:是每次都在aof檔案後面追加命令。他與主程序收到請求、處理請求是序列化的,而非非同步並行的。圖示如下

優點:
1.持久化的速度快,因為每次都只是追加,rdb每次都全量持久化
2.資料相對更可靠,丟失少,因可以配置每秒持久化、每個命令執行完就持久化

缺點:
1.災難性恢復的時候過慢,因為aof每次都只追加原命令,導致aof檔案過大,但是後面會
rewrite,但是相對於rdb也是慢的。

2.會對主程序對外提供請求的效率造成影響,接收請求、處理請求、寫aof檔案這三步是序列
原子執行的。而非非同步多執行緒執行的。Redis單執行緒!

redis可以做什麼?

正常情況下就是用來做快取,或者是分散式情況下 
的布式鎖(分散式鎖下面會詳解),還有很多情況下
用到redis,舉幾個栗子
 1.快取近期熱帖內容 (帖子內容空間佔用比較大),減少資料庫壓力  
 (hash)
 2.uv的統計(Hyperloglogs)
 3.記錄帖子的標題、摘要、作者和封面資訊,用於
 列表頁展示 (hash)
 4.延時訊息佇列。如果對訊息的可靠性不做要求的
 話,可以通過list來實現延時訊息佇列,pop的時
 候讓執行緒sleep一段時間。
 5.推薦去重、郵箱系統的垃圾郵件過濾(Bloom Filter)
 6.統計使用者的周活、月活(BitMap)

redis 的分散式鎖

  • 單例項實現的分散式鎖(假設單例項足夠可用)
使用命令SET key value NX PX 30000

但是僅僅這樣做會存在風險,大家可以想一想為什麼
因為執行緒A被阻塞了比較長時間,如果等它執行結束,然後釋放鎖,
此時的鎖早就因為超時而被釋放,在被釋放的時候執行緒B獲得了該鎖,
那麼如果A在釋放鎖的時候會把執行緒B獲得的鎖釋放掉,就出現問題了

解決辦法:把key設為全域性唯一的隨機值
  • 多例項的分散式鎖實現-redlock演算法
原理
1.獲取當前Unix時間,以毫秒為單位。
2.依次嘗試從N個例項,使用相同的key和隨機值獲
取鎖。在步驟2,當向Redis設定鎖時,客戶端應該設
置一個網路連線和響應超時時間,這個超時時間應
該小於鎖的失效時間.例如你的鎖自動失效時間為10
秒,則超時時間應該在5-50毫秒之間。這樣可以避
免伺服器端Redis已經掛掉的情況下,客戶端還在死
死地等待響應結果。如果伺服器端沒有在規定時間
內響應,客戶端應該儘快嘗試另外一個Redis例項。
3.客戶端使用當前時間減去開始獲取鎖時間(步驟1
記錄的時間)就得到獲取鎖使用的時間。當且僅當
從大多數(這裡是3個節點)的Redis節點都取到鎖
,並且使用的時間小於鎖失效時間時,鎖才算獲取
成功。
4.如果取到了鎖,key的真正有效時間等於有效時間
減去獲取鎖所使用的時間(步驟3計算的結果)。
如果因為某些原因,獲取鎖失敗(沒有在至少N/2+1
個Redis例項取到鎖或者取鎖時間已經超過了有效時
間),客戶端應該在所有的Redis例項上進行解鎖(
即便某些Redis例項根本就沒有加鎖成功)。

redis的分散式搭建

  • redis 一致性保證
 redis 並不能保證資料的強一致性.這意味這在實中叢集在特定的條件下
可能會丟失寫操作.
第一個原因是因為叢集是用了非同步複製.寫操作過程如下
 1.客戶端向主節點B寫入一條命令
 2.主節點B向客戶端回覆命令狀態
 3.主節點將寫操作複製給他得從節點 B1, B2 和 B3
 
第二個原因是因為網路分割槽
舉個例子 假設叢集包含 A 、 B 、 C 、 A1 、 B1 、 C1 六個節點, 其中 A 、B 、C 為主節點, A1
、B1 、C1 為A,B,C的從節點, 還有一個客戶端Z1 
假設叢集中發生網路分割槽,那麼叢集可能會分為
兩方,大部分的一方包含節點 A 、C 、A1 、B1 和C1 ,
小部分的一方則包含節點 B 和客戶端 Z1 .

Z1仍然能夠向主節點B中寫入, 如果網路分割槽發生時間較短,那麼叢集將會繼續正常
運作,如果分割槽的時間足夠讓大部分的一方將B1選舉
為新的master,那麼Z1寫入B中得資料便丟失了.
  • redis的叢集的資料分片
redis叢集沒有使用一致性hash, 而是引入了 雜湊槽的概念
Redis 叢集有16384個雜湊槽,叢集的最大節點數量也是 16384個 每個key通過CRC16校驗後對16384取模來決定放置哪個槽.叢集的每個節點負責一部分hash槽,舉個例子,比如當前叢集有3個節點雜湊槽會被
平均分配,當一個節點不可用,則整個叢集不可用,因為有一部分雜湊槽被叢集認為不可用,除非進行重分片

  • redis的主從複製模型
為了使在部分節點失敗或者大部分節點無法通訊的情況下叢集仍然可用,所以叢集使用了主從複製模型.
在我們例子中具有A,B,C三個節點的叢集,在沒有複製模型的情況下,如果節點B失敗了,那
麼整個叢集就會以為缺少5501-11000這個範圍的槽而不可用.

然而如果在叢集建立的時候(或者過一段時間)我們為每個節點新增一個從節點A1,B1,C1,
那麼整個叢集便有三個master節點和三個slave節點組成,這樣在節點B失敗後,叢集便會選
舉B1為新的主節點繼續服務,整個叢集便不會因為槽找不到而不可用了
不過當B和B1 都失敗後,叢集是不可用的.