Redis面試終章來了!喜歡的話就轉發並關注我,前面的系列如下:
《 》
《 》
《 》
面試開始
一個大腹便便,穿著格子襯衣的中年男子,拿著一個滿是劃痕的mac向你走來,看著快禿頂的頭髮,心想著肯定是尼瑪頂級架構師吧!但是我們腹有詩書氣自華,虛都不虛。(這不是第一篇文章的面試官麼?)
小夥子,你還記得我在第一章裡面問過你,Redis有幾種基礎數據類型麼?
嗯嗯,帥氣的面試官,我肯定記得,沒齒難忘!!!
我特麼謝謝你,都四面了還不給Offer!
那你能說一下他們的特性,還有分別的使用場景麼?
行吧,那我先從String說起。
String:
這是最簡單的類型,就是普通的 set 和 get,做簡單的 KV 緩存。
但是真實的開發環境中,很多仔可能會把很多比較複雜的結構也統一轉成String去存儲使用,比如有的仔他就喜歡把對象或者List轉換為JSONString進行存儲,拿出來再反序列話啥的。
我在這裡就不討論這樣做的對錯了,但是我還是希望大家能在最合適的場景使用最合適的數據結構,對象找不到最合適的但是類型可以選最合適的嘛,之後別人接手你的代碼一看這麼規範,誒這小夥子有點東西呀,看到你啥都是用的
String,垃圾!好了這些都是題外話了,道理還是希望大家記在心裡,習慣成自然嘛,小習慣成就你。
String的實際應用場景比較廣泛的有:
- 緩存功能:String 字符串是最常用的數據類型,不僅僅是Redis,各個語言都是最基本類型,因此,利用Redis作為緩存,配合其它數據庫作為存儲層,利用Redis支持高併發的特點,可以大大加快系統的讀寫速度、以及降低後端數據庫的壓力。
- 計數器:許多系統都會使用Redis作為系統的實時計數器,可以快速實現計數和查詢的功能。而且最終的數據結果可以按照特定的時間落地到數據庫或者其它存儲介質當中進行永久保存。
- 共享用戶Session:用戶重新刷新一次界面,可能需要訪問一下數據進行重新登錄,或者訪問頁面緩存Cookie,但是可以利用Redis將用戶的Session集中管理,在這種模式只需要保證Redis的高可用,每次用戶Session 的更新和獲取都可以快速完成。大大提高效率。
Hash:
這個是類似 Map 的一種結構,這個一般就是可以將結構化的數據,比如一個對象(前提是這個對象沒嵌套其他的對象)給緩存在 Redis 裡,然後每次讀寫緩存的時候,可以就操作 Hash 裡的某個字段。
但是這個的場景其實還是多少單一了一些,因為現在很多對象都是比較複雜的,比如你的商品對象可能裡面就包含了很多屬性,其中也有對象。我自己使用的場景用得不是那麼多。
List:
List 是有序列表,這個還是可以玩兒出很多花樣的。
比如可以通過
List 存儲一些列表型的數據結構,類似粉絲列表、文章的評論列表之類的東西。比如可以通過 lrange 命令,讀取某個閉區間內的元素,可以基於 List 實現分頁查詢,這個是很棒的一個功能,基於 Redis 實現簡單的高性能分頁,可以做類似微博那種下拉不斷分頁的東西,性能高,就一頁一頁走。
比如可以搞個簡單的消息隊列,從 List 頭懟進去,從 List 屁股那裡弄出來。
List本身就是我們在開發過程中比較常用的數據結構了,熱點數據更不用說了。
- 消息隊列:Redis的鏈表結構,可以輕鬆實現阻塞隊列,可以使用左進右出的命令組成來完成隊列的設計。比如:數據的生產者可以通過Lpush命令從左邊插入數據,多個數據消費者,可以使用 BRpop命令阻塞的“搶”列表尾部的數據。
- 文章列表或者數據分頁展示的應用。 比如,我們常用的博客網站的文章列表,當用戶量越來越多時,而且每一個用戶都有自己的文章列表,而且當文章多時,都需要分頁展示,這時可以考慮使用Redis的列表,列表不但有序同時還支持按照範圍內獲取元素,可以完美解決分頁查詢功能。大大提高查詢效率。
Set:
Set 是無序集合,會自動去重的那種。
直接基於 Set 將系統裡需要去重的數據扔進去,自動就給去重了,如果你需要對一些數據進行快速的全局去重,你當然也可以基於 JVM 內存裡的 HashSet 進行去重,但是如果你的某個系統部署在多臺機器上呢?得基於Redis進行全局的
Set 去重。可以基於 Set 玩兒交集、並集、差集的操作,比如交集吧,我們可以把兩個人的好友列表整一個交集,看看倆人的共同好友是誰?對吧。
反正這些場景比較多,因為對比很快,操作也簡單,兩個查詢一個Set搞定。
Sorted Set:
Sorted set 是排序的 Set,去重但可以排序,寫進去的時候給一個分數,自動根據分數排序。
有序集合的使用場景與集合類似,但是set集合不是自動有序的,而Sorted set可以利用分數進行成員間的排序,而且是插入時就排序好。所以當你需要一個有序且不重複的集合列表時,就可以選擇Sorted set數據結構作為選擇方案。
- 排行榜:有序集合經典使用場景。例如視頻網站需要對用戶上傳的視頻做排行榜,榜單維護可能是多方面:按照時間、按照播放量、按照獲得的贊數等。
- 用Sorted Sets來做帶權重的隊列,比如普通消息的score為1,重要消息的score為2,然後工作線程可以選擇按score的倒序來獲取工作任務。讓重要的任務優先執行。 微博熱搜榜,就是有個後面的熱度值,前面就是名稱
小結
Redis基礎類型有五種,這個我在基礎裡面也有提到了,這個問題其實一般都是對P6以下,也就是1-3年左右的小夥伴可能是會問得比較多的問題。
能回答出來五種我想大家都可以,但是不知道大家是否知道,五種類型具體的使用場景,以及什麼時候用什麼類型最合適呢?
要是你回答的不好,沒說出幾種數據類型,也沒說什麼場景,你完了,面試官對你印象肯定不好,覺得你平時就是做個簡單的 set 和 get。所以看似很簡單的面試題實則最容易看出你的深淺了,大家都要注意打好基礎。
你有沒有考慮過,如果你多個系統同時操作(併發)Redis帶來的數據問題?
嗯嗯這個問題我以前開發的時候遇到過,其實併發過程中確實會有這樣的問題,比如下面這樣的情況
系統A、B、C三個系統,分別去操作Redis的同一個Key,本來順序是1,2,3是正常的,但是因為系統A網絡突然抖動了一下,B,C在他前面操作了Redis,這樣數據不就錯了麼。
就好比下單,支付,退款三個順序你變了,你先退款,再下單,再支付,那流程就會失敗,那數據不就亂了?你訂單還沒生成你卻支付,退款了?明顯走不通了,這在線上是很恐怖的事情。
那這種情況怎麼解決呢?
我們可以找個管家幫我們管理好數據的嘛!
某個時刻,多個系統實例都去更新某個 key。可以基於 Zookeeper 實現分佈式鎖。每個系統通過 Zookeeper 獲取分佈式鎖,確保同一時間,只能有一個系統實例在操作某個 Key,別人都不允許讀和寫。
你要寫入緩存的數據,都是從 MySQL 裡查出來的,都得寫入 MySQL 中,寫入 MySQL 中的時候必須保存一個時間戳,從 MySQL 查出來的時候,時間戳也查出來。
每次要寫之前,先判斷一下當前這個 Value 的時間戳是否比緩存裡的 Value 的時間戳要新。如果是的話,那麼可以寫,否則,就不能用舊的數據覆蓋新的數據。
你只要用緩存,就可能會涉及到緩存與數據庫雙存儲雙寫,你只要是雙寫,就一定會有數據一致性的問題,那麼你如何解決一致性問題?
一般來說,如果允許緩存可以稍微的跟數據庫偶爾有不一致的情況,也就是說如果你的系統不是嚴格要求 “緩存+數據庫” 必須保持一致性的話,最好不要做這個方案,即:讀請求和寫請求串行化,串到一個內存隊列裡去。
串行化可以保證一定不會出現不一致的情況,但是它也會導致系統的吞吐量大幅度降低,用比正常情況下多幾倍的機器去支撐線上的一個請求。
把一些列的操作都放到隊列裡面,順序肯定不會亂,但是併發高了,這隊列很容易阻塞,反而會成為整個系統的弱點,瓶頸
你瞭解最經典的KV、DB讀寫模式麼?
最經典的緩存+數據庫讀寫的模式,就是 Cache Aside Pattern
- 讀的時候,先讀緩存,緩存沒有的話,就讀數據庫,然後取出數據後放入緩存,同時返回響應。
- 更新的時候,先更新數據庫,然後再刪除緩存。
為什麼是刪除緩存,而不是更新緩存?
原因很簡單,很多時候,在複雜點的緩存場景,緩存不單單是數據庫中直接取出來的值。
比如可能更新了某個表的一個字段,然後其對應的緩存,是需要查詢另外兩個表的數據並進行運算,才能計算出緩存最新的值的。
另外更新緩存的代價有時候是很高的。是不是說,每次修改數據庫的時候,都一定要將其對應的緩存更新一份?也許有的場景是這樣,但是對於
比較複雜的緩存數據計算的場景,就不是這樣了。如果你頻繁修改一個緩存涉及的多個表,緩存也頻繁更新。但是問題在於,這個緩存到底會不會被頻繁訪問到?舉個栗子:一個緩存涉及的表的字段,在 1 分鐘內就修改了 20 次,或者是 100 次,那麼緩存更新 20 次、100 次;但是這個緩存在 1 分鐘內只被讀取了 1 次,有大量的冷數據。
實際上,如果你只是刪除緩存的話,那麼在 1 分鐘內,這個緩存不過就重新計算一次而已,開銷大幅度降低。用到緩存才去算緩存。
其實刪除緩存,而不是更新緩存,就是一個 Lazy 計算的思想,不要每次都重新做複雜的計算,不管它會不會用到,而是讓它到需要被使用的時候再重新計算。
像 Mybatis,Hibernate,都有懶加載思想。查詢一個部門,部門帶了一個員工的 List,沒有必要說每次查詢部門,都裡面的 1000 個員工的數據也同時查出來啊。80% 的情況,查這個部門,就只是要訪問這個部門的信息就可以了。先查部門,同時要訪問裡面的員工,那麼這個時候只有在你要訪問裡面的員工的時候,才會去數據庫裡面查詢 1000 個員工。
Redis 和 Memcached 有啥區別,為啥選擇用Redis作為你們的緩存中間件?
Redis 支持複雜的數據結構:
Redis 相比 Memcached 來說,擁有更多的數據結構,能支持更豐富的數據操作。如果需要緩存能夠支持更復雜的結構和操作, Redis 會是不錯的選擇。
Redis 原生支持集群模式:
在 redis3.x 版本中,便能支持 Cluster 模式,而 Memcached 沒有原生的集群模式,需要依靠客戶端來實現往集群中分片寫入數據。
性能對比:
由於 Redis 只使用單核,而 Memcached 可以使用多核,所以平均每一個核上
Redis 在存儲小數據時比 Memcached 性能更高。而在 100k 以上的數據中,Memcached 性能要高於 Redis,雖然 Redis 最近也在存儲大數據的性能上進行優化,但是比起 Remcached,還是稍有遜色。Tip:其實面試官這麼問,是想看你知道為啥用這個技術棧麼?你為啥選這個技術棧,你是否做過技術選型的對比,優缺點你是否瞭解,你啥都不知道,只是為了用而用,那你可能就差點意思了。
Redis 的線程模型瞭解麼?
Redis 內部使用文件事件處理器 file event handler,這個文件事件處理器是單線程的,所以 Redis 才叫做單線程的模型。它採用 IO 多路複用機制同時監聽多個 Socket,根據 Socket 上的事件來選擇對應的事件處理器進行處理。
文件事件處理器的結構包含 4 個部分:
- 多個 Socket
- IO 多路複用程序
- 文件事件分派器
- 事件處理器(連接應答處理器、命令請求處理器、命令回覆處理器)
多個 Socket 可能會併發產生不同的操作,每個操作對應不同的文件事件,但是 IO 多路複用程序會監聽多個 Socket,會將 Socket 產生的事件放入隊列中排隊,事件分派器每次從隊列中取出一個事件,把該事件交給對應的事件處理器進行處理。
面試結束
小夥子對你面試了四輪,你說話有理有據,邏輯清晰,來公司後肯定是一把好手,我想要不你來當我的Leader吧,哈哈?
面試官別跟我開玩笑了,我跟您這樣日積月累的技術專家還是有很多差距的,您的經驗和技術上的深度,沒有很長時間的磨練是無法達到的,我還得多跟您學習。
好的,小夥子有點東西,你年少有為不自卑,知道什麼是珍貴,就是你了來上班吧。
閱讀更多 IT技術管理那些事兒 的文章