老司機帶你玩轉面試(1):緩存中間件 Redis 基礎知識以及數據持久化


引言

今天周末,我在家坐着掐指一算,馬上又要到一年一度的金九銀十招聘季了,國內今年上半年受到 YQ 沖擊,金三銀四泡湯了,這就直接導致很多今年畢業的同學會和明年畢業的同學一起參加今年下半年的秋招,這個競爭就比較激烈了。

最近后台有一些朋友給我留言,希望我能寫寫招聘相關的內容,畢竟雖然說是金九銀十,但是很多大公司的校招從 7 、 8 月份就開始了。

本來是想寫點面試技巧和簡歷技巧的,但我轉念一想,大家都是搞技術的,問題的核心還是技術能力要過關,面試技巧這東西最多只能用作錦上添花,而技術能力過不去,機會送到手里都抓不住。

再說簡歷這件事兒,說實話,這個不是短時間內能進行彌補的,之前我也有文章分享過我是怎么挑簡歷的,無外乎先看學校,再參考下實習公司,項目經驗這一欄就看心情了。

尤其是校招,學校放在第一位,在金礦里淘金子肯定要比在沙堆上淘金子效率高得多,當然,如有能有大廠的實習經驗,也會成為簡歷上的一個亮點,能去大廠實習,不管最后什么原因沒有留下,本身已經可以說明很多問題了。

MD ,老說實話,實話這么講下去得罪的人有點多啊。

當然哈,如果這兩樣都沒有出彩的地方,就只能在項目經驗上下點功夫了,在 Github 上多找找能拿來練手的項目,多動動手,動起來總比整天怨天尤人要來的強。

第一份工作可以找的不好,至少先混口飯吃,在工作中再接着學習,夯實自己的基礎,做個兩三年工作還是可以換的嘛。

Redis 安裝

扯遠了,強行扯回來,我思來想去還是用 Redis 開頭,就是因為這玩意使用頻率以及使用場景太多了,不管什么語言,做什么方向,最終都能和 Redis 扯上關系(emmm,扯不上的我也能強行扯上)。

先簡單介紹下我認為的本地最簡安裝方案,這個方案順便還實現了跨平台(一開始我並沒有想到這個,直到我寫到這才忽然想起來)。

Docker + Redis ,Docker 在各個平台都有自己的安裝包,請各位同學去官網自行下載安裝,我就不多介紹了, Windows 環境下官網提供了現成的 exe 程序,直接雙擊一路 next 到底就完。

Docker 安裝成功后,不管在哪個平台都是打開命令行模式,或者找個輸入命令的地方,Windows 平台下可以使用 CMDPowerShell 或者 Windows Terminal 等工具,其他常見平台包括 MacOS 、 CentOS 或者 Ubuntu ,就不用我多說了吧,如果自己找不到的話,我估摸着我也找不到。

然后用下面三個命令安裝 Redis :

# 下載最新版本 Redis 鏡像
docker pull redis
# 查看當前鏡像
docker images
# 啟動命令
docker run --name redis -p 6380:6379  -d redis:latest --requirepass "02wKdSs7NvWT5TdlRyN4dxkXvIDnI1uroh5t"
# 查看運行容器
docker ps
# 進入容器
docker exec -it 013a252b24d6 redis-cli

稍微解釋下容器的啟動命令 --name 是對這個啟動的容器進行命名, -p 是指定映射的端口, -d 是指后台運行容器,並返回容器的 ID , --requirepass 是指定了當前啟動的 Redis 的訪問密碼。

然后一個 Redis 就啟動好了,我們進入容器執行幾條命令看下是否正常:

# 使用剛才設置的密碼登錄
127.0.0.1:6379> auth 02wKdSs7NvWT5TdlRyN4dxkXvIDnI1uroh5t
OK
# 寫入一個 key-value
127.0.0.1:6379> set name geekdigging
OK
# 查詢 key
127.0.0.1:6379> get name
"geekdigging"
# 查詢所有 key
127.0.0.1:6379> keys *
1) "name"
# 刪除 key
127.0.0.1:6379> del name
(integer) 1
# 查詢所有 key
127.0.0.1:6379> keys *
(empty array)
# 退出
127.0.0.1:6379> quit

Redis 入門結束,看完這一段,在簡歷上寫個 Redis 達到了解級別我覺得木有任何問題。

基礎知識

