【JAVA今法修真】 第三章 關係非關係 redis法器

南橘ryc發表於2021-12-13

您好,我是南橘,萬法仙門的掌門,剛剛從九州世界穿越到地球,因為時空亂流的影響導致我的法力全失,現在不得不通過這個平臺向廣大修真天才們借去力量。你們的每一個點贊,每一個關注都是讓我回到九州世界的助力,兄弟萌來為我注入修為吧!關注WX號:南橘ryc

等我回去以後,大家都是萬法仙門的長老,我會給大家數不盡的天材地寶,人人如龍,全民飛昇。

作為一個仙俠世界的宗門,當然會存在宗門大比這種事情。

而作為我們的主角,李小庚自然也會在其中大放光彩。

萬法仙門,宗門三號擂臺,築基組淘汰賽。

“承讓!”

輕輕揮了揮衣袖,我們的主角李小庚朝已經飛出擂臺的對手點了點頭。

“三號擂臺比試結束,勝者,雲霄殿,李小庚。”裁判面無表情地給李小庚發了一個牌子。

“這人也太賴了吧!”

“草(我愛你)一上臺就用法器,完全不給我們這些普通弟子活路啊!”

“雲霄殿都是一群狗大戶,結丹期的擂臺上,雲霄殿的二弟子帶著煉器宗的oracle大殺四方,沒想到這邊又是一樣的劇情。”

李小庚聽到這些議論,嘴角露出一絲無奈的笑容,使用絕技髮膠手輕輕整了整發型,望向議論紛紛的觀眾說道:“謝謝大家的支援,我會繼續努力的!”

“不過他好帥啊,我原諒他了。”

“是啊,看到他的臉,這樣一想不尊重公平競技其實也沒啥事吧?”

“草只是一種植物而已。”


回到一週前。

雲霄殿中,李小庚一如既往的在給雲小霄獻殷勤。

“師父,下週就是宗門大比了。您天天在萬法研究院裡搞研究,肯定很累吧,小徒弟來給您鬆鬆肩膀。”

雲小霄看著滿臉諂媚的李小庚,輕哼了一下:“放心,人人都有份,給你Redis行不行?”

“Redis?煉器宗出品,號稱能解決大部分分散式問題的神器?”

“沒錯,如果你想要,那我就要考驗考驗你。你知道Redis的五種基礎資料結構是什麼嗎?”

“簡單,師父,且聽我娓娓道來。”

一、Redis基礎知識

1、1 String 字串型別

redis中最基本的資料型別,一個key對應一個value

適用情況:

1、快取: 經典使用場景,把常用資訊,字串,圖片或者視訊等資訊放到redis中,redis作為快取層,mysql做持久化層,降低mysql的讀寫壓力。

2.計數器:redis是單執行緒模型,一個命令執行完才會執行下一個,同時資料可以一步落地到其他的資料來源。

3.session:通過redis實現session共享
複製程式碼

1、2 Hash (雜湊)

對於Java中的HashMap,本身是一種KV對結構,如 value={{field1,value1},......fieldN,valueN}},非常好理解

適用情況:

HashMap作為快取,相比於string更節省空間的維護快取資訊,適合儲存如使用者資訊,視訊資訊等
複製程式碼

底層用字典dict實現

1、3 List (連結串列)

Redis 的連結串列相當於 Java 語言裡面的 LinkedList

適用情況:

1、List在Redis中既可以做佇列也可以做棧使用,它的插入和刪除操作非常快,時間複雜度為 0(1),但是索引定位很慢,時間 複雜度為 O(n)。

2、可以作為微博的時間軸,有人釋出微博,用lpush加入時間軸,展示新的列表資訊。     

3、可以實現阻塞佇列,左進右出的佇列組完成設計
複製程式碼

list底層使用quickList(快速連結串列)實現

在早期的設計中, 當列表物件中元素的長度比較小或者數量比較少的時候,採用ziplist來儲存,當列表物件中元素的長度比較大或者數量比較多的時候,則會轉而使用雙向列表linkedlist來儲存。

