面试必会系列 - 3.1 Redis知识点大汇总(数据类型,内存模型,持久化,缓存击穿,集群,一致性哈希等等)
本文已收錄至 Github(MD-Notes),若博客中圖片模糊或打不開,可以來我的 Github 倉庫,包含了完整圖文:https://github.com/HanquanHq/MD-Notes,涵蓋了互聯網大廠面試必問的知識點,講解透徹,長期更新中,歡迎一起學習探討 ~
更多內容,可以訪問:
面試必會系列專欄:https://blog.csdn.net/sinat_42483341/category_10300357.html
操作系統系列專欄:https://blog.csdn.net/sinat_42483341/category_10519484.html
目錄
- Redis
- 常問問題
- Redis 五種基本數據結構
- 1、字符串 string
- 2、列表 list
- 3、字典 hash
- 4、集合 set
- 5、有序集合 zset
- 內存模型
- 數據類型
- 使用
- 在 Java 中的 API 使用
- 持久化
- RDB
- AOF
- 緩存常見問題
- 緩存擊穿
- 緩存穿透
- 緩存雪崩
- 緩存一致性(雙寫問題)
- Redis 集群
- AKF 拆分原則
- 拆分之后,怎么知道數據存在哪臺機器了?去哪里取數據?
- AKF 存在的問題:CAP定理
- 集群一般使用奇數臺
- Sentinel 哨兵
- 一致性哈希
- 普通的哈希算法存在的問題:
- 一致性哈希算法的優點
- hash環的偏斜
- 虛擬節點
Redis
常問問題
采用單工作 worker 線程,6.x 版本有多 IO threads(Tomcat,Netty 也有 IO threads);
Redis 二進制安全:自身不會做編解碼,不會變化你的數據,你在客戶端要提供二進制的字節數組,不同客戶端之間要統一編碼。Zookeeper,HBase,Kafka 都是二進制安全的。
Redis 天生單線程,多 server 共同請求時,不需要加鎖。缺點:不能充分發揮服務器核數,所以進行了 IO/計算隔離,增加多個 IO 線程,worker 還是單線程。
聊:持久化 -> 同步 -> 集群 -> 中間件(分布式,ES…)讓對方覺得你的技術棧非常豐富
技術選型:Redis,memcached 怎么選擇?Redis 相比 memcached,計算向數據移動;支持模塊化,如布隆過濾器,也可以自己C++開發更多的數據類型
Redis 五種基本數據結構
string(字符串)、list(列表)、hash(字典)、set(集合) 和 zset(有序集合)
1、字符串 string
Redis 中的字符串是一種 動態字符串,這意味著使用者可以修改,它的底層實現有點類似于 Java 中的 ArrayList,有一個字符數組,從源碼的 sds.h/sdshdr 文件 中可以看到 Redis 底層對于字符串的定義 SDS,即 Simple Dynamic String 結構。
我們通常使用 SET 和 GET 來設置和獲取字符串值。
Redis 規定了字符串的長度不得超過 512 MB。
2、列表 list
相當于 Java 語言中的 LinkedList,注意它是鏈表而不是數組。這意味著 list 的插入和刪除操作非常快,時間復雜度為 O(1),但是索引定位很慢,時間復雜度為 O(n)。
基本操作
- LPUSH 和 RPUSH 分別可以向 list 的左邊(頭部)和右邊(尾部)添加一個新元素;
- LRANGE 命令可以從 list 中取出一定范圍的元素;
- LINDEX 命令可以從 list 中取出指定下表的元素,相當于 Java 鏈表操作中的 get(int index) 操作;
3、字典 hash
Redis 中的字典相當于 Java 中的 HashMap,內部實現也差不多類似,都是通過 “數組 + 鏈表” 的鏈地址法來解決部分 哈希沖突,同時這樣的結構也吸收了兩種不同數據結構的優點。
4、集合 set
Redis 的集合相當于 Java 語言中的 HashSet,它內部的鍵值對是無序、唯一的。它的內部實現相當于一個特殊的字典,字典中所有的 value 都是一個值 NULL。
5、有序集合 zset
這可能使 Redis 最具特色的一個數據結構了,它類似于 Java 中 SortedSet 和 HashMap 的結合體,一方面它是一個 set,保證了內部 value 的唯一性,另一方面它可以為每個 value 賦予一個 score 值,用來代表排序的權重。
它的內部實現用的是一種叫做 「跳躍表」 的數據結構,由于比較復雜,所以在這里簡單提一下原理就好了:
想象你是一家創業公司的老板,剛開始只有幾個人,大家都平起平坐。后來隨著公司的發展,人數越來越多,團隊溝通成本逐漸增加,漸漸地引入了組長制,對團隊進行劃分,于是有一些人又是員工又有組長的身份。
再后來,公司規模進一步擴大,公司需要再進入一個層級:部門。于是每個部門又會從組長中推舉一位選出部長。
跳躍表就類似于這樣的機制,最下面一層所有的元素都會串起來,都是員工,然后每隔幾個元素就會挑選出一個代表,再把這幾個代表使用另外一級指針串起來。然后再在這些代表里面挑出二級代表,再串起來。最終形成了一個金字塔的結構。
想一下你目前所在的地理位置:亞洲 > 中國 > 某省 > 某市 > …,就是這樣一個結構!
內存模型
- epoll
- Nginx 也用了 epoll
- fd
- 紅黑樹
-
零拷貝
-
sendfile
-
mmap
-
epoll 源碼沒有用到 mmap,epoll 用的是紅黑樹和雙端鏈表,是 redis 用到了 mmap
-
理解為:我們使用 epoll 的時候要注意用 mmap 輔助,來避免 read/write 多一次拷貝,而非系統內核幫你維護了 mmap
-
編譯器將 epoll_create 編譯為了 int 80 中斷等一系列匯編指令,交給CPU就行了。C代碼中雖然是顯式調用 epoll_create ,但C翻譯成匯編的時候就翻譯成了相應的0x80中斷以及相關指令的
-
-
數據類型
- string
- bitmap:可以做布隆過濾器
- list
- hashmap
- set
- sorted_set(zsest)
- 排序的實現:跳躍表
使用
-
管道
(printf "PING\r\nPING\r\nPING\r\n"; sleep 1) | nc localhost 6379- 只用了一個命令的開銷時間
- 服務器將被迫回復一個隊列答復
-
發布/訂閱 pubsub
- 發送者 publish test1 hello
- 訂閱者 subscribe test1
- 場景:聊天室,啟動多個 redis 去接收訂閱消息
- redis1:將消息實時推送給用戶
- redis2:存入 zset,用來取某個時間窗口的歷史消息
- redis3:啟一個 service ,將最新消息推給 kafka,然后異步存入db
-
事務
- 無論誰先開啟事務,exec命令先到達的client,事務先被執行
- watch 某個 key,如果發生更改,事務不執行
-
布隆過濾器
- RedisBloom模塊
- 用小空間解決大量數據匹配,避免緩存穿透
- 有一定的誤判率
布隆過濾器升級版
- Counting Bloom Filter
- 布谷鳥過濾器
在 Java 中的 API 使用
- 使用 Jedis 連接 Redis 客戶端
- google 的布隆過濾器 BloomFilter,需要定義好預估數據量的大小和誤差率
- 布隆過濾器的其他使用場景:爬蟲(防止重復爬取網頁),推薦系統(防止重復推送內容 )
持久化
默認開啟 RDB + AOF 混合使用。為什么做緩存還要持久化?這樣避免你重啟之后Redis空了,導致請求全部穿透到數據庫。
RDB
save 900 1 save 300 10 save 60 10000dbfilename dump.rdb dir /var/lib/redis/6379- 時點性
- save,阻塞的,比如關機維護
- bgsave,后臺的,fork子進程
AOF
appendonly yes appendfilename "appendonly.aof"auto-aof-rewrite-percentage 100 auto-aof-rewrite-min-size 64mbappendfsync always appendfsync everysec appendfsync no- 將寫操作記錄到文件中
- 丟失數據少
- 可以和 RDB 同時開啟
- 4.0 以前,重寫 AOF 時,刪除抵消的命令,合并重復的命令
- 4.0 以后,重寫 AOF 時,將老的數據 RDB 到 AOF 文件中,再將增量數據以指令的方式 append 到 AOF 文件中
緩存常見問題
緩存擊穿
作為緩存,受到內存大小限制,可能:
- key 超過了過期時間
- key 被 LRU LFU 清掉了
因為 某些 key 不在 redis 里面了,大量并發來找這個 key 的時候,這時候客戶端去直接請求數據庫,這就是擊穿。
這個問題怎么解決?
只要發現某個key不存在,就讓所有對這個key的請求去搶一把鎖。也就是說,
讓第一個找key的請求,執行一個setnx,類似于放一把鎖。只有獲得鎖的人才能去數據庫查,其他的請求讓它們失敗,sleep等待幾秒鐘之后,重新去 redis 取數據。
這會存在一個問題:
1、如果第一個拿到鎖的人掛了,別人也拿不到鎖,這樣就死鎖了。可以設置鎖的過期時間來避免這個問題。
2、由于我設置了過期時間,可能會發生這樣的情況:拿到鎖的人沒掛,但是可能由于網絡擁塞或者數據庫擁塞,鎖超時了,又有一個人拿到這個鎖,又去數據庫取,更加擁塞了。
針對這個問題,可以開啟多個線程,一個線程去庫里取數據,另一個線程去給鎖的超時時間延長。這樣會讓代碼邏輯變得復雜。
3、像上面這樣,你自己去實現分布式協調很麻煩。因此我們引入Zokeeper,這個以后再講~
緩存穿透
從業務接收查詢的,是你系統里面根本不存在的數據。這就是緩存穿透。
怎么解決?使用布隆過濾器
- 你可以在客戶端中包含布隆過濾器的算法
- 你可以在客戶端只包含算法,在redis中存放bitmap
- 你可以直接在redis中集成布隆模塊:RedisBloom模塊
布隆過濾器的缺點
只能增加,不能刪除,如果你的業務刪除了數據庫中的某條數據,無法在布隆過濾器中刪除這個key
解決方式
你可以使用布谷鳥過濾器等其他支持刪除操作的過濾器,或者設置一個空 key
緩存雪崩
和擊穿有些類似,都是后面有數據的情況。和緩存擊穿不同的是,緩存擊穿指 并發查同一條數據,緩存雪崩是 不同數據都過期了,很多數據都查不到從而查數據庫。
- 大量 key 同時失效,間接造成大量的訪問到達 DB
怎么解決?要考慮兩種情況:
1、每天都要更新數據的情況,例如每天零點要刷新緩存。這時候可以依賴擊穿的解決方案。或者在業務層加一個小延時:判斷如果是零點就延時,隨機sleep幾秒,這樣不會把流量一大波流量同時放過來。
對于能夠提前預知的時點數據,比如京東雙11的頁面樣式、圖片等,可以提前推到客戶端本地,到雙11零點的時候直接切換即可。
2、與時點性無關(并不需要在某個時間刷新緩存)的話,可以設置隨機過期時間。
緩存一致性(雙寫問題)
更新緩存的的Design Pattern有四種:Cache aside, Read through, Write through, Write behind caching
如何保證緩存與數據庫的雙寫一致性?https://www.jianshu.com/p/2936a5c65e6b
分布式系統里要么通過 2PC 或是 Paxos 協議保證一致性,要么就是拼命的降低并發時臟數據的概率
緩存系統適用的場景就是非強一致性的場景,所以它屬于CAP中的AP,BASE理論。
異構數據庫本來就沒辦法強一致,我們只是減少時間窗口,達到最終一致性。
還有別忘了設置過期時間,這是個兜底方案
Redis 集群
單機、單節點、單實例存在的問題
- 單點故障(主從、主備,一變多,多機數據存的是一樣的)
- 容量有限
- 單機壓力過大(分片集群,sharding 分片,不同分片存儲的是不一樣的數據)
單機單點問題的解決方式:AKF
AKF 拆分原則
- X軸(主從復制):可以是Redis實例的副本,數據庫的副本等
- 讀寫分離,增加備用性,解決單點故障 問題
- 全量鏡像,不能解決容量有限 的問題
- Y軸(按業務拆分):數據分開存儲,客戶端按照業務指定查詢哪個庫
- 解決容量有限 的問題
- Z軸(哈希取模):在按照不同業務拆分的前提下,將同一個業務將數據再拆分,存到不同的庫中
- 數據量足夠小,更易發揮單機性能
拆分之后,怎么知道數據存在哪臺機器了?去哪里取數據?
可以在客戶端自己實現;可以用代理的中間件;
AKF 存在的問題:CAP定理
- 一致性(Consistency):分布式系統中的所有數據備份,在同一時刻是否同樣的值
- 可用性(Availability):集群中一部分節點故障后,集群整體是否還能響應客戶端的讀寫請求
- 分區容忍性(Partition tolerance):是否能夠容忍發生網絡分區,對外表現出數據的不一致
- 例如,向注冊中心集群注冊50臺tomcat,有的僅成功注冊了40臺,不影響使用,可容忍
集群一般使用奇數臺
當存活節點過半的時候,就可以解決容錯問題,正常提供服務
為什么是奇數臺?你看,3個節點4個節點都最多允許1個節點掛掉,但是:
- 4臺服務器成本高于3臺服務器成本
- 不管你是4節點還是3個節點,都只允許掛1個,風險是一樣的
Sentinel 哨兵
- 監控
- 提醒
- 自動故障遷移
- 流言協議:主服務器是否下線
- 投票協議:重新選主,故障遷移
一致性哈希
由于redis是單點,但是項目中不可避免的會使用多臺Redis緩存服務器,那么怎么把緩存的Key均勻的映射到多臺Redis服務器上,且隨著緩存服務器的增加或減少時做到最小化的減少緩存Key的命中率呢?這樣就需要我們自己實現分布式。http://www.zsythink.net/archives/1182
普通的哈希算法存在的問題:
當緩存服務器數量發生變化時,會引起緩存的雪崩,大量緩存同一時間失效。
當緩存服務器數量發生變化時,幾乎所有緩存的位置都會發生改變。怎樣才能盡量減少受影響的緩存呢?
hash(服務器A的IP地址) % 2^32 hash(服務器B的IP地址) % 2^32 hash(服務器C的IP地址) % 2^32一致性哈希算法的優點
假設服務器B出現了故障,我們現在需要將服務器B移除,那么,我們將上圖中的服務器B從hash環上移除即可。圖片4仍然會被緩存到服務器C中,圖片1與圖片2仍然會被緩存到服務器A中,這與服務器B移除之前并沒有任何區別。
hash環的偏斜
如果服務器被映射成下圖中的模樣,那么被緩存的對象很有可能大部分集中緩存在某一臺服務器上。
我們應該怎樣防止hash環的偏斜呢?一致性hash算法中使用"虛擬節點"解決了這個問題
虛擬節點
“虛擬節點"是"實際節點”(實際的物理服務器)在hash環上的復制品,一個實際節點可以對應多個虛擬節點。
從上圖可以看出,A、B、C三臺服務器分別虛擬出了一個虛擬節點,當然,如果你需要,也可以虛擬出更多的虛擬節點。引入虛擬節點的概念后,緩存的分布就均衡多了。如果你還不放心,可以虛擬出更多的虛擬節點,以便減小hash環偏斜所帶來的影響,虛擬節點越多,hash環上的節點就越多,緩存被均勻分布的概率就越大。
總結
以上是生活随笔為你收集整理的面试必会系列 - 3.1 Redis知识点大汇总(数据类型,内存模型,持久化,缓存击穿,集群,一致性哈希等等)的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 左神算法:分别用递归和非递归方式实现二叉
- 下一篇: SQL 中 left join、righ