title: Redis陈潘耄考的知識點
categories: 數(shù)據(jù)庫
tags: Redis
一、Redis是什么忽肛,有什么功能村砂?
? Redis 是一個使用 C 語言開發(fā)的數(shù)據(jù)庫,也是一種Key-Value數(shù)據(jù)庫,數(shù)據(jù)存儲在內(nèi)存中屹逛,常用作緩存數(shù)據(jù)庫箍镜,速度較快。
功能:常用來作緩存煎源,分布式鎖色迂,消息隊列,排行榜等功能
二手销、Redis 和 Memcached 的對比
Memcached 只支持String類型歇僧,Reids支持更為豐富的數(shù)據(jù)類型
Redis支持?jǐn)?shù)據(jù)的持久化
Redis的速度更快
-
Memcached 是多線程,非阻塞IO復(fù)用的網(wǎng)絡(luò)模型锋拖,Redis使用單線程的IO復(fù)用
相同點就是都是內(nèi)存型數(shù)據(jù)庫诈悍,都有過期策略,性能都不錯兽埃,常用來做緩存
?
三侥钳、Redis支持的數(shù)據(jù)類型以及底層數(shù)據(jù)結(jié)構(gòu)
- ? string,底層數(shù)據(jù)結(jié)構(gòu)為簡單動態(tài)字符串(simple dynamic string柄错,SDS)舷夺,SDS 可以保存二進(jìn)制數(shù)據(jù)苦酱,并且獲取字符串長度復(fù)雜度為 O(1)(C 字符串為 O(N)),此外,Redis 的 SDS API 是安全的,不會造成緩沖區(qū)溢出给猾。
- list疫萤,底層數(shù)據(jù)結(jié)構(gòu)是鏈表,C 語言并沒有實現(xiàn)鏈表敢伸,所以 Redis 實現(xiàn)了自己的鏈表數(shù)據(jù)結(jié)構(gòu)扯饶。Redis 的 list 的實現(xiàn)為一個 雙向鏈表,即可以支持反向查找和遍歷池颈,更方便操作尾序,不過帶來了部分額外的內(nèi)存開銷,獲取表頭表尾和鏈表長度都是O(1)復(fù)雜度
- set躯砰,是一種無序集合蹲诀,集合中的元素沒有先后順序。需要存儲一個列表數(shù)據(jù)弃揽,又不希望出現(xiàn)重復(fù)時,可以選擇set则北,并且 set 提供了判斷某個成員是否在一個 set 集合內(nèi)的重要接口
- hash矿微,底層是字典結(jié)構(gòu),字典在Redis中廣泛被使用尚揣,包括數(shù)據(jù)庫和哈希鍵涌矢,每個字典有兩個哈希表,哈希表使用的是鏈地址法解決哈希沖突快骗,擴(kuò)容時采用的是漸進(jìn)式哈希
- Zset 娜庇,有點像是 Java 中 HashMap 和 TreeSet 的結(jié)合體,底層使用跳表實現(xiàn)
四方篮、Redis為什么是單線程名秀?
? Redis核心就是我所有數(shù)據(jù)都在內(nèi)存里,單線程操作效率就是最高的藕溅,為什么要多線程呢匕得?多線程會有一個代價,就是上下文切換巾表,對于當(dāng)個CPU綁定一塊內(nèi)存的數(shù)據(jù)汁掠,沒有上下文切換就是效率最高的;相反集币,如果是多次磁盤IO的話考阱,多線程更優(yōu),因為在尋道和選擇的時間鞠苟,線程在阻塞的等待磁盤乞榨,這個時間CPU可以去處理其他線程秽之。
? 總之就是CPU不是redis的瓶頸,reids的瓶頸是機(jī)器內(nèi)存和網(wǎng)絡(luò)帶寬姜凄,而單線程既不會成為瓶頸政溃,又容易實現(xiàn),那肯定單線程态秧。
五董虱、Redis是單線程嗎?
? 將第五題和第四題放在一起就是為了分辨一個大部分人的誤區(qū)申鱼,大家稱Redis是單線程愤诱,但是Redis并不是單線程,比如持久化的時候就會fork子線程捐友,包括網(wǎng)絡(luò)IO也不是單線程淫半,Redis的單線程指的是事件處理模型的單線程。
? Redis 基于 Reactor 模式開發(fā)了自己的網(wǎng)絡(luò)事件處理器:這個處理器被稱為文件事件處理器(file event handler)匣砖,通過IO 多路復(fù)用程序 來監(jiān)聽來自客戶端的大量連接(或者說是監(jiān)聽多個 socket)科吭,它會將感興趣的事件及類型(讀、寫)注冊到內(nèi)核中并監(jiān)聽每個事件是否發(fā)生猴鲫。
? 文件事件處理器(file event handler)主要是包含 4 個部分:
- 多個 socket(客戶端連接)
- IO 多路復(fù)用程序(支持多個客戶端連接的關(guān)鍵)
- 文件事件分派器(將 socket 關(guān)聯(lián)到相應(yīng)的事件處理器)
- 事件處理器(連接應(yīng)答處理器对人、命令請求處理器、命令回復(fù)處理器)
六拂共、什么是緩存雪崩牺弄,什么是緩存穿透?
? 緩存雪崩是指緩存中數(shù)據(jù)大批量到過期時間宜狐,而查詢數(shù)據(jù)量巨大势告,引起數(shù)據(jù)庫壓力過大甚至down機(jī)。
? 解決方案就是:
- ? 設(shè)置緩存添加隨機(jī)過期時間抚恒,防止大量緩存同時失效
- 采用Reids高可用架構(gòu)比如主從或者Redis Cluster咱台,避免Redis掛掉
- 及時利用本地緩存和限流,防止下游數(shù)據(jù)庫崩潰
- 開啟持久化俭驮,重啟后快速恢復(fù)數(shù)據(jù)
? 緩存穿透是指緩存和數(shù)據(jù)庫中都沒有的數(shù)據(jù)吵护,而用戶不斷發(fā)起請求,如發(fā)起為id為“-1”的數(shù)據(jù)或id為特別大不存在的數(shù)據(jù)表鳍。這時的用戶很可能是攻擊者馅而,攻擊會導(dǎo)致數(shù)據(jù)庫壓力過大
? 解決方案就是:
訪問一個不存在的參數(shù)時,將這個結(jié)果進(jìn)行緩存譬圣,下次直接返回null
使用布隆過濾器進(jìn)行過濾
七瓮恭、Redis的過期鍵的刪除策略
? 惰性刪除 :只會在取出key的時候才對數(shù)據(jù)進(jìn)行過期檢查。這樣對CPU最友好厘熟,但是可能會造成太多過期 key 沒有被刪除屯蹦。
? 定期刪除 : 每隔一段時間抽取一批 key 執(zhí)行刪除過期key操作维哈。并且,Redis 底層會通過限制刪除操作執(zhí)行的時長和頻率來減少刪除操作對CPU時間的影響登澜。
八阔挠、Redis的內(nèi)存淘汰機(jī)制
? Redis 提供 6 種數(shù)據(jù)淘汰策略:
- volatile-lru(least recently used):從已設(shè)置過期時間的數(shù)據(jù)集(server.db[i].expires)中挑選最近最少使用的數(shù)據(jù)淘汰
- volatile-ttl:從已設(shè)置過期時間的數(shù)據(jù)集(server.db[i].expires)中挑選將要過期的數(shù)據(jù)淘汰
- volatile-random:從已設(shè)置過期時間的數(shù)據(jù)集(server.db[i].expires)中任意選擇數(shù)據(jù)淘汰
- allkeys-lru(least recently used):當(dāng)內(nèi)存不足以容納新寫入數(shù)據(jù)時,在鍵空間中脑蠕,移除最近最少使用的 key(這個是最常用的)
- allkeys-random:從數(shù)據(jù)集(server.db[i].dict)中任意選擇數(shù)據(jù)淘汰
- no-eviction:禁止驅(qū)逐數(shù)據(jù)购撼,也就是說當(dāng)內(nèi)存不足以容納新寫入數(shù)據(jù)時,新寫入操作會報錯谴仙。這個應(yīng)該沒人使用吧迂求!
4.0 版本后增加以下兩種:
- volatile-lfu(least frequently used):從已設(shè)置過期時間的數(shù)據(jù)集(server.db[i].expires)中挑選最不經(jīng)常使用的數(shù)據(jù)淘汰
- allkeys-lfu(least frequently used):當(dāng)內(nèi)存不足以容納新寫入數(shù)據(jù)時,在鍵空間中晃跺,移除最不經(jīng)常使用的 key
九揩局、Reids和數(shù)據(jù)庫的雙寫一致性
? a. 先更新數(shù)據(jù)再更新緩存的話是不行的,更新結(jié)束掀虎,更新緩存失敗豈不是gg
? b. 先刪緩存再更新數(shù)據(jù)庫看起來可以凌盯,實際上也有問題:
? i. A刪緩存,B拿舊數(shù)據(jù)烹玉,放到緩存里驰怎,A更新數(shù)據(jù)庫,就出問題了
? ii. 解決方案:延時雙刪(但是第二次刪除還是會出現(xiàn)不一致問題)春霍,(要設(shè)置過期時間,保證最終一致性)
? c. 先更新數(shù)據(jù)庫再刪緩存
? i. 問題:緩存剛好失效叶眉,然后A拿到舊數(shù)據(jù)址儒,然后B更新緩存刪緩存,A把舊數(shù)據(jù)放到數(shù)據(jù)庫衅疙,但是碰上緩存剛好失效的概率比較低
十莲趣、Redis的持久化方式
? 快照(snapshotting)持久化(RDB):Redis 可以通過創(chuàng)建快照來獲得存儲在內(nèi)存里面的數(shù)據(jù)在某個時間點上的副本。Redis 創(chuàng)建快照之后饱溢,可以對快照進(jìn)行備份喧伞,可以將快照復(fù)制到其他服務(wù)器從而創(chuàng)建具有相同數(shù)據(jù)的服務(wù)器副本(Redis 主從結(jié)構(gòu),主要用來提高 Redis 性能)绩郎,還可以將快照留在原地以便重啟服務(wù)器的時候使用潘鲫。快照持久化是 Redis 默認(rèn)采用的持久化方式
? AOF(append-only file)持久化:與快照持久化相比肋杖,AOF 持久化 的實時性更好溉仑,因此已成為主流的持久化方案。默認(rèn)情況下 Redis 沒有開啟 AOF(append only file)方式的持久化状植,可以通過 appendonly 參數(shù)開啟
十一浊竟、Redis的漸進(jìn)式擴(kuò)容
? 每個字典有兩個哈希表怨喘,一個ht[0],一個ht[1],擴(kuò)展或收縮哈希表需要將 ht[0]
里面的所有鍵值對 rehash 到 ht[1]
里面振定。如果哈希表里保存的鍵值對數(shù)量非常大必怜, 那么要一次性將這些鍵值對全部 rehash 到 ht[1]
的話, 龐大的計算量可能會導(dǎo)致服務(wù)器在一段時間內(nèi)停止服務(wù)后频。為了避免此情況梳庆,所以采用漸進(jìn)式哈希。
? 哈希表漸進(jìn)式 rehash 的詳細(xì)步驟:
- 為
ht[1]
分配空間徘郭, 讓字典同時持有ht[0]
和ht[1]
兩個哈希表靠益。 - 在字典中維持一個索引計數(shù)器變量
rehashidx
, 并將它的值設(shè)置為0
残揉, 表示 rehash 工作正式開始胧后。 - 在 rehash 進(jìn)行期間, 每次對字典執(zhí)行添加抱环、刪除壳快、查找或者更新操作時, 程序除了執(zhí)行指定的操作以外镇草, 還會順帶將
ht[0]
哈希表在rehashidx
索引上的所有鍵值對 rehash 到ht[1]
眶痰, 當(dāng) rehash 工作完成之后, 程序?qū)?rehashidx
屬性的值增一梯啤。 - 隨著字典操作的不斷執(zhí)行竖伯, 最終在某個時間點上,
ht[0]
的所有鍵值對都會被 rehash 至ht[1]
因宇, 這時程序?qū)?rehashidx
屬性的值設(shè)為-1
七婴, 表示 rehash 操作已完成。
在漸進(jìn)式 rehash 進(jìn)行期間察滑, 字典的刪除(delete)打厘、查找(find)、更新(update)等操作會在兩個哈希表上進(jìn)行: 比如說贺辰, 要在字典里面查找一個鍵的話户盯, 程序會先在 ht[0]
里面進(jìn)行查找, 如果沒找到的話饲化, 就會繼續(xù)到 ht[1]
里面進(jìn)行查找莽鸭, 諸如此類。
另外吃靠, 在漸進(jìn)式 rehash 執(zhí)行期間蒋川, 新添加到字典的鍵值對一律會被保存到 ht[1]
里面, 而 ht[0]
則不再進(jìn)行任何添加操作
十二撩笆、Redis分布式鎖(后續(xù)會有單獨文章)
? 方法一:SETNX key value
? 將 key 的值設(shè)為 value捺球,當(dāng)且僅當(dāng) key 不存在缸浦。
? 若給定的 key 已經(jīng)存在,則 SETNX 不做任何動作氮兵。
? SETNX 是SET if Not eXists的簡寫
? 方法二(Redlock算法):
? 起 5 個 master 節(jié)點裂逐,分布在不同的機(jī)房盡量保證可用性。為了獲得鎖泣栈,client 會進(jìn)行如下操作:
- 得到當(dāng)前的時間卜高,微秒單位
- 嘗試順序地在 5 個實例上申請鎖,當(dāng)然需要使用相同的 key 和 random value南片,這里一個 client 需要合理設(shè)置與 master 節(jié)點溝通的 timeout 大小掺涛,避免長時間和一個 fail 了的節(jié)點浪費時間
- 當(dāng) client 在大于等于 3 個 master 上成功申請到鎖的時候,且它會計算申請鎖消耗了多少時間疼进,這部分消耗的時間采用獲得鎖的當(dāng)下時間減去第一步獲得的時間戳得到薪缆,如果鎖的持續(xù)時長(lock validity time)比流逝的時間多的話,那么鎖就真正獲取到了伞广。
- 如果鎖申請到了拣帽,那么鎖真正的 lock validity time 應(yīng)該是 origin(lock validity time) - 申請鎖期間流逝的時間
- 如果 client 申請鎖失敗了,那么它就會在少部分申請成功鎖的 master 節(jié)點上執(zhí)行釋放鎖的操作嚼锄,重置狀態(tài)
后續(xù)將會推送Reids集群的知識减拭,敬請期待!