天天看點

從應用到底層 36張圖帶你進入Redis世界1、基本類型及底層實作2、持久化3、Redis為什麼那麼快4、常見問題5、分布式鎖6、Redis 過期政策和記憶體淘汰政策7、Redis 叢集高可用8、Redis 限流9、常見知識點10、End

高清思維導圖已同步Git:https://github.com/SoWhat1412/xmindfile,關注公衆号sowhat1412擷取海量資源
從應用到底層 36張圖帶你進入Redis世界1、基本類型及底層實作2、持久化3、Redis為什麼那麼快4、常見問題5、分布式鎖6、Redis 過期政策和記憶體淘汰政策7、Redis 叢集高可用8、Redis 限流9、常見知識點10、End

1、基本類型及底層實作

從應用到底層 36張圖帶你進入Redis世界1、基本類型及底層實作2、持久化3、Redis為什麼那麼快4、常見問題5、分布式鎖6、Redis 過期政策和記憶體淘汰政策7、Redis 叢集高可用8、Redis 限流9、常見知識點10、End

1.1、String

用途:

适用于簡單key-value存儲、setnx key value實作分布式鎖、計數器(原子性)、分布式全局唯一ID。

底層:

C語言中String用char[]數組表示,源碼中用

SDS

(simple dynamic string)封裝char[],這是是Redis存儲的

最小單元

,一個SDS最大可以存儲512M資訊。

struct sdshdr{
  unsigned int len; // 标記char[]的長度
  unsigned int free; //标記char[]中未使用的元素個數
  char buf[]; // 存放元素的坑
}
           

Redis對SDS再次封裝生成了

RedisObject

,核心有兩個作用:

  1. 說明是5種類型哪一種。
  2. 裡面有指針用來指向 SDS

當你執行

set name sowhat

的時候,其實Redis會建立兩個RedisObject對象,鍵的RedisObject 和 值的RedisOjbect 其中它們type = REDIS_STRING,而SDS分别存儲的就是 name 跟 sowhat 字元串咯。

并且Redis底層對SDS有如下優化:

  1. SDS修改後大小 > 1M時 系統會多配置設定空間來進行

    空間預配置設定

  2. SDS是

    惰性釋放空間

    的,你free了空間,可是系統把資料記錄下來下次想用時候可直接使用。不用新申請空間。

1.2、List

從應用到底層 36張圖帶你進入Redis世界1、基本類型及底層實作2、持久化3、Redis為什麼那麼快4、常見問題5、分布式鎖6、Redis 過期政策和記憶體淘汰政策7、Redis 叢集高可用8、Redis 限流9、常見知識點10、End

檢視源碼底層

adlist.h

會發現底層就是個 雙端連結清單,該連結清單最大長度為2^32-1。常用就這幾個組合。

lpush + lpop = stack 先進後出的棧

lpush + rpop = queue 先進先出的隊列

lpush + ltrim = capped collection 有限集合

lpush + brpop = message queue 消息隊列

一般可以用來做簡單的消息隊列,并且當資料量小的時候可能用到獨有的壓縮清單來提升性能。當然專業點還是要 RabbitMQ、ActiveMQ等

1.3、Hash

散列非常适用于将一些相關的資料存儲在一起,比如使用者的購物車。該類型在日常用途還是挺多的。

這裡需要明确一點: Redis中隻有一個K,一個V。其中 K 絕對是字元串對象,而 V 可以是String、List、Hash、Set、ZSet任意一種。

hash的底層主要是采用字典dict的結構,整體呈現層層封裝。從小到大如下:

1.3.1、dictEntry
真正的資料節點,包括key、value 和 next 節點。
從應用到底層 36張圖帶你進入Redis世界1、基本類型及底層實作2、持久化3、Redis為什麼那麼快4、常見問題5、分布式鎖6、Redis 過期政策和記憶體淘汰政策7、Redis 叢集高可用8、Redis 限流9、常見知識點10、End
1.3.2、dictht

1、資料 dictEntry 類型的數組,每個數組的item可能都指向一個連結清單。

2、數組長度 size。

3、sizemask 等于 size - 1。

4、目前 dictEntry 數組中包含總共多少節點。

從應用到底層 36張圖帶你進入Redis世界1、基本類型及底層實作2、持久化3、Redis為什麼那麼快4、常見問題5、分布式鎖6、Redis 過期政策和記憶體淘汰政策7、Redis 叢集高可用8、Redis 限流9、常見知識點10、End
1.3.3、dict

1、dictType 類型,包括一些自定義函數,這些函數使得key和value能夠存儲

2、rehashidx 其實是一個标志量,如果為

-1

說明目前沒有擴容,如果

不為 -1

則記錄擴容位置。

3、dictht數組,兩個Hash表。

4、iterators 記錄了目前字典正在進行中的疊代器

從應用到底層 36張圖帶你進入Redis世界1、基本類型及底層實作2、持久化3、Redis為什麼那麼快4、常見問題5、分布式鎖6、Redis 過期政策和記憶體淘汰政策7、Redis 叢集高可用8、Redis 限流9、常見知識點10、End

組合後結構就是如下:

從應用到底層 36張圖帶你進入Redis世界1、基本類型及底層實作2、持久化3、Redis為什麼那麼快4、常見問題5、分布式鎖6、Redis 過期政策和記憶體淘汰政策7、Redis 叢集高可用8、Redis 限流9、常見知識點10、End
1.3.4、漸進式擴容

為什麼 dictht ht[2]是兩個呢?目的是在擴容的同時不影響前端的CURD,慢慢的把資料從ht[0]轉移到ht[1]中,同時

rehashindex

來記錄轉移的情況,當全部轉移完成,将ht[1]改成ht[0]使用。

rehashidx = -1說明目前沒有擴容,rehashidx != -1則表示擴容到數組中的第幾個了。

