有關(guān)Redis之前有單獨寫過幾篇文章
Redis緩存穿透凝危、擊穿您单、雪崩踢代,數(shù)據(jù)庫與緩存一致性
談?wù)凴edis五種數(shù)據(jù)結(jié)構(gòu)及真實應(yīng)用場景
怎么實現(xiàn)Redis的高可用兔乞?(主從、哨兵凄敢、集群)
之所以單獨寫碌冶,是因為這幾塊內(nèi)容比較大,而且也很重要涝缝,所以想寫的詳細點扑庞、深入點,讓大家在理解的基礎(chǔ)上記住它們,這讓就不容易忘記拒逮。
所以有關(guān)以上相關(guān)的面試題就不再單獨整理了,具體可以看相關(guān)文章罐氨。
- redis是什么?
- Redis為什么這么快?
- 為什么Redis 6.0之后改多線程呢滩援?
- 你了解Redis的過期策略嗎?
- 聊聊Redis內(nèi)存淘汰策略?
- Redis事務(wù)機制是怎樣的?
- 說說Redis哈希槽的概念栅隐?
- 為什么Redis Cluster會設(shè)計成16384個槽呢?
- Redis在集群中查找key的時候,是怎么定位到具體節(jié)點的?
- Redis底層使用的什么協(xié)議?
- Redis的Hash沖突怎么辦?
- Redis相比memcached有哪些優(yōu)勢租悄?
- 有哪些辦法可以降低 Redis 的內(nèi)存使用情況呢谨究?
- Redis中獲取海量數(shù)據(jù)的正確操作方式?
- 如何使用Redis的性能更高泣棋?
- 如何解決Redis的并發(fā)競爭Key問題胶哲?
- 使用過Redis做異步隊列么,你是怎么用的潭辈?
- 用Redis做過延時隊列嗎? 具體應(yīng)該怎么實現(xiàn)鸯屿?
- 使用Redis統(tǒng)計網(wǎng)站的UV,應(yīng)該怎么做萎胰?
- 什么是熱Key問題,如何解決熱key問題棚辽?
1技竟、redis是什么
Redis是C語言開發(fā)的一個開源的高性能鍵值對(key-value)的內(nèi)存數(shù)據(jù)庫,它是一種NoSQL(泛指非關(guān)系型)的數(shù)據(jù)庫屈藐。
與MySQL數(shù)據(jù)庫不同的是榔组,Redis的數(shù)據(jù)是存在內(nèi)存中的。它的讀寫速度非沉撸快搓扯,每秒支持并發(fā)10W QPS。因此Redis被廣泛應(yīng)用于緩存包归,另外锨推,Redis也經(jīng)常用來做分布式鎖,也可以用來做消息中間件等。
除此之外公壤,Redis還支持事務(wù)换可、持久化、LUA腳本厦幅、多種集群方案沾鳄。
2、Redis為什么這么快确憨?
1) 完全基于內(nèi)存存儲實現(xiàn)
完全基于內(nèi)存译荞,絕大部分請求是純粹的內(nèi)存操作,非承萜快速吞歼。數(shù)據(jù)存在內(nèi)存中,類似于HashMap塔猾,HashMap的優(yōu)勢就是查找和操作的時間復(fù)雜度都是O(1)浆熔;
2) 合理的數(shù)據(jù)編碼
Redis 支持多種數(shù)據(jù)數(shù)據(jù)類型,每種基本類型可能對多種數(shù)據(jù)編碼。什么時候,使用什么樣數(shù)據(jù)類型,使用什么樣編碼医增,是redis設(shè)計者總結(jié)優(yōu)化的結(jié)果慎皱。
3) 單線程模型
Redis是單線程模型的,而單線程避免了CPU不必要的上下文切換和競爭鎖的消耗叶骨。也正因為是單線程茫多,如果某個命令執(zhí)行過長(如keys,hgetall命令),會造成排隊阻塞。
Redis 6.0 引入了多線程提速忽刽,它的執(zhí)行命令操作內(nèi)存的仍然是個單線程的天揖。
4) 合理的線程模型
使用多路I/O復(fù)用模型,非阻塞IO跪帝;
多路I/O復(fù)用技術(shù)可以讓單個線程高效的處理多個連接請求今膊,而Redis使用epoll作為I/O多路復(fù)用技術(shù)的實現(xiàn)。并且伞剑,Redis自身的事件處理模型將epoll中的連接斑唬、讀寫、關(guān)閉都轉(zhuǎn)換為事件黎泣,不在網(wǎng)絡(luò)I/O上浪費過多的時間恕刘。
3、為什么Redis 6.0 之后改多線程呢抒倚?
Redis6.0之前褐着,Redis在處理客戶端的請求時,包括讀socket托呕、解析含蓉、執(zhí)行、寫socket等都由一個順序串行的主線程處理项郊,這就是所謂的“單線程”谴餐。
redis使用多線程并非是完全摒棄單線程,redis還是使用單線程模型來處理客戶端的請求呆抑,只是使用多線程來處理數(shù)據(jù)的讀寫和協(xié)議解析岂嗓,執(zhí)行命令還是使用單線程。
這樣做的目的是因為redis的性能瓶頸在于網(wǎng)絡(luò)IO而非CPU鹊碍,使用多線程能提升IO讀寫的效率厌殉,從而整體提高redis的性能。
4侈咕、你了解Redis的過期策略嗎?
我們在set key的時候公罕,可以給它設(shè)置一個過期時間,比如expire key 60耀销。指定這key 60s后過期楼眷,60s后redis是如何處理的?我們先來介紹幾種過期策略:
定時過期
每個設(shè)置過期時間的key都需要創(chuàng)建一個定時器,到過期時間就會立即對key進行清除罐柳。該策略可以立即清除過期的數(shù)據(jù)掌腰,對內(nèi)存很友好;但是會占用大量的CPU資源去處理過期的數(shù)據(jù)张吉,從而影響緩存的響應(yīng)時間和吞吐量齿梁。
惰性過期
只有當(dāng)訪問一個key時,才會判斷該key是否已過期肮蛹,過期則清除勺择。該策略可以最大化地節(jié)省CPU資源,卻對內(nèi)存非常不友好伦忠。極端情況可能出現(xiàn)大量的過期key沒有再次被訪問省核,從而不會被清除,占用大量內(nèi)存昆码。
定期過期
每隔一定的時間气忠,會掃描一定數(shù)量的數(shù)據(jù)庫的expires字典中一定數(shù)量的key,并清除其中已過期的key未桥。該策略是前兩者的一個折中方案笔刹。通過調(diào)整定時掃描的時間間隔和每次掃描的限定耗時芥备,可以在不同情況下使得CPU和內(nèi)存資源達到最優(yōu)的平衡效果冬耿。
Redis中同時使用了惰性過期
和定期過期
兩種過期策略。
假設(shè)Redis當(dāng)前存放30萬個key萌壳,并且都設(shè)置了過期時間亦镶,如果你每隔100ms就去檢查這全部的key,CPU負載會特別高袱瓮,最后可能會掛掉缤骨。
因此,redis采取的是定期過期尺借,每隔100ms就隨機抽取一定數(shù)量的key來檢查和刪除的绊起。
但是呢,最后可能會有很多已經(jīng)過期的key沒被刪除燎斩。這時候虱歪,redis采用惰性刪除。在你獲取某個key的時候栅表,redis會檢查一下笋鄙,這個key如果設(shè)置了過期時間并且已經(jīng)過期了,此時就會刪除怪瓶。
但是呢萧落,如果定期刪除漏掉了很多過期的key,然后也沒走惰性刪除。就會有很多過期key積在內(nèi)存中找岖,直接會導(dǎo)致內(nèi)存爆的陨倡。或者有些時候宣增,業(yè)務(wù)量大起來了玫膀,redis的key被大量使用,內(nèi)存直接不夠了爹脾,
這個時候就需要內(nèi)存淘汰策略來保護自己了帖旨。
5、聊聊Redis內(nèi)存淘汰策略?
redis有8種內(nèi)存淘汰策略灵妨。
- volatile-lru:當(dāng)內(nèi)存不足以容納新寫入數(shù)據(jù)時解阅,從設(shè)置了過期時間的key中使用LRU(最近最少使用)算法進行淘汰;
- allkeys-lru:當(dāng)內(nèi)存不足以容納新寫入數(shù)據(jù)時泌霍,從所有key中使用LRU(最近最少使用)算法進行淘汰货抄;
- volatile-lfu:4.0版本新增,當(dāng)內(nèi)存不足以容納新寫入數(shù)據(jù)時朱转,在過期的key中蟹地,使用LFU算法進行刪除key;
- allkeys-lfu:4.0版本新增藤为,當(dāng)內(nèi)存不足以容納新寫入數(shù)據(jù)時怪与,從所有key中使用LFU算法進行淘汰;
- volatile-random:當(dāng)內(nèi)存不足以容納新寫入數(shù)據(jù)時缅疟,從設(shè)置了過期時間的key中分别,隨機淘汰數(shù)據(jù);
- allkeys-random:當(dāng)內(nèi)存不足以容納新寫入數(shù)據(jù)時存淫,從所有key中隨機淘汰數(shù)據(jù)耘斩;
- volatile-ttl:當(dāng)內(nèi)存不足以容納新寫入數(shù)據(jù)時,在設(shè)置了過期時間的key中桅咆,根據(jù)過期時間進行淘汰括授,越早過期的優(yōu)先被淘汰;
- noeviction:默認策略岩饼,當(dāng)內(nèi)存不足以容納新寫入數(shù)據(jù)時荚虚,新寫入操作會報錯;
6忌愚、聊聊Redis事務(wù)機制?
Redis通過MULTI曲管、EXEC、WATCH
等一組命令集合硕糊,來實現(xiàn)事務(wù)機制院水。事務(wù)支持一次執(zhí)行多個命令腊徙,一個事務(wù)中所有命令都會被序列化。在事務(wù)執(zhí)行過程檬某,會按照順序串行化執(zhí)行隊列中的命令撬腾,其他客戶端提交的命令請求不會插入到事務(wù)執(zhí)行命令序列中。
簡言之恢恼,Redis事務(wù)就是順序性民傻、一次性、排他性的執(zhí)行一個隊列中的一系列命令场斑。
Redis執(zhí)行事務(wù)的流程如下:
- 開始事務(wù)(MULTI)
- 命令入隊
- 執(zhí)行事務(wù)(EXEC)漓踢、撤銷事務(wù)(DISCARD )
命令 | 描述 |
---|---|
EXEC | 執(zhí)行所有事務(wù)塊內(nèi)的命令 |
DISCARD | 取消事務(wù),放棄執(zhí)行事務(wù)塊內(nèi)的所有命令 |
MULTI | 標記一個事務(wù)塊的開始 |
UNWATCH | 取消 WATCH 命令對所有 key 的監(jiān)視漏隐。 |
WATCH | 監(jiān)視key 喧半,如果在事務(wù)執(zhí)行之前,該key 被其他命令所改動青责,那么事務(wù)將被打斷挺据。 |
有關(guān)redis事務(wù)需要注意的就是
1)與mysql中事務(wù)不同,在redis事務(wù)遇到執(zhí)行錯誤的時候脖隶,不會進行回滾扁耐,而是簡單的放過了,并保證其他的命令正常執(zhí)行(所以說redis的事務(wù)并不是保證原子性)产阱。
2)當(dāng)事務(wù)的執(zhí)行過程中婉称,如果redis意外的掛了。很遺憾只有部分命令執(zhí)行了心墅,后面的也就被丟棄了酿矢。
7榨乎、說說Redis哈希槽的概念怎燥?
Redis 集群沒有使用一致性 hash,而是引入了哈希槽的概念,Redis 集群有16384
個哈希槽蜜暑,每個 key 通過 CRC16
校驗后對 16384 取模來決定放置哪個槽铐姚,集群的每個節(jié)點負責(zé)一部分 hash 槽。
使用哈希槽的好處就在于可以方便的添加或移除節(jié)點肛捍。這種結(jié)構(gòu)無論是添加刪除或者修改某一個節(jié)點隐绵,都不會造成集群不可用的狀態(tài)。
當(dāng)需要增加節(jié)點時拙毫,只需要把其他節(jié)點的某些哈希槽挪到新節(jié)點就可以了依许;
當(dāng)需要移除節(jié)點時,只需要把移除節(jié)點上的哈希槽挪到其他節(jié)點就行了缀蹄;
在這一點上揍庄,我們以后新增或移除節(jié)點的時候不用先停掉所有的 redis 服務(wù)。
8翼岁、為什么RedisCluster會設(shè)計成16384個槽呢?
2的14次方就是16384,這個當(dāng)然不說一定要設(shè)計成16384個槽,作者對這個也做了解釋烟具。
地址如下: https://github.com/antirez/redis/issues/2576
1) 如果槽位為65536,發(fā)送心跳信息的消息頭達8k钉凌,發(fā)送的心跳包過于龐大。
如上所述,在消息頭中脊岳,當(dāng)槽位為65536時,這塊的大小是: 65536÷8÷1024=8kb 因為每秒鐘垛玻,redis節(jié)點需要發(fā)送一定數(shù)量的ping消息作為心跳包割捅,如果槽位為65536,這個ping消息的消息頭太大了帚桩,浪費帶寬棺牧。
2) redis的集群主節(jié)點數(shù)量基本不可能超過1000個。
如上所述朗儒,集群節(jié)點越多颊乘,心跳包的消息體內(nèi)攜帶的數(shù)據(jù)越多。如果節(jié)點過1000個醉锄,也會導(dǎo)致網(wǎng)絡(luò)擁堵乏悄。因此redis作者,不建議redis cluster節(jié)點數(shù)量超過1000個恳不。 那么檩小,對于節(jié)點數(shù)在1000以內(nèi)的redis cluster集群,16384個槽位夠用了烟勋。沒有必要拓展到65536個规求。
3) 槽位越小,節(jié)點少的情況下卵惦,壓縮率高
Redis主節(jié)點的配置信息中阻肿,它所負責(zé)的哈希槽是通過一張bitmap的形式來保存的,在傳輸過程中沮尿,會對bitmap進行壓縮丛塌,但是如果bitmap的填充率slots / N很高的話(N表示節(jié)點數(shù)),bitmap的壓縮率就很低畜疾。
如果節(jié)點數(shù)很少赴邻,而哈希槽數(shù)量很多的話,bitmap的壓縮率就很低啡捶。
9姥敛、Redis在集群中查找key的時候,是怎么定位到具體節(jié)點的瞎暑?
使用CRC16算法對key進行hash,再將hash值對16384取模彤敛,得到具體的槽位根據(jù)節(jié)點和槽位的映射信息(與集群建立連接后忿偷,客戶端可以取得槽位映射信息),找到具體的節(jié)點地址 去具體的節(jié)點找key如果key不在這個節(jié)點上臊泌,則redis集群會返回moved指令鲤桥,加上新的節(jié)點地址給客戶端。
同時渠概,客戶端會刷新本地的節(jié)點槽位映射關(guān)系如果槽位正在遷移中茶凳,那么redis集群會返回asking指令給客戶端,這是臨時糾正播揪,客戶端不會刷新本地的節(jié)點槽位映射關(guān)系
10贮喧、Redis底層,使用的什么協(xié)議?
RESP
英文全稱是Redis Serialization Protocol,它是專門為redis設(shè)計的一套序列化協(xié)議. 這個協(xié)議其實在redis的1.2版本時就已經(jīng)出現(xiàn)了,但是到了redis2.0才最終成為redis通訊協(xié)議的標準猪狈。
RESP主要有實現(xiàn)簡單箱沦、解析速度快、可讀性好等優(yōu)點雇庙。
11谓形、Redis的Hash沖突怎么辦
Redis 中的 Hash和 Java的 HashMap 更加相似,都是數(shù)組+鏈表的結(jié)構(gòu),當(dāng)發(fā)生 hash 發(fā)生碰撞時將會把元素追加到鏈表上疆前。
在Redis中hash的內(nèi)部結(jié)構(gòu)也是一樣的: 第一維是數(shù)組,第二維是鏈表.組成一個 全局哈希表
寒跳。
在 Java 中 HashMap 擴容是個很耗時的操作,需要去申請新的數(shù)組,擴容的成本并不低竹椒,因為需要遍歷一個時間復(fù)雜度為O(n)的數(shù)組童太,并且為其中的每個enrty進行hash計算。加入到新數(shù)組中胸完。
為了追求高性能,Redis 采用了漸進式 rehash
策略.這也是 hash 中最重要的部分.
redis在擴容的時候執(zhí)行 rehash 策略會保留新舊兩個 兩個全局哈希表书释,查詢時也會同時查詢兩個全局哈希表 ,Redis會將舊 全局哈希表 中的內(nèi)容一點一點的遷移到新的 全局哈希表 中,當(dāng)遷移完成時,就會用新的 全局哈希表 取代之前的。當(dāng) 全局哈希表 移除了最后一個元素之后,這個數(shù)據(jù)結(jié)構(gòu)將會被刪除.
正常情況下,當(dāng) 全局哈希表 中元素的個數(shù)等于數(shù)組的長度時,就會開始擴容,擴容的新數(shù)組是原數(shù)組大小的 2 倍赊窥。
如果 Redis 正在做 bgsave(持久化) 時,可能不會去擴容,因為要減少內(nèi)存頁的過多分離(Copy On Write).但是如果 全局哈希表 已經(jīng)非常滿了,元素的個數(shù)達到了數(shù)組長度的 5 倍時,Redis 會強制擴容爆惧。
12、Redis相比memcached有哪些優(yōu)勢
- Memcached 所有的值均是簡單的字符串誓琼,redis 作為其替代者检激,支持更為豐富的數(shù)據(jù)類
- Redis 的速度比 Memcached 快很多
- Redis 可以持久化其數(shù)據(jù)
13肴捉、有哪些辦法可以降低 Redis 的內(nèi)存使用情況呢腹侣?
當(dāng)你的業(yè)務(wù)應(yīng)用在 Redis 中存儲數(shù)據(jù)很少時,你可能并不太關(guān)心內(nèi)存資源的使用情況齿穗。但隨著業(yè)務(wù)的發(fā)展傲隶,你的業(yè)務(wù)存儲在 Redis 中的數(shù)據(jù)就會越來越多。
那在使用 Redis 時窃页,怎樣做才能更節(jié)省內(nèi)存呢跺株?這里總結(jié)了4點建議:
1) 控制 key 的長度
最簡單直接的內(nèi)存優(yōu)化复濒,就是控制 key 的長度。
在開發(fā)業(yè)務(wù)時乒省,你需要提前預(yù)估整個 Redis 中寫入 key 的數(shù)量巧颈,如果 key 數(shù)量達到了百萬級別,那么袖扛,過長的 key 名也會占用過多的內(nèi)存空間砸泛。
所以,你需要保證 key 在簡單蛆封、清晰的前提下唇礁,盡可能把 key 定義得短一些。
例如惨篱,原有的 key 為 user:book:123盏筐,則可以優(yōu)化為 u:bk:123。
這樣一來砸讳,你的 Redis 就可以節(jié)省大量的內(nèi)存琢融,這個方案對內(nèi)存的優(yōu)化非常直接和高效。
2) 避免存儲 bigkey
bigkey,意思就是這個key的value值很大簿寂。除了控制 key 的長度之外吏奸,你同樣需要關(guān)注 value 的大小,如果大量存儲 bigkey陶耍,也會導(dǎo)致 Redis 內(nèi)存增長過快奋蔚。
除此之外,客戶端在讀寫 bigkey 時烈钞,還有產(chǎn)生性能問題泊碑。
所以,你要避免在 Redis 中存儲 bigkey毯欣,一般建議是:
- String:大小控制在 10KB 以下
- List/Hash/Set/ZSet:元素數(shù)量控制在 1 萬以下
3) 盡可能地都設(shè)置過期時間
Redis 數(shù)據(jù)存儲在內(nèi)存中馒过,這也意味著其資源是有限的。你在使用 Redis 時酗钞,要把它當(dāng)做緩存來使用腹忽,而不是數(shù)據(jù)庫。
所以砚作,你的應(yīng)用寫入到 Redis中的數(shù)據(jù)窘奏,盡可能地都設(shè)置 過期時間。采用這種方案葫录,可以讓 Redis 中只保留經(jīng)常訪問的 熱數(shù)據(jù)着裹,內(nèi)存利用率也會比較高。
4) 實例設(shè)置 maxmemory + 淘汰策略
雖然你的 Redis key 都設(shè)置了過期時間米同,但如果你的業(yè)務(wù)應(yīng)用寫入量很大骇扇,并且過期時間設(shè)置得比較久摔竿,那么短期間內(nèi) Redis 的內(nèi)存依舊會快速增長。
如果不控制 Redis 的內(nèi)存上限少孝,也會導(dǎo)致使用過多的內(nèi)存資源继低。
對于這種場景,你需要提前預(yù)估業(yè)務(wù)數(shù)據(jù)量稍走,然后給這個實例設(shè)置 maxmemory 控制實例的內(nèi)存上限郁季,這樣可以避免 Redis 的內(nèi)存持續(xù)膨脹。
配置了 maxmemory钱磅,此時你還要設(shè)置數(shù)據(jù)淘汰策略梦裂,而淘汰策略如何選擇,你需要結(jié)合你的業(yè)務(wù)特點來決定盖淡。
14年柠、Redis中獲取海量數(shù)據(jù)的正確操作方式?
有時候需要從Redis實例成千上萬的key中找出特定前綴的key
列表來手動處理數(shù)據(jù)褪迟,可能是修改它的值冗恨,也可能是刪除 key。這里就有一個問題味赃,如何從海量的 key 中找出滿足特定前綴的 key 列表來掀抹?
比如我們的用戶token緩存是采用了【user_token:userid】格式的key,保存用戶的token的值心俗。這時候我們想看下有多少用戶在線傲武。
在Redis2.8版本之前,我們可以使用keys命令按照正則匹配得到我們需要的key城榛。Redis 提供了一個簡單暴力的指令 keys 用來列出所有滿足特定正則字符串規(guī)則的 key揪利。
keys user_token*
但是這個命令有一些缺點:
- 沒有 offset、limit 參數(shù)狠持,一次性吐出所有滿足條件的 key疟位,萬一實例中有幾百 w 個 key 滿足條件,當(dāng)你看到滿屏的字符串刷的沒有盡頭時喘垂,你就知道難受了甜刻。
- keys 算法是遍歷算法,復(fù)雜度是 O(n)正勒,如果實例中有千萬級以上的 key得院,這個指令就會導(dǎo)致 Redis 服務(wù)卡頓,
- 所有讀寫 Redis 的其它的指令都會被延后甚至?xí)瑫r報錯昭齐,因為 Redis 是單線程程序尿招,順序執(zhí)行所有指令,其它指令必須等到當(dāng)前的 keys 指令執(zhí)行完了才可以繼續(xù)阱驾。
- 建議生產(chǎn)環(huán)境屏蔽keys命令
在滿足需求和存在造成Redis卡頓之間究竟要如何選擇呢就谜?面對這個兩難的抉擇,Redis在2.8版本給我們提供了解決辦法——scan命令
里覆。
相比于keys命令丧荐,scan命令有兩個比較明顯的優(yōu)勢:
- scan命令的時間復(fù)雜度雖然也是O(N),但它是分次進行的喧枷,不會阻塞線程虹统。
- scan命令提供了limit參數(shù),可以控制每次返回結(jié)果的最大條數(shù)隧甚。
這兩個優(yōu)勢就幫助我們解決了上面的難題车荔,不過scan命令也并不是完美的,它返回的結(jié)果有可能重復(fù)戚扳,因此需要客戶端去重這點非常重要忧便。
15、如何使用Redis的性能更高帽借?
1)master關(guān)閉持久化
一般我們在生產(chǎn)上采用的持久化策略為master關(guān)閉持久化,slave開RDB即可珠增,必要的時候AOF和RDB都開啟。
2) 不使用復(fù)雜度過高的命令
Redis 是單線程模型處理請求砍艾,在執(zhí)行復(fù)雜度過高的命令時蒂教,會消耗更多的 CPU 資源,主線程中的其它請求只能等待脆荷,這時也會發(fā)生排隊延遲凝垛。
所以,你需要避免執(zhí)行例如 sort蜓谋、sinter苔严、sinterstore、zunionstore孤澎、zinterstore 等聚合類命令届氢。
對于這種聚合類操作,我建議你把它放到客戶端來執(zhí)行覆旭,不要讓 Redis 承擔(dān)太多的計算工作退子。
3)執(zhí)行 O(N) 命令時,關(guān)注 N 的大小
規(guī)避使用復(fù)雜度過高的命令型将,就可以高枕無憂了么寂祥?
答案是否定的。
當(dāng)你在執(zhí)行 O(N) 命令時七兜,同樣需要注意 N 的大小丸凭。
就好比上面說的使用keys命令,如果一次性能查詢過多的數(shù)據(jù),也會在網(wǎng)絡(luò)傳輸過程中耗時過長惜犀,操作延遲變大铛碑。
所以,對于容器類型(List/Hash/Set/ZSet)虽界,在元素數(shù)量未知的情況下汽烦,一定不要無腦執(zhí)行 LRANGE key 0 -1 / HGETALL / SMEMBERS / ZRANGE key 0 -1。
在查詢數(shù)據(jù)時莉御,你要遵循以下原則:
- 先查詢數(shù)據(jù)元素的數(shù)量(LLEN/HLEN/SCARD/ZCARD)
- 元素數(shù)量較少撇吞,可一次性查詢?nèi)繑?shù)據(jù)
- 元素數(shù)量非常多,分批查詢數(shù)據(jù)(LRANGE/HASCAN/SSCAN/ZSCAN)
4) 批量命令代替單個命令
當(dāng)你需要一次性操作多個 key 時礁叔,你應(yīng)該使用批量命令來處理牍颈。
批量操作相比于多次單個操作的優(yōu)勢在于,可以顯著減少客戶端琅关、服務(wù)端的來回網(wǎng)絡(luò) IO 次數(shù)煮岁。
所以我給你的建議是:
String / Hash 使用 MGET/MSET 替代 GET/SET,HMGET/HMSET 替代 HGET/HSET
其它數(shù)據(jù)類型使用 Pipeline死姚,打包一次性發(fā)送多個命令到服務(wù)端執(zhí)行
5) 避免集中過期 key
Redis 清理過期 key 是采用定時 + 懶惰的方式來做的人乓,而且這個過程都是在主線程中執(zhí)行。
如果你的業(yè)務(wù)存在大量 key 集中過期的情況都毒,那么 Redis 在清理過期 key 時色罚,也會有阻塞主線程的風(fēng)險。
想要避免這種情況發(fā)生账劲,你可以在設(shè)置過期時間時戳护,增加一個隨機時間,把這些 key 的過期時間打散瀑焦,從而降低集中過期對主線程的影響腌且。
6) 只使用 db0
盡管 Redis 提供了 16 個 db,但我只建議你使用 db0榛瓮。
為什么呢铺董?我總結(jié)了以下 3 點原因:
- 在一個連接上操作多個 db 數(shù)據(jù)時,每次都需要先執(zhí)行 SELECT禀晓,這會給 Redis 帶來額外的壓力
- 使用多個 db 的目的是精续,按不同業(yè)務(wù)線存儲數(shù)據(jù),那為何不拆分多個實例存儲呢粹懒?拆分多個實例部署重付,多個業(yè)務(wù)線不會互相影響,還能提高 Redis 的訪問性能
- Redis Cluster 只支持 db0凫乖,如果后期你想要遷移到 Redis Cluster确垫,遷移成本高
16弓颈、如何解決 Redis 的并發(fā)競爭 Key 問題
這個也是線上非常常見的一個問題,就是多客戶端同時并發(fā)寫一個 key删掀,可能本來應(yīng)該先到的數(shù)據(jù)后到了翔冀,導(dǎo)致數(shù)據(jù)版本錯了;或者是多客戶端同時獲取一個 key爬迟,修改值之后再寫回去橘蜜,只要順序錯了菊匿,數(shù)據(jù)就錯了付呕。
推薦一種方案:分布式鎖(zookeeper和redis都可以實現(xiàn)分布式鎖)。(如果不存在Redis的并發(fā)競爭Key問題跌捆,不要使用分布式鎖徽职,這樣會影響性能)
17、使用過 Redis 做異步隊列么佩厚,你是怎么用的姆钉?
一般使用list結(jié)構(gòu)作為隊列,rpush生產(chǎn)消息抄瓦,lpop消費消息
潮瓶。當(dāng)lpop沒有消息的時候,要適當(dāng)sleep一會再重試钙姊。
如果不想sleep呢毯辅?list還有個指令叫blpop,在沒有消息的時候煞额,它會阻塞住直到消息到來思恐。
但如果是這樣你發(fā)現(xiàn)redis作為消息隊列是不安全的,它不能重復(fù)消費膊毁,一旦消費就會被刪除胀莹。同時做消費者確認ACK也麻煩所以一般在實際開發(fā)中一般很少用redis中消息隊列,因為現(xiàn)在已經(jīng)有Kafka婚温、RabbitMQ等成熟的消息隊列了描焰,它們的功能更加完善。
18栅螟、用Redis做延時隊列荆秦,具體應(yīng)該怎么實現(xiàn)?
延遲隊列可以使用 zset(有序列表)實現(xiàn)嵌巷,我們將消息序列化成一個字符串作為列表的value萄凤,這個消息的到期處理時間作為score,然后用定時器定時去掃描搪哪,一旦有執(zhí)行時間小于或等于當(dāng)前時間的任務(wù)靡努,就立即執(zhí)行。
19、使用Redis統(tǒng)計網(wǎng)站的UV惑朦,應(yīng)該怎么做兽泄?
UV與PV不同,UV需要去重漾月。一般有2種方案:
1病梢、用BitMap。存的是用戶的uid梁肿,計算UV的時候蜓陌,做下bitcount就行了。
2吩蔑、用布隆過濾器钮热。將每次訪問的用戶uid都放到布隆過濾器中。優(yōu)點是省內(nèi)存烛芬,缺點是無法得 到精確的UV隧期。但是對于不需要精確知道具體UV,只需要大概的數(shù)量級的場景赘娄,是個不錯的選擇仆潮。
20、什么是熱Key問題遣臼,如何解決熱key問題
所謂熱key問題就是性置,突然有幾十萬的請求去訪問redis上的某個特定key。那么暑诸,這樣會造成流量過于集中蚌讼,達到物理網(wǎng)卡上限,從而導(dǎo)致這臺redis的服務(wù)器宕機个榕。
而熱點Key是怎么產(chǎn)生的呢篡石?主要原因有兩個:
- 用戶消費的數(shù)據(jù)遠大于生產(chǎn)的數(shù)據(jù),如秒殺西采、熱點新聞等讀多寫少的場景凰萨。
- 請求分片集中,超過單Redi服務(wù)器的性能械馆,比如固定名稱key胖眷,Hash落入同一臺服務(wù)器,瞬間訪問量極大霹崎,超過機器瓶頸珊搀,產(chǎn)生熱點Key問題。
那么在日常開發(fā)中尾菇,如何識別到熱點key呢境析?
- 憑經(jīng)驗判斷哪些是熱Key囚枪;
- 客戶端統(tǒng)計上報;
- 服務(wù)代理層上報
如何解決熱key問題劳淆?
- Redis集群擴容:增加分片副本链沼,均衡讀流量;
- 將熱key分散到不同的服務(wù)器中沛鸵;
- 使用二級緩存括勺,即JVM本地緩存,減少Redis的讀請求。
參考
[1] Redis 最佳實踐指南: https://mp.weixin.qq.com/s/Fz1EbsmJP5k2Rh6ir_a1pQ
[2] Redis經(jīng)典面試題: https://mp.weixin.qq.com/s/fBShKZbuR54yaIzzMR3R7g
[3] Redis面試20題: http://www.gameboys.cn/article/57
關(guān)注公眾號:后端元宇宙曲掰。持續(xù)輸出優(yōu)質(zhì)好文