既然是從面試出發,那么接下來的內容將會以面試題為導向進行解答,面試題來源於網絡或者我自己的杜撰。

為什么在項目中使用 Redis ?

現在基本上只要問到緩存,第一個問題基本上都是哪里用了緩存?為啥要用?不用行不行?用了以后會不會有什么風險?

就是單純的看你背后對緩存有沒有思考,還是說只是單純的傻乎乎的用。如果沒辦法給一個還可以的回答,那這個映像分一下就降下來了。

首先哈,我們在項目中使用 Redis 肯定是為了更高的性能和更好的並發。因為傳統的關系型數據庫已經無法滿足現在所有的使用場景了,最常見的秒殺場景,或者查詢時的流量洪峰等等,都很容易把傳統的 MySQL 或者是 Oracle 打崩,所以引入了緩存中間件 Redis 。

高性能:

假設一個場景,一個請求過來,開始查詢數據庫,亂七八糟一頓 SQL 操作,查了個結果,結果耗時可能有個 500ms 左右,就比如商城的首頁,各種類目的商品信息,各種推薦信息,如果走數據庫查詢,並且查完了可能接下來好幾個小時都沒什么變化,那每次請求都走到數據庫里,就有點不大合適了。

這時,我們把查到的結果放到扔到緩存里面,下次再來查詢,不走數據庫,直接走緩存查詢,算上網絡消耗可能 10ms 左右就能響應結果了,性能瞬間提升 50 倍。

這就是說,對於一些需要復雜操作耗時查出來的結果,且確定后面不怎么變化,但是有很多讀請求,那么直接將查詢出來的結果放在緩存中,后面直接讀緩存就好了。

高並發:

MySQL 或者 Oracle 這種關系型數據庫壓根就不是用來玩並發的,雖然也可以支撐一定的並發,單機 8C16G 的 MySQL 優化基本上極限能撐到 900 左右的 TPS , QPS 極限能撐到 9000 左右。別看這個數字不小,請注意是極限情況,這個情況下 CPU 全都已經爆表,整個服務已經處於不健康的狀態。

這時業務場景如果 1s 有 1w 的請求過來,使用一個 MySQL 單機肯定直接崩掉,但是如果使用 Redis 緩存,把大量的熱點數據放在緩存,因為是走內存的操作,單機輕松支撐幾萬甚至於幾十萬的訪問。單機的並發承載量是 MySQL 的幾十倍。

除了 Redis 還有考慮過其他緩存么?

這個問題實際上是在問知識廣度,因為現在市面上比較常見的緩存有兩個,一個是 Redis 還有一個是 Memcached ,而大家現在基本上都在用 Redis 而逐漸的拋棄掉了 Memcached ,這么做肯定是由原因的,說明 Memcached 是存在明顯的短板的。

  1. Redis 相比較 Memcached 而言,它支持更復雜的數據結構,能支持更豐富的數據操作。
  2. Redis 在 3.x 版本以后,原生支持了集群模式,而 Memcached 沒有原生的集群模式,需要依靠客戶端來實現往集群中分片寫入數據。
  3. Redis 擁有更加豐富的附加功能,如:pub/sub 功能, Lua 腳本支持, 序列化支持等等。
  4. Redis 支持數據持久化, RDB 和 AOF。

Redis 的線程模型是什么?為什么 Redis 單線程卻能支撐高並發?

這兩個問題我放在一起,實際上是一個遞進的關系。

首先明確第一點, Redis 是單線程的模型。

而單線程卻能擁有很好的性能以及支撐高並發則得益於它自身的另一套機制「 I/O 多路復用機制」。

Redis 內部使用文件事件處理器( file event handler ) 是單線程的,所以 Redis 才叫做單線程的模型。

它采用 IO 多路復用機制同時監聽多個 socket ,將產生事件的 socket 壓入內存隊列中,事件分派器根據 socket 上的事件類型來選擇對應的事件處理器進行處理。

盡管多個文件事件操作可能會並發的出現,但 I/O 多路復用系統總是會將所有產生的套接字( Socket ) 放到一個隊列里面,然后通過這個隊列,以有序、同步、每次一個套接字的方式向文件分派器傳送套接字。只有當上一個套接字產生的事件被處理完畢之后, I/O 多路復用系統才會繼續向文件分派器傳送下一個套接字。

同時,單線程的模型反而帶來了另一個好處是無需頻繁的切換上下文,預防了多線程可能產生的競爭問題。