擴容之後的數組大小為大于used*2的2的n次方的最小值,跟 HashMap 類似。然後挨個周遊數組同時調整rehashidx的值,對每個dictEntry[i] 再挨個周遊連結清單将資料 Hash 後重新映射到 dictht[1]裡面。并且 dictht[0].use 跟 dictht[1].use 是動态變化的。

從應用到底層 36張圖帶你進入Redis世界1、基本類型及底層實作2、持久化3、Redis為什麼那麼快4、常見問題5、分布式鎖6、Redis 過期政策和記憶體淘汰政策7、Redis 叢集高可用8、Redis 限流9、常見知識點10、End

整個過程的重點在于

rehashidx

,其為第一個數組正在移動的下标位置,如果目前記憶體不夠,或者作業系統繁忙,擴容的過程可以随時停止。

停止之後如果對該對象進行操作,那是什麼樣子的呢?

1、如果是新增,則直接新增後第二個數組,因為如果新增到第一個數組,以後還是要移過來,沒必要浪費時間

2、如果是删除,更新,查詢,則先查找第一個數組,如果沒找到,則再查詢第二個數組。

從應用到底層 36張圖帶你進入Redis世界1、基本類型及底層實作2、持久化3、Redis為什麼那麼快4、常見問題5、分布式鎖6、Redis 過期政策和記憶體淘汰政策7、Redis 叢集高可用8、Redis 限流9、常見知識點10、End

1.4、Set

如果你明白Java中HashSet是HashMap的簡化版那麼這個Set應該也了解了。都是一樣的套路而已。這裡你可以認為是沒有Value的Dict。看源碼

t.set.c

就可以了解本質了。

