1. 簡(jiǎn)單介紹一下 Redis
簡(jiǎn)單來(lái)說(shuō) Redis 就是一個(gè)使用 C 語(yǔ)言開(kāi)發(fā)的數(shù)據(jù)庫(kù)摇肌,不過(guò)與傳統(tǒng)數(shù)據(jù)庫(kù)不同的是 Redis 的數(shù)據(jù) 是存在內(nèi)存中的 宏粤,也就是它是內(nèi)存數(shù)據(jù)庫(kù)徙融,所以讀寫(xiě)速度非巢度快舍扰,因此 Redis 被廣泛應(yīng)用于緩存方向坦敌。
另外侣诵,Redis 除了做緩存之外痢法,Redis 也經(jīng)常用來(lái)做分布式鎖,甚至是消息隊(duì)列杜顺。
Redis 提供了多種數(shù)據(jù)類(lèi)型來(lái)支持不同的業(yè)務(wù)場(chǎng)景财搁。Redis 還支持事務(wù) 、持久化躬络、Lua 腳本尖奔、多種集群方案。
3. 分布式緩存常?的技術(shù)選型方案有哪些?
分布式緩存的話穷当,使用的比較多的主要是 Memcached 和 Redis提茁。不過(guò),現(xiàn)在基本沒(méi)有看過(guò)還有項(xiàng)目使用 Memcached 來(lái)做緩存馁菜,都是直接用 Redis茴扁。
Memcached 是分布式緩存最開(kāi)始興起的那會(huì),比??常用的汪疮。后來(lái)峭火,隨著 Redis 的發(fā)展,大家慢 慢都轉(zhuǎn)而使用更加強(qiáng)大的 Redis 了智嚷。
分布式緩存主要解決的是單機(jī)緩存的容量受服務(wù)器限制并且無(wú)法保存通用的信息卖丸。因?yàn)椋镜鼐?存只在當(dāng)前服務(wù)里有效盏道,比如如果你部署了兩個(gè)相同的服務(wù)稍浆,他們兩者之間的緩存數(shù)據(jù)是無(wú)法共同的。
3. 說(shuō)一下 Redis 和 Memcached 的區(qū)別和共同點(diǎn)
現(xiàn)在公司一般都是用 Redis 來(lái)實(shí)現(xiàn)緩存猜嘱,而且 Redis 自身也越來(lái)越強(qiáng)大了!不過(guò)衅枫,了解 Redis 和 Memcached 的區(qū)別和共同點(diǎn),有助于我們?cè)谧鱿鄳?yīng)的技術(shù)選型的時(shí)候泉坐,能夠做到有理有據(jù)!
共同點(diǎn) :
- 都是基于內(nèi)存的數(shù)據(jù)庫(kù)为鳄,一般都用來(lái)當(dāng)做緩存使用。
- 都有過(guò)期策略腕让。
- 兩者的性能都非常高孤钦。
區(qū)別 :
- Redis 支持更豐富的數(shù)據(jù)類(lèi)型(支持更復(fù)雜的應(yīng)用場(chǎng)景)。Redis 不僅僅支持簡(jiǎn)單的 k/v 類(lèi) 型的數(shù)據(jù)纯丸,同時(shí)還提供 list偏形,set,zset觉鼻,hash 等數(shù)據(jù)結(jié)構(gòu)的存儲(chǔ)俊扭。Memcached 只支持最簡(jiǎn) 單的 k/v 數(shù)據(jù)類(lèi)型。
- Redis 支持?jǐn)?shù)據(jù)的持久化坠陈,可以將內(nèi)存中的數(shù)據(jù)保持在磁盤(pán)中萨惑,重啟的時(shí)候可以再次加載進(jìn) 行使用,而 Memecache 把數(shù)據(jù)全部存在內(nèi)存之中捐康。
- Redis 有災(zāi)難恢復(fù)機(jī)制。 因?yàn)榭梢园丫彺嬷械臄?shù)據(jù)持久化到磁盤(pán)上庸蔼。
- Redis 在服務(wù)器內(nèi)存使用完之后解总,可以將不用的數(shù)據(jù)放到磁盤(pán)上。但是姐仅,Memcached 在服
務(wù)器內(nèi)存使用完之后花枫,就會(huì)直接報(bào)異常。 - Memcached 沒(méi)有原生的集群模式掏膏,需要依靠客戶端來(lái)實(shí)現(xiàn)往集群中分片寫(xiě)入數(shù)據(jù);但是
Redis 目前是原生支持 cluster 模式的. - Memcached 是多線程劳翰,非阻塞 IO 復(fù)用的網(wǎng)絡(luò)模型;Redis 使用單線程的多路 IO 復(fù)用模
型。 (Redis 6.0 引入了多線程 IO ) - Redis 支持發(fā)布訂閱模型馒疹、Lua 腳本佳簸、事務(wù)等功能,而 Memcached 不支持行冰。并且溺蕉,Redis
支持更多的編程語(yǔ)言伶丐。 - Memcached過(guò)期數(shù)據(jù)的刪除策略只用了惰性刪除悼做,而 Redis 同時(shí)使用了惰性刪除與定期刪除。
相信看了上面的對(duì)比之后哗魂,我們已經(jīng)沒(méi)有什么理由可以選擇使用 Memcached 來(lái)作為自己項(xiàng)目的分布式緩存了肛走。
4. 緩存數(shù)據(jù)的處理流程是怎樣的?
簡(jiǎn)單來(lái)說(shuō)就是:
- 如果用戶請(qǐng)求的數(shù)據(jù)在緩存中就直接返回。
- 緩存中不存在的話就看數(shù)據(jù)庫(kù)中是否存在录别。
- 數(shù)據(jù)庫(kù)中存在的話就更新緩存中的數(shù)據(jù)朽色。
- 數(shù)據(jù)庫(kù)中不存在的話就返回空數(shù)據(jù)。
5. 為什么要用 Redis/為什么要用緩存?
簡(jiǎn)單组题,來(lái)說(shuō)使用緩存主要是為了提升用戶體驗(yàn)以及應(yīng)對(duì)更多的用戶葫男。
下面我們主要從“高性能”和“高并發(fā)”這兩點(diǎn)來(lái)看待這個(gè)問(wèn)題。
高性能 :
我們?cè)O(shè)想這樣的場(chǎng)景:
假如用戶第一次訪問(wèn)數(shù)據(jù)庫(kù)中的某些數(shù)據(jù)的話崔列,這個(gè)過(guò)程是比??慢梢褐,畢竟是從硬盤(pán)中讀取的。但 是赵讯,如果說(shuō)盈咳,用戶訪問(wèn)的數(shù)據(jù)屬于高頻數(shù)據(jù)并且不會(huì)經(jīng)常改變的話,那么我們就可以很放心地將 該用戶訪問(wèn)的數(shù)據(jù)存在緩存中边翼。
這樣有什么好處呢? 那就是保證用戶下一次再訪問(wèn)這些數(shù)據(jù)的時(shí)候就可以直接從緩存中獲取了鱼响。 操作緩存就是直接操作內(nèi)存,所以速度相當(dāng)快组底。
不過(guò)丈积,要保持?jǐn)?shù)據(jù)庫(kù)和緩存中的數(shù)據(jù)的一致性筐骇。 如果數(shù)據(jù)庫(kù)中的對(duì)應(yīng)數(shù)據(jù)改變的之后,同步改變 緩存中相應(yīng)的數(shù)據(jù)即可!
高并發(fā):
一般像 MySQL 這類(lèi)的數(shù)據(jù)庫(kù)的 QPS 大概都在 1w 左右(4 核 8g) 江滨,但是使用 Redis 緩存之后 很容易達(dá)到 10w+拥褂,甚至最高能達(dá)到 30w+(就單機(jī) redis 的情況,redis 集群的話會(huì)更高)牙寞。
QPS(Query Per Second):服務(wù)器每秒可以執(zhí)行的查詢次數(shù)
所以饺鹃,直接操作緩存能夠承受的數(shù)據(jù)庫(kù)請(qǐng)求數(shù)量是遠(yuǎn)遠(yuǎn)大于直接訪問(wèn)數(shù)據(jù)庫(kù)的,所以我們可以考 慮把數(shù)據(jù)庫(kù)中的部分?jǐn)?shù)據(jù)轉(zhuǎn)移到緩存中去间雀,這樣用戶的一部分請(qǐng)求會(huì)直接到緩存這里而不用經(jīng)過(guò) 數(shù)據(jù)庫(kù)悔详。進(jìn)而,我們也就提高的系統(tǒng)整體的并發(fā)惹挟。
6. Redis 常?數(shù)據(jù)結(jié)構(gòu)以及使用場(chǎng)景分析
可以自己本機(jī)安裝 redis 或者通過(guò) redis 官網(wǎng)提供的在線 redis 環(huán)境茄螃。
6.1. string
- 介紹 :string 數(shù)據(jù)結(jié)構(gòu)是簡(jiǎn)單的 key-value 類(lèi)型。雖然 Redis 是用 C 語(yǔ)言寫(xiě)的连锯,但是 Redis 并沒(méi)有使用 C 的字符串表示归苍,而是自己構(gòu)建了一種簡(jiǎn)單動(dòng)態(tài)字符串(simple dynamic string,SDS)运怖。相比于 C 的原生字符串拼弃,Redis 的 SDS 不光可以保存文本數(shù)據(jù)還可以保存 二進(jìn)制數(shù)據(jù),并且獲取字符串?度復(fù)雜度為 O(1)(C 字符串為 O(N)),除此之外,Redis 的 SDS API 是安全的摇展,不會(huì)造成緩沖區(qū)溢出吻氧。
- 常用命令: set,get,strlen,exists,dect,incr,setex 等等。
- 應(yīng)用場(chǎng)景 :一般常用在需要計(jì)數(shù)的場(chǎng)景咏连,比如用戶的訪問(wèn)次數(shù)盯孙、熱點(diǎn)文章的點(diǎn)贊轉(zhuǎn)發(fā)數(shù)量等等。
下面我們簡(jiǎn)單看看它的使用!
普通字符串的基本操作:
批量設(shè)置 :
計(jì)數(shù)器(字符串的內(nèi)容為整數(shù)的時(shí)候可以使用):
過(guò)期:
6.2. list
- 介紹 :list 即是 鏈表祟滴。鏈表是一種非常常?的數(shù)據(jù)結(jié)構(gòu)振惰,特點(diǎn)是易于數(shù)據(jù)元素的插入和刪除 并且且可以靈活調(diào)整鏈表?度,但是鏈表的隨機(jī)訪問(wèn)困難垄懂。許多高級(jí)編程語(yǔ)言都內(nèi)置了鏈表 的實(shí)現(xiàn)比如 Java 中的 LinkedList骑晶,但是 C 語(yǔ)言并沒(méi)有實(shí)現(xiàn)鏈表,所以 Redis 實(shí)現(xiàn)了自己 的鏈表數(shù)據(jù)結(jié)構(gòu)埠偿。Redis 的 list 的實(shí)現(xiàn)為一個(gè) 雙向鏈表透罢,即可以支持反向查找和遍歷,更方 便操作冠蒋,不過(guò)帶來(lái)了部分額外的內(nèi)存開(kāi)銷(xiāo)羽圃。
- 常用命令: rpush,lpop,lpush,rpop,lrange,llen 等
- 應(yīng)用場(chǎng)景: 發(fā)布與訂閱或者說(shuō)消息隊(duì)列、慢查詢。
下面我們簡(jiǎn)單看看它的使用!
通過(guò) rpush/lpop 實(shí)現(xiàn)隊(duì)列:
通過(guò) rpush/rpop 實(shí)現(xiàn)棧:
通過(guò) lrange 查看對(duì)應(yīng)下標(biāo)范圍的列表元素:
通過(guò) lrange 命令朽寞,你可以基于 list 實(shí)現(xiàn)分?查詢识窿,性能非常高!
通過(guò) llen 查看鏈表?度:
6.3. hash
- 介紹 :hash 類(lèi)似于 JDK1.8 前的 HashMap,內(nèi)部實(shí)現(xiàn)也差不多(數(shù)組 + 鏈表)脑融。不過(guò)喻频, Redis 的 hash 做了更多優(yōu)化。另外肘迎,hash 是一個(gè) string 類(lèi)型的 field 和 value 的映射表甥温, 特別適合用于存儲(chǔ)對(duì)象,后續(xù)操作的時(shí)候妓布,你可以直接僅僅修改這個(gè)對(duì)象中的某個(gè)字段的 值姻蚓。 比如我們可以 hash 數(shù)據(jù)結(jié)構(gòu)來(lái)存儲(chǔ)用戶信息,商品信息等等匣沼。
- 常用命令: hset,hmset,hexists,hget,hgetall,hkeys,hvals 等狰挡。
- 應(yīng)用場(chǎng)景: 系統(tǒng)中對(duì)象數(shù)據(jù)的存儲(chǔ)。
下面我們簡(jiǎn)單看看它的使用!
6.4. set
- 介紹 : set 類(lèi)似于 Java 中的 HashSet 释涛。Redis 中的 set 類(lèi)型是一種無(wú)序集合加叁,集合中的元 素沒(méi)有先后順序。當(dāng)你需要存儲(chǔ)一個(gè)列表數(shù)據(jù)唇撬,又不希望出現(xiàn)重復(fù)數(shù)據(jù)時(shí)它匕,set 是一個(gè)很好 的選擇,并且 set 提供了判斷某個(gè)成員是否在一個(gè) set 集合內(nèi)的重要接口局荚,這個(gè)也是 list 所 不能提供的超凳∮郏可以基于 set 輕易實(shí)現(xiàn)交集耀态、并集、差集的操作暂雹。比如:你可以將一個(gè)用戶所 有的關(guān)注人存在一個(gè)集合中首装,將其所有粉絲存在一個(gè)集合。Redis 可以非常方便的實(shí)現(xiàn)如共 同關(guān)注杭跪、共同粉絲仙逻、共同喜好等功能。這個(gè)過(guò)程也就是求交集的過(guò)程涧尿。
- 常用命令: sadd,spop,smembers,sismember,scard,sinterstore,sunion 等系奉。
- 應(yīng)用場(chǎng)景: 需要存放的數(shù)據(jù)不能重復(fù)以及需要獲取多個(gè)數(shù)據(jù)源交集和并集等場(chǎng)景
下面我們簡(jiǎn)單看看它的使用
6.5. sorted set
- 介紹: 和 set 相比,sorted set 增加了一個(gè)權(quán)重參數(shù) score姑廉,使得集合中的元素能夠按 score 進(jìn)行有序排列缺亮,還可以通過(guò) score 的范圍來(lái)獲取元素的列表。有點(diǎn)像是 Java 中 HashMap 和 TreeSet 的結(jié)合體桥言。
- 常用命令: zadd,zcard,zscore,zrange,zrevrange,zrem 等萌踱。
- 應(yīng)用場(chǎng)景: 需要對(duì)數(shù)據(jù)根據(jù)某個(gè)權(quán)重進(jìn)行排序的場(chǎng)景葵礼。比如在直播系統(tǒng)中,實(shí)時(shí)排行信息包含直播間在線用戶列表并鸵,各種禮物排行榜鸳粉,彈幕消息(可以理解為按消息維度的消息排行榜)等信息。
7. Redis 單線程模型詳解
Redis 基于 Reactor 模式來(lái)設(shè)計(jì)開(kāi)發(fā)了自己的一套高效的事件處理模型 (Netty 的線程模型也基 于 Reactor 模式园担,Reactor 模式不愧是高性能 IO 的基石)届谈,這套事件處理模型對(duì)應(yīng)的是 Redis 中的文件事件處理器(file event handler)。由于文件事件處理器(file event handler)是單線程 方式運(yùn)行的弯汰,所以我們一般都說(shuō) Redis 是單線程模型疼约。
Redis 通過(guò)IO 多路復(fù)用程序 來(lái)監(jiān)聽(tīng)來(lái)自客戶端的大量連接(或者說(shuō)是監(jiān)聽(tīng)多個(gè) socket),它會(huì)將感興趣的事件及類(lèi)型(讀蝙泼、寫(xiě))注冊(cè)到內(nèi)核中并監(jiān)聽(tīng)每個(gè)事件是否發(fā)生程剥。
這樣的好處非常明顯: I/O 多路復(fù)用技術(shù)的使用讓 Redis 不需要額外創(chuàng)建多余的線程來(lái)監(jiān)聽(tīng)客戶端的大量連接,降低了資源的消耗(和 NIO 中的 Selector 組件很像)汤踏。
另外织鲸, Redis 服務(wù)器是一個(gè)事件驅(qū)動(dòng)程序,服務(wù)器需要處理兩類(lèi)事件: 1. 文件事件; 2. 時(shí)間事件溪胶。
時(shí)間事件不需要多花時(shí)間了解搂擦,我們接觸最多的還是 文件事件(客戶端進(jìn)行讀取寫(xiě)入等操作,涉 及一系列網(wǎng)絡(luò)通信)哗脖。
《Redis 設(shè)計(jì)與實(shí)現(xiàn)》有一段話是如是介紹文件事件的
Redis 基于 Reactor 模式開(kāi)發(fā)了自己的網(wǎng)絡(luò)事件處理器:這個(gè)處理器被稱(chēng)為文件事件處理器 (file event handler)瀑踢。文件事件處理器使用 I/O 多路復(fù)用(multiplexing)程序來(lái)同時(shí)監(jiān)聽(tīng) 多個(gè)套接字,并根據(jù) 套接字目前執(zhí)行的任務(wù)來(lái)為套接字關(guān)聯(lián)不同的事件處理器才避。
當(dāng)被監(jiān)聽(tīng)的套接字準(zhǔn)備好執(zhí)行連接應(yīng)答(accept)橱夭、讀取(read)、寫(xiě)入(write)桑逝、關(guān) 閉 (close)等操作時(shí)棘劣,與操作相對(duì)應(yīng)的文件事件就會(huì)產(chǎn)生,這時(shí)文件事件處理器就會(huì)調(diào)用套 接字之前關(guān)聯(lián)好的事件處理器來(lái)處理這些事件楞遏。
雖然文件事件處理器以單線程方式運(yùn)行茬暇,但通過(guò)使用 I/O 多路復(fù)用程序來(lái)監(jiān)聽(tīng)多個(gè)套接字, 文件事件處理器既實(shí)現(xiàn)了高性能的網(wǎng)絡(luò)通信模型寡喝,又可以很好地與 Redis 服務(wù)器中其他同樣 以單線程方式運(yùn)行的模塊進(jìn)行對(duì)接糙俗,這保持了 Redis 內(nèi)部單線程設(shè)計(jì)的簡(jiǎn)單性。
可以看出预鬓,文件事件處理器(file event handler)主要是包含 4 個(gè)部分:
- 多個(gè) socket(客戶端連接)
- IO 多路復(fù)用程序(支持多個(gè)客戶端連接的關(guān)鍵)
- 文件事件分派器(將 socket 關(guān)聯(lián)到相應(yīng)的事件處理器)
- 事件處理器(連接應(yīng)答處理器巧骚、命令請(qǐng)求處理器、命令回復(fù)處理器)
8. Redis 沒(méi)有使用多線程?為什么不使用多線程?
雖然說(shuō) Redis 是單線程模型,但是网缝, 實(shí)際上巨税,Redis 在 4.0 之后的版本中就已經(jīng)加入了對(duì)多線程 的支持。
不過(guò)粉臊,Redis 4.0 增加的多線程主要是針對(duì)一些大鍵值對(duì)的刪除操作的命令草添,使用這些命令就會(huì) 使用主處理之外的其他線程來(lái)“異步處理”。
大體上來(lái)說(shuō)扼仲,Redis 6.0 之前主要還是單線程處理远寸。
那,Redis6.0 之前 為什么不使用多線程?
我覺(jué)得主要原因有下面 3 個(gè):
- 單線程編程容易并且更容易維護(hù);
- Redis 的性能瓶頸不再 CPU 屠凶,主要在內(nèi)存和網(wǎng)絡(luò);
- 多線程就會(huì)存在死鎖驰后、線程上下文切換等問(wèn)題,甚至?xí)绊懶阅堋?/li>
9. Redis6.0 之后為何引入了多線程?
Redis6.0 引入多線程主要是為了提高網(wǎng)絡(luò) IO 讀寫(xiě)性能矗愧,因?yàn)檫@個(gè)算是 Redis 中的一個(gè)性能瓶頸(Redis 的瓶頸主要受限于內(nèi)存和網(wǎng)絡(luò))灶芝。
雖然,Redis6.0 引入了多線程唉韭,但是 Redis 的多線程只是在網(wǎng)絡(luò)數(shù)據(jù)的讀寫(xiě)這類(lèi)耗時(shí)操作上使用了夜涕, 執(zhí)行命令仍然是單線程順序執(zhí)行。因此属愤,你也不需要擔(dān)心線程安全問(wèn)題女器。
Redis6.0 的多線程默認(rèn)是禁用的,只使用主線程住诸。如需開(kāi)啟需要修改 redis 配置文件
redis.conf
開(kāi)啟多線程后驾胆,還需要設(shè)置線程數(shù),否則是不生效的贱呐。同樣需要修改 redis 配置文件
redis.conf
推薦閱讀:
Redis 6.0 新特性-多線程連環(huán) 13 問(wèn)!
10. Redis 給緩存數(shù)據(jù)設(shè)置過(guò)期時(shí)間有啥用?
一般情況下丧诺,我們?cè)O(shè)置保存的緩存數(shù)據(jù)的時(shí)候都會(huì)設(shè)置一個(gè)過(guò)期時(shí)間。為什么呢?
因?yàn)閮?nèi)存是有限的吼句,如果緩存中的所有數(shù)據(jù)都是一直保存的話锅必,分分鐘直接Out of memory。 Redis 自帶了給緩存數(shù)據(jù)設(shè)置過(guò)期時(shí)間的功能惕艳,比如:
注意:Redis中除了字符串類(lèi)型有自己獨(dú)有設(shè)置過(guò)期時(shí)間的命令 setex 外,其他方法都需要依靠 expire 命令來(lái)設(shè)置過(guò)期時(shí)間 驹愚。另外远搪, persist 命令可以移除一個(gè)鍵的過(guò)期時(shí)間:
過(guò)期時(shí)間除了有助于緩解內(nèi)存的消耗,還有什么其他用么?
很多時(shí)候逢捺,我們的業(yè)務(wù)場(chǎng)景就是需要某個(gè)數(shù)據(jù)只在某一時(shí)間段內(nèi)存在谁鳍,比如我們的短信驗(yàn)證碼可 能只在1分鐘內(nèi)有效,用戶登錄的 token 可能只在 1 天內(nèi)有效。
如果使用傳統(tǒng)的數(shù)據(jù)庫(kù)來(lái)處理的話倘潜,一般都是自己判斷過(guò)期绷柒,這樣更麻煩并且性能要差很多。
11. Redis是如何判斷數(shù)據(jù)是否過(guò)期的呢?
Redis 通過(guò)一個(gè)叫做過(guò)期字典(可以看作是hash表)來(lái)保存數(shù)據(jù)過(guò)期的時(shí)間涮因。過(guò)期字典的鍵指向 Redis數(shù)據(jù)庫(kù)中的某個(gè)key(鍵)废睦,過(guò)期字典的值是一個(gè)long long類(lèi)型的整數(shù),這個(gè)整數(shù)保存了key所 指向的數(shù)據(jù)庫(kù)鍵的過(guò)期時(shí)間(毫秒精度的UNIX時(shí)間戳)养泡。
過(guò)期字典是存儲(chǔ)在redisDb這個(gè)結(jié)構(gòu)里的:
12. 過(guò)期的數(shù)據(jù)的刪除策略了解么?
如果假設(shè)你設(shè)置了一批 key 只能存活 1 分鐘嗜湃,那么 1 分鐘后,Redis 是怎么對(duì)這批 key 進(jìn)行刪除的呢?
常用的過(guò)期數(shù)據(jù)的刪除策略就兩個(gè)(重要!自己造緩存輪子的時(shí)候需要格外考慮的東?):
- 惰性刪除 :只會(huì)在取出key的時(shí)候才對(duì)數(shù)據(jù)進(jìn)行過(guò)期檢查澜掩。這樣對(duì)CPU最友好购披,但是可能會(huì) 造成太多過(guò)期 key 沒(méi)有被刪除。
- 定期刪除 : 每隔一段時(shí)間抽取一批 key 執(zhí)行刪除過(guò)期key操作肩榕。并且刚陡,Redis 底層會(huì)通過(guò)限制刪除操作執(zhí)行的時(shí)?和頻率來(lái)減少刪除操作對(duì)CPU時(shí)間的影響。
定期刪除對(duì)內(nèi)存更加友好株汉,惰性刪除對(duì)CPU更加友好橘荠。兩者各有千秋,所以Redis 采用的是 定期 刪除+惰性/懶漢式刪除 郎逃。
但是哥童,僅僅通過(guò)給 key 設(shè)置過(guò)期時(shí)間還是有問(wèn)題的。因?yàn)檫€是可能存在定期刪除和惰性刪除漏掉 了很多過(guò)期 key 的情況褒翰。這樣就導(dǎo)致大量過(guò)期 key 堆積在內(nèi)存里贮懈,然后就Out of memory了。
怎么解決這個(gè)問(wèn)題呢?答案就是: Redis 內(nèi)存淘汰機(jī)制优训。
13. Redis 內(nèi)存淘汰機(jī)制了解么?
Redis 提供 6 種數(shù)據(jù)淘汰策略:
- volatile-lru(least recently used):從已設(shè)置過(guò)期時(shí)間的數(shù)據(jù)集(server.db[i].expires) 中挑選最近最少使用的數(shù)據(jù)淘汰
- volatile-ttl:從已設(shè)置過(guò)期時(shí)間的數(shù)據(jù)集(server.db[i].expires)中挑選將要過(guò)期的數(shù)據(jù)淘汰
- volatile-random:從已設(shè)置過(guò)期時(shí)間的數(shù)據(jù)集(server.db[i].expires)中任意選擇數(shù)據(jù)淘汰
- allkeys-lru(least recently used):當(dāng)內(nèi)存不足以容納新寫(xiě)入數(shù)據(jù)時(shí)朵你,在鍵空間中,移除最近最少使用的 key(這個(gè)是最常用的)
- allkeys-random:從數(shù)據(jù)集(server.db[i].dict)中任意選擇數(shù)據(jù)淘汰
- no-eviction:禁止驅(qū)逐數(shù)據(jù)揣非,也就是說(shuō)當(dāng)內(nèi)存不足以容納新寫(xiě)入數(shù)據(jù)時(shí)抡医,新寫(xiě)入操作會(huì)報(bào)錯(cuò)。這個(gè)應(yīng)該沒(méi)人使用吧!
4.0 版本后增加以下兩種:
- volatile-lfu(least frequently used):從已設(shè)置過(guò)期時(shí)間的數(shù)據(jù)集(server.db[i].expires)中 挑選最不經(jīng)常使用的數(shù)據(jù)淘汰
- allkeys-lfu(least frequently used):當(dāng)內(nèi)存不足以容納新寫(xiě)入數(shù)據(jù)時(shí)早敬,在鍵空間中忌傻,移 除最不經(jīng)常使用的 key
14. Redis 持久化機(jī)制(怎么保證 Redis 掛掉之后再重啟數(shù)據(jù)可以進(jìn)
行恢復(fù))
很多時(shí)候我們需要持久化數(shù)據(jù)也就是將內(nèi)存中的數(shù)據(jù)寫(xiě)入到硬盤(pán)里面,大部分原因是為了之后重用數(shù)據(jù)(比如重啟機(jī)器搞监、機(jī)器故障之后恢復(fù)數(shù)據(jù))水孩,或者是為了防止系統(tǒng)故障而將數(shù)據(jù)備份到一 個(gè)遠(yuǎn)程位置。
Redis 不同于 Memcached 的很重要一點(diǎn)就是琐驴,Redis 支持持久化俘种,而且支持兩種不同的持久化操作秤标。Redis 的一種持久化方式叫快照(snapshotting,RDB)宙刘,另一種方式是只追加文件 (append-only file, AOF)苍姜。這兩種方法各有千秋,下面我會(huì)詳細(xì)這兩種持久化方法是什么悬包,怎么用衙猪,如何選擇適合自己的持久化方法。
快照(snapshotting)持久化(RDB)
Redis 可以通過(guò)創(chuàng)建快照來(lái)獲得存儲(chǔ)在內(nèi)存里面的數(shù)據(jù)在某個(gè)時(shí)間點(diǎn)上的副本玉罐。Redis 創(chuàng)建快照 之后屈嗤,可以對(duì)快照進(jìn)行備份,可以將快照復(fù)制到其他服務(wù)器從而創(chuàng)建具有相同數(shù)據(jù)的服務(wù)器副本 (Redis 主從結(jié)構(gòu)吊输,主要用來(lái)提高 Redis 性能)饶号,還可以將快照留在原地以便重啟服務(wù)器的時(shí)候使用。
快照持久化是 Redis 默認(rèn)采用的持久化方式季蚂,在 Redis.conf 配置文件中默認(rèn)有此下配置:
AOF(append-only file)持久化
與快照持久化相比茫船,AOF 持久化 的實(shí)時(shí)性更好,因此已成為主流的持久化方案扭屁。默認(rèn)情況下Redis 沒(méi)有開(kāi)啟 AOF(append only file)方式的持久化算谈,可以通過(guò)appendonly 參數(shù)開(kāi)啟:
開(kāi)啟 AOF 持久化后每執(zhí)行一條會(huì)更改 Redis 中的數(shù)據(jù)的命令,Redis 就會(huì)將該命令寫(xiě)入硬盤(pán)中 的 AOF 文件料滥。AOF 文件的保存位置和 RDB 文件的位置相同然眼,都是通過(guò) dir 參數(shù)設(shè)置的,默認(rèn)的 文件名是 appendonly.aof葵腹。
在 Redis 的配置文件中存在三種不同的 AOF 持久化方式高每,它們分別是:
為了兼顧數(shù)據(jù)和寫(xiě)入性能,用戶可以考慮 appendfsync everysec 選項(xiàng) 践宴,讓 Redis 每秒同步一次 AOF 文件鲸匿,Redis 性能幾乎沒(méi)受到任何影響。而且這樣即使出現(xiàn)系統(tǒng)崩潰阻肩,用戶最多只會(huì)丟失一 秒之內(nèi)產(chǎn)生的數(shù)據(jù)带欢。當(dāng)硬盤(pán)忙于執(zhí)行寫(xiě)入操作的時(shí)候亩进,Redis 還會(huì)優(yōu)雅的放慢自己的速度以便適 應(yīng)硬盤(pán)的最大寫(xiě)入速度俺猿。
相關(guān) issue :783:Redis 的 AOF 方式
拓展:Redis 4.0 對(duì)于持久化機(jī)制的優(yōu)化
Redis 4.0 開(kāi)始支持 RDB 和 AOF 的混合持久化(默認(rèn)關(guān)閉,可以通過(guò)配置項(xiàng) aof-use-rdb- preamble 開(kāi)啟)榄鉴。
如果把混合持久化打開(kāi)撕氧,AOF 重寫(xiě)的時(shí)候就直接把 RDB 的內(nèi)容寫(xiě)到 AOF 文件開(kāi)頭瘤缩。這樣做的 好處是可以結(jié)合 RDB 和 AOF 的優(yōu)點(diǎn), 快速加載同時(shí)避免丟失過(guò)多的數(shù)據(jù)。當(dāng)然缺點(diǎn)也是有的伦泥, AOF 里面的 RDB 部分是壓縮格式不再是 AOF 格式剥啤,可讀性比較差。
補(bǔ)充內(nèi)容:AOF 重寫(xiě)
AOF 重寫(xiě)可以產(chǎn)生一個(gè)新的 AOF 文件不脯,這個(gè)新的 AOF 文件和原有的 AOF 文件所保存的數(shù)據(jù)庫(kù)狀態(tài)一樣府怯,但體積更小。
AOF 重寫(xiě)是一個(gè)有歧義的名字防楷,該功能是通過(guò)讀取數(shù)據(jù)庫(kù)中的鍵值對(duì)來(lái)實(shí)現(xiàn)的牺丙,程序無(wú)須對(duì)現(xiàn)有AOF 文件進(jìn)行任何讀入、分析或者寫(xiě)入操作复局。
在執(zhí)行 BGREWRITEAOF 命令時(shí)冲簿,Redis 服務(wù)器會(huì)維護(hù)一個(gè) AOF 重寫(xiě)緩沖區(qū),該緩沖區(qū)會(huì)在子 進(jìn)程創(chuàng)建新 AOF 文件期間亿昏,記錄服務(wù)器執(zhí)行的所有寫(xiě)命令峦剔。當(dāng)子進(jìn)程完成創(chuàng)建新 AOF 文件的工 作之后,服務(wù)器會(huì)將重寫(xiě)緩沖區(qū)中的所有內(nèi)容追加到新 AOF 文件的末尾角钩,使得新舊兩個(gè) AOF 文 件所保存的數(shù)據(jù)庫(kù)狀態(tài)一致吝沫。最后,服務(wù)器用新的 AOF 文件替換舊的 AOF 文件递礼,以此來(lái)完成 AOF 文件重寫(xiě)操作
15. Redis 事務(wù)
Redis 可以通過(guò) MULTI惨险,EXEC,DISCARD 和 WATCH 等命令來(lái)實(shí)現(xiàn)事務(wù)(transaction)功能脊髓。
使用 MULTI命令后可以輸入多個(gè)命令辫愉。Redis不會(huì)立即執(zhí)行這些命令,而是將它們放到隊(duì)列将硝,當(dāng) 調(diào)用了EXEC命令將執(zhí)行所有命令恭朗。
Redis官網(wǎng)相關(guān)介紹 如下:
但是,Redis 的事務(wù)和我們平時(shí)理解的關(guān)系型數(shù)據(jù)庫(kù)的事務(wù)不同袋哼。我們知道事務(wù)具有四大特性: 1. 原子性冀墨,2. 隔離性,3. 持久性涛贯,4. 一致性诽嘉。
- 原子性(Atomicity): 事務(wù)是最小的執(zhí)行單位,不允許分割弟翘。事務(wù)的原子性確保動(dòng)作要么 全部完成虫腋,要么完全不起作用;
- 隔離性(Isolation): 并發(fā)訪問(wèn)數(shù)據(jù)庫(kù)時(shí),一個(gè)用戶的事務(wù)不被其他事務(wù)所干擾稀余,各并發(fā) 事務(wù)之間數(shù)據(jù)庫(kù)是獨(dú)立的;
- 持久性(Durability): 一個(gè)事務(wù)被提交之后悦冀。它對(duì)數(shù)據(jù)庫(kù)中數(shù)據(jù)的改變是持久的,即使數(shù) 據(jù)庫(kù)發(fā)生故障也不應(yīng)該對(duì)其有任何影響睛琳。
- 一致性(Consistency): 執(zhí)行事務(wù)前后盒蟆,數(shù)據(jù)保持一致踏烙,多個(gè)事務(wù)對(duì)同一個(gè)數(shù)據(jù)讀取的結(jié) 果是相同的;
Redis 是不支持 roll back 的,因而不滿足原子性的(而且不滿足持久性)历等。
Redis官網(wǎng)也解釋了自己為啥不支持回滾讨惩。簡(jiǎn)單來(lái)說(shuō)就是Redis開(kāi)發(fā)者們覺(jué)得沒(méi)必要支持回滾,這 樣更簡(jiǎn)單便捷并且性能更好寒屯。Redis開(kāi)發(fā)者覺(jué)得即使命令執(zhí)行錯(cuò)誤也應(yīng)該在開(kāi)發(fā)過(guò)程中就被發(fā)現(xiàn) 而不是生產(chǎn)過(guò)程中荐捻。
你可以將Redis中的事務(wù)就理解為 :Redis事務(wù)提供了一種將多個(gè)命令請(qǐng)求打包的功能。然后寡夹, 再按順序執(zhí)行打包的所有命令处面,并且不會(huì)被中途打斷。
相關(guān)issue :issue452: 關(guān)于 Redis 事務(wù)不滿足原子性的問(wèn)題 菩掏,推薦閱 讀:https://zhuanlan.zhihu.com/p/43897838 魂角。
16. 緩存穿透
16.1. 什么是緩存穿透?
緩存穿透說(shuō)簡(jiǎn)單點(diǎn)就是大量請(qǐng)求的 key 根本不存在于緩存中,導(dǎo)致請(qǐng)求直接到了數(shù)據(jù)庫(kù)上患蹂,根本沒(méi)有經(jīng)過(guò)緩存這一層或颊。
舉個(gè)例子:某個(gè)黑客故意制造我們緩存中不存在的 key 發(fā)起大量請(qǐng)求,導(dǎo) 致大量請(qǐng)求落到數(shù)據(jù)庫(kù)传于。
16.2. 緩存穿透情況的處理流程是怎樣的?
如下圖所示囱挑,用戶的請(qǐng)求最終都要跑到數(shù)據(jù)庫(kù)中查詢一遍。
16.3. 有哪些解決辦法?
最基本的就是首先做好參數(shù)校驗(yàn)沼溜,一些不合法的參數(shù)請(qǐng)求直接拋出異常信息返回給客戶端平挑。比如查詢的數(shù)據(jù)庫(kù) id 不能小于 0、傳入的郵箱格式不對(duì)的時(shí)候直接返回錯(cuò)誤消息給客戶端等等系草。
- 緩存無(wú)效 key
如果緩存和數(shù)據(jù)庫(kù)都查不到某個(gè) key 的數(shù)據(jù)就寫(xiě)一個(gè)到 Redis 中去并設(shè)置過(guò)期時(shí)間通熄,具體命令如 下: SET key value EX 10086 。這種方式可以解決請(qǐng)求的 key 變化不頻繁的情況找都,如果黑客惡意 攻擊唇辨,每次構(gòu)建不同的請(qǐng)求 key,會(huì)導(dǎo)致 Redis 中緩存大量無(wú)效的 key 能耻。很明顯赏枚,這種方案并不 能從根本上解決此問(wèn)題。如果非要用這種方式來(lái)解決穿透問(wèn)題的話晓猛,盡量將無(wú)效的 key 的過(guò)期時(shí) 間設(shè)置短一點(diǎn)比如 1 分鐘饿幅。
另外,這里多說(shuō)一嘴戒职,一般情況下我們是這樣設(shè)計(jì) key 的:
表名: 列名:主鍵名:主鍵值
如果用 Java 代碼展示的話栗恩,差不多是下面這樣的:
- 布隆過(guò)濾器
布隆過(guò)濾器是一個(gè)非常神奇的數(shù)據(jù)結(jié)構(gòu),通過(guò)它我們可以非常方便地判斷一個(gè)給定數(shù)據(jù)是否存在 于海量數(shù)據(jù)中洪燥。我們需要的就是判斷 key 是否合法磕秤,有沒(méi)有感覺(jué)布隆過(guò)濾器就是我們想要找的那 個(gè)“人”乳乌。
具體是這樣做的:把所有可能存在的請(qǐng)求的值都存放在布隆過(guò)濾器中,當(dāng)用戶請(qǐng)求過(guò)來(lái)亲澡,先判斷 用戶發(fā)來(lái)的請(qǐng)求的值是否存在于布隆過(guò)濾器中钦扭。不存在的話纫版,直接返回請(qǐng)求參數(shù)錯(cuò)誤信息給客戶 端床绪,存在的話才會(huì)走下面的流程。
加入布隆過(guò)濾器之后的緩存處理流程圖如下其弊。
但是癞己,需要注意的是布隆過(guò)濾器可能會(huì)存在誤判的情況∷蠓ィ總結(jié)來(lái)說(shuō)就是: 布隆過(guò)濾器說(shuō)某個(gè)元素 存在痹雅,小概率會(huì)誤判。布隆過(guò)濾器說(shuō)某個(gè)元素不在糊识,那么這個(gè)元素一定不在绩社。
為什么會(huì)出現(xiàn)誤判的情況呢? 我們還要從布隆過(guò)濾器的原理來(lái)說(shuō)!
我們先來(lái)看一下,當(dāng)一個(gè)元素加入布隆過(guò)濾器中的時(shí)候赂苗,會(huì)進(jìn)行哪些操作:
- 使用布隆過(guò)濾器中的哈希函數(shù)對(duì)元素值進(jìn)行計(jì)算愉耙,得到哈希值(有幾個(gè)哈希函數(shù)得到幾個(gè)哈希值)
- 根據(jù)得到的哈希值,在位數(shù)組中把對(duì)應(yīng)下標(biāo)的值置為 1拌滋。
我們?cè)賮?lái)看一下朴沿,當(dāng)我們需要判斷一個(gè)元素是否存在于布隆過(guò)濾器的時(shí)候,會(huì)進(jìn)行哪些操作:
- 對(duì)給定元素再次進(jìn)行相同的哈希計(jì)算;
- 得到值之后判斷位數(shù)組中的每個(gè)元素是否都為 1败砂,如果值都為 1赌渣,那么說(shuō)明這個(gè)值在布隆過(guò)濾器中,如果存在一個(gè)值不為 1昌犹,說(shuō)明該元素不在布隆過(guò)濾器中坚芜。
然后,一定會(huì)出現(xiàn)這樣一種情況:不同的字符串可能哈希出來(lái)的位置相同斜姥。 (可以適當(dāng)增加位數(shù)組大小或者調(diào)整我們的哈希函數(shù)來(lái)降低概率)
布隆過(guò)濾器
17. 緩存雪崩
17.1. 什么是緩存雪崩?
實(shí)際上鸿竖,緩存雪崩描述的就是這樣一個(gè)簡(jiǎn)單的場(chǎng)景:緩存在同一時(shí)間大面積的失效,后面的請(qǐng)求 都直接落到了數(shù)據(jù)庫(kù)上疾渴,造成數(shù)據(jù)庫(kù)短時(shí)間內(nèi)承受大量請(qǐng)求千贯。 這就好比雪崩一樣,摧枯拉朽之 勢(shì)搞坝,數(shù)據(jù)庫(kù)的壓力可想而知搔谴,可能直接就被這么多請(qǐng)求弄宕機(jī)了。
舉個(gè)例子:系統(tǒng)的緩存模塊出了問(wèn)題比如宕機(jī)導(dǎo)致不可用桩撮。造成系統(tǒng)的所有訪問(wèn)敦第,都要走數(shù)據(jù)庫(kù)峰弹。
還有一種緩存雪崩的場(chǎng)景是:有一些被大量訪問(wèn)數(shù)據(jù)(熱點(diǎn)緩存)在某一時(shí)刻大面積失效,導(dǎo)致 對(duì)應(yīng)的請(qǐng)求直接落到了數(shù)據(jù)庫(kù)上芜果。 這樣的情況鞠呈,有下面幾種解決辦法:
舉個(gè)例子 :秒殺開(kāi)始 12 個(gè)小時(shí)之前,我們統(tǒng)一存放了一批商品到 Redis 中右钾,設(shè)置的緩存過(guò)期時(shí) 間也是 12 個(gè)小時(shí)蚁吝,那么秒殺開(kāi)始的時(shí)候,這些秒殺的商品的訪問(wèn)直接就失效了舀射。導(dǎo)致的情況就是窘茁,相應(yīng)的請(qǐng)求直接就落到了數(shù)據(jù)庫(kù)上,就像雪崩一樣可怕脆烟。
17.2. 有哪些解決辦法?
針對(duì) Redis 服務(wù)不可用的情況:
- 采用 Redis 集群山林,避免單機(jī)出現(xiàn)問(wèn)題整個(gè)緩存服務(wù)都沒(méi)辦法使用。
- 限流邢羔,避免同時(shí)處理大量的請(qǐng)求驼抹。
針對(duì)熱點(diǎn)緩存失效的情況:
- 設(shè)置不同的失效時(shí)間比如隨機(jī)設(shè)置緩存的失效時(shí)間。
- 緩存永不失效拜鹤。
18. 如何保證緩存和數(shù)據(jù)庫(kù)數(shù)據(jù)的一致性?
如果為了短時(shí)間的不一致性問(wèn)題框冀,選擇讓系統(tǒng)設(shè)計(jì)變得更加復(fù)雜的話,完全沒(méi)必要署惯。
下面單獨(dú)對(duì)** Cache Aside Pattern(旁路緩存模式) **來(lái)聊聊左驾。
Cache Aside Pattern 中遇到寫(xiě)請(qǐng)求是這樣的:更新 DB,然后直接刪除 cache 极谊。
如果更新數(shù)據(jù)庫(kù)成功诡右,而刪除緩存這一步失敗的情況的話,簡(jiǎn)單說(shuō)兩個(gè)解決方案:
- 緩存失效時(shí)間變短(不推薦轻猖,治標(biāo)不治本) :我們讓緩存數(shù)據(jù)的過(guò)期時(shí)間變短帆吻,這樣的話緩存就會(huì)從數(shù)據(jù)庫(kù)中加載數(shù)據(jù)。另外咙边,這種解決辦法對(duì)于先操作緩存后操作數(shù)據(jù)庫(kù)的場(chǎng)景不適用猜煮。
- 增加cache更新重試機(jī)制(常用): 如果 cache 服務(wù)當(dāng)前不可用導(dǎo)致緩存刪除失敗的話, 我們就隔一段時(shí)間進(jìn)行重試败许,重試次數(shù)可以自己定王带。如果多次重試還是失敗的話,我們可以 把當(dāng)前更新失敗的 key 存入隊(duì)列中市殷,等緩存服務(wù)可用之后愕撰,再將緩存中對(duì)應(yīng)的 key 刪除即可。
參考
《Redis 開(kāi)發(fā)與運(yùn)維》
《Redis 設(shè)計(jì)與實(shí)現(xiàn)》
Redis 命令總結(jié):http://Redisdoc.com/string/set.html
通俗易懂的 Redis 數(shù)據(jù)結(jié)構(gòu)基礎(chǔ)教程:https://juejin.im/post/5b53ee7e5188251aaa2d2e16 WHY Redis choose single thread (vs multi threads): https://medium.com/@jychen7/sharing-redis-single-thread-vs-multi-threads- 5870bd44d153------