為什么效率比較高氢伟?
答:
是純內(nèi)存數(shù)據(jù)庫,一般都是簡單的存取操作或粮,讀取速度快
使用非阻塞IO导饲,IO多路復(fù)用
采用單線程模型,保證每個(gè)操作的原子性,減少了線程的上下文切換和競爭
全程使用hash結(jié)構(gòu)渣锦,讀取速度快硝岗,再比如有序集合使用的跳表,不同場景下采用不同的底層數(shù)據(jù)結(jié)構(gòu)來實(shí)現(xiàn)袋毙。
簡述下Redis的過期策略型檀?
答:我們都知道,Redis是key-value數(shù)據(jù)庫听盖,我們可以設(shè)置Redis中緩存的key的過期時(shí)間胀溺。Redis的過期策略就是指當(dāng)Redis中緩存的key過期了,Redis如何處理皆看。
過期策略通常有以下三種:
定時(shí)過期:每個(gè)設(shè)置過期時(shí)間的key都需要?jiǎng)?chuàng)建一個(gè)定時(shí)器仓坞,到過期時(shí)間就會(huì)立即清除。該策略可以立即清除過期的數(shù)據(jù)腰吟,對內(nèi)存很友好无埃;但是會(huì)占用大量的CPU資源去處理過期的數(shù)據(jù),從而影響緩存的響應(yīng)時(shí)間和吞吐量毛雇。
惰性過期:只有當(dāng)訪問一個(gè)key時(shí)嫉称,才會(huì)判斷該key是否已過期,過期則清除灵疮。該策略可以最大化地節(jié)省CPU資源织阅,卻對內(nèi)存非常不友好。極端情況可能出現(xiàn)大量的過期key沒有再次被訪問始藕,從而不會(huì)被清除蒲稳,占用大量內(nèi)存。
定期過期:每隔一定的時(shí)間伍派,會(huì)掃描一定數(shù)量的數(shù)據(jù)庫的expires字典中一定數(shù)量的key江耀,并清除其中已過期的key。該策略是前兩者的一個(gè)折中方案诉植。通過調(diào)整定時(shí)掃描的時(shí)間間隔和每次掃描的限定耗時(shí)祥国,可以在不同情況下使得CPU和內(nèi)存資源達(dá)到最優(yōu)的平衡效果。
(expires字典會(huì)保存所有設(shè)置了過期時(shí)間的key的過期時(shí)間數(shù)據(jù)晾腔,其中舌稀,key是指向鍵空間中的某個(gè)鍵的指針,value是該鍵的毫秒精度的UNIX時(shí)間戳表示的過期時(shí)間灼擂。鍵空間是指該Redis集群中保存的所有鍵壁查。)
Redis的過期刪除策略就是:惰性刪除和定期刪除兩種策略配合使用。
惰性刪除:Redis的惰性刪除策略由 db.c/expireIfNeeded 函數(shù)實(shí)現(xiàn)剔应,所有鍵讀寫命令執(zhí)行之前都會(huì)調(diào)用 expireIfNeeded 函數(shù)對其進(jìn)行檢查睡腿,如果過期语御,則刪除該鍵,然后執(zhí)行鍵不存在的操作席怪;未過期則不作操作应闯,繼續(xù)執(zhí)行原有的命令。
定期刪除:由redis.c/activeExpireCycle 函數(shù)實(shí)現(xiàn)挂捻,函數(shù)以一定的頻率運(yùn)行碉纺,每次運(yùn)行時(shí),都從一定數(shù)量的數(shù)據(jù)庫中取出一定數(shù)量的隨機(jī)鍵進(jìn)行檢查刻撒,并刪除其中的過期鍵骨田。
注意:并不是一次運(yùn)行就檢查所有的庫,所有的鍵疫赎,而是隨機(jī)檢查一定數(shù)量的鍵盛撑。
定期刪除函數(shù)的運(yùn)行頻率,在Redis2.6版本中捧搞,規(guī)定每秒運(yùn)行10次抵卫,大概100ms運(yùn)行一次。在Redis2.8版本后胎撇,可以通過修改配置文件redis.conf 的 hz 選項(xiàng)來調(diào)整這個(gè)次數(shù)介粘。
補(bǔ)充:1. 定期刪除:1.redis每過100ms,從設(shè)置了過期時(shí)間的key中隨機(jī)取出20個(gè)緩存key2.清除其中的過期key3.若過期key占比超過1/4晚树,則重復(fù)步驟1姻采。為什么定期刪除采用的是隨機(jī)策略,而不是對全部數(shù)據(jù)做清理呢爵憎?因?yàn)槊?00ms過濾所有緩存數(shù)據(jù)慨亲,對CPU壓力較大。雖然定期刪除解決了一部分過期數(shù)據(jù)的問題宝鼓,但是還有不少數(shù)據(jù)并沒有被清除出緩存刑棵,所以此時(shí)就有了惰性刪除。2. 惰性刪除:這種方式是被動(dòng)觸發(fā)的愚铡,部分過期緩存key未被清理出緩存蛉签,長久占用緩存資源(隨機(jī)策略存在的缺陷性),隨著緩存數(shù)據(jù)的不斷增加沥寥,無法通過刪除過期key的方式騰出空間碍舍,來儲存新的熱點(diǎn)數(shù)據(jù)。所以這里就需要我們的內(nèi)存淘汰策略
簡述下Redis復(fù)制邑雅?
老版實(shí)現(xiàn)(2.8版本以前)
1. 過程:
- 同步(一開始啟動(dòng)的時(shí)候)
- 從服務(wù)器向主服務(wù)器發(fā)送SYNC命令
- 收到命令的服務(wù)器執(zhí)行bgsave命令片橡,生成一個(gè)RDB文件,并且在這個(gè)過程中淮野,緩存所有的寫命令锻全,因?yàn)樵诤笈_線程生成RDB文件的時(shí)候狂塘,這些新產(chǎn)生的寫命令是不會(huì)記錄的
- 當(dāng)主服務(wù)器的bgsave執(zhí)行完后录煤,將RDB文件發(fā)送給從服務(wù)器鳄厌,然后從服務(wù)器載入這個(gè)RDB文件,
到達(dá)執(zhí)行bgsave的主服務(wù)器狀態(tài) - 主服務(wù)器再將緩存的寫命令發(fā)送給從服務(wù)器妈踊,從服務(wù)器就可以恢復(fù)到主服務(wù)器當(dāng)前的狀態(tài)
- 傳播命令(啟動(dòng)之后的方式)
- 將命令傳播給從服務(wù)器了嚎,從服務(wù)器執(zhí)行即可
缺點(diǎn):初次復(fù)制的時(shí)候完全沒問題,但是斷線后重連時(shí)效率很低廊营,重復(fù)的內(nèi)容很多歪泳,降低了效率
新版實(shí)現(xiàn)(2.8版本開始)
-
完整重同步(用于處理初次復(fù)制的情況)
- 同上
-
部分重同步(用于斷線后重復(fù)制的情況)
- 當(dāng)從服務(wù)器斷線后,主服務(wù)去記錄那些斷線期間的寫命令露筒,當(dāng)重連的時(shí)候只將這部分發(fā)送給從服務(wù)器
- 實(shí)現(xiàn)方式呐伞,是有一個(gè)復(fù)制偏移量,每當(dāng)主服務(wù)器向從服務(wù)器發(fā)送N字節(jié)數(shù)據(jù)的時(shí)候慎式,就將自己的復(fù)制偏移量增加N伶氢,然后從服務(wù)器收到N字節(jié)的數(shù)據(jù)時(shí),將自己的復(fù)制偏移量加N瘪吏。然后還有一個(gè)復(fù)制積壓緩沖區(qū)癣防,是一個(gè)隊(duì)列,這里會(huì)存最近一些發(fā)送給從服務(wù)器的數(shù)據(jù)掌眠,每次同步的時(shí)候蕾盯,從服務(wù)器會(huì)將自己的復(fù)制偏移量發(fā)送給主服務(wù)器,然后主服務(wù)器檢查是否同步蓝丙,如果不同步就計(jì)算偏移量的差值级遭,并將緩沖區(qū)中相應(yīng)的數(shù)據(jù)發(fā)送給服務(wù)器
簡述下Redis持久化的方式?
答:
-
RDB
- 簡述: RDB即將當(dāng)前數(shù)據(jù)生成快照渺尘,并保存于硬盤中挫鸽。可以通過手動(dòng)命令沧烈,也可以設(shè)置自動(dòng)觸發(fā)掠兄。
- 方式: save 和 bgsave
- 文件保存的東西:TYPE KEY VALUE TTL(time to live 如果有的話,也會(huì)有過期時(shí)間)
-
AOF
- 簡述: AOF通過日志锌雀,對數(shù)據(jù)的寫入修改操作進(jìn)行記錄蚂夕。這種持久化方式實(shí)時(shí)性更好。通過配置文件打開-**AOF腋逆。
-
保存的方式:比如
RPUSH LIST "A" "B" [A,B]
RPUSH LIST "C" [A,B,C]
RPUSH LIST "D" "E" [ A,B,C,D,E]
LPOP LIST[B,C,D,E]
LPOP LIST[C,D,E]
這里是5條指令婿牍,但是不是保存這五條,我們只需要保存RPUSH "C" "D" "E"
AOF 日志文件即使過大的時(shí)候惩歉,出現(xiàn)后臺重寫操作等脂,也不會(huì)影響客戶端的讀寫俏蛮。因?yàn)樵?rewrite log 的時(shí)候,會(huì)對其中的指令進(jìn)行壓縮上遥,創(chuàng)建出一份需要恢復(fù)數(shù)據(jù)的最小日志出來搏屑。在創(chuàng)建新日志文件的時(shí)候,老的日志文件還是照常寫入粉楚。當(dāng)新的 merge 后的日志文件 ready 的時(shí)候辣恋,再交換新老日志文件即可。
redis優(yōu)化從哪些方面入手模软?
答:
- 在沒有必要開啟持久化的時(shí)候伟骨,關(guān)閉持久化
- 縮短鍵值對的存儲長度,從以上數(shù)據(jù)可以看出燃异,在 key 不變的情況下携狭,value 值越大操作效率越慢,因?yàn)?Redis 對于同一種數(shù)據(jù)類型會(huì)使用不同的內(nèi)部編碼進(jìn)行存儲回俐,比如字符串的內(nèi)部編碼就有三種:int(整數(shù)編碼)逛腿、raw(優(yōu)化內(nèi)存分配的字符串編碼)、embstr(動(dòng)態(tài)字符串編碼)鲫剿,這是因?yàn)?Redis 的作者是想通過不同編碼實(shí)現(xiàn)效率和空間的平衡鳄逾,然而數(shù)據(jù)量越大使用的內(nèi)部編碼就越復(fù)雜,而越是復(fù)雜的內(nèi)部編碼存儲的性能就越低灵莲。
- 禁用長耗時(shí)的查詢命令雕凹;
- 使用 Pipeline(管道技術(shù))批量操作數(shù)據(jù),在平常都是一個(gè)命令政冻,一個(gè)返回結(jié)果枚抵,使用管道技術(shù)我們可以一次性發(fā)送多個(gè)命令,然后等得到總的結(jié)果后再返回明场。
Redis 內(nèi)存淘汰機(jī)制了解么汽摹?
為什么會(huì)有內(nèi)存淘汰機(jī)制:其實(shí)是因?yàn)镽edis的刪除機(jī)制并不是一次性刪除所有過期的數(shù)據(jù),而是每次隨機(jī)選一部分刪除苦锨。所以最終還是有可能內(nèi)存溢出逼泣,另外內(nèi)存淘汰機(jī)制的合理性,最理想的就是沒有過期的數(shù)據(jù)不會(huì)刪除舟舒,但是還是有可能沒過期的數(shù)據(jù)被刪除了拉庶,但redis的定位是緩存,所以你那些強(qiáng)業(yè)務(wù)性的東西秃励,本來就不應(yīng)該防在redis里氏仗,他是一個(gè)有誤差的東西
答:
- volatile-lru(least recently used):從已設(shè)置過期時(shí)間的數(shù)據(jù)集(server.db[i].expires)中挑選最近最少使用的數(shù)據(jù)淘汰
- volatile-ttl:從已設(shè)置過期時(shí)間的數(shù)據(jù)集(server.db[i].expires)中挑選將要過期的數(shù)據(jù)淘汰
- volatile-random:從已設(shè)置過期時(shí)間的數(shù)據(jù)集(server.db[i].expires)中任意選擇數(shù)據(jù)淘汰
- allkeys-lru(least recently used):當(dāng)內(nèi)存不足以容納新寫入數(shù)據(jù)時(shí),在鍵空間中夺鲜,移除最近最少使用的 key(這個(gè)是最常用的)
- allkeys-random:從數(shù)據(jù)集(server.db[i].dict)中任意選擇數(shù)據(jù)淘汰
- no-eviction:禁止驅(qū)逐數(shù)據(jù)皆尔,也就是說當(dāng)內(nèi)存不足以容納新寫入數(shù)據(jù)時(shí)呐舔,新寫入操作會(huì)報(bào)錯(cuò)。這個(gè)應(yīng)該沒人使用吧慷蠕!
- volatile-lfu(least frequently used):從已設(shè)置過期時(shí)間的數(shù)據(jù)集(server.db[i].expires)中挑選最不經(jīng)常使用的數(shù)據(jù)淘汰
- allkeys-lfu(least frequently used):當(dāng)內(nèi)存不足以容納新寫入數(shù)據(jù)時(shí)珊拼,在鍵空間中,移除最不經(jīng)常使用的 key
Redis如何實(shí)現(xiàn)分布式鎖砌们?
- 加鎖: 1杆麸、加鎖本質(zhì)上就是在redis上面設(shè)置一個(gè)key,這樣當(dāng)其他服務(wù)想獲取這個(gè)鎖的時(shí)候浪感,都會(huì)執(zhí)行set一個(gè)key的操作,這里這里有兩個(gè)參數(shù)需要注意饼问,nx是當(dāng)key不存在的時(shí)候才進(jìn)行設(shè)置影兽,如果存在的話返回失敗,然后還有一個(gè)ex參數(shù)莱革,這里是設(shè)置key的過期時(shí)間峻堰,主要是為了防止如果某個(gè)服務(wù)獲取鎖后宕機(jī)了,不會(huì)導(dǎo)致死鎖盅视。2捐名、解鎖的時(shí)候也要注意,這里要防止一個(gè)誤解鎖問題闹击,因?yàn)檎兦懊嬲f了鎖是有過期時(shí)間的镶蹋,那么如果鎖過期了,業(yè)務(wù)代碼還沒有執(zhí)行完赏半,但是此時(shí)鎖是已經(jīng)釋放了贺归,也就是說其他人可能會(huì)去獲取這把鎖,此時(shí)當(dāng)我們之前沒執(zhí)行完的業(yè)務(wù)代碼執(zhí)行完之后断箫,并解鎖的話拂酣,就會(huì)導(dǎo)致鎖誤解開。3仲义、誤解鎖解決方案婶熬,為每個(gè)加鎖生成一個(gè)uid,讓解鎖的時(shí)候,加鎖的人可以知道這把鎖是否是自己加的埃撵,這里注意赵颅,解鎖此時(shí)也分為兩步走了,第一步是判斷是否是自己加的盯另,第二步是進(jìn)行刪除性含。這里的兩步操作同樣需要保證原子性,為什么要保證原子性呢鸳惯?假如A請求在獲取鎖對應(yīng)的value值驗(yàn)證requestId相等后商蕴,下達(dá)刪除指令叠萍。但是由于網(wǎng)絡(luò)等原因,刪除的指令阻塞住了绪商。而此時(shí)鎖因?yàn)槌瑫r(shí)自動(dòng)解鎖了苛谷,并且B請求獲取到了鎖,重新加鎖格郁。這時(shí)候A請求到刪除指令執(zhí)行了腹殿,結(jié)果把B請求好不容易獲取到的鎖給刪了。
出現(xiàn)的問題
- 加鎖沒有保證原子性:將加鎖和設(shè)置過期時(shí)間分為兩步例书,如果加完鎖后出現(xiàn)錯(cuò)誤锣尉,導(dǎo)致過期時(shí)間沒有設(shè)置,這樣就會(huì)導(dǎo)致鎖永遠(yuǎn)不會(huì)過期决采,解決方案:1自沧、 最新的redis命令已經(jīng)提供API同步。 2 树瞭、在redis中拇厢,lua腳本是當(dāng)作命令來執(zhí)行的,所以是原子的操作晒喷,所以我們可以利用lua命令來進(jìn)行加鎖
- 鎖誤解除:如果線程 A 成功獲取到了鎖孝偎,并且設(shè)置了過期時(shí)間 30 秒,但線程 A 執(zhí)行時(shí)間超過了 30 秒凉敲,鎖過期自動(dòng)釋放衣盾,此時(shí)線程 B 獲取到了鎖;隨后 A 執(zhí)行完成荡陷,線程 A 使用 DEL 命令來釋放鎖雨效,但此時(shí)線程 B 加的鎖還沒有執(zhí)行完成,線程 A 實(shí)際釋放的線程 B 加的鎖废赞。
public static void wrongReleaseLock2(Jedis jedis, String lockKey, String requestId) {
// 判斷加鎖與解鎖是不是同一個(gè)客戶端
if (requestId.equals(jedis.get(lockKey))) {
// 若在此時(shí)徽龟,這把鎖突然不是這個(gè)客戶端的,則會(huì)誤解鎖
jedis.del(lockKey);
}
}
如代碼注釋唉地,問題在于如果調(diào)用jedis.del()方法的時(shí)候据悔,這把鎖已經(jīng)不屬于當(dāng)前客戶端的時(shí)候會(huì)解除他人加的鎖。那么是否真的有這種場景耘沼?答案是肯定的极颓,比如客戶端A加鎖,一段時(shí)間之后客戶端A解鎖群嗤,在執(zhí)行jedis.del()之前菠隆,鎖突然過期了,此時(shí)客戶端B嘗試加鎖成功,然后客戶端A再執(zhí)行del()方法骇径,則將客戶端B的鎖給解除了躯肌。
注意加鎖和解鎖的操作原子性
簡述 Redis 中跳表的應(yīng)用以及優(yōu)缺點(diǎn)?
答:
跳表是一個(gè)可以快速查找的有序鏈表, 搜索、插入破衔、刪除操作的時(shí)間均為O(logn), 跳表雖然是非常有用的數(shù)據(jù)結(jié)構(gòu)清女,但是很多書里都沒有寫這個(gè),我在大學(xué)的數(shù)據(jù)結(jié)構(gòu)課本里也沒有寫跳表晰筛,就導(dǎo)致很多人對跳表不熟悉嫡丙。
跳表本質(zhì)上是一個(gè)鏈表,因?yàn)殒湵淼碾S機(jī)查找性能太差读第,是O(N)曙博,查找元素只能從頭結(jié)點(diǎn)或者尾結(jié)點(diǎn)遍歷。
跳表的優(yōu)缺點(diǎn):
作為快速查找的數(shù)據(jù)結(jié)構(gòu)卦方,跳表常用來和紅黑樹 做比較羊瘩,列一下跳表和紅黑樹的優(yōu)缺點(diǎn)吧
跳表的優(yōu)點(diǎn):
跳表實(shí)現(xiàn)起來相對簡單。紅黑樹的定義和左旋右旋操作盼砍,確實(shí)復(fù)雜,我資質(zhì)愚鈍逝她,理解起來還是有單困難浇坐。后面我會(huì)解釋跳表實(shí)現(xiàn)簡單的原因的.
區(qū)間查找方便。在跳表中找到一個(gè)節(jié)點(diǎn)后黔宛,就可以通過前后指針找到相鄰的元素近刘。紅黑樹則需要通過父節(jié)點(diǎn),子節(jié)點(diǎn)去尋找臀晃,相對麻煩觉渴。
紅黑樹的優(yōu)點(diǎn):
內(nèi)存占用小,只需要3個(gè)指針就可以(左子樹徽惋,右子樹案淋,父節(jié)點(diǎn)) 而跳表有一個(gè)向后的指針,每一層都有一個(gè)向前的指針
紅黑樹的查找穩(wěn)定险绘,紅黑樹有著嚴(yán)格的定義踢京,每次插入和刪除數(shù)據(jù)都會(huì)通過左旋右旋來平衡樹的結(jié)構(gòu),通過紅黑樹查找有著穩(wěn)定的查找時(shí)間O(logn) 宦棺,為啥跳表是不穩(wěn)定的瓣距,看到跳表是怎樣確定層數(shù)的就明白了
跳表和普通鏈表相比,除了費(fèi)內(nèi)存代咸,好像沒啥缺點(diǎn)了
如何解決緩存與數(shù)據(jù)庫不一致的問題蹈丸?
Redis事務(wù)?
Redis三種高級數(shù)據(jù)結(jié)構(gòu)?
答:
bitmap相關(guān)使用
三種高級數(shù)據(jù)結(jié)構(gòu)
- HyperLogLog:
- bitMap:
- GEO: