上海免費(fèi)網(wǎng)站建設(shè)百度關(guān)鍵詞seo推廣
什么是Redis?
Redis 和 Memcached 有什么區(qū)別??
為什么用 Redis 作為 MySQL 的緩存??
主要是因?yàn)镽edis具備高性能和高并發(fā)兩種特性。
高性能:MySQL中數(shù)據(jù)是從磁盤(pán)讀取的,而Redis是直接操作內(nèi)存,速度相當(dāng)快。
高并發(fā):單臺(tái)設(shè)備的Redis的QPS(每秒鐘處理完請(qǐng)求的次數(shù))是MySQL的十倍,Redis單機(jī)的QPS能輕松破10w,而MySQL單機(jī)的QPS很難破1w。所以,直接訪問(wèn)Redis能夠承受的請(qǐng)求是遠(yuǎn)遠(yuǎn)大于直接訪問(wèn)MySQL的。
Redis數(shù)據(jù)類(lèi)型
介紹Redis數(shù)據(jù)結(jié)構(gòu)之前,先來(lái)介紹以下Redis對(duì)象系統(tǒng)。
什么是Redis對(duì)象系統(tǒng)?
Redis中基于雙向鏈表、簡(jiǎn)單動(dòng)態(tài)字符串、字典、跳躍表、整數(shù)集合、壓縮列表、快速列表等數(shù)據(jù)結(jié)構(gòu)實(shí)現(xiàn)了一個(gè)對(duì)象系統(tǒng),并且實(shí)現(xiàn)了5種不同的對(duì)象,每種對(duì)象都使用了至少一種前面的數(shù)據(jù)結(jié)構(gòu),優(yōu)化對(duì)象在不同場(chǎng)合下的使用效率。
對(duì)象結(jié)構(gòu) RedisObject 包含:type、encoding、lru、refcount、*ptr,下面逐一介紹:
type:表示對(duì)象的數(shù)據(jù)類(lèi)型,包括字符串類(lèi)型、列表類(lèi)型、集合類(lèi)型、有序集合類(lèi)型、哈希類(lèi)型,占4位。
encoding:對(duì)象的編碼類(lèi)型,占4位,標(biāo)識(shí)了該對(duì)象使用了哪種底層的數(shù)據(jù)結(jié)構(gòu)。
lru:該字段是一個(gè)時(shí)間戳,表示對(duì)象最近一次被訪問(wèn)的時(shí)間,用于實(shí)現(xiàn)內(nèi)存管理。當(dāng) Redis 設(shè)置了最大內(nèi)存限制并需要進(jìn)行逐出策略時(shí),LRU 被用來(lái)決定哪些數(shù)據(jù)應(yīng)該被淘汰。
refcount:表示對(duì)該 RedisObject 的引用計(jì)數(shù),當(dāng)某個(gè)操作引用這個(gè)對(duì)象時(shí),refcount 會(huì)加1;當(dāng)引用被釋放時(shí),refcount減1。引用計(jì)數(shù)為0時(shí),對(duì)象會(huì)被銷(xiāo)毀,釋放內(nèi)存。
*ptr:指向底層數(shù)據(jù)實(shí)現(xiàn)的指針。
對(duì)象結(jié)構(gòu)的功能?
- 為5種不同的對(duì)象類(lèi)型提供同一的表示形式。
- 針對(duì)不同的場(chǎng)景,Redis 支持同一種對(duì)象類(lèi)型使用多種不同的數(shù)據(jù)結(jié)構(gòu)。
- 支持引用計(jì)數(shù),實(shí)現(xiàn)對(duì)象共享機(jī)制。
- 記錄對(duì)象的訪問(wèn)時(shí)間,便于刪除對(duì)象。
字符串對(duì)象底層實(shí)現(xiàn):
列表對(duì)象底層實(shí)現(xiàn):
集合對(duì)象底層實(shí)現(xiàn):
哈希對(duì)象底層實(shí)現(xiàn):
有序集合對(duì)象底層實(shí)現(xiàn):
Redis常見(jiàn)數(shù)據(jù)類(lèi)型和應(yīng)用場(chǎng)景
String
介紹
內(nèi)部實(shí)現(xiàn)
具體如下:
常用命令
普通字符串的基本操作:
批量設(shè)置:
計(jì)數(shù)器(字符串的內(nèi)容為整數(shù)的時(shí)候可以使用):?
過(guò)期(默認(rèn)為永不過(guò)期):
不存在就插入:
應(yīng)用場(chǎng)景
1.緩存對(duì)象
2.常規(guī)計(jì)數(shù)
因?yàn)?Redis 處理命令是單線程,所以執(zhí)行命令的過(guò)程是原子的。因此 String 數(shù)據(jù)類(lèi)型適合計(jì)數(shù)場(chǎng)
景,比如計(jì)算訪問(wèn)次數(shù)、點(diǎn)贊、轉(zhuǎn)發(fā)、庫(kù)存數(shù)量等等。
比如計(jì)算文章的閱讀量:
3.分布式鎖
4.共享 Session 信息
List
介紹
內(nèi)部實(shí)現(xiàn)
常用命令
應(yīng)用場(chǎng)景
消息隊(duì)列