注意: Redis 6.0 之后的版本拋棄了單線程模型這一設計,原本使用單線程運行的 Redis 也開始選擇性地使用多線程模型。

前面還在強調 Redis 單線程模型的高效性,現在為什么又要引入多線程?這其實說明 Redis 在有些方面,單線程已經不具有優勢了。因為讀寫網絡的 Read/Write 系統調用在 Redis 執行期間占用了大部分 CPU 時間,如果把網絡讀寫做成多線程的方式對性能會有很大提升。

Redis 的多線程部分只是用來處理網絡數據的讀寫和協議解析,執行命令仍然是單線程。 之所以這么設計是不想 Redis 因為多線程而變得復雜,需要去控制 key、lua、事務、LPUSH/LPOP 等等的並發問題。

Redis 的數據結構有哪些呀?

首先最基礎的五種數據結構必須爛熟於心:String 、 Hash 、 List 、 Set 、 SortedSet 。

如果連這五種基礎數據結構都記不住的話那就真的需要自己多補補課了。

那么,是不是只有這五種基礎數據結構,答案並不是,比如在 2.8.9 版本添加的 HyperLogLog ,或者在 3.2 版本提供的 GEO ,又或者在 2.2 版本后新增的 Bitmap 以及在比較近的 5.0 版本新增的 Stream 。

  • HyperLogLog: 基數統計,這個結構可以非常省內存的去統計各種計數,比如注冊 IP 數、每日訪問 IP 數、頁面實時UV)、在線用戶數等。但是它也有局限性,就是只能統計數量,而沒辦法去知道具體的內容是什么。
  • GEO: 這個功能可以將用戶給定的地理位置信息儲存起來, 並對這些信息進行操作。
  • Bitmap: BitMap 就是通過一個 bit 位來表示某個元素對應的值或者狀態, 其中的 key 就是對應元素本身,實際上底層也是通過對字符串的操作來實現。
  • Stream: 從功能層面來講, Streams 加上它的指令實現了一個完備的分布式消息隊列。

上面這幾種種數據結構都不復雜,大家百度下看看文章就能和面試官吹牛皮了。

能答出來后面這幾種數據類型,基本上面試官都會對你另眼相看,如果還能接着說出來使用場景以及具體應用,那么這道題基本上你的發揮已經超出了面試官的預期。

這時你還可以接着聊下去,比如你還用過一些 Redis 的第三方模塊,這個是 Redis 在 4.0 版本以后提供了插件功能,比如非常常用的一個插件布隆過濾器「 BloomFilter 」。

布隆過濾器主要是用來去重使用的,在空間上可以節省 90% 以上,但是稍微有點不精確,有一定的誤判概率。可以簡單的把布隆過濾器理解成一個不怎么精確的 set 結構,當使用它的 contains 方案判斷某個對象是否存在時,它可能會誤判。但是布隆過濾器也不是特別不精確,只要參數設置的合理,它的精確度也可以控制的相對足夠精確,只會有小小的誤判概率。

除了 「BloomFilter」 ,還有一些比較常用的,如: 「RedisSearch」 和 「rediSQL」 。

「RedisSearch」 是一個強大全文檢索插件而 「rediSQL」 則是一個使得 Redis 能使用 SQL 做查詢的插件。

Redis 的持久化有哪幾種方式?

Redis 數據持久化有兩種方式: RDB 和 AOF 。

  • RDB:RDB 持久化機制,是對 Redis 中的數據執行定期的持久化。
  • AOF: AOF 機制對每條寫入命令作為日志,以 append-only 的模式寫入一個日志文件中,在 Redis 重啟的時候,可以通過回放 AOF 日志中的寫入指令來重新構建整個數據集。

通過 RDB 或者 AOF ,都可以將 Redis 內存中的數據持久化到硬盤上面,如果有需要,還可以將硬盤上的數據備份到其他地方去。

如果 Redis 掛了,這時止不僅 Redis 服務掛掉,內存數據丟失,同時產生硬件損壞,硬盤上的數據也丟失或者復發恢復,我們還可以從其他地方將數據拷貝回來進行數據恢復。

不同的持久化機制都有什么優缺點?

