redis的線程模型
- Server socket 監(jiān)聽到客戶端發(fā)的請(qǐng)求,會(huì)產(chǎn)生一個(gè)AE_REABLE的事件
- IO多路復(fù)用程序?qū)E_REABLE事件壓入隊(duì)列中,依次執(zhí)行
- <font color='red'>文件事件分派器</font>從隊(duì)列中取數(shù)據(jù)交給<font color='red'>鏈接應(yīng)答處理器</font>
- 鏈接應(yīng)答處理器創(chuàng)建與客戶端的長(zhǎng)鏈接socket,并將這個(gè)長(zhǎng)鏈接和需要執(zhí)行的命令給到<font color='red'>命令請(qǐng)求處理器</font>
- 命令請(qǐng)求處理器進(jìn)行命令執(zhí)行,執(zhí)行完成之后將socket的長(zhǎng)鏈接與<font color='red'>命令回復(fù)處理器</font>關(guān)聯(lián)
- 文件事件分派器使用對(duì)應(yīng)的socket關(guān)聯(lián)與命令回復(fù)處理器的執(zhí)行斷開socket請(qǐng)求
線程模型的意義
- 使用非阻塞IO多路復(fù)用程序,所以支持高并發(fā)
- 使用的文件事件分派器是單線程的,所以redis是單線程的
redis的擊穿(穿透)
出現(xiàn)原因
出現(xiàn)大量redis中的key不存在的請(qǐng)求,導(dǎo)致創(chuàng)建了太多的jdbc鏈接從而跳過了redis的緩存機(jī)制,給數(shù)據(jù)庫帶來太大的壓力
解決辦法
1. 增加數(shù)據(jù)規(guī)則的驗(yàn)證,只有符合規(guī)則的key才進(jìn)行查詢,防止人為惡意的進(jìn)行請(qǐng)求
2. 數(shù)據(jù)庫中查詢?yōu)閚ull的對(duì)應(yīng)key值信息也進(jìn)行緩存
redis的緩存雪崩
出現(xiàn)原因
同一時(shí)間點(diǎn)大量的緩存數(shù)據(jù)失效,導(dǎo)致透過redis直接請(qǐng)求數(shù)據(jù)庫出現(xiàn)問題
解決辦法
1. 均勻分配緩存的過期時(shí)間(業(yè)務(wù)邏輯角度)
2. 創(chuàng)建多級(jí)緩存來輔助修改(springBoot+redis+Ecache)
3. 使用分布式鎖的邏輯,保證獲取數(shù)據(jù)庫資源有一個(gè)一定的上限,超過上限就進(jìn)行等待知道鎖釋放
redis的主從、集群和哨兵
主從復(fù)制

產(chǎn)生的原因
1. 容錯(cuò)性(只有一臺(tái)的情況,一旦掛掉就會(huì)影像業(yè)務(wù)邏輯)
2. 并發(fā)性(一臺(tái)的并發(fā)能力必然很低,讀的能力會(huì)很差)
使用主從的結(jié)果
1. 數(shù)據(jù)冗余
2. 讀寫分離
原理
1. 主節(jié)點(diǎn)負(fù)責(zé)讀/寫,從節(jié)點(diǎn)只能進(jìn)行讀操作
2. 可以一主多從
3. 數(shù)據(jù)同步:一般初次會(huì)講所有主節(jié)點(diǎn)數(shù)據(jù)同步到從節(jié)點(diǎn),后續(xù)都是補(bǔ)發(fā)更新的數(shù)據(jù),并且主從節(jié)點(diǎn)有長(zhǎng)鏈接的心跳機(jī)制
哨兵機(jī)制

