面試提問(wèn):Redis 為什么這么快?
我們都知道,在面試的環(huán)節(jié)中,會(huì)有各種千奇百怪的問(wèn)題,最經(jīng)典的就是各種數(shù)據(jù)庫(kù),這種中間件,還有就是底層原理,還有就是關(guān)于緩存數(shù)據(jù)庫(kù)這塊,今天了不起就來(lái)說(shuō)說(shuō)這個(gè)某東最喜歡問(wèn)的一個(gè)內(nèi)容,那就是關(guān)于 Redis 的一些問(wèn)題,比如 Redis 為什么快?
Redis
redis是一個(gè)key-value存儲(chǔ)系統(tǒng)。
和Memcached類似,它支持存儲(chǔ)的value類型相對(duì)更多。
包括string(字符串)、list(鏈表)、set(集合)、zset(sorted set --有序集合)和hash(哈希類型)。
這些數(shù)據(jù)類型都支持push/pop、add/remove及取交集并集和差集及更豐富的操作,而且這些操作都是原子性的。在此基礎(chǔ)上,redis支持各種不同方式的排序。與memcached一樣,為了保證效率,數(shù)據(jù)都是緩存在內(nèi)存中。
區(qū)別的是redis會(huì)周期性的把更新的數(shù)據(jù)寫入磁盤或者把修改操作寫入追加的記錄文件,并且在此基礎(chǔ)上實(shí)現(xiàn)了master-slave(主從)同步。
Redis 為什么快?
- 純內(nèi)存訪問(wèn)
- 單線程,避免上下文的切換
- 漸進(jìn)式ReHash,緩存時(shí)間戳
這也是我們?cè)诿嬖囍薪?jīng)常會(huì)被問(wèn)到的內(nèi)容,而我們的基礎(chǔ)回答都是前兩個(gè),一個(gè)是純內(nèi)存訪問(wèn),一個(gè)就是單線程,但是如果你在面試的時(shí)候,只是回答了這兩個(gè),那么只能說(shuō)你是對(duì) Redis 有了一些簡(jiǎn)單的了解,如果評(píng)分制,那么你只能算是及格,如果你想拿到滿分,那么第三個(gè),就必不可少了。
其實(shí) Redis 快的原因,是因?yàn)?Redis 的內(nèi)部,采用了很多效率高的機(jī)制,就比如我們說(shuō)的第三個(gè),漸進(jìn)式的ReHash 和緩存時(shí)間戳。
什么是漸進(jìn)式 ReHash
我們?cè)陂_篇中也說(shuō)了,Redis是一個(gè) key-value 存儲(chǔ)系統(tǒng)。
這樣就不得不提一下另外的一個(gè)概念了,全局Hash表
為了實(shí)現(xiàn)從鍵到值的快速訪問(wèn),Redis使用了一個(gè)全局哈希表來(lái)保存所有鍵值對(duì)。而一個(gè) Hash 表,其實(shí)本質(zhì)上就是一個(gè)數(shù)組,數(shù)組的每一個(gè)元素稱為一個(gè) Hash 桶,所以,這也是為什么很多人稱 Hash 表是由多個(gè) Hash 桶組成的,而每個(gè) Hash 桶中保存了鍵值對(duì)數(shù)據(jù)。
圖片
如果他們出現(xiàn)了 Hash 沖突了,那么就會(huì)使用連表的方式進(jìn)行解決。
一般的,當(dāng)我們插入數(shù)據(jù)的時(shí)候,數(shù)組的長(zhǎng)度不會(huì)很長(zhǎng),但是當(dāng)我們?cè)诓粩嗟耐鶅?nèi)部插入數(shù)據(jù)的過(guò)程中,就會(huì)擴(kuò)容,比如我們擴(kuò)容是N倍,這個(gè)時(shí)候就會(huì)涉及到我們?cè)袛?shù)據(jù)元素的移動(dòng),而這個(gè)過(guò)程,我們流稱之為 ReHash 了。
但是隨著移動(dòng),就會(huì)伴生出一些問(wèn)題,比如我們的元素非常的多,上千條,甚至上萬(wàn)條,那么如果元素移動(dòng)的話,一次性移動(dòng)上千甚至上萬(wàn)條的時(shí)候,就必然的導(dǎo)致一種情況的出現(xiàn),那都不用說(shuō),IO阻塞了, 因?yàn)樵氐囊苿?dòng),就有可能導(dǎo)致我們的元素從1的位置,挪到了4的位置,甚至挪動(dòng)到n的位置,數(shù)據(jù)只要移動(dòng),那么久一定會(huì)出現(xiàn)阻塞的問(wèn)題,一旦開始阻塞了,那 Redis 的速度,必然會(huì)下降。
所以為了解決這個(gè)阻塞的問(wèn)題,所以為了解決這個(gè)問(wèn)題,所以 Redis 使用的是漸進(jìn)式的 ReHash。
而這個(gè)漸進(jìn)式的 ReHash 其實(shí)原理也不復(fù)雜,總結(jié)大白話就是把一次大量拷貝的開銷,分?jǐn)偟蕉啻翁幚碚?qǐng)求的過(guò)程中,避免耗時(shí)操作,保證數(shù)據(jù)的快速訪問(wèn)。
那么他這個(gè)分?jǐn)傉?qǐng)求是怎么處理的呢?
首先、Redis 默認(rèn)使用了兩個(gè)全局哈希表: 哈希表 1 和哈希表 2。一開始,當(dāng)你剛插入數(shù)據(jù)時(shí),默認(rèn)使用哈希表1,此時(shí)的哈希表 2 并沒有被分配空間。隨著數(shù)據(jù)逐步增多,Redis 開始執(zhí)行 ReHash。
- 給哈希表 2 分配更大的空間,例如是當(dāng)前哈希表 1 大小的兩倍
- 把哈希表 1 中的數(shù)據(jù)重新映射并拷貝到哈希表 2 中
- 釋放哈希表 1的空間
在上面的第二步涉及大量的數(shù)據(jù)拷貝,如果一次性把哈希表 1 中的數(shù)據(jù)都遷移完,會(huì)造成 Redis 線程阻塞,無(wú)法服務(wù)其他請(qǐng)求。此時(shí),Redis 就無(wú)法快速訪問(wèn)數(shù)據(jù)了。
圖片
在Redis 開始執(zhí)行 ReHash,Redis仍然正常處理客戶端請(qǐng)求,但是要加入一個(gè)額外的處理處理
第1個(gè)請(qǐng)求時(shí),把哈希表 1中的第1個(gè)索引位置上的所有 entries 拷貝到哈希表 2 中
處理第2個(gè)請(qǐng)求時(shí),把哈希表1中的第2個(gè)索引位置上的所有 entries 拷貝到哈希表 2中
如此循環(huán),直到把所有的索引位置的數(shù)據(jù)都拷貝到哈希表 2 中。
這樣就實(shí)現(xiàn)了剛才了不起說(shuō)的,把一次大量拷貝的開銷,分?jǐn)偟蕉啻翁幚碚?qǐng)求的過(guò)程中,避免耗時(shí)操作,保證數(shù)據(jù)的快速訪問(wèn)。
關(guān)于 漸進(jìn)式的 ReHash 就說(shuō)完了,那么這個(gè)緩存時(shí)間戳又是用來(lái)干嘛的呢?
緩存時(shí)間戳
這個(gè)緩存時(shí)間戳,也是 Redis 快的另外一個(gè)主要的原因,幾乎是 ReHash 并列的呀。
我們?cè)陂_發(fā)中使用時(shí)間戳,一般都是使用的 System 的方法,也就是 currentTimeMillis()來(lái)獲取時(shí)間戳的,但是這是我們?cè)?Java 代碼中的,而 Redis 顯然不能這么用,因?yàn)槊恳淮潍@取系統(tǒng)時(shí)間戳都是一次系統(tǒng)調(diào)用(涉及到上下文切換),所以系統(tǒng)調(diào)用相對(duì)來(lái)說(shuō)是比較費(fèi)時(shí)間的,作為單線程的 Redis 承受不起,所以它需要對(duì)時(shí)間進(jìn)行緩存,由一個(gè)定時(shí)任務(wù),每毫秒更新一次時(shí)間緩存獲取時(shí)間都是從緩存中直接拿。
而這就是 緩存時(shí)間戳,所以,在面試中如果有面試官問(wèn)到 Redis 為什么這么快的時(shí)候,你知道應(yīng)該怎么回答了么?