這兩種儲存方式都各有優缺點

  • 雙向連結串列linkedlist便於在表的兩端進行push和pop操作,在插入節點上覆雜度很低,但是它的記憶體開銷比較大。首先,它在每個節點上除了要儲存資料之外,還要額外儲存兩個指標;其次,雙向連結串列的各個節點是單獨的記憶體塊,地址不連續,節點多了容易產生記憶體碎片。
  • ziplist儲存在一段連續的記憶體上,所以儲存效率很高。但是,它不利於修改操作,插入和刪除操作需要頻繁的申請和釋放記憶體。特別是當ziplist長度很長的時候,一次realloc可能會導致大批量的資料拷貝。

3.2版本更新之後,list的底層實現變成了quickList

quickList是 zipList 和 linkedList 的混合體,它將 linkedList 按段切分,每一段使用 zipList 來緊湊儲存,多個 zipList 之間使用雙向指標串接起來。

1、4 Set 集合

Redis 的集合相當於 Java 語言裡面的 HashSet ,它內部的鍵值對是無序的、唯一 的。

它的內部實現相當於一個特殊的字典,字典中所有的 value 都是一個值 NULL 當集合中最後一個元素被移除之後,資料結構被自動刪除,記憶體被回收。
複製程式碼

適用情況:

1、標籤(tag),給使用者新增標籤,或者使用者給訊息新增標籤,這樣有同一標籤或者類似標籤的可以給推薦關注的事或者關注的人。

2、點贊,或點踩,收藏等,可以放到set中實現

3、可以用來儲存在某活動中中獎的使用者 ID ,因為有去重功能,可以保證同 一個使用者不會中獎兩次。
複製程式碼

1、5 Zset 有序集合

它類似於 Java SortedSet HashMap 的結合體, 方面它是個 set ,保證 了內部 value 的唯性,另方面它可 以給每個 value 賦予一個 score ,代表 這個 value 的排序權重。它的內部實現 用的是一種叫作“跳躍表”的資料 結構。

從這張圖片,我們可以看出來:跳躍表的底層是一個順序連結串列,每隔一個節點有一個上層的指標指向下一個節點,並層層向上遞迴。這樣設計成類似樹形的結構,可以使得對於連結串列的查詢可以到達二分查詢的時間複雜度。

skiplist他不要求上下兩層連結串列之間個數的嚴格對應關係,他為每個節點隨機出一個層數。比如上圖第三個節點的隨機出的層數是4,那麼就把它插入到四層的空間上,而第四個節點隨機出的層數是1,那它就只存在第一層空間上。

  • 當資料較少的時候,zset是由一個ziplist來實現的,就和list底層之前是一樣的

ziplist是由一系列特殊編碼的連續記憶體塊組成的順序儲存結構,類似於陣列,ziplist在記憶體中是連續儲存的,但是不同於陣列,為了節省記憶體 ziplist的每個元素所佔的記憶體大小可以不同。 ziplist將一些必要的偏移量資訊記錄在了每一個節點裡,使之能跳到上一個節點或下一個節點。

  • 當資料較多的時候,zset是一個由dict 和一個 skiplist來實現的,dict用來查詢資料到分數的對應關係,而skiplist用來根據分數查詢資料

除了這五大基礎資料結構,Redis還有更加專業的資料結構 HyperLogLog(基數統計的演算法)、Geo(地理位置系列)、Pub\Sub(訊息佇列)、Pipeline(管道)、BloomFiler(布隆過濾器),都在不同的地方有用到,有些我會在下文向大家介紹。

1、6 Pipeline

可以將多次IO往返時間縮減為一次,前提是pipleline執行的指令之間沒有因果關係

管道(pipeline)可以一次性傳送多條命令並在執行完後一次性將結果返回,pipeline 通過減少客戶端與redis的通訊次數來實現降低往返延時時間,而且Pipeline 實現的原理是佇列,而佇列的原理是時先進先出,這樣就保證資料的順序性。

注意:pipeline機制可以優化吞吐量,但無法提供原子性/事務保障

“哎喲,不錯哦,還能舉一反三,是不是一早就打起Redis的主意了?”雲小霄玩味的盯著李小庚的眼睛.

不過李小庚是何許人物?舉世譽之而不加勸,舉世非之而不加沮,臉色毫無任何變化:“哪能啊師父,你也知道我除了修煉就是去圖書館了,關於redis的書籍這段時間也是小有涉獵,不信您再考考我?”

“長本事了嘛小庚同學!”雲小霄來了興趣。