RDB 的優缺點:

  • RDB 持久化既可以通過手動執行,也可以通過配置文件選項定期執行,它可以使得某個時間點上的數據庫的狀態保存到一個 RDB 文件中。有兩個命令可以用於生成 RDB 文件,一個是 save ,另一個是 bgsavesave 命令會阻塞當前的 Redis 進程,直到 RDB 文件創建完畢,在這期間,服務器不能處理任何命令請求。而 bgsave 則會派生出一個子進程,然后由子進程生成 RDB 文件,服務進程不受干擾,繼續處理命令請求。
  • RDB 會生成多個數據文件,每個數據文件都代表了某一個時刻中 Redis 的數據,這種多個數據文件的方式,非常適合做冷備,可以將這種完整的數據文件發送到一些遠程的安全存儲上去。
  • RDB 對 Redis 對外提供的讀寫服務,影響非常小,可以讓 Redis 保持高性能,因為 Redis 主進程只需要 fork 一個子進程,讓子進程執行磁盤 IO 操作來進行 RDB 持久化即可。
  • 相對於 AOF 持久化機制來說,直接基於 RDB 數據文件來重啟和恢復 Redis 進程,更加快速。
  • 在 Redis 故障時,盡可能少的丟失數據,那么 RDB 沒有 AOF 好。一般來說,RDB 數據快照文件,都是每隔 5 分鍾,或者更長時間生成一次,這個時候就得接受一旦 Redis 進程宕機,那么會丟失最近 5 分鍾的數據。
  • RDB 每次在 fork 子進程來執行 RDB 快照數據文件生成的時候,如果數據文件特別大,可能會導致對客戶端提供的服務暫停數毫秒,或者甚至數秒。

AOF 的優缺點:

  • AOF 可以更好的保護數據不丟失,一般 AOF 會每隔 1 秒,通過一個后台線程執行一次 fsync 操作,最多丟失 1 秒鍾的數據。
  • AOF 日志文件以 append-only 模式寫入,所以沒有任何磁盤尋址的開銷,寫入性能非常高,而且文件不容易破損,即使文件尾部破損,也很容易修復。
  • AOF 日志文件即使過大的時候,出現后台重寫操作,也不會影響客戶端的讀寫。因為在 rewrite log 的時候,會對其中的指令進行壓縮,創建出一份需要恢復數據的最小日志出來。在創建新日志文件的時候,老的日志文件還是照常寫入。當新的 merge 后的日志文件 ready 的時候,再交換新老日志文件即可。
  • AOF 日志文件的命令通過可讀較強的方式進行記錄,這個特性非常適合做災難性的誤刪除的緊急恢復。
  • 對於同一份數據來說,AOF 日志文件通常比 RDB 數據快照文件更大。
  • AOF 開啟后,支持的寫 QPS 會比 RDB 支持的寫 QPS 低,因為 AOF 一般會配置成每秒 fsync 一次日志文件,當然,每秒一次 fsync ,性能也還是很高的。(如果實時寫入,那么 QPS 會大降,Redis 性能會大大降低)
  • 以前 AOF 發生過 bug,就是通過 AOF 記錄的日志,進行數據恢復的時候,沒有恢復一模一樣的數據出來。所以說,類似 AOF 這種較為復雜的基於命令日志 / merge / 回放的方式,比基於 RDB 每次持久化一份完整的數據快照文件的方式,更加脆弱一些,容易有 bug。不過 AOF 就是為了避免 rewrite 過程導致的 bug,因此每次 rewrite 並不是基於舊的指令日志進行 merge 的,而是基於當時內存中的數據進行指令的重新構建,這樣健壯性會好很多。

兩種持久化方式選擇:

  • 不要僅僅使用 RDB,因為那樣會導致你丟失很多數據。
  • 也不要僅僅使用 AOF,因為那樣有兩個問題:第一,你通過 AOF 做冷備,沒有 RDB 做冷備來的恢復速度更快;第二,RDB 每次簡單粗暴生成數據快照,更加健壯,可以避免 AOF 這種復雜的備份和恢復機制的 bug。
  • Redis 支持同時開啟開啟兩種持久化方式,我們可以綜合使用 AOF 和 RDB 兩種持久化機制,用 AOF 來保證數據不丟失,作為數據恢復的第一選擇; 用 RDB 來做不同程度的冷備,在 AOF 文件都丟失或損壞不可用的時候,還可以使用 RDB 來進行快速的數據恢復。

參考

https://github.com/doocs/advanced-java/blob/master/docs/high-concurrency/redis-persistence.md


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM