老司機帶你玩轉面試(1):快取中介軟體 Redis 基礎知識以及資料持久化

極客挖掘機發表於2020-07-12

引言

今天週末,我在家坐著掐指一算,馬上又要到一年一度的金九銀十招聘季了,國內今年上半年受到 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

相關文章