開篇詞 | 這樣學Redis,才能技高一籌
0.如何設計緩存?
高性能,存儲,高可用,可擴展,持久化,空間利用最大化,過期策略。
解決問題:數據一致性問題,雪崩,穿透,擊穿等問題。
1.高性能:C語言,nio多路復用,單線程
2.高可用:集群--主從,RedisPlus,哨兵模式。
3.可擴展:hash槽13864個,分片。
4.持久化:aof,rdb
5.空間利用最大化:豐富的數據類型,不同的數據類型采用不同的數據結構存儲。
6.過期策略:luf最近最少使用。刪除key時,在空閑時刪除,或者設置過期執行更新修改刪除。
01 | 基本架構
1.基於內存得KV數據庫,
2.單線程,
3.多路復用機制,
4.內存模型:https://mp.weixin.qq.com/s/GaCpNatAII4iBIUlwVdYOA
1.內存統計
命令:info memory
2.內存划分
1.數據庫本身
2.進程運行占用
3.緩沖內存,包含AOF緩沖區
4.內存碎片
3.數據存儲的細節:
dicEntry->redisObject=6個屬性;
embStr :RedisObject(16)+sdshdr((int)8+(str)39+1=48)=64(一個存儲單元);
raw:在不同存儲單元。raw=n個(RedisObject+sdshdr),數據是不連續的。‘
skiplist:順序排序,多級索引,每層索引比較,查詢時間復雜度為O(logn)。
1.跳表的索引高度: h = log2n
2.每次遍歷三個元素:即O(3*log2n)約等於O(logn)
h的計算:
假設原始的鏈表有n個元素;
則一級索引有n/2 個元素、二級索引有 n/4 個元素、h級索引就有 n/2h次方個元素;
即:最高級索引 h 滿足 2 = n/2h次方,即 h = log2n。
set hello world 時,所涉及到的數據模型:
1.dictEntry:每個鍵值對都會有一個 dictEntry,里面存儲了指向 Key 和 Value 的指針;next 指向下一個 dictEntry,與本 Key-Value 無關
2.Key:Key(”hello”)並不是直接以字符串存儲,而是存儲在 SDS 結構中。
3.RedisObject:Value(“world”)既不是直接以字符串存儲,也不是像 Key 一樣直接存儲在 SDS 中,而是存儲在 RedisObject 中。
1.RedisObject 中的 type 字段指明了 Value 對象的類型,ptr 字段則指向對象所在的地址。
2.Redis 對象的類型、內部編碼、內存回收、共享對象等功能,都需要 RedisObject 支持。
3.RedisObject對象結構:
1.type:
2.encoding:支持多種編碼
3.lru:最后一次訪問的時間
4.refcount:記錄對象的引用次數。
5、ptr:指針指向的數據
4.一個 RedisObject 對象的大小為 16 字節:4bit+4bit+24bit+4Byte+8Byte=16Byte
4.jemalloc:內存分配器
1.jemalloc 在 64 位系統中,將內存空間划分為小、大、巨大三個范圍;
每個范圍內又划分了許多小的內存塊單位;
當 Redis 存儲數據時,會選擇大小最合適的內存塊進行存儲。
4.Redis 的對象類型與內部編碼
1.字符串:
1.int 8 個字節的長整型,字符串值是整型時,這個值使用 long 整型表示.
2.embStr:<=39 字節的字符串
3.raw: raw:大於 39 個字節的字符串
1.redisObject占16個字節,當buf內的字符串長度是39時,sdshdr的大小為8+39+1=48。加起來剛好64。jemalloc會分配8,16,32,64等字節的內存。
2.embstr和raw都使用redisObject結構和sdshdr結構來表示字符串對象,但是raw會分別兩次創建redisObject結構與sdshdr結構,內存不一定是連續的,而embstr直接創建一塊連續的內存
3.embstr開辟連續的內存可以帶來的優勢:
1.內存釋放是embstr只需要釋放一次,而raw需要釋放兩次
2.emstr查找的更快
2.List(列表)
1.壓縮列表(ziplist)
2.雙端鏈表(linkedlist)
3.hash:
1.壓縮列表(ziplist)和哈希表(hashtable)
4.集合:
整數集合(intset)或哈希表(hashtable)
五、有序集合
壓縮列表(ziplist)或跳躍表(skiplist)
02 | 數據結構:快速的Redis有哪些慢操作?
1. 鍵和值用什么結構組織?
2.為什么哈希表操作變慢了?
有哪些底層數據結構?
集合類型的底層數據結構主要有 5 種:整數數組、雙向鏈表、哈希表、壓縮列表和跳表。
其中,哈希表的操作特點我們剛剛已經學過了;整數數組和雙向鏈表也很常見,它們的操作特征都是順序讀寫,也就是通過數組下標或者鏈表的指針逐個元素訪問,操作復雜度基本是 O(N),操作效率比較低;壓縮列表和跳表我們平時接觸得可能不多,但它們也是 Redis 重要的數據結構,所以我來重點解釋一下。
不同操作的復雜度
在壓縮列表中,如果我們要查找定位第一個元素和最后一個元素,可以通過表頭三個字段的長度直接定位,復雜度是 O(1)。而查找其他元素時,就沒有這么高效了,只能逐個查找,此時的復雜度就是 O(N) 了。
我們再來看下跳表。
有序鏈表只能逐一查找元素,導致操作起來非常緩慢,於是就出現了跳表。
跳表在鏈表的基礎上,增加了多級索引處理,通過比較索引位置的大小,判斷元素位置在該位置左側還是右側,幾個跳轉,實現數據的快速定位.
單元素操作,是指每一種集合類型對單個數據實現的增刪改查操作。例如,Hash 類型的 HGET、HSET 和 HDEL,Set 類型的 SADD、SREM、SRANDMEMBER 等。這些操作的復雜度由集合采用的數據結構決定,例如,HGET、HSET 和 HDEL 是對哈希表做操作,所以它們的復雜度都是 O(1);Set 類型用哈希表作為底層數據結構時,它的 SADD、SREM、SRANDMEMBER 復雜度也是 O(1)。
范圍操作,是指集合類型中的遍歷操作,可以返回集合中的所有數據,比如 Hash 類型的 HGETALL 和 Set 類型的 SMEMBERS,或者返回一個范圍內的部分數據,比如 List 類型的 LRANGE 和 ZSet 類型的 ZRANGE。這類操作的復雜度一般是 O(N),比較耗時,我們應該盡量避免
03 | 高性能IO模型:為什么單線程Redis能那么快?
Redis 為什么用單線程?
多線程的開銷
單線程 Redis 為什么那么快?
基本 IO 模型與阻塞點
06 | 數據同步:主從庫如何實現數據一致?
主從使用rdb同步,首次全量同步,后面使用增量同步,掉線重連也是增量同步
優秀原文鏈接:https://www.cnblogs.com/daofaziran/p/10978628.html
1.全量同步:
1.Slave初始化階段,這時Slave需要將Master上的所有數據都復制一份。
1. 從服務器連接主服務器,發送SYNC命令;
2.主服務器接收到SYNC命名后,開始執行BGSAVE命令生成RDB文件並使用緩沖區記錄此后執行的所有寫命令;
3.主服務器BGSAVE執行完后,向所有從服務器發送快照文件,並在發送期間繼續記錄被執行的寫命令;
4.從服務器收到快照文件后丟棄所有舊數據,載入收到的快照;
5.主服務器快照發送完畢后開始向從服務器發送緩沖區中的寫命令;
6.從服務器完成對快照的載入,開始接收命令請求,並執行來自主服務器緩沖區的寫命令;
7.完成上面幾個步驟后就完成了從服務器數據初始化的所有操作,從服務器此時可以接收來自用戶的讀請求。
2.增量同步:
1.Redis增量復制是指Slave初始化后開始正常工作時主服務器發生的寫操作同步到從服務器的過程。
2.增量復制的過程主要是主服務器每執行一個寫命令就會向從服務器發送相同的寫命令,從服務器接收並執行收到的寫命令。
07 | 哨兵機制:主庫掛了,如何不間斷服務?
08 | 哨兵集群:哨兵掛了,主從庫還能切換嗎?
哨兵集群 節點互相同步。
09 | 切片集群:數據增多了,是該加內存還是加實例?
橫向擴展:增加容量,cpu核數,磁盤容量,操作較為簡單。但同同時也因為數據量大,導致單機壓力過大,出現性能瓶頸。
縱向擴展:分片擴容,增加單機實例。分攤節點壓力到多個實例上。
分槽:共有16384個hash槽,按照實例取模分配hash槽,自定義分配hash槽。將模映射到實例上。
數據遷移:
redis cluster的重定向機制:客戶端向原實例發送沒有數據時,會返回一個move:數據,重新請求move實例。
ask:正在遷移過程中,客戶端發送指令時,會返回ask錯誤,客戶端需要發送asking指令(新實例),get正在遷移的數據(舊實例)。
ask和move的區別:ask不會更新客戶端hash槽分配緩存,move會更新。