int setTypeAdd(robj *subject, robj *value) {
    long long llval;
    if (subject->encoding == REDIS_ENCODING_HT) {
         // 看到底層調用的還是dictAdd,隻不過第三個參數= NULL
         if (dictAdd(subject->ptr,value,NULL) == DICT_OK) {
            incrRefCount(value);
            return 1;
        }
        ....
           

1.5、ZSet

範圍查找 的天敵就是 有序集合,看底層

redis.h

後就會發現 Zset用的就是可以跟二叉樹媲美的

跳躍表

來實作有序。跳表就是多層連結清單的結合體,跳表分為許多層(level),每一層都可以看作是資料的索引,這些索引的意義就是加快跳表查找資料速度。

每一層的資料都是有序的,上一層資料是下一層資料的子集,并且第一層(level 1)包含了全部的資料;層次越高,跳躍性越大,包含的資料越少。并且随便插入一個資料該資料是否會是跳表索引完全随機的跟玩骰子一樣。

跳表包含一個表頭,它查找資料時,是

從上往下,從左往右

進行查找。現在找出值為37的節點為例,來對比說明跳表和普遍的連結清單。

  1. 沒有跳表查詢

    比如我查詢資料37,如果沒有上面的索引時候路線如下圖:

    從應用到底層 36張圖帶你進入Redis世界1、基本類型及底層實作2、持久化3、Redis為什麼那麼快4、常見問題5、分布式鎖6、Redis 過期政策和記憶體淘汰政策7、Redis 叢集高可用8、Redis 限流9、常見知識點10、End
  2. 有跳表查詢

    有跳表查詢37的時候路線如下圖:

    從應用到底層 36張圖帶你進入Redis世界1、基本類型及底層實作2、持久化3、Redis為什麼那麼快4、常見問題5、分布式鎖6、Redis 過期政策和記憶體淘汰政策7、Redis 叢集高可用8、Redis 限流9、常見知識點10、End
    應用場景:
積分排行榜、時間排序新聞、延時隊列。

1.6、Redis Geo

以前寫過Redis Geo核心原了解析,想看的直接跳轉即可。他的核心思想就是将地球近似為球體來看待,然後 GEO利用 GeoHash 将二維的經緯度轉換成字元串,來實作位置的劃分跟指定距離的查詢。

從應用到底層 36張圖帶你進入Redis世界1、基本類型及底層實作2、持久化3、Redis為什麼那麼快4、常見問題5、分布式鎖6、Redis 過期政策和記憶體淘汰政策7、Redis 叢集高可用8、Redis 限流9、常見知識點10、End

1.7、HyperLogLog

HyperLogLog :是一種

機率

資料結構,它使用機率算法來統計集合的近似基數。而它算法的最本源則是

伯努利過程 + 分桶 + 調和平均數

。具體實作可看 HyperLogLog 講解。

功能:誤差允許範圍内做基數統計 (基數就是指一個集合中不同值的個數) 的時候非常有用,每個HyperLogLog的鍵可以計算接近2^64不同元素的基數,而大小隻需要12KB。錯誤率大概在0.81%。是以如果用做 UV 統計很合适。

HyperLogLog底層 一共分了 2^14 個桶,也就是 16384 個桶。每個(registers)桶中是一個 6 bit 的數組,這裡有個騷操作就是一般人可能直接用一個位元組當桶浪費2個bit空間,但是Redis底層隻用6個然後通過前後拼接實作對記憶體用到了極緻,最終就是 16384*6/8/1024 = 12KB。

1.8、bitmap

BitMap 原本的含義是用一個比特位來映射某個元素的狀态。由于一個比特位隻能表示 0 和 1 兩種狀态,是以 BitMap 能映射的狀态有限,但是使用比特位的優勢是能大量的節省記憶體空間。

在 Redis 中BitMap 底層是基于字元串類型實作的,可以把 Bitmaps 想象成一個以比特位為機關的數組,數組的每個單元隻能存儲0和1,數組的下标在 Bitmaps 中叫做偏移量,BitMap 的 offset 值上限 2^32 - 1。

從應用到底層 36張圖帶你進入Redis世界1、基本類型及底層實作2、持久化3、Redis為什麼那麼快4、常見問題5、分布式鎖6、Redis 過期政策和記憶體淘汰政策7、Redis 叢集高可用8、Redis 限流9、常見知識點10、End
  1. 使用者簽到
key = 年份:使用者id offset = (今天是一年中的第幾天) % (今年的天數)
  1. 統計活躍使用者
使用日期作為 key,然後使用者 id 為 offset 設定不同offset為0 1 即可。

PS : Redis 它的通訊協定是基于TCP的應用層協定 RESP(REdis Serialization Protocol)。

1.9、Bloom Filter

使用布隆過濾器得到的判斷結果:

不存在的一定不存在,存在的不一定存在

布隆過濾器 原理:

當一個元素被加入集合時,通過K個散列函數将這個元素映射成一個位數組中的K個點(有效降低沖突機率),把它們置為1。檢索時,我們隻要看看這些點是不是都是1就知道集合中有沒有它了:如果這些點有任何一個為0,則被檢元素一定不在;如果都是1,則被檢元素很可能在。這就是布隆過濾器的基本思想。

想玩的話可以用Google的

guava

包玩耍一番。

從應用到底層 36張圖帶你進入Redis世界1、基本類型及底層實作2、持久化3、Redis為什麼那麼快4、常見問題5、分布式鎖6、Redis 過期政策和記憶體淘汰政策7、Redis 叢集高可用8、Redis 限流9、常見知識點10、End

1.10 釋出訂閱

redis提供了

釋出、訂閱

模式的消息機制,其中消息訂閱者與釋出者不直接通信,釋出者向指定的頻道(channel)釋出消息,訂閱該頻道的每個用戶端都可以接收到消息。不過比專業的MQ(RabbitMQ RocketMQ ActiveMQ Kafka)相比不值一提,這個功能就算球了。

從應用到底層 36張圖帶你進入Redis世界1、基本類型及底層實作2、持久化3、Redis為什麼那麼快4、常見問題5、分布式鎖6、Redis 過期政策和記憶體淘汰政策7、Redis 叢集高可用8、Redis 限流9、常見知識點10、End

2、持久化

因為Redis資料在記憶體,斷電既丢,是以持久化到磁盤是必須得有的,Redis提供了RDB跟AOF兩種模式。

2.1、RDB

RDB 持久化機制,是對 Redis 中的資料執行周期性的持久化。更适合做冷備。

優點:

1、壓縮後的二進制文,适用于備份、全量複制,用于災難恢複加載RDB恢複資料遠快于AOF方式,适合大規模的資料恢複。

2、如果業務對資料完整性和一緻性要求不高,RDB是很好的選擇。資料恢複比AOF快。

缺點:

1、RDB是周期間隔性的快照檔案,資料的完整性和一緻性不高,因為RDB可能在最後一次備份時當機了。

2、備份時占用記憶體,因為Redis 在備份時會獨立fork一個子程序,将資料寫入到一個臨時檔案(此時記憶體中的資料是原來的兩倍哦),最後再将臨時檔案替換之前的備份檔案。是以要考慮到大概兩倍的資料膨脹性。

注意手動觸發及COW:

1、

SAVE

直接調用 rdbSave ,

阻塞

Redis 主程序,導緻無法提供服務。

2、

BGSAVE

則 fork 出一個子程序,子程序負責調用 rdbSave ,在儲存完成後向主程序發送信号告知完成。 在BGSAVE 執行期間仍可以繼續處理用戶端的請求。

3、Copy On Write 機制,備份的是開始那個時刻記憶體中的資料,隻複制被修改記憶體頁資料,不是全部記憶體資料。

4、Copy On Write 時如果父子程序大量寫操作會導緻分頁錯誤。

從應用到底層 36張圖帶你進入Redis世界1、基本類型及底層實作2、持久化3、Redis為什麼那麼快4、常見問題5、分布式鎖6、Redis 過期政策和記憶體淘汰政策7、Redis 叢集高可用8、Redis 限流9、常見知識點10、End

2.2、AOF

AOF 機制對每條寫入指令作為日志,以 append-only 的模式寫入一個日志檔案中,因為這個模式是隻追加的方式,是以沒有任何磁盤尋址的開銷,是以很快,有點像 Mysql 中的binlog。AOF更适合做熱備。

優點:

AOF是一秒一次去通過一個背景的線程fsync操作,資料丢失不用怕。

缺點:

1、對于相同數量的資料集而言,AOF檔案通常要大于RDB檔案。RDB 在恢複大資料集時的速度比 AOF 的恢複速度要快。

2、根據同步政策的不同,AOF在運作效率上往往會慢于RDB。總之,每秒同步政策的效率是比較高的。

AOF整個流程分兩步:

第一步是指令的實時寫入,不同級别可能有1秒資料損失。指令先追加到

aof_buf

然後再同步到AO磁盤,如果實時寫入磁盤會帶來非常高的磁盤IO,影響整體性能。

第二步是對aof檔案的重寫,目的是為了減少AOF檔案的大小,可以自動觸發或者手動觸發(BGREWRITEAOF),是Fork出子程序操作,期間Redis服務仍可用。

從應用到底層 36張圖帶你進入Redis世界1、基本類型及底層實作2、持久化3、Redis為什麼那麼快4、常見問題5、分布式鎖6、Redis 過期政策和記憶體淘汰政策7、Redis 叢集高可用8、Redis 限流9、常見知識點10、End
1、在重寫期間,由于主程序依然在響應指令,為了保證最終備份的完整性;它

依然會寫入舊

的AOF中,如果重寫失敗,能夠保證資料不丢失。

2、為了把重寫期間響應的寫入資訊也寫入到新的檔案中,是以也會

為子程序保留一個buf

,防止新寫的file丢失資料。

3、重寫是直接把

目前記憶體的資料生成對應指令

,并不需要讀取老的AOF檔案進行分析、指令合并。

4、無論是 RDB 還是 AOF 都是先寫入一個臨時檔案,然後通過

rename

完成檔案的替換工作。

關于Fork的建議:

1、降低fork的頻率,比如可以手動來觸發RDB生成快照、與AOF重寫;

2、控制Redis最大使用記憶體,防止fork耗時過長;

3、配置牛逼點,合理配置Linux的記憶體配置設定政策,避免因為實體記憶體不足導緻fork失敗。

4、Redis在執行

BGSAVE

BGREWRITEAOF

指令時,哈希表的負載因子>=5,而未執行這兩個指令時>=1。目的是盡量減少寫操作,避免不必要的記憶體寫入操作。

5、哈希表的擴充因子:哈希表已儲存節點數量 / 哈希表大小。因子決定了是否擴充哈希表。

2.3、恢複

啟動時會先檢查AOF(資料更完整)檔案是否存在,如果不存在就嘗試加載RDB。

從應用到底層 36張圖帶你進入Redis世界1、基本類型及底層實作2、持久化3、Redis為什麼那麼快4、常見問題5、分布式鎖6、Redis 過期政策和記憶體淘汰政策7、Redis 叢集高可用8、Redis 限流9、常見知識點10、End

2.4、建議

既然單獨用RDB會丢失很多資料。單獨用AOF,資料恢複沒RDB來的快,是以出現問題了第一時間用RDB恢複,然後AOF做資料補全才說王道。

3、Redis為什麼那麼快

3.1、 基于記憶體實作:

資料都存儲在記憶體裡,相比磁盤IO操作快百倍,操作速率很快。

3.2、高效的資料結構:

Redis底層多種資料結構支援不同的資料類型,比如HyperLogLog它連2個位元組都不想浪費。

3.3、豐富而合理的編碼:

Redis底層提供了 豐富而合理的編碼 ,五種資料類型根據長度及元素的個數适配不同的編碼格式。

String:自動存儲int類型,非int類型用raw編碼。

List:字元串長度且元素個數小于一定範圍使用 ziplist 編碼,否則轉化為 linkedlist 編碼。

Hash:hash 對象儲存的鍵值對内的鍵和值字元串長度小于一定值及鍵值對。

Set:儲存元素為整數及元素個數小于一定範圍使用 intset 編碼,任意條件不滿足,則使用 hashtable 編碼。

Zset:儲存的元素個數小于定值且成員長度小于定值使用 ziplist 編碼,任意條件不滿足,則使用 skiplist 編碼。

3.4、合适的線程模型:

I/O 多路複用

模型同時監聽用戶端連接配接,多線程是需要上下文切換的,對于記憶體資料庫來說這點很緻命。
從應用到底層 36張圖帶你進入Redis世界1、基本類型及底層實作2、持久化3、Redis為什麼那麼快4、常見問題5、分布式鎖6、Redis 過期政策和記憶體淘汰政策7、Redis 叢集高可用8、Redis 限流9、常見知識點10、End

3.5、 Redis6.0後引入

多線程

提速:

要知道 讀寫網絡的read/write系統耗時

>>

Redis運作執行耗時,Redis的瓶頸主要在于網絡的 IO 消耗, 優化主要有兩個方向:

提高網絡 IO 性能,典型的實作比如使用 DPDK 來替代核心網絡棧的方式

使用多線程充分利用多核,典型的實作比如 Memcached。

協定棧優化的這種方式跟 Redis 關系不大,支援多線程是一種最有效最便捷的操作方式。是以Redis支援多線程主要就是兩個原因:

可以充分利用伺服器 CPU 資源,目前主線程隻能利用一個核

多線程任務可以分攤 Redis 同步 IO 讀寫負荷

關于多線程須知:

  1. Redis 6.0 版本 預設多線程是關閉的 io-threads-do-reads no
  2. Redis 6.0 版本 開啟多線程後 線程數也要 謹慎設定。
  3. 多線程可以使得性能翻倍,但是多線程隻是用來處理網絡資料的讀寫和協定解析,執行指令仍然是單線程順序執行。

4、常見問題

4.1、緩存雪崩

從應用到底層 36張圖帶你進入Redis世界1、基本類型及底層實作2、持久化3、Redis為什麼那麼快4、常見問題5、分布式鎖6、Redis 過期政策和記憶體淘汰政策7、Redis 叢集高可用8、Redis 限流9、常見知識點10、End

雪崩定義:

Redis中大批量key在同一時間同時失效導緻所有請求都打到了MySQL。而MySQL扛不住導緻大面積崩塌。

雪崩解決方案:

1、緩存資料的過期時間加上個随機值,防止同一時間大量資料過期現象發生。

2、如果緩存資料庫是分布式部署,将熱點資料均勻分布在不同搞得緩存資料庫中。

3、設定熱點資料永遠不過期。

4.2、緩存穿透

穿透定義:

緩存穿透 是 指緩存和資料庫中

都沒有

的資料,比如ID預設>0,黑客一直 請求ID= -12的資料那麼就會導緻資料庫壓力過大,嚴重會擊垮資料庫。

穿透解決方案:

1、後端接口層增加 使用者鑒權校驗,參數做校驗等。

2、單個IP每秒通路次數超過門檻值直接拉黑IP,關進小黑屋1天,在擷取IP代理池的時候我就被拉黑過。

3、從緩存取不到的資料,在資料庫中也沒有取到,這時也可以将key-value對寫為key-null 失效時間可以為15秒防止惡意攻擊。

4、用Redis提供的 Bloom Filter 特性也OK。

4.3、緩存擊穿

從應用到底層 36張圖帶你進入Redis世界1、基本類型及底層實作2、持久化3、Redis為什麼那麼快4、常見問題5、分布式鎖6、Redis 過期政策和記憶體淘汰政策7、Redis 叢集高可用8、Redis 限流9、常見知識點10、End

擊穿定義:

現象:大并發集中對這一個熱點key進行通路,當這個Key在失效的瞬間,持續的大并發就穿破緩存,直接請求資料庫。

擊穿解決:

設定熱點資料永遠不過期

加上互斥鎖也能搞定了

4.4、雙寫一緻性

雙寫:

緩存

資料庫

均更新資料,如何保證資料一緻性?

1、先更新資料庫,再更新緩存

安全問題:線程A更新資料庫->線程B更新資料庫->線程B更新緩存->線程A更新緩存。

導緻髒讀

業務場景:讀多寫少場景,頻繁更新資料庫而緩存根本沒用。更何況如果緩存是疊加計算後結果更

浪費性能

2、先删緩存,再更新資料庫

A 請求寫來更新緩存。

B 發現緩存不在去資料查詢舊值後寫入緩存。

A 将資料寫入資料庫,此時緩存跟資料庫不一緻。

是以 FackBook 提出了 Cache Aside Pattern

失效:應用程式先從cache取資料,沒有得到,則從資料庫中取資料,成功後,放到緩存中。

命中:應用程式從cache中取資料,取到後傳回。

更新:

先把資料存到資料庫中,成功後,再讓緩存失效

4.5、腦裂

從應用到底層 36張圖帶你進入Redis世界1、基本類型及底層實作2、持久化3、Redis為什麼那麼快4、常見問題5、分布式鎖6、Redis 過期政策和記憶體淘汰政策7、Redis 叢集高可用8、Redis 限流9、常見知識點10、End

腦裂是指因為網絡原因,導緻master節點、slave節點 和 sentinel叢集處于不用的網絡分區,此時因為sentinel叢集無法感覺到master的存在,是以将slave節點提升為master節點 此時存在兩個不同的master節點就像一個大腦分裂成了兩個。其實在

Hadoop

Spark

叢集中都會出現這樣的情況,隻是解決方法不同而已(用ZK配合強制殺死)。

叢集腦裂問題中,如果用戶端還在基于原來的master節點繼續寫入資料那麼新的master節點将無法同步這些資料,當網絡問題解決後sentinel叢集将原先的master節點降為slave節點,此時再從新的master中同步資料将造成大量的資料丢失。

Redis處理方案是redis的配置檔案中存在兩個參數

min-replicas-to-write 3  表示連接配接到master的最少slave數量
min-replicas-max-lag 10  表示slave連接配接到master的最大延遲時間
           

如果連接配接到master的slave數量 < 第一個參數 且 ping的延遲時間 <= 第二個參數那麼master就會拒絕寫請求,配置了這兩個參數後如果發生了叢集腦裂則原先的master節點接收到用戶端的寫入請求會拒絕就可以減少資料同步之後的資料丢失。

4.6、事務

MySQL中的事務還是挺多道道的還要,而在Redis中的事務隻要有如下三步:

從應用到底層 36張圖帶你進入Redis世界1、基本類型及底層實作2、持久化3、Redis為什麼那麼快4、常見問題5、分布式鎖6、Redis 過期政策和記憶體淘汰政策7、Redis 叢集高可用8、Redis 限流9、常見知識點10、End

關于事務具體結論:

1、redis事務就是一次性、順序性、排他性的執行一個隊列中的一系列指令。 

2、Redis事務沒有隔離級别的概念:批量操作在發送 EXEC 指令前被放入隊列緩存,并不會被實際執行,也就不存在事務内的查詢要看到事務裡的更新,事務外查詢不能看到。

3、Redis不保證原子性:Redis中單條指令是原子性執行的,但事務不保證原子性。

4、Redis編譯型錯誤事務中所有代碼均不執行,指令使用錯誤。運作時異常是錯誤指令導緻異常,其他指令可正常執行。

5、watch指令類似于樂觀鎖,在事務送出時,如果watch監控的多個KEY中任何KEY的值已經被其他用戶端更改,則使用EXEC執行事務時,事務隊列将不會被執行。

4.7、常用用戶端

  1. jedis:經典工具,提供了全面Redis操作指令。
  2. Redisson:提供了分布式操作跟可擴充Java資料結構,有分布式鎖跟分布式集合。
  3. Lettuce:基于Netty實作,底層是異步調動,感興趣可以一試。

4.8 緩存預熱跟降級

緩存預熱:

系統上線後先将相關緩存資料加載到緩存系統中防止MySQL壓力過大,

緩存降級:

當緩存失效後或者無法通路的時候,作出正确的舉措,不給資料庫過大壓力,而是直接傳回預設值或者傳回設定的預設值。

4.9、正确開發步驟

上線前

:Redis 高可用,主從+哨兵,Redis cluster,避免全盤崩潰。

上線時

:本地 ehcache 緩存 + Hystrix 限流 + 降級,避免MySQL扛不住。

上線後

:Redis 持久化采用 RDB + AOF 來保證斷點後自動從磁盤上加載資料,快速恢複緩存資料。

5、分布式鎖

日常開發中我們可以用 synchronized 、Lock 實作并發程式設計。但是Java中的鎖隻能保證在同一個JVM程序内中執行。如果在分布式叢集環境下用鎖呢?日常一般有兩種選擇方案。

5.1、 Zookeeper實作分布式鎖

你需要知道一點基本

zookeeper

知識:

1、持久節點:用戶端斷開連接配接zk不删除persistent類型節點

2、臨時節點:用戶端斷開連接配接zk删除ephemeral類型節點

3、順序節點:節點後面會自動生成類似0000001的數字表示順序

4、節點變化的通知:用戶端注冊了監聽節點變化的時候,會調用回調方法

大緻流程如下,其中注意每個節點

監控它前面那個節點狀态,進而避免

羊群效應

。關于模闆代碼百度即可。

從應用到底層 36張圖帶你進入Redis世界1、基本類型及底層實作2、持久化3、Redis為什麼那麼快4、常見問題5、分布式鎖6、Redis 過期政策和記憶體淘汰政策7、Redis 叢集高可用8、Redis 限流9、常見知識點10、End

缺點:

頻繁的建立删除節點,加上注冊watch事件,對于zookeeper叢集的壓力比較大,性能也比不上Redis實作的分布式鎖。

5.2、 Redis實作分布式鎖

本身原理也比較簡單,Redis 自身就是一個單線程處理器,具備互斥的特性,通過setNX,exist等指令就可以完成簡單的分布式鎖,處理好逾時釋放鎖的邏輯即可。

SETNX

SETNX 是SET if Not eXists的簡寫,日常指令是

SETNX key value

,如果 key 不存在則set成功傳回 1,如果這個key已經存在了傳回0。

SETEX

SETEX key seconds value 表達的意思是 将值 value 關聯到 key ,并将 key 的生存時間設為多少秒。如果 key 已經存在,setex指令将覆寫舊值。并且 setex是一個

原子性

(atomic)操作。

加鎖:

一般就是用一個辨別唯一性的字元串比如UUID 配合 SETNX 實作加鎖。

解鎖:

這裡用到了LUA腳本,LUA可以保證是原子性的,思路就是判斷一下Key和入參是否相等,是的話就删除,傳回成功1,0就是失敗。

缺點:

這個鎖是無法重入的,且自己實心的話各種邊邊角角都要考慮到,是以了解個大緻思路流程即可,工程化還是用開源工具包就行。

5.3、 Redisson實作分布式鎖

Redisson 是在Redis基礎上的一個服務,采用了基于NIO的Netty架構,不僅能作為Redis底層驅動用戶端,還能将原生的RedisHash,List,Set,String,Geo,HyperLogLog等資料結構封裝為Java裡大家最熟悉的映射(Map),清單(List),集(Set),通用對象桶(Object Bucket),地理空間對象桶(Geospatial Bucket),基數估計算法(HyperLogLog)等結構。

這裡我們隻是用到了關于分布式鎖的幾個指令,他的大緻底層原理:

從應用到底層 36張圖帶你進入Redis世界1、基本類型及底層實作2、持久化3、Redis為什麼那麼快4、常見問題5、分布式鎖6、Redis 過期政策和記憶體淘汰政策7、Redis 叢集高可用8、Redis 限流9、常見知識點10、End

Redisson加鎖解鎖 大緻流程圖如下:

從應用到底層 36張圖帶你進入Redis世界1、基本類型及底層實作2、持久化3、Redis為什麼那麼快4、常見問題5、分布式鎖6、Redis 過期政策和記憶體淘汰政策7、Redis 叢集高可用8、Redis 限流9、常見知識點10、End

6、Redis 過期政策和記憶體淘汰政策

6.1、Redis的過期政策

Redis中 過期政策 通常有以下三種:

1、定時過期:

每個設定過期時間的key都需要建立一個定時器,到過期時間就會立即對key進行清除。該政策可以立即清除過期的資料,對記憶體很友好;但是會占用大量的CPU資源去處理過期的資料,進而影響緩存的響應時間和吞吐量。

2、惰性過期:

隻有當通路一個key時,才會判斷該key是否已過期,過期則清除。該政策可以最大化地節省CPU資源,卻對記憶體非常不友好。極端情況可能出現大量的過期key沒有再次被通路,進而不會被清除,占用大量記憶體。

3、定期過期:

每隔一定的時間,會掃描一定數量的資料庫的expires字典中一定數量的key,并清除其中已過期的key。該政策是前兩者的一個折中方案。通過調整定時掃描的時間間隔和每次掃描的限定耗時,可以在不同情況下使得CPU和記憶體資源達到最優的平衡效果。

expires字典會儲存所有設定了過期時間的key的過期時間資料,其中 key 是指向鍵空間中的某個鍵的指針,value是該鍵的毫秒精度的UNIX時間戳表示的過期時間。鍵空間是指該Redis叢集中儲存的所有鍵。

Redis采用的過期政策:

惰性删除

+

定期删除

。memcached采用的過期政策:

惰性删除

6.2、6種記憶體淘汰政策

Redis的記憶體淘汰政策是指在Redis的用于緩存的記憶體不足時,怎麼處理需要新寫入且需要申請額外空間的資料。

1、volatile-lru:從已設定過期時間的資料集(server.db[i].expires)中挑選最近最少使用的資料淘汰

2、volatile-ttl:從已設定過期時間的資料集(server.db[i].expires)中挑選将要過期的資料淘汰

3、volatile-random:從已設定過期時間的資料集(server.db[i].expires)中任意選擇資料淘汰

4、allkeys-lru:從資料集(server.db[i].dict)中挑選最近最少使用的資料淘汰

5、allkeys-random:從資料集(server.db[i].dict)中任意選擇資料淘汰

6、no-enviction(驅逐):禁止驅逐資料,不删除的意思。

面試常問常考的也就是LRU了,大家熟悉的

LinkedHashMap

中也實作了

LRU

算法的,LinkedHashMap 是通過雙向連結清單和散清單這兩種資料結構組合實作的。LinkedHashMap 中的

Linked

實際上是指的是雙向連結清單,并非指用連結清單法解決散列沖突。稍微修改就是個LRU:

class SelfLRUCache<K, V> extends LinkedHashMap<K, V> {
    private final int CACHE_SIZE;
    /**
     * 傳遞進來最多能緩存多少資料
     * @param cacheSize 緩存大小
     */
    public SelfLRUCache(int cacheSize) {
  // true 表示讓 linkedHashMap 按照通路順序來進行排序,最近通路的放在頭部,最老通路的放在尾部。
        super((int) Math.ceil(cacheSize / 0.75) + 1, 0.75f, true);
        CACHE_SIZE = cacheSize;
    }
    @Override
    protected boolean removeEldestEntry(Map.Entry<K, V> eldest) {
        // 當 map中的資料量大于指定的緩存個數的時候,就自動删除最老的資料。
        return size() > CACHE_SIZE;
    }
}
           

6.2、總結

Redis的記憶體淘汰政策的選取并不會影響過期的key的處理。記憶體淘汰政策用于處理記憶體不足時的需要申請額外空間的資料,過期政策用于處理過期的緩存資料。

7、Redis 叢集高可用

單機問題有機器故障、容量瓶頸、QPS瓶頸。在實際應用中,Redis的多機部署時候會涉及到

redis主從複制

Sentinel哨兵模式

Redis Cluster

模式 優點 缺點
單機版 架構簡單,部署友善 機器故障、容量瓶頸、QPS瓶頸
主從複制 高可靠性,讀寫分離 故障恢複複雜,主庫的寫跟存受單機限制
Sentinel 哨兵 叢集部署簡單,HA 原理繁瑣,slave存在資源浪費,不能解決讀寫分離問題
Redis Cluster 資料動态存儲solt,可擴充,高可用 用戶端動态感覺後端變更,批量操作支援查

7.1、redis主從複制

該模式下 具有高可用性且讀寫分離, 會采用

增量同步

全量同步

兩種機制。

7.1.1、全量同步
從應用到底層 36張圖帶你進入Redis世界1、基本類型及底層實作2、持久化3、Redis為什麼那麼快4、常見問題5、分布式鎖6、Redis 過期政策和記憶體淘汰政策7、Redis 叢集高可用8、Redis 限流9、常見知識點10、End

Redis全量複制一般發生在Slave初始化階段,這時Slave需要将Master上的所有資料都複制一份:

1、slave連接配接master,發送

psync

指令。

2、master接收到

psync

命名後,開始執行bgsave指令生成RDB檔案并使用緩沖區記錄此後執行的所有寫指令。

3、master發送快照檔案到slave,并在發送期間繼續記錄被執行的寫指令。

4、slave收到快照檔案後丢棄所有舊資料,載入收到的快照。

5、master快照發送完畢後開始向slave發送緩沖區中的寫指令。

6、slave完成對快照的載入,開始接收指令請求,并執行來自master緩沖區的寫指令。

7.1.2、增量同步

也叫指令同步,就是從庫重放在主庫中進行的指令。Redis會把指令存放在一個環形隊列當中,因為記憶體容量有限,如果備機一直起不來,不可能把所有的記憶體都去存指令,也就是說,如果備機一直未同步,指令可能會被覆寫掉。

Redis增量複制是指Slave初始化後開始正常工作時master發生的寫操作同步到slave的過程。

增量複制的過程主要是master每執行一個寫指令就會向slave發送相同的寫指令。

從應用到底層 36張圖帶你進入Redis世界1、基本類型及底層實作2、持久化3、Redis為什麼那麼快4、常見問題5、分布式鎖6、Redis 過期政策和記憶體淘汰政策7、Redis 叢集高可用8、Redis 限流9、常見知識點10、End
7.1.3、Redis主從同步政策:
1、

主從剛剛連接配接的時候,進行全量同步;全同步結束後,進行增量同步

。當然,如果有需要,slave 在任何時候都可以發起全量同步。redis 政策是,無論如何,首先會嘗試進行增量同步,如不成功,要求從機進行全量同步。

2、slave在同步master資料時候如果slave丢失連接配接不用怕,slave在重新連接配接之後

丢失重補

3、一般通過主從來實作讀寫分離,但是如果master挂掉後如何保證Redis的 HA呢? 引入

Sentinel

進行master的選擇。

7.2、高可用之哨兵模式

從應用到底層 36張圖帶你進入Redis世界1、基本類型及底層實作2、持久化3、Redis為什麼那麼快4、常見問題5、分布式鎖6、Redis 過期政策和記憶體淘汰政策7、Redis 叢集高可用8、Redis 限流9、常見知識點10、End

Redis-sentinel 本身是一個獨立運作的程序,一般sentinel叢集 節點數至少三個且奇數個,它能監控多個master-slave叢集,sentinel節點發現master當機後能進行自動切換。Sentinel可以監視任意多個主伺服器以及主伺服器屬下的從伺服器,并在被監視的主伺服器下線時,自動執行故障轉移操作。這裡需注意

sentinel

也有

single-point-of-failure

問題。大緻羅列下哨兵用途:

叢集監控:循環監控master跟slave節點。

消息通知:當它發現有redis執行個體有故障的話,就會發送消息給管理者

故障轉移:這裡分為主觀下線(單獨一個哨兵發現master故障了)。客觀下線(多個哨兵進行抉擇發現達到quorum數時候開始進行切換)。

配置中心:如果發生了故障轉移,它會通知将master的新位址寫在配置中心告訴用戶端。

7.3、Redis Cluster

RedisCluster是Redis的分布式解決方案,在3.0版本後推出的方案,有效地解決了Redis分布式的需求。

從應用到底層 36張圖帶你進入Redis世界1、基本類型及底層實作2、持久化3、Redis為什麼那麼快4、常見問題5、分布式鎖6、Redis 過期政策和記憶體淘汰政策7、Redis 叢集高可用8、Redis 限流9、常見知識點10、End
7.3.1、分區規則
從應用到底層 36張圖帶你進入Redis世界1、基本類型及底層實作2、持久化3、Redis為什麼那麼快4、常見問題5、分布式鎖6、Redis 過期政策和記憶體淘汰政策7、Redis 叢集高可用8、Redis 限流9、常見知識點10、End

常見的分區規則

  1. 節點取餘

    :hash(key) % N
  2. 一緻性哈希

    : 一緻性哈希環
  3. 虛拟槽哈希

    :CRC16[key] & 16383

Redis Cluster采用了

虛拟槽分區

方式,具題的實作細節如下:

1、采用去中心化的思想,它使用虛拟槽solt分區覆寫到所有節點上,取資料一樣的流程,節點之間使用輕量協定通信Gossip來減少帶寬占用是以性能很高,

2、自動實作負載均衡與高可用,自動實作failover并且支援動态擴充,官方已經玩到可以1000個節點 實作的複雜度低。

3、每個Master也需要配置主從,并且内部也是采用哨兵模式,如果有半數節點發現某個異常節點會共同決定更改異常節點的狀态。

4、如果叢集中的master沒有slave節點,則master挂掉後整個叢集就會進入fail狀态,因為叢集的slot映射不完整。如果叢集超過半數以上的master挂掉,叢集都會進入fail狀态。

5、官方推薦 叢集部署至少要3台以上的master節點。

8、Redis 限流

經常乘坐北京西二旗地鐵或者在北京西站乘坐的時候經常會遇到一種情況就是如果人很多,地鐵的從業人員拿個小牌前面一檔讓你等會兒再檢票,這就是實際生活應對人流量巨大的措施。

在開發高并發系統時,有三把利器用來保護系統:

緩存

降級

限流

。那麼何為限流呢?顧名思義,限流就是限制流量,就像你寬帶包了1個G的流量,用完了就沒了。通過限流,我們可以很好地控制系統的qps,進而達到保護系統的目的。

1、基于Redis的setnx、zset

1.2、setnx

比如我們需要在10秒内限定20個請求,那麼我們在setnx的時候可以設定過期時間10,當請求的setnx數量達到20時候即達到了限流效果。

缺點:比如當統計1-10秒的時候,無法統計2-11秒之内,如果需要統計N秒内的M個請求,那麼我們的Redis中需要保持N個key等等問題。

1.3、zset

其實限流涉及的最主要的就是滑動視窗,上面也提到1-10怎麼變成2-11。其實也就是起始值和末端值都各+1即可。 我們可以将請求打造成一個zset數組,當每一次請求進來的時候,value保持唯一,可以用UUID生成,而score可以用目前時間戳表示,因為score我們可以用來計算目前時間戳之内有多少的請求數量。而zset資料結構也提供了range方法讓我們可以很輕易的擷取到2個時間戳内有多少請求,

缺點:就是zset的資料結構會越來越大。

2、漏桶算法

漏桶算法思路:把水比作是請求,漏桶比作是系統處理能力極限,水先進入到漏桶裡,漏桶裡的水按一定速率流出,當流出的速率小于流入的速率時,由于漏桶容量有限,後續進入的水直接溢出(拒絕請求),以此實作限流。

從應用到底層 36張圖帶你進入Redis世界1、基本類型及底層實作2、持久化3、Redis為什麼那麼快4、常見問題5、分布式鎖6、Redis 過期政策和記憶體淘汰政策7、Redis 叢集高可用8、Redis 限流9、常見知識點10、End

3、令牌桶算法

令牌桶算法的原理:可以了解成醫院的挂号看病,隻有拿到号以後才可以進行診病。

從應用到底層 36張圖帶你進入Redis世界1、基本類型及底層實作2、持久化3、Redis為什麼那麼快4、常見問題5、分布式鎖6、Redis 過期政策和記憶體淘汰政策7、Redis 叢集高可用8、Redis 限流9、常見知識點10、End

細節流程大緻:

1、所有的請求在處理之前都需要拿到一個可用的令牌才會被處理。

2、根據限流大小,設定按照一定的速率往桶裡添加令牌。

3、設定桶最大可容納值,當桶滿時新添加的令牌就被丢棄或者拒絕。

4、請求達到後首先要擷取令牌桶中的令牌,拿着令牌才可以進行其他的業務邏輯,處理完業務邏輯之後,将令牌直接删除。

5、令牌桶有最低限額,當桶中的令牌達到最低限額的時候,請求處理完之後将不會删除令牌,以此保證足夠的限流。

工程化:

1、自定義注解、aop、Redis + Lua 實作限流。

2、推薦 guava 的RateLimiter實作。

9、常見知識點

  1. 字元串模糊查詢時用

    Keys

    可能導緻線程阻塞,盡量用

    scan

    指令進行無阻塞的取出資料然後去重下即可。
  2. 多個操作的情況下記得用

    pipeLine

    把所有的指令一次發過去,避免頻繁的發送、接收帶來的網絡開銷,提升性能。
  3. bigkeys可以掃描redis中的大key,底層是使用scan指令去周遊所有的鍵,對每個鍵根據其類型執行STRLEN、LLEN、SCARD、HLEN、ZCARD這些指令擷取其長度或者元素個數。缺陷是線上試用并且個數多不一定空間大,
  4. 線上應用記得開啟Redis慢查詢日志哦,基本思路跟MySQL類似。
  5. Redis中因為記憶體配置設定政策跟增删資料是會導緻

    記憶體碎片

    ,你可以重新開機服務也可以執行

    activedefrag yes

    進行記憶體重新整理來解決此問題。
    從應用到底層 36張圖帶你進入Redis世界1、基本類型及底層實作2、持久化3、Redis為什麼那麼快4、常見問題5、分布式鎖6、Redis 過期政策和記憶體淘汰政策7、Redis 叢集高可用8、Redis 限流9、常見知識點10、End

Ratio >1 表明有記憶體碎片,越大表明越多嚴重。

Ratio < 1 表明正在使用虛拟記憶體,虛拟記憶體其實就是硬碟,性能比記憶體低得多,這是應該增強機器的記憶體以提高性能。

一般來說,mem_fragmentation_ratio的數值在1 ~ 1.5之間是比較健康的。

10、End

關于Redis先吹逼這麼多(本來想寫秒殺的,怕寫太長),如果你感覺沒看夠那

得價錢

從應用到底層 36張圖帶你進入Redis世界1、基本類型及底層實作2、持久化3、Redis為什麼那麼快4、常見問題5、分布式鎖6、Redis 過期政策和記憶體淘汰政策7、Redis 叢集高可用8、Redis 限流9、常見知識點10、End

往期精選:

  1. 順豐快遞:請簽收MySQL靈魂十連
  2. 面試HashMap看這篇就夠了
  3. 爛大街的Spring循環依賴如何說
  4. 由淺入深逐漸了解 Synchronized
  5. 快速上手JUC下常見并發容器
  6. 3W字玩轉SpringCloud

繼續閱讀