Redis 是一個基于內(nèi)存的k-v結(jié)構(gòu)數(shù)據(jù)庫
- 基于內(nèi)存存儲,讀寫性能高
- 適合存儲熱點數(shù)據(jù)(熱點商品, 資訊, 新聞)
- 企業(yè)應(yīng)用廣泛
Redis入門
簡介:
應(yīng)用場景
- ?緩存
- 任務(wù)隊列
- 消息隊列
- 分布式鎖
數(shù)據(jù)類型
常用命令
redis常用命令鏈接 redis.net.cn
java中操作redis
介紹
:::info
redis啟動默認(rèn)有16個數(shù)據(jù)庫, 設(shè)置使用0號數(shù)據(jù)庫
key的序列化器 JdkSerializationRedisSerializer
:::
Redis 面試題
1. 簡單介紹?下 Redis 唄!
Redis 就是?個使? C 語?開發(fā)的數(shù)據(jù)庫, Redis 的數(shù)據(jù)是存在內(nèi)存中
它是內(nèi)存數(shù)據(jù)庫,所以讀寫速度?常快,因此 Redis 被?泛應(yīng)?于緩存
Redis 也經(jīng)常?來做分布式鎖,甚?是消息隊列。數(shù)據(jù)庫也行;
多種數(shù)據(jù)類型?持不同的業(yè)務(wù)場景. ?持事務(wù) 、持久化、Lua 腳本、多種集群?案
2. 分布式緩存常?的技術(shù)選型?案
分布式緩存的話,使?的?較多的主要是 Memcached 和 Redis
分布式緩存主要解決的是單機緩存的容量受服務(wù)器限制并且?法保存通?的信息。因為,本地緩存只在當(dāng)前服務(wù)?有效,?如如果你部署了兩個相同的服務(wù),他們兩者之間的緩存數(shù)據(jù)是?法共同的。
3. 說?下 Redis 和 Memcached 的區(qū)別和共同點
共同點 :
- 都是基于內(nèi)存的數(shù)據(jù)庫,?般都?來當(dāng)做緩存使?。
- 都有過期策略。
- 兩者的性能都?常?。
區(qū)別 :
-
- Redis ?持更豐富的數(shù)據(jù)類型(?持更復(fù)雜的應(yīng)?場景)。Redis 不僅僅?持簡單的 k/v 類型的數(shù)據(jù),同時還提供 list,set,zset,hash 等數(shù)據(jù)結(jié)構(gòu)的存儲。Memcached 只?持最簡單的 k/v 數(shù)據(jù)類型。
-
- Redis ?持?jǐn)?shù)據(jù)的持久化,可以將內(nèi)存中的數(shù)據(jù)保持在磁盤中,重啟的時候可以再次加載進(jìn)?使?,? Memecache 把數(shù)據(jù)全部存在內(nèi)存之中。
-
- Redis 有災(zāi)難恢復(fù)機制。 因為可以把緩存中的數(shù)據(jù)持久化到磁盤上。
-
- Redis 在服務(wù)器內(nèi)存使?完之后,可以將不?的數(shù)據(jù)放到磁盤上。但是,Memcached 在服務(wù)器內(nèi)存使?完之后,就會直接報異常。
-
- Memcached 沒有原?的集群模式,需要依靠客戶端來實現(xiàn)往集群中分?寫?數(shù)據(jù);但是Redis ?前是原??持 cluster 模式的.
-
- Memcached 是多線程,?阻塞 IO 復(fù)?的?絡(luò)模型;Redis 使?單線程的多路 IO 復(fù)?模型。 (Redis 6.0 引?了多線程 IO )
-
- Redis ?持發(fā)布訂閱模型、Lua 腳本、事務(wù)等功能,? Memcached 不?持。并且,Redis?持更多的編程語?。
-
- Memcached過期數(shù)據(jù)的刪除策略只?了惰性刪除,? Redis 同時使?了惰性刪除與定期刪除。
4. 緩存數(shù)據(jù)的處理流程
5. 為什么要? Redis/為什么要?緩存?
- 高性能 硬盤/內(nèi)存 數(shù)據(jù)一致性
- 高并發(fā) QPS(Query Per Second) : 服務(wù)器每秒可執(zhí)行的查詢次數(shù)
6. Redis 常?數(shù)據(jù)結(jié)構(gòu)以及使?場景分析
分類 | 主要命令 | 場景 |
---|---|---|
String k-v 簡單動態(tài)字符串(simple dynamic string SDS) | set/get/strlen/exists/dect/incr/setex | 需要計數(shù)的場景,?如?戶的訪問次數(shù)、熱點?章的點贊轉(zhuǎn)發(fā)數(shù)量等緩存限流分布式鎖分布式Session |
list 鏈表 雙向鏈表 | rpush/lpop實現(xiàn)隊列 rpush/rpop實現(xiàn)棧 lrange llen | 發(fā)布與訂閱或者說消息隊列、慢查詢,簡單隊列,關(guān)注列表時間軸 |
hash | hset,hmset,hexists,hget,hgetall,hkeys,hvals | 系統(tǒng)中對象數(shù)據(jù)的存儲。用戶信息,用戶主頁訪問量,組合查詢 |
set | sadd,spop,smembers,sismember,scard,sinterstore,sunion | 需要存放的數(shù)據(jù)不能重復(fù)以及需要獲取多個數(shù)據(jù)源交集和并集等場景,踩、贊、標(biāo)簽等 |
sorted set | zadd,zcard,zscore,zrange,zrevrange,zrem | 對數(shù)據(jù)根據(jù)某個權(quán)重進(jìn)?排序的場景 實時排?信息,排行榜,好友關(guān)系鏈表 |
拓展數(shù)據(jù)結(jié)構(gòu):
- 跳表
Redis使用跳表來實現(xiàn)有序集合和有序字典。跳表是一種基于鏈表的數(shù)據(jù)結(jié)構(gòu),它允許快速地查找元素,同時保
持了元素的順序。它是一種可以在O(log n)的時間復(fù)雜度下進(jìn)行查找、插入和刪除的數(shù)據(jù)結(jié)構(gòu)。
跳表中每個元素都包含一個鍵和一個值,元素按照鍵的大小來排序。在實際的應(yīng)用中,鍵通常是一個數(shù)字,而
值可以是任何數(shù)據(jù)類型。
跳表通過維護(hù)多層鏈表來加速查找。每一層鏈表的元素都是下一層鏈表中元素的子集,且每一層鏈表只保留一
部分元素。每一層鏈表都有一個指向下一層鏈表的指針,這些指針就是跳表的核心。
查找操作從頂層鏈表開始,沿著指針進(jìn)行跳躍,直到找到目標(biāo)元素或無法跳躍為止。由于每一層鏈表的元素
數(shù)量比下一層鏈表少得多,跳表的查詢時間復(fù)雜度平均為O(log n)。
插入和刪除操作也非常高效,只需要在跳表中找到插入或刪除位置,然后進(jìn)行簡單的指針更新操作即可。由于
跳表每層鏈表的元素數(shù)量被控制在一個較小的范圍內(nèi),插入和刪除操作的時間復(fù)雜度也平均為O(log n)。
總的來說,跳表是一種高效的數(shù)據(jù)結(jié)構(gòu),可以用于實現(xiàn)有序集合等需要快速查找、插入和刪除操作的場景。
在Redis中,跳表被廣泛用于實現(xiàn)有序集合和有序字典,成為了性能優(yōu)秀的鍵值對存儲方案之一。
- HyperLogLog
HyperLogLog是一種基數(shù)估計算法,用于統(tǒng)計大型數(shù)據(jù)集合中唯一元素的數(shù)量。它可以在極短的時間內(nèi)估算一
個數(shù)據(jù)集中不重復(fù)元素的數(shù)量,且誤差率非常小。這個算法的特別之處在于相對于傳統(tǒng)的基數(shù)算法,它占
用的內(nèi)存空間更少,適合處理海量數(shù)據(jù)。
HyperLogLog的估算算法比較簡單,它通過一些思維上巧妙的方法使用哈希函數(shù)對元素進(jìn)行映射和計算。
HyperLogLog主要由以下兩個步驟構(gòu)成:
- 使用哈希函數(shù)將每個元素映射成一個唯一的整數(shù);
- 統(tǒng)計哈希函數(shù)返回的整數(shù)中前導(dǎo)0的個數(shù)。
HyperLogLog使用多個哈希函數(shù),將一個元素映射成多個整數(shù)。這些整數(shù)被分組,每一組中的整數(shù)前綴0的
個數(shù)被統(tǒng)計。通過這些0的個數(shù)來估算數(shù)據(jù)集合中唯一元素的數(shù)量。這種方法可以有效地壓縮數(shù)據(jù),
并且是一種概率算法,誤差率可以控制在0.81%以內(nèi)。
在Redis中,HyperLogLog被廣泛應(yīng)用于統(tǒng)計用戶訪問、統(tǒng)計頁面瀏覽量、統(tǒng)計城市訪問量等場景中,可以
提供快速的基數(shù)估計功能。Redis的HyperLogLog可用命令包括PFADD、PFCOUNT、PFMERGE等。
- Geo
Geo是Redis提供的地理位置數(shù)據(jù)類型,支持對地理信息數(shù)據(jù)進(jìn)行存儲、查詢和計算。其可用命令包括GEOADD、
GEORADIUS、GEOHASH等。
Geo將地理位置數(shù)據(jù)表示為經(jīng)度和緯度坐標(biāo),并使用ZSET有序集合來存儲它們,其中ZSET的分?jǐn)?shù)用于排序位置。
這使得可以輕松地將數(shù)據(jù)存儲在Redis的內(nèi)存中,而且允許進(jìn)行地理位置搜索和聚合。
Geo提供了四個命令:GEOADD用于添加位置數(shù)據(jù)和信息,GEODIST用于計算兩個位置之間的距離,GEORADIUS
和GEORADIUSBYMEMBER用于以圓形或矩形方式搜索給定地理區(qū)域的位置,以及GEOHASH用于獲取給定位
置的GeoHash值。
在實際的應(yīng)用中,Geo通常用于社交網(wǎng)絡(luò)應(yīng)用、物流和分布式系統(tǒng)的位置路由、廣告投放、附近的人等場景。
可以使用Geo在Redis中實現(xiàn)簡單的位置索引,以提高相關(guān)數(shù)據(jù)的訪問效率和查詢效率。
- Sup/Sub
Sup和Sub是兩種不同的訂閱協(xié)議,它們用于在WebSocket中支持實時消息廣播和推送。
Sup是WebSocket的“發(fā)布-訂閱”(Publish-Subscribe)協(xié)議,它與傳統(tǒng)的HTTP請求/響應(yīng)模型不同,采用數(shù)據(jù)
流模式??蛻舳耸褂?span id="n5n3t3z" class="token class-name">Sup協(xié)議向服務(wù)器發(fā)送訂閱請求,服務(wù)器收到請求后訂閱相應(yīng)的數(shù)據(jù)源,并將數(shù)據(jù)源的
數(shù)據(jù)流發(fā)送給所有訂閱該數(shù)據(jù)源的客戶端。
Sub是WebSocket的“訂閱-發(fā)布”(Subscribe-Publish)協(xié)議,它與Sup協(xié)議相反。客戶端使用Sub協(xié)議向服務(wù)
器發(fā)送訂閱請求,服務(wù)器收到請求后創(chuàng)建一個數(shù)據(jù)源,然后將數(shù)據(jù)源的ID返回給客戶端。當(dāng)服務(wù)器有數(shù)據(jù)
可用時,會將數(shù)據(jù)發(fā)送到指定的數(shù)據(jù)源,然后所有訂閱該數(shù)據(jù)源的客戶端都會收到數(shù)據(jù)。
通過使用Sup和Sub協(xié)議,可以實現(xiàn)實時的消息廣播和推送功能,使得客戶端能夠接收到實時的數(shù)據(jù)更新和事件
通知。這些協(xié)議已經(jīng)被廣泛地應(yīng)用于實時聊天、在線游戲、數(shù)據(jù)可視化和監(jiān)控等應(yīng)用場景。
- Bitmap
Bitmap是一種特殊的序列化數(shù)據(jù)結(jié)構(gòu),它由多個二進(jìn)制位構(gòu)成,通常用于對大量的布爾型數(shù)據(jù)進(jìn)行存儲和操作。
在Redis中,Bitmap是一種內(nèi)置的數(shù)據(jù)結(jié)構(gòu),可以使用BITFIELD命令進(jìn)行操作。
位圖可以通過一個整數(shù)來表示,其中每一位對應(yīng)于某個對象的狀態(tài),比如0/1表示某個用戶是否在線,或者某個
產(chǎn)品是否存在庫存等。由于位圖的每個元素只占用一位二進(jìn)制空間,所以可以有效地壓縮存儲空間,
并且可以使用位運算來進(jìn)行高效的位操作。
在Redis中,Bitmap可用于多種應(yīng)用場景,包括統(tǒng)計在線用戶數(shù)、過濾無效訪問請求、記錄用戶行為、標(biāo)志
位圖等。比如,可以使用Bitmap來記錄一段時間內(nèi)所有訪問過網(wǎng)站的用戶,來統(tǒng)計日活躍用戶數(shù)和
月活躍用戶數(shù)。
使用Bitmap進(jìn)行數(shù)據(jù)操作的優(yōu)點是它所占用的內(nèi)存空間比傳統(tǒng)的數(shù)據(jù)類型更少,可以大大降低存儲成本,并且
支持快速的數(shù)據(jù)操作。然而它也存在一些局限性,比如它只能存儲布爾類型的值,如果需要存儲其他
類型的數(shù)據(jù),則需要進(jìn)行轉(zhuǎn)換。
7. Redis 單線程模型詳解
Redis 基于 Reactor 模式來設(shè)計開發(fā)了??的?套?效的事件處理模型
Redis中的?件事件處理器(file event handler)。 由于feh是單線程方式運行,一般說redis是單線程模式
Redis 通過IO 多路復(fù)?程序 來監(jiān)聽來?客戶端的?量連接(或者說是監(jiān)聽多個 socket),它會將感興趣的事件及類型(讀、寫)注冊到內(nèi)核中并監(jiān)聽每個事件是否發(fā)?。
I/O 多路復(fù)?技術(shù)的使?讓 Redis 不需要額外創(chuàng)建多余的線程來監(jiān)聽客戶端的?量連接,降低了資源的消耗(和 NIO 中的 Selector 組件很像)。
Redis 服務(wù)器是?個事件驅(qū)動程序,服務(wù)器需要處理兩類事件:** 1. ?件事件**; 2. 時間事件。
?件事件(客戶端進(jìn)?讀取寫?等操作,涉及?系列?絡(luò)通信)
?件事件處理器(file event handler)主要是包含 4 個部分:
- 多個 socket(客戶端連接)
- IO 多路復(fù)?程序(?持多個客戶端連接的關(guān)鍵)
- ?件事件分派器(將 socket 關(guān)聯(lián)到相應(yīng)的事件處理器)
- 事件處理器(連接應(yīng)答處理器、命令請求處理器、命令回復(fù)處理器)
8. Redis 沒有使?多線程? Redis6.0 之前為什么不使?多線程?
我覺得主要原因有下? 3 個:
- 單線程編程容易并且更容易維護(hù);
- Redis 的性能瓶頸不再 CPU ,主要在內(nèi)存和?絡(luò);
- 多線程就會存在死鎖、線程上下?切換等問題,甚?會影響性能。
9. Redis6.0 之后為何引?了多線程?
Redis6.0 引?多線程主要是為了提??絡(luò) IO 讀寫性能,因為這個算是 Redis 中的?個性能瓶頸(Redis 的瓶頸主要受限于內(nèi)存和?絡(luò))。
?絡(luò)數(shù)據(jù)的讀寫這類耗時操作上使?, 執(zhí)?命令仍然是單線程順序執(zhí)?。因此,你也不需要擔(dān)?線程安全問題。
Redis6.0 的多線程默認(rèn)是禁?的,只使?主線程。如需開啟需要修改 redis 配置?件 redis.conf ,開啟多線程后,還需要設(shè)置線程數(shù),否則是不?效的
10. Redis 給緩存數(shù)據(jù)設(shè)置過期時間有啥??
因為內(nèi)存是有限的,如果緩存中的所有數(shù)據(jù)都是?直保存的話,分分鐘直接Out of memory。緩解內(nèi)存消耗
Redis ?帶了給緩存數(shù)據(jù)設(shè)置過期時間的功能,比如 exp key 60 setex key 60 value
:::info
注意:Redis中除了字符串類型有??獨有設(shè)置過期時間的命令 setex 外,其他?法都需要依靠expire 命令來設(shè)置過期時間 。另外, persist 命令可以移除?個鍵的過期時間:
:::
業(yè)務(wù)場景: 某數(shù)據(jù)某段時間內(nèi)存在, 比如短信驗證碼1分鐘有效, 用戶登錄 token 一天內(nèi)有效 傳統(tǒng)數(shù)據(jù)庫處理自己判斷過期,更麻煩并且性能差很多
11. Redis是如何判斷數(shù)據(jù)是否過期的呢?
Redis 通過?個叫做過期字典(可以看作是hash表)來保存數(shù)據(jù)過期的時間。過期字典的鍵指向Redis數(shù)據(jù)庫中的某個key(鍵),過期字典的值是?個long long類型的整數(shù),這個整數(shù)保存了key所指向的數(shù)據(jù)庫鍵的過期時間(毫秒精度的UNIX時間戳)。
過期字典是存儲在redisDb這個結(jié)構(gòu)?的
12. 過期的數(shù)據(jù)的刪除策略了解么?
常?的過期數(shù)據(jù)的刪除策略就兩個(重要!??造緩存輪?的時候需要格外考慮的東?):
- 惰性刪除 :只會在取出key的時候才對數(shù)據(jù)進(jìn)?過期檢查。這樣對CPU最友好,但是可能會造成太多過期 key 沒有被刪除。
-
定期刪除 : 每隔?段時間抽取?批 key 執(zhí)?刪除過期key操作。對內(nèi)存更友好. 并且,Redis 底層會通過限制刪除操作執(zhí)?的時?和頻率來減少刪除操作對CPU時間的影響。
Redis 采?的是 定期刪除+惰性/懶漢式刪除 。
Redis 內(nèi)存淘汰機制(存在漏網(wǎng)之key)
13. Redis 內(nèi)存淘汰機制了解么?
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 版本后增加以下兩種:
7. volatile-lfu(least frequently used):從已設(shè)置過期時間的數(shù)據(jù)集(server.db[i].expires)中
挑選最不經(jīng)常使?的數(shù)據(jù)淘汰
8. allkeys-lfu(least frequently used):當(dāng)內(nèi)存不?以容納新寫?數(shù)據(jù)時,在鍵空間中,移
除最不經(jīng)常使?的 key
數(shù)據(jù)刪除和淘汰策略小結(jié)
14. Redis 持久化機制
Redis 的?種持久化叫快照(snapshotting,RDB),另?種是只追加?件(append-only file, AOF)
快照(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)采?的持久化?式,在 Redis.conf 配置?件中默認(rèn)有此下配置:
save 900 1 #在900秒(15分鐘)之后,如果?少有1個key發(fā)?變化,Redis就會?動觸發(fā)BGSAVE命令創(chuàng)建快照。
save 300 10 #在300秒(5分鐘)之后,如果?少有10個key發(fā)?變化,Redis就會?動觸發(fā)BGSAVE命令創(chuàng)建快照。
save 60 10000 #在60秒(1分鐘)之后,如果?少有10000個key發(fā)?變化,Redis就會?動觸發(fā)BGSAVE命令創(chuàng)建快照。
AOF(append-only file)持久化
與快照持久化相?,AOF 持久化 的實時性更好,因此已成為主流的持久化?案。默認(rèn)情況下
Redis 沒有開啟 AOF(append only file)?式的持久化,可以通過 appendonly 參數(shù)開啟:
appendonly yes
appendfsync always #每次有數(shù)據(jù)修改發(fā)?時都會寫?AOF?件,這樣會嚴(yán)重降低Redis的速度
appendfsync everysec #每秒鐘同步?次,顯示地將多個寫命令同步到硬盤
appendfsync no #讓操作系統(tǒng)決定何時進(jìn)?同步
開啟 AOF 持久化后每執(zhí)??條會更改 Redis 中的數(shù)據(jù)的命令,Redis 就會將該命令寫?硬盤中的 AOF ?件。AOF ?件的保存位置和 RDB ?件的位置相同,都是通過 dir 參數(shù)設(shè)置的,默認(rèn)的?件名是 appendonly.aof。
為了兼顧數(shù)據(jù)和寫?性能,?戶可以考慮 appendfsync everysec 選項 ,讓 Redis 每秒同步?次AOF ?件,Redis 性能?乎沒受到任何影響。?且這樣即使出現(xiàn)系統(tǒng)崩潰,?戶最多只會丟失?秒之內(nèi)產(chǎn)?的數(shù)據(jù)。當(dāng)硬盤忙于執(zhí)?寫?操作的時候,Redis 還會優(yōu)雅的放慢??的速度以便適應(yīng)硬盤的最?寫?速度。
:::info
- redis4.0 支持 混合持久化 (默認(rèn)關(guān)閉,通過配置項 aof-use-rdb-preamble 開啟)此時,AOF 重寫的時候就直接把 RDB 的內(nèi)容寫到 AOF ?件開頭,快速加載同時避免丟失過多的數(shù)據(jù)。但是這里rdb部分是壓縮格式不再是aof格式,可讀性差
- AOF 重寫可以產(chǎn)??個新的 AOF ?件,這個新的 AOF ?件和原有的 AOF ?件所保存的數(shù)據(jù)庫狀態(tài)?樣,但體積更?。
AOF重寫功能是通過讀取DB的鍵值對實現(xiàn),程序無需對現(xiàn)有AOF文件進(jìn)行任何讀入,分析或?qū)懭氩僮?br> 在執(zhí)? BGREWRITEAOF 命令時,Redis 服務(wù)器會維護(hù)?個 AOF 重寫緩沖區(qū),該緩沖區(qū)會在?進(jìn)程創(chuàng)建新 AOF ?件期間,記錄服務(wù)器執(zhí)?的所有寫命令。當(dāng)?進(jìn)程完成創(chuàng)建新 AOF ?件的?作之后,服務(wù)器會將重寫緩沖區(qū)中的所有內(nèi)容追加到新 AOF ?件的末尾,使得新舊兩個 AOF ?件所保存的數(shù)據(jù)庫狀態(tài)?致。最后,服務(wù)器?新的 AOF ?件替換舊的 AOF ?件,以此來完成AOF ?件重寫操作
:::
RDB(redis database backup file)Redis數(shù)據(jù)備份文件
AOF append only file (追加文件)
15. Redis 事務(wù)
Redis事務(wù)提供了?種將多個命令請求打包的功能。然后,再按順序執(zhí)?打包的所有命令,并且不會被中途打斷。
Redis 是不?持 roll back 的,因?不滿?原?性的(?且不滿?持久性)
Redis 可以通過 MULTI,EXEC,DISCARD 和 WATCH 等命令來實現(xiàn)事務(wù)(transaction)功能。用 MULTI命令后可以輸?多個命令。Redis不會?即執(zhí)?這些命令,?是將它們放到隊列,當(dāng)調(diào)?了EXEC命令將執(zhí)?所有命令。
16. 緩存穿透
緩存穿透是 查詢一個一定不存在的數(shù)據(jù),如果從存儲層查不到數(shù)據(jù)則不寫入緩存,將導(dǎo)致這個不存在的數(shù)據(jù)每次請求直接落到DB,可能導(dǎo)致DB掛掉.這種情況大概率是遭到攻擊。
解決通常用布隆過濾器
布隆過濾器主要用于檢索一個元素是否在集合中,可使用 Redisson 實現(xiàn)布隆過濾器。
它的底層先初始化一個比較大數(shù)組,里面存放二進(jìn)制0或1,一開始都是0,當(dāng)一個key來了經(jīng)過三次hash計算,取模數(shù)組長度找數(shù)據(jù)下標(biāo)然后將數(shù)組的0改為1,這樣的話,三個數(shù)組位置可標(biāo)明一個key的存在,查找過程一樣
當(dāng)然它的缺點就是可能會發(fā)生誤判,但可以設(shè)置誤判率,大概不超過5%;因為誤判必然存在,不然就得增加數(shù)組長度,其實5個點的誤判率一般項目可以接收,不至于高并發(fā)下壓倒數(shù)據(jù)庫
緩存穿透說簡單點就是?量請求的 key 根本不存在于緩存中,導(dǎo)致請求直接到了數(shù)據(jù)庫上,根本沒有經(jīng)過緩存這?層。
?先做好參數(shù)校驗,?些不合法的參數(shù)請求直接拋出異常信息返回給客戶端
1)緩存?效 key
如果緩存和數(shù)據(jù)庫都查不到某個 key 的數(shù)據(jù)就寫?個到 Redis 中去并設(shè)置過期時間,具體命令如下: SET key value EX 10086 。這種?式可以解決請求的 key 變化不頻繁的情況, 盡量將?效的 key 的過期時間設(shè)置短?點?如 1 分鐘
public Object getObjectInclNullById(Integer id) {
// 從緩存中獲取數(shù)據(jù)
Object cacheValue = cache.get(id);
// 緩存為空
if (cacheValue == null) {
// 從數(shù)據(jù)庫中獲取
Object storageValue = storage.get(key);
// 緩存空對象
cache.set(key, storageValue);
// 如果存儲數(shù)據(jù)為空,需要設(shè)置?個過期時間(300秒)
if (storageValue == null) {
// 必須設(shè)置過期時間,否則有被攻擊的?險
cache.expire(key, 60 * 5);
}
return storageValue;
}
return cacheValue;
}
2)布隆過濾器
布隆過濾器是?個?常神奇的數(shù)據(jù)結(jié)構(gòu),通過它我們可以?常?便地判斷?個給定數(shù)據(jù)是否存在于海量數(shù)據(jù)中。我們需要的就是判斷 key 是否合法,有沒有感覺布隆過濾器就是我們想要找的那個“?”。
布隆過濾器說某個元素存在,?概率會誤判。布隆過濾器說某個元素不在,那么這個元素?定不在。
當(dāng)?個元素加?布隆過濾器中的時候,會進(jìn)?哪些操作:
- 使?布隆過濾器中的哈希函數(shù)對元素值進(jìn)?計算,得到哈希值(有?個哈希函數(shù)得到?個哈希值)。
- 根據(jù)得到的哈希值,在位數(shù)組中把對應(yīng)下標(biāo)的值置為 1。
當(dāng)我們需要判斷?個元素是否存在于布隆過濾器的時候,會進(jìn)?哪些操作: - 對給定元素再次進(jìn)?相同的哈希計算;
- 得到值之后判斷位數(shù)組中的每個元素是否都為 1,如果值都為 1,那么說明這個值在布隆過濾器中,如果存在?個值不為 1,說明該元素不在布隆過濾器中。
不同的字符串可能哈希出來的位置相同。 (可以適當(dāng)增加位數(shù)組??或者調(diào)整我們的哈希函數(shù)來降低概率)
17.緩存擊穿
緩存擊穿是對于設(shè)置過期時間的key,緩存在某時間點過期時,恰好這個時間點對于這個key有大量并發(fā)請求過來,這些請求發(fā)現(xiàn)緩存過期一般從后端DB加載數(shù)據(jù)并回設(shè)到緩存,這個時候大并發(fā)的i請求可能會瞬間把DB壓垮
解決方式:第一種用互斥鎖,當(dāng)緩存失效,不是立即load DB,先使用Redis的setnx設(shè)置一個互斥鎖,操作成功返回時再進(jìn)行l(wèi)oad DB操作并回設(shè)緩存,否則重試get緩存的方法
第二種設(shè)置當(dāng)前key邏輯過期: ①設(shè)置key時候,設(shè)置一個過期時間字段一塊存緩存,不給當(dāng)前key設(shè)置過期時間;②查詢時候,從redis取出數(shù)據(jù)后判斷時間上是否過期;③如果過期開通另一個線程進(jìn)行數(shù)據(jù)同步,當(dāng)前線程正常返回數(shù)據(jù),這個數(shù)據(jù)不是最新;
如果選擇數(shù)強一致性,建議用分布式鎖,性能沒那么高,鎖需要等,也可能產(chǎn)生死鎖問題;如果選擇key邏輯刪除,優(yōu)先考慮高可用性,性能比較高,但數(shù)據(jù)同步做不到強一致性;
18. 緩存雪崩
緩存雪崩是設(shè)置緩存時用了相同的過期時間,導(dǎo)致緩存在某一時刻同時失效,請求全部轉(zhuǎn)發(fā)到DB,DB瞬時壓力過重雪崩,與緩存擊穿區(qū)別,雪崩是很多Key,擊穿是某一個key緩存
解決方案一可以將緩存失效時間分散開,比如在原有失效時間基礎(chǔ)上加一個隨機值,比如1-5分鐘隨機,這樣每一個緩存過期時間重復(fù)率會降低,很難引發(fā)集體失效事件(給不同key的TTL添加隨機值)
其它可考慮用Redis集群提高服務(wù)可用性(哨兵模式,集群);
給緩存業(yè)務(wù)添加降級限流策略(nginx,或gateway)
給業(yè)務(wù)添加多級緩存 (Guava或Caffeine)
緩存在同?時間??積的失效,后?的請求都直接落到了數(shù)據(jù)庫上,造成數(shù)據(jù)庫短時間內(nèi)承受?量請求。
舉個例?:系統(tǒng)的緩存模塊出了問題?如宕機導(dǎo)致不可?。造成系統(tǒng)的所有訪問,都要?數(shù)據(jù)庫。
有?些被?量訪問數(shù)據(jù)(熱點緩存)在某?時刻??積失效,導(dǎo)致對應(yīng)的請求直接落到了數(shù)據(jù)庫上。
數(shù)據(jù)庫的壓?可想?知,可能直接就被這么多請求弄宕機
針對 Redis 服務(wù)不可?的情況:
- 采? Redis 集群,避免單機出現(xiàn)問題整個緩存服務(wù)都沒辦法使?。
- 限流,避免同時處理?量的請求。
針對熱點緩存失效的情況: - 設(shè)置不同的失效時間?如隨機設(shè)置緩存的失效時間。
- 緩存永不失效。
19.緩存三兄弟小結(jié)
<緩存三兄弟>
穿透不中生有key , 布隆過濾null隔離;
緩存擊穿過期key , 鎖與非期解難題;
雪崩大量過期key , 過期時間要隨機;
面試必考三兄弟 , 可用限流來保底;
20. 如何保證緩存和數(shù)據(jù)庫數(shù)據(jù)的?致性?
Cache Aside Pattern(旁路緩存模式)
遇到寫請求是這樣的:更新 DB,然后直接刪除 cache 。
如果更新數(shù)據(jù)庫成功,?刪除緩存這?步失敗的情況的話,簡單說兩個解決?案:
- 緩存失效時間變短(不推薦,治標(biāo)不治本) :我們讓緩存數(shù)據(jù)的過期時間變短,這樣的話緩存就會從數(shù)據(jù)庫中加載數(shù)據(jù)。另外,這種解決辦法對于先操作緩存后操作數(shù)據(jù)庫的場景不適?。
-
增加cache更新重試機制(常?): 如果 cache 服務(wù)當(dāng)前不可?導(dǎo)致緩存刪除失敗的話,我們就隔?段時間進(jìn)?重試,重試次數(shù)可以??定。如果多次重試還是失敗的話,我們可以把當(dāng)前更新失敗的 key 存?隊列中,等緩存服務(wù)可?之后,再將 緩存中對應(yīng)的 key 刪除即可。
關(guān)鍵詞 : 雙寫一致
- 比如業(yè)務(wù):當(dāng)時將文章的熱點數(shù)據(jù)存入緩存,雖然是熱點數(shù)據(jù),但是實時要求行并不是特別高,所以,當(dāng)時采用的是異步方案來同步數(shù)據(jù);
- 再比如業(yè)務(wù):當(dāng)時將搶券的庫存存入到緩存中,這個需要實時進(jìn)行數(shù)據(jù)同步,為保證數(shù)據(jù)強一致性,當(dāng)時采用的是 redisson提供的讀寫鎖保證數(shù)據(jù)同步
方案介紹:
允許延時一致的業(yè)務(wù),采用異步通知
①用MQ中間件,更新數(shù)據(jù)后,通知緩存刪除
②用canal中間件,不用改業(yè)務(wù)代碼,偽裝為 mysql 的一個從節(jié)點,canal通過讀取 binlog數(shù)據(jù)更新緩存
強一致,用 Redisson 提供的讀寫鎖
①共享鎖:讀取readLock,加鎖之后,其它線程可以共享讀操作;
②排他鎖:獨占鎖writeLock寫鎖,加鎖之后,阻塞其它線程讀寫操作
21.Redis 分布式鎖
// 利用 hash 結(jié)構(gòu)記錄線程id和重入次數(shù)
// key是somelock value是對象{field:t1 value:1}
public void add1(){
RLock lock = lock.tryLock();
boolean isLock = lock.tryLock();
// 執(zhí)行業(yè)務(wù)
add2();
// 釋放鎖
lock.unlock();
}
public void add2(){
RLock lock = redissonClient.getLock("somelock");
boolean isLock = lock.tryLock();
// 執(zhí)行業(yè)務(wù)
// 釋放鎖
lock.unlock();
}
Q: redis分布式鎖如何實現(xiàn)?
A: redis中提供一個命令 setnx (set if not exists); 由于redis單線程, 用命令之后, 只有一個客戶端對某一個Key設(shè)置值, 在沒有過期或刪除key的時候,其他客戶端不能設(shè)置這個Key
Q : 有效時長
A: 的確,redis的setnx指令不太好控制這個問題,采用redis的一個框架 redisson 實現(xiàn)的
在redisson 中需要手動加鎖,并且可以控制鎖失效時間和等待時間, 當(dāng)鎖住的一個業(yè)務(wù)還沒執(zhí)行完時, redisson 引入一個看門狗機制, 就是每隔一段時間檢查當(dāng)前業(yè)務(wù)是否持有鎖,如果持有就增加鎖的持有時間, 當(dāng)業(yè)務(wù)執(zhí)行完以后需要使用釋放鎖就行
還有一個好處,高并發(fā)下,業(yè)務(wù)可能執(zhí)行很快,先 客戶1 持有鎖時候,客戶2來以后不會馬上拒絕,會自旋 不斷嘗試獲取鎖,如果客戶1釋放, 客戶2就可以馬上持有鎖,性能也得到提升
Q: 可重入嗎?
A: 可以重入,這樣做為避免死鎖產(chǎn)生, 這個重入在內(nèi)部判斷是否當(dāng)前線程持有的鎖,如果是當(dāng)前線程持有的鎖就會計數(shù),如果釋放鎖就會在計算上減一,在存儲數(shù)據(jù)時候采用hash結(jié)構(gòu),大key可按照自己業(yè)務(wù)進(jìn)行定制,其中小Key 是當(dāng)前線程唯一標(biāo)識, value是當(dāng)前線程重入次數(shù)
Q: 主從一致性問題
A: 不能, 比如: 當(dāng)線程1加鎖成功,master節(jié)點數(shù)會異步復(fù)制到slave節(jié)點, 此時當(dāng)前持有redis鎖的master 節(jié)點宕機, slave節(jié)點被提升為新master節(jié)點, 加入現(xiàn)在來了一個線程2
22.主從數(shù)據(jù)同步原理
哨兵作用
Q : 保證Redis 的高并發(fā)高可用?
A : 哨兵模式,實現(xiàn)主從集群的自動故障恢復(fù)(監(jiān)控/自動故障恢復(fù)/通知)
首先搭建主從集群,加上redis的哨兵模式,它可以實現(xiàn)主從集群的自動故障恢復(fù),里面包括對主從服務(wù)的監(jiān)控,自動故障恢復(fù)和通知;如果master故障,Sentinel會將一個從提升為主,即使故障實例恢復(fù)也以新的master為主;同時Sentinel也扮演Redis客戶端服務(wù)發(fā)現(xiàn)來源,當(dāng)集群故障轉(zhuǎn)移時,會將最新信息推送給Redis客戶端,所以一般項目采用哨兵模式保證redis高并發(fā)高可用
Q:redis是哪種集群
A: 主從(1主1從)+哨兵就可以,單節(jié)點不超過10G內(nèi)存,如果Redis內(nèi)存不足可以給不同服務(wù)分配獨立的Redis主從節(jié)點
用主從(1主1從)加哨兵,一般單節(jié)點不超過10G內(nèi)存,如果內(nèi)存不足可以給不同服務(wù)分配獨立的Redis主從節(jié)點。盡量不做分片集群,因為集群維護(hù)起來比較麻煩,并且集群間心跳檢測和數(shù)據(jù)通信會消耗大量網(wǎng)絡(luò)帶寬,也沒辦法使用lua腳本和事務(wù)
Q:腦裂怎么解決
集群腦裂由于主節(jié)點和從節(jié)點和sentinel處于不同網(wǎng)絡(luò)分區(qū),然后未心跳感知到主節(jié)點,所以通過選舉方式提升一個從節(jié)點為主,這樣存在2個master,像大腦分裂一樣,導(dǎo)致客戶端還在老的主節(jié)點那寫數(shù)據(jù),新節(jié)點無法同步數(shù)據(jù),網(wǎng)絡(luò)恢復(fù)后,sentinel會將老的主節(jié)點降為從節(jié)點,這是再從新master同步數(shù)據(jù),就會導(dǎo)致數(shù)據(jù)丟失;
解決: 可以修改redis配置,設(shè)置最少的從節(jié)點數(shù)量以及縮短主從數(shù)據(jù)同步的延遲時間。達(dá)不到要求就拒絕請求,可以避免大量數(shù)據(jù)丟失;
由于網(wǎng)絡(luò)等原因可能會出現(xiàn),那就是master和salve和sentinel處于不同網(wǎng)絡(luò)分區(qū),使得sentinel沒有能夠心跳感知到master,所以通過選舉方式提升一個salve為master,這樣存在2個主。像大腦分裂一樣,會導(dǎo)致客戶端還在老主節(jié)點那寫數(shù)據(jù),新節(jié)點無法同步數(shù)據(jù),當(dāng)網(wǎng)絡(luò)恢復(fù)后,sentinel會將老的主降為salve,這時再從新master同步數(shù)據(jù),導(dǎo)致老主的大量數(shù)據(jù)丟失;
在redis配置中可以設(shè)置,第一設(shè)置最少的salve節(jié)點個數(shù),比如設(shè)置至少要有一個從節(jié)點才能同步數(shù)據(jù),第二個可以設(shè)置主從數(shù)據(jù)復(fù)制和同步延遲時間,達(dá)不到要求就拒絕請求,可以避免大量數(shù)據(jù)丟失;
分片集群結(jié)構(gòu)
I/O多路復(fù)用模型
IO多路復(fù)用
不過監(jiān)聽socket的方式,通知的方式有多種實現(xiàn)文章來源:http://www.zghlxwxcb.cn/news/detail-470569.html
- select poll 只通知用戶進(jìn)程有socket就緒,但不確定具體的socket,需要用戶逐個遍歷確認(rèn)
- epoll 通知用戶進(jìn)程Socket就緒的同時,把已經(jīng)就緒的Socket寫入用戶空間.
文章來源地址http://www.zghlxwxcb.cn/news/detail-470569.html
到了這里,關(guān)于第四章--Redis基礎(chǔ)知識和面試題的文章就介紹完了。如果您還想了解更多內(nèi)容,請在右上角搜索TOY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!