當年收李小庚入門的時候,百分之五十(其實是百分之百)是看臉,剩下百分之五十的考量也是他的基礎非常紮實,完全不像普通的煉氣期。李小庚本人也十分爭氣,入門半年便突破了築基期,並且不急不躁,一步一個腳印的踐行著萬法仙門三年築基的規劃。學習今法,雖然快速的掌握各種先進技能能很快的突破,但是若是沒有堅實的基礎,那麼未來的道路肯定會遇到各種各樣的問題。

“那麼我來考考你,Redis如何實現分散式鎖?”

“我有一言,請諸位靜聽。”

二、叢集與分散式鎖

2、1 Redis實現分散式鎖

隨著網際網路技術的飛速發展,越來越多的單體架構已經轉型成了分散式架構,,分散式架構確實能帶來效能和效率上的提升,但是也會帶來資料一致性的問題。

分散式鎖,就是解決分散式架構中資料一致性的專用武器,分散式鎖需要滿足一下三個方面方可放心使用:

  • 排他性:在同一時間只會有一個客戶端能獲取到鎖,其它客戶端無法同時獲取

  • 避免死鎖:這把鎖在一段有限的時間之後,一定會被釋放(正常釋放或異常釋放)

  • 高可用:獲取或釋放鎖的機制必須高可用且效能佳

目前,我所知道的分散式鎖大概有三種主流方式實現,分別是zookpeer,redis,還有本地資料庫,今天我就介紹一下如何用redis實現分散式鎖。

基於Redis實現的鎖機制,主要是依賴redis自身的原子操作

setnx爭搶鎖,再用expire新增過期時間

沒有看錯,就是這麼簡單,如果害怕不妥,比如爭搶鎖的時候還沒有設定過期時間就突然當機之類的問題,可以直接用jedis等封裝好的RedisTemplate把setnx和expire合成一條指令使用。

“但是這樣的分散式鎖真的安全嗎?如果法器伺服器當機怎麼辦?”

“當然,這樣的單體redis所使用的分散式鎖的安全性並不高。”李小庚彷彿知道雲小霄會這樣問,嘴角不經意泛起一抹微笑。

首先,單點故障的問題不可避免

其次,因為使用鎖的客戶端,和redis伺服器,不在一起啊!時間是有延遲的,我們只能依靠redis的TTL命令來查詢鎖的剩餘時間,然後根據這個剩餘時間來判斷鎖是否超時。
然而在通常的計算機系統中,很難獲取到一個可靠的時間。

  • 系統可能由於時間伺服器同步調整時間,
  • 虛擬機器可能調整時間,
  • JVM GC可能導致時間停頓
    RedLock的出現在一定程度上解決了這個問題,它的執行流程如下:
  1. 客戶端獲取當前時間,生成一個隨機值作為鎖的值(目的是更加精確的獲得時間)
  2. 依次嘗試在所有5個redis上取得同一個鎖(使用類似單機redis鎖的方法, 使用同樣的key和同一個隨機值)
    獲取鎖的操作本身需要設定一個比較小的超時時間(如5-50ms), 防止在一個掛掉的redis上浪費太多時間
    如果一個redis不可用,要儘快開始嘗試下一個
  3. 客戶端計算獲取鎖一共用了多長時間,通過用當前時間減去第1步得到的時間
    如果客戶端獲取了多數redis上的這個鎖(3到五個5),並且這時還沒有超過鎖的超時時間,
    這個鎖就算是獲取成功了
  4. 如果鎖獲取成功了,有效時間就按鎖超時時間-獲取鎖花費時間算
  5. 如果失敗,嘗試在所有redis上解除鎖
    (解除鎖的操作是一段lua script,刪除一個key如果key的value是第1步生成的隨機值)

當然,它也不能解決問題,但是, redis鎖只會在比較極端的情況下出錯,如果不是需要特別精確,只需要保證絕大多數可靠的時候,可以放心使用redis叢集或者redlock。

“你說你使用RedLock工具,那麼我直接擊毀你的redis單體怎麼辦?你還如何保持分散式鎖的健壯性?而且我們的萬法玄天陣也是用到了分散式鎖來確保出入弟子的身份ID不被盜用,按你的方式來使用,如果結果不夠精確,那對於其他門派來說我們的陣法就和篩子沒有區別了。”雲小霄揮手從殿內的盆栽上弄來了幾個小橘子,望向了第三面牆外的各位,好像在提示大家可以喝口水動一動了。

