《我們一起進大廠》系列-Redis雙寫一致性、併發競爭、執行緒模型

敖丙發表於2019-11-11

你知道的越多,你不知道的越多

點贊再看,養成習慣

GitHub上已經開源github.com/JavaFamily,有一線大廠面試點腦圖,歡迎Star和完善

絮叨

男兒何不帶吳鉤,收取關山五十州 FPX ?B,LPL兩年連冠? ?B!

看著金色的雨落下,我到窗邊,發現天有點藍,風有點綿,我的眼角又溼了!

最近雙十一講道理有點忙的說,直接肝爆,就是這樣作為暖男的我,還是給你們擠出時間搞出終章,忍不住給自己點贊?

放個雙十一照片證明真的忙,希望別取關!!!

現在你們在看的時候,我應該還在睡覺哈哈。困?

之前跟你們說的,限流降級,是不是在雙十一又應驗了,下單介面其實沒掛,犧牲部分使用者體驗,保住伺服器,你多點幾下是可以成功的,等流量高峰過去了,所有的使用者全部都恢復正常訪問,伺服器也沒啥事。

去年退款介面被打崩了,今年阿里明顯也聰明瞭很多。

正文

上幾期吊打系列我們提到了Redis的很多知識,還沒看的小夥伴可以回顧一下

那提到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 計算的思想,不要每次都重新做複雜的計算,不管它會不會用到,而是讓它到需要被使用的時候再重新計算。

MybatisHibernate,都有懶載入思想。查詢一個部門,部門帶了一個員工的 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吧,哈哈?

面試官別跟我開玩笑了,我跟您這樣日積月累的技術專家還是有很多差距的,您的經驗和技術上的深度,沒有很長時間的磨練是無法達到的,我還得多跟您學習。

好的,小夥子有點東西,你年少有為不自卑,知道什麼是珍貴,就是你了來上班吧。

好的面試官,不過我想我在Java基礎,MQ,Dubbo等等領域還有好多知識點您沒問我,要不下次繼續面我?

強行,為吊打下一期埋伏筆哈哈,下期寫啥你們定!!!

能撐到最後,你自己都忍不住自己給自己點個讚了!

暗示點贊,每次都看了不點贊,你們想白嫖我麼?你們好壞喲,不過我喜歡)。


《吊打面試官》Redis系列 ---- 全劇終


總結

既然都說了是Redis的終章我最後也做個Redis方面常見面試題,題目的總結,答案大家要去思考我前面的文章基本上都提到了,結果可以去我公眾號回覆【答案】獲取,不過我還是希望大家能看到題目就能想到答案,並且記在心中,教大家怎麼回答只是幫大家組織下語言,真正的場景解決方案還是要大家理解的。

(週三以後出答案,我先睡會)

  • 0、在叢集模式下,Redis 的 Key 是如何定址的?分散式定址都有哪些演算法?瞭解一致性 Hash 演算法嗎?
  • 1、使用Redis有哪些好處?
  • 2、Redis相比Memcached有哪些優勢?
  • 3、Redis常見效能問題和解決方案
  • 4、MySQL裡有2000w資料,Redis中只存20w的資料,如何保證Redis中的資料都是熱點資料?
  • 5、Memcache與Redis的區別都有哪些?
  • 6、Redis 常見的效能問題都有哪些?如何解決?
  • 7、在什麼樣的場景下可以充分的利用Redis的特性,大大提高Redis的效率?
  • 8、Redis的快取雪崩、穿透、擊穿瞭解麼?有什麼異同點?分別怎麼解決?
  • 9、Redis的基本型別有哪些?他們的使用場景瞭解麼?比較高階的用法你使用過麼?
  • 10、Redis主從怎麼同步資料的?叢集的高可用怎麼保證?持久化機制瞭解麼?
  • 11、為什麼 redis 單執行緒卻能支撐高併發?
  • 12、如何保證快取和資料庫資料的一致性?
  • 13、專案中是怎麼用快取的,用了快取之後會帶來什麼問題?

絮叨+

最後我想說的就是,我這四章只是介紹到了一些Redis面試比較常見的問題,其實還有很多點我都沒回答到,大家如果為了對付面試可能是夠用了,但是我們技術人員還是要保持對技術的敬畏心,你不能淺嘗即止,還是要深究的。

你永遠只會用,不去考慮用了會帶來的問題,以及出現問題之後的解決方案,我覺得你大概率會停滯不前,既然入都入了這行了,為啥不武裝一下自己。

其實學習技術是個反哺的過程,學習的時候可能你只是感覺知識廣度、深度上去了,一個知識點你這樣,兩個、三個知識點你都這樣,最後你發現你的技術已經跟身邊一樣P6的仔不一樣了,這樣你可能在團隊重大專案的貢獻都上去了,那P7的晉升機率是不是大了,錢是不是上去了,女朋友是不是好看了,房子是不是大了。

點關注,不迷路

好了各位,以上就是這篇文章的全部內容了,能看到這裡的人呀,都是人才

我後面會每週都更新幾篇一線網際網路大廠面試和常用技術棧相關的文章,非常感謝人才們能看到這裡,如果這個文章寫得還不錯,覺得「敖丙」我有點東西的話 求點贊? 求關注❤️ 求分享? 對暖男我來說真的 非常有用!!!

白嫖不好,創作不易,各位的支援和認可,就是我創作的最大動力,我們下篇文章見!

敖丙 | 文 【原創】

如果本篇部落格有任何錯誤,請批評指教,不勝感激 !


文章每週持續更新,可以微信搜尋「 三太子敖丙 」第一時間閱讀和催更(比部落格早一到兩篇喲),本文 GitHub github.com/JavaFamily 已經收錄,有一線大廠面試點思維導圖,也整理了很多我的文件,歡迎Star和完善,大家面試可以參照考點複習,希望我們一起有點東西。

相關文章