產(chǎn)生的原因
在搭建主從之后,主節(jié)點(diǎn)只有一個(gè),一旦發(fā)生錯(cuò)誤之后就沒有主節(jié)點(diǎn)信息了
原理
- 在主節(jié)點(diǎn)外層新增一套哨兵邏輯
- 暴露給外部的是哨兵的信息,相當(dāng)于哨兵進(jìn)行了一層封裝
- 一旦主節(jié)點(diǎn)出現(xiàn)問題,哨兵機(jī)制會(huì)在剩下的從節(jié)點(diǎn)中通過<font color='red'>選舉算法</font>選出一個(gè)節(jié)點(diǎn)充當(dāng)主節(jié)點(diǎn)
哨兵原理
- 每個(gè)
sentinel會(huì)以心跳機(jī)制請(qǐng)求master、slave進(jìn)行PING命令,如果一個(gè)實(shí)例響應(yīng)超過設(shè)置的時(shí)間. - 當(dāng)有足夠數(shù)量的額sentinel確定master下線,就會(huì)認(rèn)定下線,就會(huì)進(jìn)行選舉算法選舉出新節(jié)點(diǎn)作為主節(jié)點(diǎn)
當(dāng)使用哨兵模式之后,就不要再直接連redis的ip和端口了,而是訪問哨兵的信息,由哨兵進(jìn)行<font color='red'>轉(zhuǎn)發(fā)</font>
redis淘汰策略
1.noeviction:禁止驅(qū)逐數(shù)據(jù),當(dāng)內(nèi)存上限的時(shí)候,再添加數(shù)據(jù)會(huì)產(chǎn)生異常。從而保證數(shù)據(jù)不會(huì)丟失
2.allkeys-lru: 全體數(shù)據(jù)中找最近使用最少的數(shù)據(jù)進(jìn)行淘汰
3.volatile-lru: 從有設(shè)置過期時(shí)間的數(shù)據(jù)中找最近使用最少的數(shù)據(jù)進(jìn)行淘汰
4.allkeys-random: 全體數(shù)據(jù),隨機(jī)淘汰
5.volatile-random: 設(shè)置過期時(shí)間的數(shù)據(jù),隨機(jī)淘汰
6.volatile-ttl: 從設(shè)置過期時(shí)間的數(shù)據(jù)中,隨機(jī)找一些數(shù)據(jù)淘汰。距離過期時(shí)間越短,淘汰優(yōu)先級(jí)越高
Redis淘汰策略主要分為L(zhǎng)RU淘汰、TTL淘汰、隨機(jī)淘汰三種機(jī)制。
LRU淘汰
LRU(Least recently used,<font color="red">最近最少使用</font>)算法根據(jù)數(shù)據(jù)的歷史訪問記錄來進(jìn)行淘汰數(shù)據(jù),其核心思想是“如果數(shù)據(jù)最近被訪問過,那么將來被訪問的幾率也更高”。
在服務(wù)器配置中保存了 lru 計(jì)數(shù)器 server.lrulock,會(huì)定時(shí)(redis 定時(shí)程序 serverCorn())更新,server.lrulock 的值是根據(jù) server.unixtime 計(jì)算出來進(jìn)行排序的,然后選擇最近使用時(shí)間最久的數(shù)據(jù)進(jìn)行刪除。另外,從 struct redisObject 中可以發(fā)現(xiàn),每一個(gè) redis 對(duì)象都會(huì)設(shè)置相應(yīng)的 lru。每一次訪問數(shù)據(jù),會(huì)更新對(duì)應(yīng)redisObject.lru。
在Redis中,LRU算法是一個(gè)近似算法,默認(rèn)情況下,Redis會(huì)隨機(jī)挑選5個(gè)鍵,并從中選擇一個(gè)最久未使用的key進(jìn)行淘汰。在配置文件中,按maxmemory-samples選項(xiàng)進(jìn)行配置,選項(xiàng)配置越大,消耗時(shí)間就越長(zhǎng),但結(jié)構(gòu)也就越精準(zhǔn)。
TTL淘汰
也就是按照過期時(shí)間進(jìn)行淘汰。距離過期時(shí)間越短,淘汰的優(yōu)先級(jí)越高。
隨機(jī)淘汰
在隨機(jī)淘汰的場(chǎng)景下獲取待刪除的鍵值對(duì),隨機(jī)找hash桶再次hash指定位置的dictEntry即可。
Redis中的淘汰機(jī)制都是幾近于算法實(shí)現(xiàn)的,主要從性能和可靠性上做平衡,所以并不是完全可靠,所以開發(fā)者們?cè)诔浞至私釸edis淘汰策略之后還應(yīng)在平時(shí)多主動(dòng)設(shè)置或更新key的expire時(shí)間,主動(dòng)刪除沒有價(jià)值的數(shù)據(jù),提升Redis整體性能和空間。
總結(jié)六種:
- 不淘汰
- 隨機(jī)淘汰
- 有過期時(shí)間的隨機(jī)淘汰
- 使用最少
- 有過期時(shí)間的使用最少
- 過期時(shí)間越短淘汰率越高
redis的八種數(shù)據(jù)結(jié)構(gòu)
- String,字符串類型
- hash,對(duì)象類型
- list,有序可重復(fù)類型
- set,無需不可重復(fù)類型
- zset,無需可重復(fù)類型
- Geospatial,地理位置類型
- bitmap,位圖類型(key:value(1,2))
- Hyperloglog,數(shù)學(xué)上的集合類型
隊(duì)列模式:
list作為隊(duì)列,rpush生產(chǎn)消息,lpop消費(fèi)消息
redis持久化
不建議開啟redis的持久化操作
RDB
原理
快照的方式保存數(shù)據(jù),每隔一段時(shí)間有固定多少key值發(fā)生變更,將數(shù)據(jù)進(jìn)行快照備份一次
由父進(jìn)程fork開啟一個(gè)子進(jìn)程,使用子進(jìn)程進(jìn)行I/O操作將內(nèi)存中的快照保存在硬盤上
缺點(diǎn)
耗時(shí),耗性能
AOF
原理
將操作日志保存下來,做的是增量保存.在重啟的時(shí)候執(zhí)行所有操作一遍
缺點(diǎn)
文件體積大,恢復(fù)速度慢
現(xiàn)在一般采用兩者集合的方式來進(jìn)行持久化。底層對(duì)這兩種方案做了融合處理
redis的事務(wù)
redis支持事務(wù),但是整體redis集群不支持事務(wù)
1. 事務(wù)的一般執(zhí)行流程
1. 開啟事務(wù)`MULTI`
2. 執(zhí)行操作,也就是執(zhí)行你需要進(jìn)行的操作set等
3. 提交事務(wù)`EXEC`,會(huì)執(zhí)行一并所有的操作一起提交
2. 使用SpringBoot+redis執(zhí)行事務(wù)
//1. 創(chuàng)建對(duì)應(yīng)執(zhí)行的RedisTemplate對(duì)象
@Autowired
StringRedisTemplate stringRedisTemplate
//2. 開啟事務(wù)權(quán)限
stringRedisTemplate.setEnableTransationSupport(true);
//3. 開啟事務(wù)
stringRedisTemplate.multi();
//4. 回滾
stringRedisTemplate.discard();
//5. 提交
stringRedisTemplate.exec();
如果開啟事務(wù),所有的命令只有在執(zhí)行了exec。才會(huì)往redis中插入
redis實(shí)現(xiàn)分布式鎖
原理
- redis是單線程的
如何實(shí)現(xiàn)
1.線程 A setnx(上鎖的對(duì)象,超時(shí)時(shí)的時(shí)間戳 t1),如果返回 true,獲得鎖。
2.線程 B 用 get 獲取 t1,與當(dāng)前時(shí)間戳比較,判斷是是否超時(shí),沒超時(shí) false,若超時(shí)執(zhí)行第 3 步;
3.計(jì)算新的超時(shí)時(shí)間 t2,使用 getset 命令返回 t3(該值可能其他線程已經(jīng)修改過),如果
t1==t3,獲得鎖,如果 t1!=t3 說明鎖被其他線程獲取了。
4.獲取鎖后,處理完業(yè)務(wù)邏輯,再去判斷鎖是否超時(shí),如果沒超時(shí)刪除鎖,如果已超時(shí),不用處理(防止刪除其他線程的鎖)。
核心
<font color='red'>setnx</font>關(guān)鍵字,將key對(duì)應(yīng)的資源鎖定,set成功返回1,失敗返回0
對(duì)比redis與zookeeper實(shí)現(xiàn)分布式鎖
edis實(shí)現(xiàn)分布式鎖與Zookeeper實(shí)現(xiàn)分布式鎖的區(qū)別
相同點(diǎn)
在集群的環(huán)境下,保證只允許有一個(gè)jvm進(jìn)行執(zhí)行
從技術(shù)上分析
Redis是nosql數(shù)據(jù)庫,主要特點(diǎn)是緩存
Zookeeper是分布式協(xié)調(diào)工具,主要用戶分布式解決方案
實(shí)現(xiàn)思路分析
獲取鎖
Zookeeper,多個(gè)客戶端(jvm)會(huì)在Zookeeper上創(chuàng)建同一個(gè)臨時(shí)節(jié)點(diǎn),因?yàn)閆ookeeper節(jié)點(diǎn)命名路徑保證唯一,不允許出現(xiàn)重復(fù),只要誰能創(chuàng)建成功就能獲取這個(gè)鎖
redis,多個(gè)jvm會(huì)在redis中使用setnx命令創(chuàng)建相同的一個(gè)key,因?yàn)閞edis的key保證唯一,不允許重復(fù)。只要誰先創(chuàng)建成功,誰就能獲取鎖
釋放鎖
Zookeeper直接關(guān)閉 臨時(shí)節(jié)點(diǎn)session會(huì)話連接,因?yàn)榕R時(shí)節(jié)點(diǎn)生命周期session會(huì)話綁定在一起,如果session會(huì)話連接關(guān)閉的話,就會(huì)使這個(gè)臨時(shí)節(jié)點(diǎn)被刪除
然后客戶端使用事件監(jiān)聽,監(jiān)聽到臨時(shí)節(jié)點(diǎn)被刪除,就釋放鎖
redis釋放鎖的時(shí)候,為了保證是鎖的一致性問題,在刪除鎖的時(shí)候需要判斷對(duì)應(yīng)的value是否是對(duì)應(yīng)創(chuàng)建的那個(gè)業(yè)務(wù)的id
死鎖解決
Zookeeper使用會(huì)話有效方式解決死鎖的現(xiàn)象
redis是對(duì)key設(shè)置有效期來解決死鎖現(xiàn)象
性能上:
因?yàn)閞edis是nosql,所以redis比Zookeeper性能好
可靠性
Zookeeper更加可靠。因?yàn)閞edis有效求不是很好控制,可能會(huì)產(chǎn)生延遲
其余面試問題
7、一個(gè)字符串類型的值能存儲(chǔ)最大容量是多少?
512M
8、為什么 Redis 需要把所有數(shù)據(jù)放到內(nèi)存中?
Redis 為了達(dá)到最快的讀寫速度將數(shù)據(jù)都讀到內(nèi)存中,并通過異步的方式將數(shù)據(jù)寫入磁盤。
所以 redis 具有快速和數(shù)據(jù)持久化的特征,如果不將數(shù)據(jù)放在內(nèi)存中,磁盤 I/O 速度為嚴(yán)重影響 redis 的性能。
在內(nèi)存越來越便宜的今天,redis 將會(huì)越來越受歡迎, 如果設(shè)置了最大使用的內(nèi)存,則數(shù)據(jù)已有記錄數(shù)達(dá)到內(nèi)存限值后不能繼續(xù)插入新值。
11、MySQL 里有 2000w 數(shù)據(jù),redis 中只存 20w 的數(shù)據(jù),如何保證 redis 中的數(shù)據(jù)都是熱點(diǎn)數(shù)據(jù)?
使用的淘汰策略可以是LRU策略,可以實(shí)現(xiàn)淘汰最近最少使用的數(shù)據(jù)淘汰掉
22、Redis 中的管道有什么用?
一次請(qǐng)求/響應(yīng)服務(wù)器能實(shí)現(xiàn)處理新的請(qǐng)求即使舊的請(qǐng)求還未被響應(yīng),這樣就可以將多個(gè)命令發(fā)送到服務(wù)器,而不用等待回復(fù),最后在一個(gè)步驟中讀取該答復(fù)。
這就是管道(pipelining),是一種幾十年來廣泛使用的技術(shù)。例如許多 POP3 協(xié)議已經(jīng)實(shí)現(xiàn)支持這個(gè)功能,大大加快了從服務(wù)器下載新郵件的過程。
25、Redis key 的過期時(shí)間和永久有效分別怎么設(shè)置?
EXPIRE 和 PERSIST 命令
26、Redis 如何做內(nèi)存優(yōu)化?
盡可能使用散列表(hashes),散列表(是說散列表里面存儲(chǔ)的數(shù)少)使用的內(nèi)存非常小,所以你應(yīng)該盡可能的將你的數(shù)據(jù)模型抽象到一個(gè)散列表里面。
比如你的 web 系統(tǒng)中有一個(gè)用戶對(duì)象,不要為這個(gè)用戶的名稱,姓氏,郵箱,密碼設(shè)置單獨(dú)的 key,而是應(yīng)該把這個(gè)用戶的所有信息存儲(chǔ)到一張散列表里面。
27、Redis 回收進(jìn)程如何工作的?
一個(gè)客戶端運(yùn)行了新的命令,添加了新的數(shù)據(jù)。Redi 檢查內(nèi)存使用情況,如果大于 maxmemory 的限制, 則根據(jù)設(shè)定好的策略進(jìn)行回收。一個(gè)新的命令被執(zhí)行,等等。
所以我們不斷地穿越內(nèi)存限制的邊界,通過不斷達(dá)到邊界然后不斷地回收回到邊界以下。
如果一個(gè)命令的結(jié)果導(dǎo)致大量?jī)?nèi)存被使用(例如很大的集合的交集保存到一個(gè)新的鍵),不用多久內(nèi)存限制就會(huì)被這個(gè)內(nèi)存使用量超越。
29.鎖互斥機(jī)制
那么在這個(gè)時(shí)候,如果客戶端 2 來嘗試加鎖,執(zhí)行了同樣的一段 lua 腳本,會(huì)咋樣呢?很簡(jiǎn)單,第一個(gè) if 判斷會(huì)執(zhí)行“exists myLock”,發(fā)現(xiàn) myLock 這個(gè)鎖 key 已經(jīng)存在了。接著第二個(gè) if 判斷,判斷一下,myLock 鎖 key 的 hash 數(shù)據(jù)結(jié)構(gòu)中,是否包含客戶端 2 的 ID,但是明顯不是的,因?yàn)槟抢锇氖强蛻舳?1 的 ID。
所以,客戶端 2 會(huì)獲取到 pttl myLock 返回的一個(gè)數(shù)字,這個(gè)數(shù)字代表了 myLock 這個(gè)鎖 key的剩余生存時(shí)間。比如還剩 15000 毫秒的生存時(shí)間。此時(shí)客戶端 2 會(huì)進(jìn)入一個(gè) while 循環(huán),不停的嘗試加鎖。
30.watch dog 自動(dòng)延期機(jī)制
客戶端 1 加鎖的鎖 key 默認(rèn)生存時(shí)間才 30 秒,如果超過了 30 秒,客戶端 1 還想一直持有這把鎖,怎么辦呢?
簡(jiǎn)單!只要客戶端 1 一旦加鎖成功,就會(huì)啟動(dòng)一個(gè) watch dog 看門狗,他是一個(gè)后臺(tái)線程,會(huì)每隔 10 秒檢查一下,如果客戶端 1 還持有鎖 key,那么就會(huì)不斷的延長(zhǎng)鎖 key 的生存時(shí)間。
緩存與數(shù)據(jù)庫不一致怎么辦
假設(shè)采用的主存分離,讀寫分離的數(shù)據(jù)庫,
如果一個(gè)線程 A 先刪除緩存數(shù)據(jù),然后將數(shù)據(jù)寫入到主庫當(dāng)中,這個(gè)時(shí)候,主庫和從庫同步?jīng)]有完成,線程 B 從緩存當(dāng)中讀取數(shù)據(jù)失敗,從從庫當(dāng)中讀取到舊數(shù)據(jù),然后更新至緩存,這個(gè)時(shí)候,緩存當(dāng)中的就是舊的數(shù)據(jù)。
發(fā)生上述不一致的原因在于,主從庫數(shù)據(jù)不一致問題,加入了緩存之后,主從不一致的時(shí)間被拉長(zhǎng)了
處理思路:在從庫有數(shù)據(jù)更新之后,將緩存當(dāng)中的數(shù)據(jù)也同時(shí)進(jìn)行更新,即當(dāng)從庫發(fā)生了數(shù)據(jù)更新之后,向緩存發(fā)出刪除,淘汰這段時(shí)間寫入的舊數(shù)據(jù)。
主從數(shù)據(jù)庫不一致如何解決場(chǎng)景描述,對(duì)于主從庫,讀寫分離,如果主從庫更新同步有時(shí)差,就會(huì)導(dǎo)致主從庫數(shù)據(jù)的不一致
1、忽略這個(gè)數(shù)據(jù)不一致,在數(shù)據(jù)一致性要求不高的業(yè)務(wù)下,未必需要時(shí)時(shí)一致性
2、強(qiáng)制讀主庫,使用一個(gè)高可用的主庫,數(shù)據(jù)庫讀寫都在主庫,添加一個(gè)緩存,提升數(shù)據(jù)讀取的性能。
3、選擇性讀主庫,添加一個(gè)緩存,用來記錄必須讀主庫的數(shù)據(jù),將哪個(gè)庫,哪個(gè)表,哪個(gè)主鍵,作為緩存的 key,設(shè)置緩存失效的時(shí)間為主從庫同步的時(shí)間,如果緩存當(dāng)中有這個(gè)數(shù)據(jù),直接讀取主庫,如果緩存當(dāng)中沒有這個(gè)主鍵,就到對(duì)應(yīng)的從庫中讀取。