“那麼,就要提到Redis叢集了。”李小庚推了推眼鏡。

2、2Redis叢集

叢集同步機制

Redis中有主從機制,一個主節點對應一個或多個從節點,主節點提供資料存取,從節點則是從主節點拉取資料備份,當這個主節點掛掉後,就會有這個從節點選取一個來充當主節點,從而保證叢集不會掛掉。先講一下Redis主從同步的流程:

  • 1.第一次同步時,從伺服器向主伺服器傳送一次SYNC命令,主伺服器收到之後做一次bgsave、並同時將後續修改操作記錄到記憶體buffer,待完成後將RDB檔案全量同步到複製節點
  • 2.複製節點接收完成後將RDB映象載入到記憶體中,載入完成後,再通知主節點
  • 3.後續的增量資料通過AOF日誌同步即可,有點類似資料庫的binlog

同時,在2.8版本之後,Redis可以自動判斷是需要全量同步還是增量同步,效率比較高,增量同步其實就是在完成全量同步後,開始新複製時向主伺服器傳送PSYNC( )命令(runid是上次複製的主伺服器id,offset是從伺服器的複製偏移量),主伺服器會根據這個兩個引數來決定做哪種同步,判斷伺服器id是否和本機相同,複製偏移量是否在緩衝區中。

高可用性:

  • Redis Sentinal(哨兵模式)叢集著眼於高可用,在master當機時自動將slave提升為master,繼續提供服務
  • Redis Cluster叢集著眼於擴充套件性,在單個redis記憶體不足時,使用Cluster進行分片儲存

“原來是在這裡等著我呢。李小庚,如果我給你一套redis叢集,你如何去實現你所設想的方案呢?”雲小霄笑出了聲,本來只想給一個單體redis法器,再憑藉李小庚自己的紮實的基礎,就有希望在築基期大比上奪魁的,沒想到他的目標一直都是redis叢集。

“不急不急,我從書中看到了三套實現redis叢集的方案,還請師父幫忙演示一二。”李小庚也知道雲小霄知道了自己的目的,縱使臉皮再厚也開始微微泛紅,配上一雙劍眉下一對細長的桃花眼,雲小霄的口水不爭氣的流了下來。

雲小霄伸了伸懶腰,然後單手指天:“劍來!”。

無數紅白相間細小飛劍從房間的各個角落出現並環繞在她的身邊,這邊是李小庚一直心心念唸的redis叢集了。

“接下來叢集的指揮權交給你,看你表演咯~”

李小庚學有樣學樣:“今日我李小庚便要重回陸地神仙之境。”

三、Redis叢集的實現

3、1傳統的主從模式

大概是所有的叢集都有主從模式

主從模式的一個作用是備份資料,這樣當一個節點損壞(指不可恢復的硬體損壞)時,資料因為有備份,可以方便恢復。
另一個作用是負載均衡,所有客戶端都訪問一個節點肯定會影響Redis工作效率,有了主從以後,查詢操作就可以通過查詢從節點來完成。

在主從模式中,一個Master可以有多個Slaves,預設配置下,master節點可以進行讀和寫,slave節點只能進行讀操作,無法進行寫操作

如果修改預設配置,可以讓slave進行寫,但是這毫無意義,因為寫入的資料不會同步給其他slave,同時,master節點如果修改了,slave上的資料會馬上被覆蓋

slave節點掛了不影響其他slave節點的讀和master節點的讀和寫,重新啟動後會將資料從master節點同步過來。master節點掛了以後,不影響slave節點的讀,Redis將不再提供寫服務,master節點啟動後Redis將重新對外提供寫服務

所以,我們可以發現Redis的主從和Zookeeper的主從完全不一樣!它竟然不會選舉!

這個缺點影響是很大的,尤其是對生產環境來說,是一刻都不能停止服務的,所以一般的生產壞境是不會單單隻有主從模式的。所以有了下面的sentinel模式。

3、2sentinel模式(哨兵模式)

哨兵模式要搭配主從模式來使用,主從不能自己選舉,那我們就加一個哨兵,當sentinel發現master節點掛了以後,sentinel就會從slave中重新選舉一個master。

哨兵的作用就是監控Redis系統的執行狀況。它的功能包括以下兩個。

(1)監控主伺服器和從伺服器是否正常執行。 
(2)主伺服器出現故障時自動將從伺服器轉換為主伺服器。

這不就皆大歡喜了嗎?

哨兵的工作方式:

  • 1、每個Sentinel(哨兵)程式以每秒鐘一次的頻率向整個叢集中的Master主伺服器,Slave從伺服器以及其他Sentinel(哨兵)程式傳送一個 PING 命令。
  • 2、如果一個例項(instance)距離最後一次有效回覆 PING 命令的時間超過 down-after-milliseconds 選項所指定的值, 則這個例項會被 Sentinel(哨兵)程式標記為主觀下線(SDOWN)
  • 3、如果一個Master主伺服器被標記為主觀下線(SDOWN),則正在監視這個Master主伺服器的所有 Sentinel(哨兵)程式要以每秒一次的頻率確認Master主伺服器的確進入了主觀下線狀態
    當有足夠數量的 Sentinel(哨兵)程式(大於等於配置檔案指定的值)在指定的時間範圍內確認Master主伺服器進入了主觀下線狀態(SDOWN), 則Master主伺服器會被標記為客觀下線(ODOWN)
  • 4、在一般情況下, 每個 Sentinel(哨兵)程式會以每 10 秒一次的頻率向叢集中的所有Master主伺服器、Slave從伺服器傳送 INFO 命令。
    當Master主伺服器被 Sentinel(哨兵)程式標記為客觀下線(ODOWN)時,Sentinel(哨兵)程式向下線的 Master主伺服器的所有 Slave從伺服器傳送 INFO 命令的頻率會從 10 秒一次改為每秒一次。
  • 5、若沒有足夠數量的 Sentinel(哨兵)程式同意 Master主伺服器下線, Master主伺服器的客觀下線狀態就會被移除。若 Master主伺服器重新向 Sentinel(哨兵)程式傳送 PING 命令返回有效回覆,Master主伺服器的主觀下線狀態就會被移除。

sentinel模式基本可以滿足一般生產的需求,具備高可用性。但是當資料量過大到一臺伺服器存放不下的情況時,主從模式或sentinel模式就不能滿足需求了,這個時候需要對儲存的資料進行分片,將資料儲存到多個Redis例項中,這就是cluster模式。

3、3cluster模式

cluster的出現是為了解決單機Redis容量有限的問題,將Redis的資料根據一定的規則分配到多臺機器。

Redis-Cluster採用無中心結構,它的特點如下:

  • 所有的redis節點彼此互聯(PING-PONG機制),內部使用二進位制協議優化傳輸速度和頻寬。

  • 節點的失效是通過叢集中超過半數的節點檢測失效時才生效。

  • 客戶端與redis節點直連,不需要中間代理層.客戶端不需要連線叢集所有節點,連線叢集中任何一個可用節點即可。

cluster可以說是sentinel和主從模式的結合體,通過cluster可以實現主從和master重選功能,所以如果配置三個副本三個分片的話,就需九六個Redis例項。
因為Redis的資料是根據一定規則分配到cluster的不同機器的,當資料量過大時,可以新增機器進行擴容,這種模式適合資料量巨大的快取要求,當資料量不是很大使用sentinel即可。

引用一張大佬的圖片來直觀展現一下什麼是 Redis-Cluster

每個請求訪問Redis-Cluster叢集的時候,都會進行一個路由,路由可以通過Hash(也可以用別的)來進行隨機分片,但是如果完全hash的話很可能導致分片們旱的旱死,澇的澇死。,所以,提出了一致性雜湊(自動快取遷移)+虛擬節點(自動負載均衡)的方法來解決問題

一致性雜湊的原理 :將所有master node落在一個圓環上面,然後,有一個key過來之後。同樣就是hash值,然後會用hash值在圓環對應的各個點上(每個點都有一個hash值)去對比,看hash值落在那個位置,落在圓環上面以後,就會順時針旋轉去尋找距離自己最近的一個節點,資料的儲存於讀取都在該節點進行。

一致性雜湊的優勢 :保證了任何一個master當機,只會影響之前在那個master上面的資料,因為照著順時針走,全部在之前的master上面找不到了,master也當機了,就會繼續順著順時針走到下一個master節點去。這樣就只會有一部分資料丟失。