2.如何處理重復(fù)的消息?
3.如何保證消息可靠性?
總結(jié):
list 作為消息隊(duì)列有什么缺陷?
list 不支持多個(gè)消費(fèi)者消費(fèi)同一條消息。Redis 從 5.0 版本開(kāi)始提供 Stream 數(shù)據(jù)類(lèi)型,同樣滿足消息隊(duì)列三大需求,而且它還支持多個(gè)消費(fèi)者消費(fèi)同一條消息。
Hash
介紹
內(nèi)部實(shí)現(xiàn)?
常用命令
應(yīng)用場(chǎng)景
1.緩存對(duì)象
2.購(gòu)物車(chē)
以用戶 id 為 key,商品 id 為 field,商品數(shù)量為 value,恰好構(gòu)成了購(gòu)物車(chē)的3個(gè)要素。
Set
介紹
內(nèi)部實(shí)現(xiàn)
常用命令
Set 常用操作
Set 運(yùn)算操作
交集運(yùn)算:
并集運(yùn)算:
差集運(yùn)算:
?
應(yīng)用場(chǎng)景
1.點(diǎn)贊
Set 類(lèi)型可以保證一個(gè)用戶只能點(diǎn)一個(gè)贊。
2.共同關(guān)注
Set 類(lèi)型支持交集運(yùn)算,所以可以用來(lái)計(jì)算共同關(guān)注的好友、公眾號(hào)等。
3.抽獎(jiǎng)活動(dòng)
存儲(chǔ)某活動(dòng)中中獎(jiǎng)的用戶名 ,Set 類(lèi)型因?yàn)橛腥ブ毓δ?#xff0c;可以保證同一個(gè)用戶不會(huì)中獎(jiǎng)兩次。
Zset
介紹
內(nèi)部實(shí)現(xiàn)
常用命令
Zset 常用操作
Zset 運(yùn)算操作(相比于 Set 類(lèi)型,ZSet 類(lèi)型沒(méi)有支持差集運(yùn)算)
應(yīng)用場(chǎng)景
1.排行榜
有序集合比較典型的使用場(chǎng)景就是排行榜。例如學(xué)生成績(jī)的排名榜、游戲積分排行榜、視頻播放排名、電商系統(tǒng)中商品的銷(xiāo)量排名等。
2.電話、姓名排序
獲取所有號(hào)碼:?
ZRANGEBYLEX phone - +
?獲取 132 號(hào)段的號(hào)碼:
ZRANGEBYLEX phone [132 (133
?獲取132、133號(hào)段的號(hào)碼:
ZRANGEBYLEX phone [132 (134
BitMap
介紹
內(nèi)部實(shí)現(xiàn)
常用命令
bitmap 基本操作
bitmap 運(yùn)算操作
# BitMap間的運(yùn)算
# operations 位移操作符,枚舉值A(chǔ)ND 與運(yùn)算 &OR 或運(yùn)算 |XOR 異或 ^NOT 取反 ~
# result 計(jì)算的結(jié)果,會(huì)存儲(chǔ)在該key中
# key1 … keyn 參與運(yùn)算的key,可以有多個(gè),空格分割,not運(yùn)算只能一個(gè)key
# 當(dāng) BITOP 處理不同長(zhǎng)度的字符串時(shí),較短的那個(gè)字符串所缺少的部分會(huì)被看作 0。返回值是保存到 destkey 的字符串的長(zhǎng)度(以字節(jié)byte為單位),和輸入 key 中最長(zhǎng)的字符串長(zhǎng)度相等。BITOP [operations] [result] [key1] [keyn…]
# 返回指定key中第一次出現(xiàn)指定value(0/1)的位置
BITPOS [key] [value]
應(yīng)用場(chǎng)景
1.簽到統(tǒng)計(jì)
2.判斷用戶登錄狀態(tài)
3.連續(xù)簽到的用戶總數(shù)
如何統(tǒng)計(jì)出連續(xù) 7 天打卡的用戶總數(shù)呢?
我們把每天的日期作為 Bitmap 的 key,userId 作為 offset,若是打卡則將 offset 位置的 bit 設(shè)置成 1。key 對(duì)應(yīng)的集合的每個(gè) bit 位的數(shù)據(jù)則是一個(gè)用戶在該日期的打卡記錄。
一共有 7 個(gè)這樣的 Bitmap,對(duì)這 7 個(gè) Bitmap 對(duì)應(yīng)的 bit 位做與運(yùn)算。如果最終 userId 對(duì)應(yīng)的 bit 位為 1,就說(shuō)明該用戶 7 天連續(xù)打卡。將結(jié)果保存到一個(gè)新 Bitmap 中,我們?cè)偻ㄟ^(guò) BITCOUNT 統(tǒng)計(jì) bit = 1 的個(gè)數(shù)便得到了連續(xù)打卡 7 天的用戶總數(shù)了。
HyperLogLog
介紹
內(nèi)部實(shí)現(xiàn)
常用命令
127.0.0.1:6379>pfadd uv1 a b c d e#uv1中5個(gè)元素:[a,b,c,d,e]
(integer) 1
127.0.0.1:6379>pfcount uv1 #uv1中數(shù)量為5
(integer)5
127.0.0.1:6379>pfadd uv2 b c d e f #uv2中5個(gè)元素:[b,c,d,e,f]
(integer) 1
127.0.0.1:6379>pfcount uv2 #uv2中數(shù)量為5
(integer)5
127.0.0.1:6379>pfcount uv1 uv2#獲取uv1和uv2去重之后數(shù)量合集:[a,b,c,d,e,f], 數(shù)量為6
(integer)6
應(yīng)用場(chǎng)景
百萬(wàn)級(jí)網(wǎng)頁(yè) UV(獨(dú)立訪客)計(jì)數(shù)
Redis HyperLogLog 優(yōu)勢(shì)在于只需要花費(fèi) 12 KB 內(nèi)存,就可以計(jì)算接近 2^64 個(gè)元素的基數(shù),和元
素越多就越耗費(fèi)內(nèi)存的 Set 和 Hash 類(lèi)型相比,HyperLogLog 就非常節(jié)省空間。
在統(tǒng)計(jì) UV 時(shí),可以用 PFADD 命令(用于向 HyperLogLog 中添加新元素)把訪問(wèn)頁(yè)面的每個(gè)用戶都添加到 HyperLogLog 中。
PFADD page1:uv user1 user2 user3 user4 user5
接下來(lái),就可以用 PFCOUNT 命令直接獲得 page1 的 UV 值了,這個(gè)命令的作用就是返回
HyperLogLog 的統(tǒng)計(jì)結(jié)果。
PFCOUNT page1:uv
GEO
介紹
內(nèi)部實(shí)現(xiàn)
常用命令
# 存儲(chǔ)指定的地理空間位置,可以將一個(gè)或多個(gè)經(jīng)度(longitude)、緯度(latitude)、位置名稱(chēng)(member)添加到指定的 key 中。
GEOADD key longitude latitude member [longitude latitude member ...]# 從給定的 key 里返回所有指定名稱(chēng)(member)的位置(經(jīng)度和緯度),不存在的返回 nil。
GEOPOS key member [member ...]# 返回兩個(gè)給定位置之間的距離。
GEODIST key member1 member2 [m|km|ft|mi]# 根據(jù)用戶給定的經(jīng)緯度坐標(biāo)來(lái)獲取指定范圍內(nèi)的地理位置集合。單位:[m|km|ft|mi]->[米|干米|英里|英尺]
GEORADIUS key longitude latitude radius m|km|ft|mi [WITHCOORD] [WITHDIST] [WITHHASH] [COUNT count] [ASC|DESC] [STORE key] [STOREDIST key]
應(yīng)用場(chǎng)景
滴滴叫車(chē)
Stream
介紹
內(nèi)部實(shí)現(xiàn)
常用命令
應(yīng)用場(chǎng)景
消息隊(duì)列
注:前面介紹的這些操作 List 也支持。
Stream 特有功能
Stream 可以使用 XGROUP 創(chuàng)建消費(fèi)組,創(chuàng)建消費(fèi)組之后,Stream 可以使用 XREADGROUP 命
令讓消費(fèi)組內(nèi)的消費(fèi)者讀取消息。
到消費(fèi)者使用 XACK 命令通知 Streams“消息已經(jīng)處理完成”。
消費(fèi)完成。如果消費(fèi)者沒(méi)有成功處理消息,它就不會(huì)給 Streams 發(fā)送 XACK 命令,消息仍然會(huì)留存。此時(shí),消費(fèi)者可以在重啟后,用 XPENDING 命令查看已讀取、但尚未確認(rèn)處理完成的消息。
一旦消息被消費(fèi)者處理了,消費(fèi)者就可以使用 XACK 命令通知 Streams,然后這條消息就會(huì)被刪除。

- 消息不丟
- 消息可堆積



總結(jié)

Redis數(shù)據(jù)結(jié)構(gòu)
SDS
C語(yǔ)言 char* 字符數(shù)組的缺陷
SDS 做了哪些優(yōu)化??
總結(jié):Redis 的 SDS 結(jié)構(gòu)是在原本字符數(shù)組基礎(chǔ)上,增加了三個(gè)元數(shù)據(jù):len、alloc、flags,用來(lái)解決 C 語(yǔ)言字符串的缺陷。
具體來(lái)說(shuō) SDS 的優(yōu)勢(shì):
SDS 擴(kuò)容規(guī)則:?
比如 sdshdr16 和 sdshdr32 這兩個(gè)類(lèi)型,它們的定義分別如下:
這樣設(shè)計(jì)有什么好處?
舉例:使用編譯優(yōu)化,由原來(lái)的 8 字節(jié)變?yōu)?5 字節(jié)。
鏈表
Redis 的 List 對(duì)象的底層實(shí)現(xiàn)之一就是鏈表。下面我們來(lái)看一下鏈表的結(jié)構(gòu)設(shè)計(jì):
鏈表的優(yōu)點(diǎn)
1.listNode 鏈表節(jié)點(diǎn)的結(jié)構(gòu)里帶有 prev 和 next 指針,獲取某個(gè)節(jié)點(diǎn)的前置節(jié)點(diǎn)或后置節(jié)點(diǎn)的時(shí)
間復(fù)雜度只需O(1),而且這兩個(gè)指針都可以指向 NULL,所以鏈表是無(wú)環(huán)鏈表;
2.list 結(jié)構(gòu)因?yàn)樘峁┝吮眍^指針 head 和表尾節(jié)點(diǎn) tail,所以獲取鏈表的表頭節(jié)點(diǎn)和表尾節(jié)點(diǎn)的時(shí)
間復(fù)雜度只需O(1);
3.list 結(jié)構(gòu)因?yàn)樘峁┝随湵砉?jié)點(diǎn)數(shù)量 len,所以獲取鏈表中的節(jié)點(diǎn)數(shù)量的時(shí)間復(fù)雜度只需O(1);
4.listNode 鏈表節(jié)點(diǎn)使用 void* 指針保存節(jié)點(diǎn)值,并且可以通過(guò) list 結(jié)構(gòu)的 dup、free、match 函數(shù)
指針為節(jié)點(diǎn)設(shè)置該節(jié)點(diǎn)類(lèi)型特定的函數(shù),因此鏈表節(jié)點(diǎn)可以保存各種不同類(lèi)型的值;
鏈表的缺陷

