Redis常見面試題連環(huán)問鉴象,你能回答到第幾問忙菠?(上)
Redis常見面試題連環(huán)問,你能回答到第幾問纺弊?(中)
Redis常見面試題連環(huán)問牛欢,你能回答到第幾問?(下)
Redis是后端工程師必備的一項技能淆游,下面分享一位求職者在面試過程中遇到的問題傍睹。
面試官說:“我們開始吧∮塘猓看了你的簡歷拾稳,覺得你對redis應(yīng)該掌握的不錯,我們今天就來討論下redis…”腊脱。我想:“來就來访得,兵來將擋水來土掩”。
一虑椎、Redis是什么
面試官:你先來說下redis是什么吧
我:(這不就是總結(jié)下redis的定義和特點嘛)Redis是C語言開發(fā)的一個開源的(遵從BSD協(xié)議)高性能鍵值對(key-value)的內(nèi)存數(shù)據(jù)庫,可以用作數(shù)據(jù)庫俱笛、緩存捆姜、消息中間件等。它是一種NoSQL(not-only sql迎膜,泛指非關(guān)系型數(shù)據(jù)庫)的數(shù)據(jù)庫泥技。
我頓了一下,接著說:Redis作為一個內(nèi)存數(shù)據(jù)庫磕仅。
性能優(yōu)秀珊豹,數(shù)據(jù)在內(nèi)存中,讀寫速度非抽哦快店茶,支持并發(fā)10W QPS;
單進(jìn)程單線程劫恒,是線程安全的贩幻,采用IO多路復(fù)用機制;
豐富的數(shù)據(jù)類型两嘴,支持字符串(strings)丛楚、散列(hashes)、列表(lists)憔辫、集合(sets)趣些、有序集合(sorted sets)等;
支持?jǐn)?shù)據(jù)持久化贰您』灯剑可以將內(nèi)存中數(shù)據(jù)保存在磁盤中拢操,重啟時加載;
主從復(fù)制功茴,哨兵庐冯,高可用;
可以用作分布式鎖坎穿;
可以作為消息中間件使用展父,支持發(fā)布訂閱
注:答主回答的還是不錯的,我們看一下官方簡介
Redis是一個基于BSD開源的項目玲昧,是一個把結(jié)構(gòu)化的數(shù)據(jù)放在內(nèi)存中的一個存儲系統(tǒng)栖茉,你可以把它作為數(shù)據(jù)庫,緩存和消息中間件來使用孵延。同時支持strings吕漂,lists,hashes尘应,sets惶凝,sorted sets,bitmaps犬钢,hyperloglogs和geospatial indexes等數(shù)據(jù)類型苍鲜。
它還內(nèi)建了復(fù)制,lua腳本玷犹,LRU(Least Recently Used混滔,最近最少使用),事務(wù)等功能歹颓,通過redis sentinel(即哨兵)實現(xiàn)高可用坯屿,通過redis cluster實現(xiàn)了自動分片。以及事務(wù)巍扛,發(fā)布/訂閱领跛,自動故障轉(zhuǎn)移等等。
面試官:總結(jié)的不錯撤奸,看來是早有準(zhǔn)備啊隔节。剛來聽你提到redis支持五種數(shù)據(jù)類型,那你能簡單說下這五種數(shù)據(jù)類型嗎寂呛?
二怎诫、五種數(shù)據(jù)類型
我:當(dāng)然可以,但是在說之前贷痪,我覺得有必要先來了解下Redis內(nèi)部內(nèi)存管理是如何描述這5種數(shù)據(jù)類型的幻妓。說著,我拿著筆給面試官畫了一張圖:
?
我:首先redis內(nèi)部使用一個redisObject對象來表示所有的key和value,redisObject最主要的信息如上圖所示:
type表示一個value對象具體是何種數(shù)據(jù)類型肉津,
encoding是不同數(shù)據(jù)類型在redis內(nèi)部的存儲方式强胰。比如:type=string表示value存儲的是一個普通字符串,那么encoding可以是raw或者int妹沙。
我頓了一下偶洋,接著說:下面我簡單說下5種數(shù)據(jù)類型:
string是redis最基本的類型,可以理解成與memcached一模一樣的類型距糖,一個key對應(yīng)一個value玄窝。value不僅是string,也可以是數(shù)字悍引。string類型是二進(jìn)制安全的恩脂,意思是redis的string類型可以包含任何數(shù)據(jù),比如jpg圖片或者序列化的對象趣斤。string類型的值最大能存儲512M俩块。
Hash是一個鍵值(key-value)的集合。redis的hash是一個string的key和value的映射表浓领,Hash特別適合存儲對象玉凯。常用命令:hget,hset,hgetall等。
list列表是簡單的字符串列表联贩,按照插入順序排序漫仆。可以添加一個元素到列表的頭部(左邊)或者尾部(右邊) 常用命令:lpush撑蒜、rpush歹啼、lpop玄渗、rpop座菠、lrange(獲取列表片段)等。
應(yīng)用場景:list應(yīng)用場景非常多藤树,也是Redis最重要的數(shù)據(jù)結(jié)構(gòu)之一浴滴,比如twitter的關(guān)注列表,粉絲列表都可以用list結(jié)構(gòu)來實現(xiàn)岁钓。
數(shù)據(jù)結(jié)構(gòu):list就是鏈表升略,可以用來當(dāng)消息隊列。redis提供了List的push和pop操作屡限,還提供了操作某一段的api品嚣,可以直接查詢或者刪除某一段的元素。
實現(xiàn)方式:redis list的是實現(xiàn)是一個雙向鏈表钧大,支持反向查找和遍歷翰撑,更方便操作,不過帶來了額外的內(nèi)存開銷啊央。set是string類型的無序集合眶诈。集合是通過hashtable實現(xiàn)的涨醋。set中的元素是沒有順序的,而且是沒有重復(fù)的逝撬。
常用命令:sdd浴骂、spop、smembers宪潮、sunion等溯警。
應(yīng)用場景:redis set對外提供的功能和list一樣是一個列表,特殊之處在于set是自動去重的坎炼,而且set提供了判斷某個成員是否在一個set集合中愧膀。zset和set一樣是string類型元素的集合,且不允許重復(fù)的元素谣光。常用命令:zadd檩淋、zrange、zrem萄金、zcard等蟀悦。
使用場景:sorted set可以通過用戶額外提供一個優(yōu)先級(score)的參數(shù)來為成員排序,并且是插入有序的氧敢,即自動排序日戈。當(dāng)你需要一個有序的并且不重復(fù)的集合列表,那么可以選擇sorted set結(jié)構(gòu)孙乖。和set相比浙炼,sorted set關(guān)聯(lián)了一個double類型權(quán)重的參數(shù)score,使得集合中的元素能夠按照score進(jìn)行有序排列唯袄,redis正是通過分?jǐn)?shù)來為集合中的成員進(jìn)行從小到大的排序弯屈。
實現(xiàn)方式:Redis sorted set的內(nèi)部使用HashMap和跳躍表(skipList)來保證數(shù)據(jù)的存儲和有序,HashMap里放的是成員到score的映射恋拷,而跳躍表里存放的是所有的成員资厉,排序依據(jù)是HashMap里存的score,使用跳躍表的結(jié)構(gòu)可以獲得比較高的查找效率蔬顾,并且在實現(xiàn)上比較簡單宴偿。
數(shù)據(jù)類型應(yīng)用場景總結(jié)
類型 | 簡介 | 特性 | 場景 |
---|---|---|---|
string(字符串) | 二進(jìn)制安全 | 可以包含任何數(shù)據(jù),比如jpg圖片或者序列化對象 | |
Hash(字典) | 鍵值對集合诀豁,即編程語言中的map類型 | 適合存儲對象窄刘,并且可以像數(shù)據(jù)庫中的update一個屬性一樣只修改某一項屬性值 | 存儲、讀取舷胜、修改用戶屬性 |
List(列表) | 鏈表(雙向鏈表) | 增刪快娩践,提供了操作某一元素的api | 最新消息排行;消息隊列 |
set(集合) | hash表實現(xiàn),元素不重復(fù) | 添加欺矫、刪除纱新、查找的復(fù)雜度都是O(1),提供了求交集穆趴、并集脸爱、差集的操作 | 共同好友;利用唯一性未妹,統(tǒng)計訪問網(wǎng)站的所有Ip |
sorted set(有序集合) | 將set中的元素增加一個權(quán)重參數(shù)score簿废,元素按score有序排列 | 數(shù)據(jù)插入集合時,已經(jīng)進(jìn)行了天然排序 | 排行榜络它;帶權(quán)重的消息隊列 |
面試官:那Redis緩存你一定用過的吧族檬,用的過程中遇到過什么問題嗎?雪崩了解嗎化戳?
我:緩存和數(shù)據(jù)庫數(shù)據(jù)一致性問題:分布式環(huán)境下非常容易出現(xiàn)緩存和數(shù)據(jù)庫間數(shù)據(jù)一致性問題单料,針對這一點,如果項目對緩存的要求是強一致性的点楼,那么就不要使用緩存扫尖。我們只能采取合適的策略來降低緩存和數(shù)據(jù)庫間數(shù)據(jù)不一致的概率,而無法保證兩者間的強一致性掠廓。合適的策略包括合適的緩存更新策略换怖,更新數(shù)據(jù)庫后及時更新緩存、緩存失敗時增加重試機制蟀瞧。
緩存雪崩
緩存雪崩是指在我們設(shè)置緩存時采用了相同的過期時間沉颂,導(dǎo)致緩存在某一時刻同時失效,請求全部轉(zhuǎn)發(fā)到DB悦污,DB瞬時壓力過重雪崩铸屉。
舉個栗子:
如果某電商網(wǎng)站首頁所有Key的失效時間都是12小時,中午12點刷新的塞关,我零點有個大促活動大量用戶涌入抬探,假設(shè)每秒6000個請求子巾,本來緩存可以抗住每秒5000個請求帆赢,但是緩存中所有Key都失效了。此時6000個/秒的請求全部落在了數(shù)據(jù)庫上线梗,數(shù)據(jù)庫必然扛不住椰于,真實情況可能DBA都沒反應(yīng)過來直接掛了,此時仪搔,如果沒什么特別的方案來處理瘾婿,DBA很著急,重啟數(shù)據(jù)庫,但是數(shù)據(jù)庫立馬又被新流量給打死了偏陪。這就是我理解的緩存雪崩抢呆。
我心想:同一時間大面積失效,瞬間Redis跟沒有一樣笛谦,那這個數(shù)量級別的請求直接打到數(shù)據(jù)庫幾乎是災(zāi)難性的抱虐,你想想如果掛的是一個用戶服務(wù)的庫,那其他依賴他的庫所有接口幾乎都會報錯饥脑,如果沒做熔斷等策略基本上就是瞬間掛一片的節(jié)奏恳邀,你怎么重啟用戶都會把你打掛,等你重啟好的時候灶轰,用戶早睡覺去了谣沸。
解決方案
緩存失效時的雪崩效應(yīng)對底層系統(tǒng)的沖擊非常可怕笋颤。有一個簡單方案就是將緩存失效時間分散開乳附,比如我們可以在原有的失效時間基礎(chǔ)上增加一個隨機值,比如1-5分鐘隨機伴澄,這樣每一個緩存的過期時間的重復(fù)率就會降低许溅,就很難引發(fā)集體失效的事件。
如果Redis是集群部署秉版,將熱點數(shù)據(jù)均勻分布在不同的Redis庫中也能避免全部失效贤重。
或者設(shè)置熱點數(shù)據(jù)永不過期,有更新操作就更新緩存就好了(比如更新了首頁商品清焕,那你刷下緩存就好了并蝗,不要設(shè)置過期時間),電商首頁的數(shù)據(jù)也可以用這個操作秸妥,保險滚停。
面試官:那你了解緩存穿透和擊穿么,可以說說他們跟雪崩的區(qū)別嗎粥惧?
緩存穿透 與 緩存擊穿
我:緩存穿透是指查詢一個一定不存在的數(shù)據(jù)键畴,由于緩存是不命中時被動寫的,并且出于容錯考慮突雪,如果從存儲層查不到數(shù)據(jù)則不寫入緩存起惕,這將導(dǎo)致這個不存在的數(shù)據(jù)每次請求都要到存儲層去查詢,失去了緩存的意義咏删。而用戶(黑客)不斷發(fā)起請求惹想,這就是漏洞。
舉個栗子:我們數(shù)據(jù)庫的id都是從1自增的督函,如果發(fā)起id=-1的數(shù)據(jù)或者id特別大不存在的數(shù)據(jù)嘀粱,這樣的不斷攻擊導(dǎo)致數(shù)據(jù)庫壓力很大激挪,嚴(yán)重會擊垮數(shù)據(jù)庫。
我又接著說:至于緩存擊穿嘛锋叨,這個跟緩存雪崩有點像垄分,但是又有一點不一樣,緩存雪崩是因為大面積的緩存失效娃磺,打崩了DB锋喜,而緩存擊穿不同的是緩存擊穿是指一個Key非常熱點,在不停地扛著大量的請求豌鸡,大并發(fā)集中對這一個點進(jìn)行訪問嘿般,當(dāng)這個Key在失效的瞬間,持續(xù)的大并發(fā)直接落到了數(shù)據(jù)庫上涯冠,就在這個Key的點上擊穿了緩存炉奴。并發(fā)的請求可能會瞬間把后端DB壓垮。
面試官露出欣慰的眼光:那他們分別怎么解決蛇更?
緩存穿透我會在接口層增加校驗瞻赶,比如用戶鑒權(quán),參數(shù)做校驗派任,不合法的校驗直接return砸逊,比如id做基礎(chǔ)校驗,id<=0直接攔截掌逛。
從緩存取不到的數(shù)據(jù)师逸,在數(shù)據(jù)庫中也沒有取到,這時也可以將key-value對寫為key-null豆混,緩存有效時間可以設(shè)置短點篓像,如30秒(設(shè)置太長會導(dǎo)致正常情況也沒法使用)。這樣可以防止攻擊用戶反復(fù)用同一個id暴力攻擊皿伺。
Redis里還有一個高級用法布隆過濾器(Bloom Filter)這個也能很好的預(yù)防緩存穿透的發(fā)生员辩,他的原理也很簡單,就是利用高效的數(shù)據(jù)結(jié)構(gòu)和算法快速判斷出你這個Key是否在數(shù)據(jù)庫中存在鸵鸥,不存在你return就好了奠滑,存在你就去查DB刷新KV再return。但布隆過濾器有一定的誤判性妒穴。
緩存擊穿的話宋税,主要有三種解決方法:
使用互斥鎖(mutex key):這種解決方案思路比較簡單,就是只讓一個線程構(gòu)建緩存宰翅,其他線程等待構(gòu)建緩存的線程執(zhí)行完弃甥,重新從緩存獲取數(shù)據(jù)就可以了爽室。
"提前"使用互斥鎖(mutex key):在value內(nèi)部設(shè)置1個超時值(timeout1), timeout1比實際的緩存失效時間timeout(timeout2)小汁讼。當(dāng)從cache讀取到timeout1發(fā)現(xiàn)它已經(jīng)過期時候淆攻,馬上獲取新的數(shù)據(jù)到cache并延長timeout1并重新設(shè)置到cache。
“永遠(yuǎn)不過期”:然后通過定時job去刷新緩存嘿架。
加鎖偽代碼如下:
public function getData($key)
{
$data = redis->get($key);
if (!is_null($data)) {
//緩存未過期
if ($data['expire'] > time()){
return $data['data'];
}
//加鎖失敗說明已經(jīng)有請求執(zhí)行加鎖瓶珊,返回之前的緩存數(shù)據(jù)
if (!Redis::setnx($lockKey,1)) {
return $data['data'];
}
}
usleep(100);
$data_new = $this->searchDB($key);
$data = [
'data' => $data_new,
'expire' => time() + $expire
];
$r = redis->set($key, $data, $expire);
//解鎖
redis->del($lockKey);
return $data['data'];
}
今天就分享到這里,預(yù)知后事如何且聽下回分解耸彪。
更多精彩歡迎關(guān)注公眾號伞芹。