在李小庚的指揮下,reids叢集也慢慢的變成了他的模樣,一會兒在空中變成,一會兒變成,好不快樂。

“不要急,小庚,除此之外,關於redis還有一些其他的東西你需要掌握。”

四、Redis進階知識

4、1非同步佇列

Redis的本職工作是快取,但是由於它多才多藝,成為佇列也不錯,有一些阻塞式的API讓其有能力做訊息佇列;另外,做訊息佇列的其他特性例如FIFO(先入先出)也很容易實現,只需要一個List物件從頭取資料,從尾部塞資料即可

  • 在Redis中,如果讓List結構作為佇列、rpush生產訊息、lpop消費訊息、當lpop沒有訊息的時候,可以當sleep一會再重試,這就相當於生產者消費模式模式了。同時List有個指令叫blpop,在沒有訊息的時候,它會阻塞住直到訊息到來。
  • pub\sub主題訂閱者模式、可以實現1對N的訊息佇列,實現生產一次,消費多次。但是,它也有不足之處,如果讓pub\sub主題訂閱者模式、消費者下線的情況下,訊息會丟失、不如直接用MQ

4、2延時佇列

在Redis中,可以利用 sorted-set 來做延時佇列

zadd key score1 value1 score2 value2

  • socre為執行時間,key為佇列名,value為資料
  • 消費佇列迴圈從sorted-set根據score獲取(zrangebyscore)小於等於當前時間的且score最小的一條資料輪詢處理
  • 如果沒有取到資料,睡一會再去獲取

但是,Redis的延時佇列無法返回ACK,所以需要自己實現

4、3 持久化

Redis有兩種持久化的方式,分別是RDB和AOF

RDB做映象全量持久化、AOF做增量持久化,因為RDB會耗費較長時間,不夠實時,在停機的時候會導致大量有效資料丟失,所以需要AOF來配合使用,在redis例項重啟時,會使用RDB持久化檔案重新構建記憶體,再使用AOF重放近期的操作指令來實現完整恢復重啟之前的狀態。

RDB機制

RDB持久化是指在指定的時間間隔內將記憶體中的資料集快照寫入磁碟。也是預設的持久化方式,這種方式是就是將記憶體中資料以快照的方式寫入到二進位制檔案中,預設的檔名為dump.rdb。RDB提供了三種機制來觸發持久化

  • 1、save觸發方式---客戶端發起save請求

    執行save命令期間,Redis不能處理其他命令,直到RDB過程完成為止

  • 2、bgsave觸發方式--客戶端發起bgsave請求

    執行該命令時,Redis會在後臺非同步進行快照操作,快照同時還可以響應客戶端請求

  • 3、自動觸發

    自動觸發是由我們的配置檔案來完成的,在redis.conf檔案中配置,大家可以去了解一下,這裡就不寫那麼多東西了

AOF機制

全量備份總是耗時的(隨機的傳說總是好的???),有時候我們提供一種更加高效的方式AOF,Redis會將每一個收到的寫命令都通過write函式追加到檔案中,就是日誌記錄。

和RDB一樣,AOF也有三種同步機制:

  • 1、always:同步持久化 每次發生資料變更會被立即記錄到磁碟 效能較差但資料完整性比較好
  • 2、everysec:每秒同步,非同步操作,每秒記錄 如果一秒內當機,有資料丟失
  • 3、no:從不同步

Redis本身的機制是AOF持久化開啟存在AOF檔案時,優先載入AOF檔案。AOF檔案不存在時候,載入RDB檔案。載入AOF\RDB檔案後,Redis啟動成功。AOF\RDB檔案存在錯誤時,Redis啟動失敗並列印錯誤資訊。

不要問AOF和RDB用哪個,我的經驗就是,全都用。 RDB同步快,但是要損失最多五分鐘的內容,AOF同步慢,但是每秒同步的情況下最多損失1s的內容,損失的內容也可以通過日誌去找回。

解決機器斷電對資料丟失的影響

AOF日誌中可以進行sync屬性的配置,如果不要求效能,在每條寫指令時都sync一下磁碟,就不會丟失資料,但在高效能要求下每次都sync是不現實的,一般都使用定時sync,比如1s一次,這個時候就最多丟失1s的資料。

相關文章