壓縮列表
? zlbytes:占4個(gè)字節(jié),記錄整個(gè)壓縮列表占用的內(nèi)存字節(jié)數(shù)(總字節(jié)數(shù))。
? zltail:占4個(gè)字節(jié),記錄壓縮列表尾節(jié)點(diǎn) entryN 距離壓縮列表的起始地址的字節(jié)數(shù)。
? zllen:占2個(gè)字節(jié),記錄了壓縮列表的節(jié)點(diǎn)數(shù)量。
? entry[1-N]:長(zhǎng)度不定,保存數(shù)據(jù)。
? zlend:占1個(gè)字節(jié),標(biāo)記壓縮列表的末端,固定值 0xFF(十進(jìn)制255)。
舉例:假如有三個(gè)節(jié)點(diǎn):?
注:如果 prevlen 屬性的長(zhǎng)度為 5 字節(jié),那么第一字節(jié)會(huì)被設(shè)置為0xFE(十進(jìn)制值254),而之后的四個(gè)字節(jié)則用于保存前一節(jié)點(diǎn)的長(zhǎng)度。?
舉例說(shuō)明:?
連鎖更新
總結(jié)壓縮列表的缺陷
quicklist
typedef struct quicklist {//quicklist的鏈表頭quicklistNode* head;//quicklist的鏈表尾quicklistNode* tail;//所有壓縮列表中的總元素個(gè)數(shù)unsigned long count;//quicklistNodes的個(gè)數(shù)unsigned long len;...
} quicklist;
typedef struct quicklistNode {//前一個(gè)quicklistNodestruct quicklistNode* prev;//后一個(gè)quicklistNodestruct quicklistNode* next;//quicklistNode指向的壓縮列表unsigned char* zl;//壓縮列表ziplist的總長(zhǎng)度unsigned int sz;//壓縮列表ziplist中的元素個(gè)數(shù)unsigned int count : 16;....
} quicklistNode;
用一張圖更好的理解一下:?
listpack
listpack 結(jié)構(gòu)設(shè)計(jì)
哈希表
Redis哈希表結(jié)構(gòu)如下:
哈希表節(jié)點(diǎn)結(jié)構(gòu)如下:
dictEntry 結(jié)構(gòu)里不僅包含指向鍵和值的指針,還包含了指向下一個(gè)哈希表節(jié)點(diǎn)的指針,這個(gè)指針
可以將多個(gè)哈希值相同的鍵值對(duì)連接起來(lái),以此來(lái)解決哈希沖突的問(wèn)題,這就是鏈?zhǔn)焦!?/p>
除此之外,dictEntry 結(jié)構(gòu)里鍵值對(duì)中的值是一個(gè)「聯(lián)合體 v」定義的,因此,鍵值對(duì)中的值可以是一個(gè)指向?qū)嶋H值的指針,或者是一個(gè)無(wú)符號(hào)的 64 位整數(shù)或有符號(hào)的 64 位整數(shù)或 double 類(lèi)型的值。這么做的好處是可以節(jié)省內(nèi)存空間,因?yàn)楫?dāng)「值」是整數(shù)或浮點(diǎn)數(shù)時(shí),就可以將值的數(shù)據(jù)內(nèi)嵌在 dictEntry 結(jié)構(gòu)里,無(wú)需再用一個(gè)指針指向?qū)嶋H的值,從而節(jié)省了內(nèi)存空間。
哈希沖突
什么是哈希沖突?
哈希表實(shí)際上是一個(gè)數(shù)組,數(shù)組里每一個(gè)元素就是一個(gè)哈希桶。當(dāng)一個(gè)鍵值對(duì)的鍵經(jīng)過(guò) Hash 函數(shù)計(jì)算后得到哈希值,再經(jīng)過(guò)(哈希值 % 哈希表大小),得到的結(jié)果值就是該 key-value 對(duì)應(yīng)的數(shù)組元素位置,也就是第幾個(gè)哈希桶。當(dāng)有兩個(gè)以上數(shù)量的 key 被分到哈希表中同一個(gè)哈希桶上時(shí),此時(shí)稱(chēng)這些 key 發(fā)生了沖突。
鏈?zhǔn)焦?/span>
Redis 采用了「鏈?zhǔn)焦!沟姆椒▉?lái)解決哈希沖突。實(shí)現(xiàn)的方式就是每個(gè)哈希表節(jié)點(diǎn)都有一個(gè) next 指針,用于指向下一個(gè)哈希表節(jié)點(diǎn),因此多個(gè)哈希表節(jié)點(diǎn)可以用 next 指針構(gòu)成一個(gè)單向鏈表,被分配到同一個(gè)哈希桶上的多個(gè)節(jié)點(diǎn)可以用這個(gè)單向鏈表連接起來(lái),這樣就解決了哈希沖突。
鏈?zhǔn)焦5娜毕?#xff1a;
隨著鏈表長(zhǎng)度的增加,在查詢(xún)?cè)撐恢蒙系臄?shù)據(jù)的耗時(shí)就會(huì)增加,最差情況下時(shí)間復(fù)雜度為O(n)。
要想解決這一問(wèn)題,就需要進(jìn)行 rehash,也就是對(duì)哈希表的大小進(jìn)行擴(kuò)展。
rehash
在實(shí)際使用哈希表時(shí),Redis 定義了一個(gè) dict 結(jié)構(gòu)體,這個(gè)結(jié)構(gòu)體里定義了兩個(gè)哈希表(ht[2]),原因就是 rehash 的時(shí)候需要兩個(gè)哈希表。
漸進(jìn)式 rehash
rehash 觸發(fā)條件
整數(shù)集合
整數(shù)集合是 Set 對(duì)象的底層實(shí)現(xiàn)之一。當(dāng)一個(gè) Set 對(duì)象只包含整數(shù)值元素,并且元素?cái)?shù)量不大時(shí),
就會(huì)使用整數(shù)集合這個(gè)數(shù)據(jù)結(jié)構(gòu)作為底層實(shí)現(xiàn)。
整數(shù)集合結(jié)構(gòu)設(shè)計(jì)
整數(shù)集合本質(zhì)上是一塊連續(xù)內(nèi)存空間,它的結(jié)構(gòu)定義如下:
整數(shù)集合的升級(jí)操作
整數(shù)集合升級(jí)有什么好處?
整數(shù)集合支持降級(jí)操作嗎?
跳表
跳表結(jié)構(gòu)體
跳表節(jié)點(diǎn)結(jié)構(gòu)體
跨度是用來(lái)干什么的?
首先明確的是,跨度與遍歷操作無(wú)關(guān),遍歷操作只需要用前向指針(struct zskiplistNode *forward)就可以完成,跨度實(shí)際是為了計(jì)算這個(gè)節(jié)點(diǎn)在跳表中的排位。具體怎么做的呢?因?yàn)樘碇械墓?jié)點(diǎn)都是按序排列的,那么計(jì)算某個(gè)節(jié)點(diǎn)排位的時(shí)候,從頭節(jié)點(diǎn)到該節(jié)點(diǎn)的查詢(xún)路徑上,將沿途訪問(wèn)過(guò)的所有層的跨度累加起來(lái),得到的結(jié)果就是目標(biāo)節(jié)點(diǎn)在跳表中的排位。
跳表節(jié)點(diǎn)查詢(xún)過(guò)程
跳表節(jié)點(diǎn)層數(shù)設(shè)置
如何維持相鄰兩層的節(jié)點(diǎn)數(shù)量的比例為 2 : 1 呢?
對(duì)于跳表最高的層數(shù),Redis 7.0 定義為 32,Redis 5.0 定義為 64, Redis 3.0 定義為 32。
面試題:為什么 Zset 的實(shí)現(xiàn)用跳表而不用平衡樹(shù)(如 AVL樹(shù)、紅黑樹(shù)等)?
Redis線程模式
Redis是單線程嗎?
Redis采用單線程為什么還這么快?
Redis6.0之前為什么使用單線程?
1.官方回答:CPU并不是制約Redis性能表現(xiàn)的瓶頸(即使單線程的程序無(wú)法利用多核CPU),更多情況下是受內(nèi)存大小和網(wǎng)絡(luò)I/O的限制,所以Redis核心網(wǎng)絡(luò)模型采用單線程沒(méi)有問(wèn)題,如果想使用服務(wù)器的多核CPU,可以在一臺(tái)服務(wù)器上啟動(dòng)多個(gè)節(jié)點(diǎn)或者采用分片集群的方式。
2.使用單線程后,可維護(hù)性高,多線程可能會(huì)導(dǎo)致并發(fā)讀寫(xiě)問(wèn)題,增加系統(tǒng)復(fù)雜性,同時(shí)存在線程切換、鎖帶來(lái)的性能損耗。
Redis 6.0 之后為什么引入了多線程?
隨著網(wǎng)絡(luò)硬件性能提升,Redis性能瓶頸有時(shí)會(huì)出現(xiàn)在網(wǎng)絡(luò)I/O處理上。為了提高網(wǎng)絡(luò)I/O并行度,Redis6.0對(duì)于網(wǎng)絡(luò)I/O采用多線程處理(對(duì)于命令的執(zhí)行,仍然采用單線程處理)。引入多線程I/O特性對(duì)性能提升至少一倍以上。
Redis 持久化
Redis 如何實(shí)現(xiàn)數(shù)據(jù)不丟失?
AOF 持久化是如何實(shí)現(xiàn)的??
三種寫(xiě)回策略?xún)?yōu)缺點(diǎn):
AOF日志過(guò)大,會(huì)觸發(fā)什么機(jī)制?
AOF采用文件追加方式,文件會(huì)越來(lái)越大,當(dāng)AOF文件的大小超過(guò)所設(shè)定的閾值時(shí),Redis就會(huì)啟動(dòng)AOF重寫(xiě)機(jī)制,對(duì)AOF文件的內(nèi)容壓縮,只保留可以恢復(fù)數(shù)據(jù)的最小指令集。
重寫(xiě) AOF 日志的過(guò)程是怎樣的?
觸發(fā)重寫(xiě)機(jī)制后,主進(jìn)程會(huì)創(chuàng)建重寫(xiě)AOF的子進(jìn)程,重寫(xiě)AOF子進(jìn)程會(huì)讀取數(shù)據(jù)庫(kù)中所有數(shù)據(jù),并逐一把內(nèi)存數(shù)據(jù)的鍵值對(duì)轉(zhuǎn)換成一條命令,再將命令寫(xiě)入新的AOF文件。
問(wèn)題:在重寫(xiě)過(guò)程中,主進(jìn)程依然可以正常處理命令,如果在子進(jìn)程重寫(xiě)AOF日志過(guò)程中,主進(jìn)程修改了已經(jīng)存在的 key-value,那么會(huì)發(fā)生寫(xiě)時(shí)復(fù)制,此時(shí)會(huì)導(dǎo)致主進(jìn)程與子進(jìn)程數(shù)據(jù)不一致,如何解決?
RDB 持久化是如何實(shí)現(xiàn)的呢??
RDB 持久化是把當(dāng)前進(jìn)程數(shù)據(jù)生成時(shí)間點(diǎn)快照保存到硬盤(pán)的過(guò)程(默認(rèn)保存到dump.rdb中),避免數(shù)據(jù)意外丟失(Redis默認(rèn)的持久化方式)。相比于AOF日志,RDB快照記錄的是實(shí)際數(shù)據(jù),因此在Redis恢復(fù)數(shù)據(jù)時(shí),RDB恢復(fù)數(shù)據(jù)效率更高,只需將RDB文件讀入內(nèi)存即可,不需要像AOF那樣需要額外執(zhí)行操作命令才能恢復(fù)。
RDB做快照時(shí)會(huì)阻塞線程嗎?
注:Redis的快照是全量快照,也就是每次執(zhí)行快照,都是把內(nèi)存中所有數(shù)據(jù)都記錄到磁盤(pán)中。如果太頻繁,會(huì)對(duì)Redis性能造成影響;如果頻率太低,服務(wù)器故障時(shí),丟失的數(shù)據(jù)會(huì)更多。
為什么會(huì)有混合持久化?
混合持久化工作流程:
Redis的大Key對(duì)持久化有什么影響??
如何避免大Key??
Redis 集群
Redis 如何實(shí)現(xiàn)服務(wù)高可用?
主從復(fù)制
在Redis中,用戶可以通過(guò)執(zhí)行 SLAVEOF 命令或者設(shè)置 slaveof 選項(xiàng),讓一個(gè)服務(wù)器去復(fù)制另一個(gè)服務(wù)器,我們稱(chēng)被復(fù)制的服務(wù)器為主服務(wù)器(master),而對(duì)主服務(wù)器進(jìn)行復(fù)制的服務(wù)器則被稱(chēng)為從服務(wù)器(slave)。進(jìn)行復(fù)制中的主從服務(wù)器雙方的數(shù)據(jù)庫(kù)將保存相同的數(shù)據(jù),且主從服務(wù)器之間采用讀寫(xiě)分離的方式,主服務(wù)器只寫(xiě),從服務(wù)器只讀。
為什么進(jìn)行主從復(fù)制?
1.讀寫(xiě)分離,性能擴(kuò)展,降低主服務(wù)器的壓力
2.容災(zāi),快速恢復(fù),主機(jī)掛掉時(shí),從機(jī)變?yōu)橹鳈C(jī)
如何確定誰(shuí)是主服務(wù)器,誰(shuí)是從服務(wù)器?
?
?
命令傳播:
主從服務(wù)器在完成第一次同步后,雙方之間就會(huì)維護(hù)一個(gè)TCP長(zhǎng)連接,目的是避免頻繁的TCP連接和斷開(kāi)帶來(lái)的性能開(kāi)銷(xiāo)。后續(xù)主服務(wù)器可以通過(guò)這個(gè)長(zhǎng)連接繼續(xù)將寫(xiě)操作命令傳輸給從服務(wù)器,然后從服務(wù)器執(zhí)行該命令,使主從一致。
分?jǐn)傊鞣?wù)器的壓力:
從服務(wù)器也可以擁有自己的從服務(wù)器,此時(shí)該從服務(wù)器不僅可以接受主服務(wù)器的同步數(shù)據(jù),自己也可以作為主服務(wù)器將數(shù)據(jù)同步給自己所擁有的從服務(wù)器。通過(guò)這種方式,主服務(wù)器生成RDB和傳輸RDB的壓力可以分?jǐn)偟皆搹姆?wù)器上。
增量復(fù)制:
如果在命令傳輸中,TCP長(zhǎng)連接斷開(kāi),后續(xù)又恢復(fù)正常,怎么保證主從數(shù)據(jù)一致性?
在Redis2.8之前做法是主從服務(wù)器重新進(jìn)行一次全量復(fù)制,從Redis2.8開(kāi)始,主從服務(wù)器采用增量復(fù)制的方式繼續(xù)進(jìn)行同步,即只把網(wǎng)絡(luò)斷開(kāi)期間的主服務(wù)器的寫(xiě)操作同步給從服務(wù)器。
面試題
Redis主從節(jié)點(diǎn)是長(zhǎng)連接還是短連接?
長(zhǎng)連接
怎么判斷 Redis 某個(gè)節(jié)點(diǎn)是否正常工作??
主從復(fù)制架構(gòu)中,過(guò)期key如何處理?
Redis 是同步復(fù)制還是異步復(fù)制?
Redis 主節(jié)點(diǎn)每次收到寫(xiě)命令之后,先寫(xiě)到內(nèi)部的緩沖區(qū),然后異步發(fā)送給從節(jié)點(diǎn)。
主從復(fù)制中兩個(gè) Buffer(replication buffer 、repl backlog buffer)有什么區(qū)別??
為什么會(huì)出現(xiàn)主從數(shù)據(jù)不一致?
如何應(yīng)對(duì)主從數(shù)據(jù)不一致??
主從切換如何減少數(shù)據(jù)丟失??
主從切換中,產(chǎn)生數(shù)據(jù)丟失的情況有兩種:
1.異步復(fù)制導(dǎo)致的數(shù)據(jù)丟失
解決:對(duì)于主節(jié)點(diǎn),一旦所有從節(jié)點(diǎn)數(shù)據(jù)復(fù)制和同步的延遲超過(guò) min-slaves-max-lag 定義的值,此時(shí)主節(jié)點(diǎn)拒絕接受任何請(qǐng)求。對(duì)于客戶端,當(dāng)發(fā)現(xiàn) master 不可寫(xiě)后,采用降級(jí)措施,將數(shù)據(jù)暫時(shí)寫(xiě)入本地緩存和磁盤(pán)中。待 master 恢復(fù)正常后再重新寫(xiě)入。
2.集群產(chǎn)生腦裂導(dǎo)致數(shù)據(jù)丟失
解決:設(shè)置配置文件中參數(shù),如果與主節(jié)點(diǎn)連接的從節(jié)點(diǎn)數(shù)量小于指定值或者主從復(fù)制延遲超過(guò)指定值,則主節(jié)點(diǎn)禁止寫(xiě)數(shù)據(jù)。
主從如何做到故障自動(dòng)切換?
由哨兵自動(dòng)完成故障發(fā)現(xiàn)和故障轉(zhuǎn)移。
哨兵模式?
為什么有哨兵機(jī)制?
哨兵機(jī)制是如何工作的??
1.監(jiān)控:哨兵每隔1秒給所有主從節(jié)點(diǎn)發(fā)送PING命令,如果主從節(jié)點(diǎn)沒(méi)有在規(guī)定時(shí)間響應(yīng)哨兵的PING命令,哨兵會(huì)將它們標(biāo)記為主觀下線。為了減少誤判,會(huì)部署多個(gè)哨兵節(jié)點(diǎn)組成哨兵集群(最少需要三臺(tái)機(jī)器組成哨兵集群)。當(dāng)一個(gè)哨兵判斷主節(jié)點(diǎn)為主觀下線后,就會(huì)向其他哨兵發(fā)起命令,其他哨兵收到命令后,根據(jù)自身和主節(jié)點(diǎn)的網(wǎng)絡(luò)狀況,做出贊成投票或拒絕投票的響應(yīng)。當(dāng)這個(gè)哨兵的贊同票數(shù)達(dá)到哨兵配置文件中的 quorum 設(shè)定的值后,此時(shí)主節(jié)點(diǎn)被哨兵標(biāo)記為客觀下線。(客觀下線只適用于主節(jié)點(diǎn))
由哪個(gè)哨兵做故障轉(zhuǎn)移?
先選舉候選者:哪個(gè)哨兵判斷主節(jié)點(diǎn)為客觀下線哪個(gè)就是候選者。
候選者選舉成為leader:?2.選主+通知:
主從故障轉(zhuǎn)移包含以下步驟:
步驟一:選出新主節(jié)點(diǎn)
步驟二:將從節(jié)點(diǎn)指向新主節(jié)點(diǎn)
步驟三:通知客戶端主節(jié)點(diǎn)已更換
步驟四:將舊主節(jié)點(diǎn)變?yōu)閺墓?jié)點(diǎn)
哨兵繼續(xù)監(jiān)視舊主節(jié)點(diǎn),當(dāng)舊主節(jié)點(diǎn)重新上線時(shí),哨兵集群就會(huì)向它發(fā)送 SLAVEOF 命令,讓它成為新主節(jié)點(diǎn)的從節(jié)點(diǎn)。至此,故障轉(zhuǎn)移完成。
哨兵集群是如何組成的?
切片集群模式
哈希槽如何映射到具體的Redis節(jié)點(diǎn)上?
注意:如果采用手動(dòng)分配哈希槽,需要把 16384 個(gè)槽都分配完,否則Redis集群無(wú)法正常工作。?
集群腦裂導(dǎo)致數(shù)據(jù)丟失怎么辦?
什么是集群腦裂?
如何解決??
Redis 過(guò)期刪除與內(nèi)存淘汰
過(guò)期刪除策略
Redis是可以對(duì)key設(shè)置過(guò)期時(shí)間的,過(guò)期刪除策略用于將已過(guò)期的鍵值對(duì)刪除。
如何設(shè)置過(guò)期時(shí)間?
如果想查看某個(gè) key 剩余的存活時(shí)間,可以使用?TTL <key>
?命令。?
如果想取消 key 的過(guò)期時(shí)間,則可以使用?PERSIST <key>
?命令。
如何判定 key 已過(guò)期了?
過(guò)期刪除策略有哪些?
定時(shí)刪除、惰性刪除、定期刪除
?
Redis 過(guò)期刪除策略是什么??
Redis采用惰性刪除+定期刪除這兩種策略配合使用。
惰性刪除:Redis在訪問(wèn)或者修改key之前,會(huì)檢查key是否過(guò)期,如果過(guò)期,則刪除該key,返回null給客戶端;如果沒(méi)過(guò)期,直接返回正常結(jié)果給客戶端。
定期刪除:默認(rèn)每100ms進(jìn)行一次過(guò)期檢查(該值可在Redis配置文件中修改,默認(rèn)hz 10,即每秒進(jìn)行十次過(guò)期檢查),每次從過(guò)期字典中隨機(jī)抽取20個(gè)key(固定值),檢查是否過(guò)期,刪除過(guò)期的key;如果本輪檢查已過(guò)期的key的超過(guò)5個(gè)(即占比超過(guò)25%),則繼續(xù)抽取;如果已過(guò)期的key比例小于25%,則停止刪除,等待下一輪再檢查。
可見(jiàn),定期刪除是一個(gè)循環(huán)的過(guò)程。為了保證定期刪除不會(huì)出現(xiàn)循環(huán)過(guò)度,導(dǎo)致線程卡死的現(xiàn)象,為此增加了定期刪除循環(huán)流程的時(shí)間上限,默認(rèn)不會(huì)超過(guò)25ms。
內(nèi)存淘汰策略
當(dāng)Redis運(yùn)行內(nèi)存超過(guò)最大運(yùn)行內(nèi)存時(shí),就會(huì)觸發(fā)內(nèi)存淘汰策略刪除符合條件的key,以此保障Redis的高效運(yùn)行。
如何設(shè)置 Redis 最大運(yùn)行內(nèi)存?
Redis 內(nèi)存淘汰策略有哪些?
LRU 算法和 LFU 算法有什么區(qū)別?
什么是LRU算法?
Redis是如何實(shí)現(xiàn)LRU算法的?
什么是LFU算法?
Redis是如何實(shí)現(xiàn)LFU算法的?
?
Redis 緩存設(shè)計(jì)?
什么是緩存雪崩?
大量緩存數(shù)據(jù)在同一時(shí)間過(guò)期或者Redis故障宕機(jī)時(shí),如果此時(shí)有大量的用戶請(qǐng)求,都無(wú)法在Redis中處理,于是全部請(qǐng)求都直接訪問(wèn)數(shù)據(jù)庫(kù),從而導(dǎo)致數(shù)據(jù)庫(kù)壓力驟增,甚至崩潰。
如何解決?
針對(duì)大量數(shù)據(jù)同時(shí)過(guò)期:
1.均勻設(shè)置過(guò)期時(shí)間:避免將大量數(shù)據(jù)設(shè)置成同一過(guò)期時(shí)間,在對(duì)緩存數(shù)據(jù)設(shè)置過(guò)期時(shí)間時(shí),給這些數(shù)據(jù)的過(guò)期時(shí)間加上一個(gè)隨機(jī)數(shù),保證數(shù)據(jù)不會(huì)在同一時(shí)間過(guò)期。
2.互斥鎖:如果發(fā)現(xiàn)數(shù)據(jù)不在Redis中,就加一個(gè)互斥鎖,保證同一時(shí)間只有一個(gè)請(qǐng)求來(lái)構(gòu)建緩存(從數(shù)據(jù)庫(kù)讀取數(shù)據(jù),再將數(shù)據(jù)更新到Redis中),當(dāng)緩存構(gòu)建完成后,再釋放鎖。
3.監(jiān)控緩存過(guò)期,提前更新
針對(duì)Redis故障宕機(jī):
1.服務(wù)熔斷或請(qǐng)求限流機(jī)制:服務(wù)熔斷即暫停對(duì)緩存的訪問(wèn),直接返回錯(cuò)誤,不再訪問(wèn)數(shù)據(jù)庫(kù)。
請(qǐng)求限流機(jī)制只將少部分請(qǐng)求發(fā)送到數(shù)據(jù)庫(kù)進(jìn)行處理。
2.構(gòu)建Redis集群:當(dāng)Redis主節(jié)點(diǎn)故障宕機(jī),從節(jié)點(diǎn)切換成為主節(jié)點(diǎn),繼續(xù)提供緩存服務(wù)。
什么是緩存擊穿?
如果緩存中某個(gè)熱點(diǎn)數(shù)據(jù)過(guò)期了,此時(shí)大量的請(qǐng)求訪問(wèn)該熱點(diǎn)數(shù)據(jù),將無(wú)法從緩存中獲取,直接訪問(wèn)數(shù)據(jù)庫(kù),導(dǎo)致數(shù)據(jù)庫(kù)容易被高并發(fā)的請(qǐng)求擊垮。
如何解決?
1.互斥鎖
2.不給熱點(diǎn)數(shù)據(jù)設(shè)置過(guò)期時(shí)間
什么是緩存穿透?
?如何解決?
布隆過(guò)濾器如何工作?
如何設(shè)計(jì)一個(gè)緩存策略,可以動(dòng)態(tài)緩存熱點(diǎn)數(shù)據(jù)呢?
說(shuō)說(shuō)常見(jiàn)的緩存更新策略??
?Cache Aside策略:
Read/Write Through策略:?
?
Write Back策略:
數(shù)據(jù)庫(kù)和緩存如何保證一致性?
先更新數(shù)據(jù)庫(kù),再更新緩存:
解決:
先更新緩存,再更新數(shù)據(jù)庫(kù):?
Cache Aside 策略:
該策略可以細(xì)分為讀策略與寫(xiě)策略:
先刪除緩存,再更新數(shù)據(jù)庫(kù):
A請(qǐng)求先刪除緩存,在A更新數(shù)據(jù)庫(kù)之前,B請(qǐng)求到來(lái),B先訪問(wèn)緩存,發(fā)現(xiàn)緩存未命中,于是訪問(wèn)數(shù)據(jù)庫(kù),讀取數(shù)據(jù)庫(kù)的值并更新到緩存,此時(shí)A更新數(shù)據(jù)庫(kù)為新值,但緩存中還是舊值,造成二者不一致。所以,此策略在讀寫(xiě)并發(fā)時(shí),存在緩存與數(shù)據(jù)庫(kù)不一致問(wèn)題。
先更新數(shù)據(jù)庫(kù),再刪除緩存:
存在問(wèn)題: 如果刪除緩存失敗,導(dǎo)致緩存中的數(shù)據(jù)還是舊值,而數(shù)據(jù)庫(kù)中的數(shù)據(jù)是新值。
解決:利用消息隊(duì)列進(jìn)行刪除的補(bǔ)償。在刪除緩存時(shí),將數(shù)據(jù)加入到消息隊(duì)列,由消費(fèi)者從消息隊(duì)列中讀數(shù)據(jù),執(zhí)行刪除操作,刪除成功后,將數(shù)據(jù)從消息隊(duì)列中移除。
Redis 實(shí)戰(zhàn)
Redis 如何實(shí)現(xiàn)延遲隊(duì)列?
Redis 的大 key 如何處理?
什么是 Redis 大 key?
如何查找大 key??
1.redis-cli --bigkeys 查找大key
?2.使用 SCAN 命令查找大 key
3.使用 RdbTools 工具查找大 key
如何刪除大 key?
刪除操作的的本質(zhì)是釋放鍵值對(duì)所占用的內(nèi)存空間,在釋放內(nèi)存時(shí),操作系統(tǒng)會(huì)將釋放的內(nèi)存塊插入到空閑內(nèi)存塊的鏈表中,以便后續(xù)進(jìn)行管理和再分配。如果一下釋放大量?jī)?nèi)存,操作空閑內(nèi)存塊鏈表時(shí)間就會(huì)增加,進(jìn)而造成Redis主線程阻塞,導(dǎo)致其他請(qǐng)求無(wú)法響應(yīng)。
1.分批次刪除:先獲取一定數(shù)量的大key,再將這些大key分批次刪除。
2.異步刪除:
Redis 管道有什么用?
管道技術(shù)是客戶端提供的一種批處理技術(shù),用于一次處理多個(gè)Redis命令,從而提高交互的性能。
Redis 事務(wù)支持回滾嗎?
不支持。對(duì)于MySQL事務(wù)中,只要有一條命令執(zhí)行失敗,事務(wù)就會(huì)回滾到之前的狀態(tài),但Redis事務(wù)中,一條命令失敗不會(huì)影響其他正確的命令,其他正確的命令仍然可以執(zhí)行成功。
如何用 Redis 實(shí)現(xiàn)分布式鎖的?
如何使用分布式鎖?
如何解鎖?
基于 Redis 實(shí)現(xiàn)分布式鎖有什么優(yōu)缺點(diǎn)?
優(yōu)點(diǎn):
1.性能高效:redis讀寫(xiě)速度快,適合高并發(fā)的鎖操作場(chǎng)景。
2.實(shí)現(xiàn)方便:可直接使用setnx方法實(shí)現(xiàn)。
3.避免單點(diǎn)故障:Redis是跨集群部署的,自然就避免了單點(diǎn)故障。
缺點(diǎn):
1.超時(shí)時(shí)間不好設(shè)置:時(shí)間過(guò)長(zhǎng),影響性能;時(shí)間過(guò)短,起不到保護(hù)共享資源的目的。
2.Redis主從復(fù)制中數(shù)據(jù)是異步復(fù)制的,導(dǎo)致分布式鎖的不可靠性: