【JAVA程序員進階之路】Redis基礎知識兩篇就知足(一)

導言

你們好,我是南橘,從接觸java到如今也有差很少兩年時間了,兩年時間,從一名連java有幾種數據結構都不懂超級小白,到如今懂了一點點的進階小白,學到了很多的東西。知識越分享越值錢,我這段時間總結(包括從別的大佬那邊學習,引用)了一些日常學習和麪試中的重點(自我認爲),但願給你們帶來一些幫助java

這篇文章的出現,首先要感謝一我的三太子敖丙 ,就是他的文章讓我發現,原來Redis的知識如此的多姿多彩。恩恩,他的文章,我是期期都看mysql

這是這篇文章的思惟導圖,由於用的是免費版的軟件,因此有很多水印,須要原版的能夠問我要程序員

一、Redis基礎知識

要學習Redis基礎知識,首先要知道Redis的五種基礎數據結構,咱們先從這五種數據結構的使用場景和一些工做中(面試中)容易出現的點來介紹面試

一、1 String 字符串類型

是redis中最基本的數據類型,一個key對應一個valueredis

適用狀況:算法

一、緩存: 經典使用場景,把經常使用信息,字符串,圖片或者視頻等信息放到redis中,redis做爲緩存層,mysql作持久化層,下降mysql的讀寫壓力。

2.計數器:redis是單線程模型,一個命令執行完纔會執行下一個,同時數據能夠一步落地到其餘的數據源。

3.session:經過redis實現session共享
複製代碼

一、2 Hash (哈希)

對於Java中的HashMap,自己是一種KV對結構,如 value={{field1,value1},......fieldN,valueN}},很是好理解sql

適用狀況:數據庫

HashMap做爲緩存,相比於string更節省空間的維護緩存信息,適合存儲如用戶信息,視頻信息等
複製代碼

底層用字典dict實現數組

一、3 List (鏈表)

Redis 的鏈表至關於 Java 語言裏面的 LinkedList緩存

適用狀況:

一、List在Redis中既能夠作隊列也能夠作棧使用,它的插入和刪除操做很是快,時間複雜度爲 0(1),可是索引定位很慢,時間 複雜度爲 O(n)。

二、能夠做爲微博的時間軸,有人發佈微博,用lpush加入時間軸,展現新的列表信息。     

三、能夠實現阻塞隊列,左進右出的隊列組完成設計
複製代碼

list底層使用quickList(快速鏈表)實現

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

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

  • 雙向鏈表linkedlist便於在表的兩端進行push和pop操做,在插入節點上覆雜度很低,可是它的內存開銷比較大。首先,它在每一個節點上除了要保存數據以外,還要額外保存兩個指針;其次,雙向鏈表的各個節點是單獨的內存塊,地址不連續,節點多了容易產生內存碎片。
  • ziplist存儲在一段連續的內存上,因此存儲效率很高。可是,它不利於修改操做,插入和刪除操做須要頻繁的申請和釋放內存。特別是當ziplist長度很長的時候,一次realloc可能會致使大批量的數據拷貝。

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

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

一、4 Set 集合

Redis 的集合至關於 Java 語言裏面的 HashSet ,它內部的鍵值對是無序的、惟一 的。 它的內部實現至關於一個特殊的字典,字典中全部的 value 都是一個值 NULL 當集合中最後一個元素被移除以後,數據結構被自動刪除,內存被回收。

適用狀況:

一、標籤(tag),給用戶添加標籤,或者用戶給消息添加標籤,這樣有同一標籤或者相似標籤的能夠給推薦關注的事或者關注的人。

二、點贊,或點踩,收藏等,能夠放到set中實現

三、能夠用來存儲在某活動中中獎的用戶 ID ,由於有去重功能,能夠保證同 一個用戶不會中獎兩次。
複製代碼

一、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(布隆過濾器),都在不一樣的地方有用到,有些我會在下文向你們介紹,還有一些深刻的我沒有了解這麼多,你們能夠去敖丙的文章中進一步瞭解

Pipeline

能夠將屢次IO往返時間縮減爲一次,前提是pipleline執行的指令之間沒有因果關係

管道(pipeline)能夠一次性發送多條命令並在執行完後一次性將結果返回,pipeline 經過減小客戶端與redis的通訊次數來實現下降往返延時時間,並且Pipeline 實現的原理是隊列,而隊列的原理是時先進先出,這樣就保證數據的順序性。

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

二、進階知識

二、1 Redis實現分佈式鎖

隨着互聯網技術的飛速發展,愈來愈多的單體架構已經轉型成了分佈式架構,,分佈式架構確實能帶來性能和效率上的提高,可是也會帶來數據一致性的問題。

分佈式鎖,就是解決分佈式架構中數據一致性的專用武器,分佈式鎖須要知足一下三個方面方可放心使用:

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

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

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

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

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

setnx爭搶鎖,再用expire添加過時時間

你沒有看錯,就是這麼簡單,若是懼怕不妥,好比爭搶鎖的時候尚未設置過時時間就忽然宕機之類的問題,能夠直接用jedis等封裝好的RedisTemplate把setnx和expire合成一條指令使用。

可是,這樣的分佈式鎖不是絕對安全的

首先,單點故障的問題不可避免 其次,由於使用鎖的客戶端,和redis服務器,不在一塊兒啊!時間是有延遲的,咱們只能依靠redis的TTL命令來查詢鎖的剩餘時間,而後根據這個剩餘時間來判斷鎖是否超時。 然而在一般的計算機系統中,很難獲取到一個可靠的時間。

  • 系統可能因於時間服務器同步調整時間,
  • 虛擬機可能調整時間,
  • JVM GC可能致使時間停頓

怎麼辦?

其實若是咱們使用的場景不須要那麼強的安全性精確性,這樣也足夠用了,至少我如今的公司夠用了(一個排名前列的第三方支付企業)。可是,精益求精是程序員們的本質,RedLock的出如今必定程度上解決了這個問題

我不是很瞭解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。

2.2Redis集羣

解決了分佈式鎖的問題,可是仍是沒有解決各類天災人禍的問題,因此,這就須要Redis集羣出馬了

集羣同步機制

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進行分片存儲

關於這些集羣的東西一章內容確定寫不完,我會在之後的文章裏向你們介紹

二、3異步隊列

Redis的本職工做是緩存,可是因爲它多才多藝,成爲隊列也不錯,有一些阻塞式的API讓其有能力作消息隊列;另外,作消息隊列的其餘特性例如FIFO(先入先出)也很容易實現,只須要一個List對象從頭取數據,從尾部塞數據便可

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

二、4延時隊列

在Redis中,能夠利用 sorted-set 來作延時隊列

zadd key score1 value1 score2 value2

  • socre爲執行時間,key爲隊列名,value爲數據
  • 消費隊列循環從sorted-set根據score獲取(zrangebyscore)小於等於當前時間的且score最小的一條數據輪詢處理
  • 若是沒有取到數據,睡一會再去獲取

可是,Redis的延時隊列沒法返回ACK,因此須要本身實現

二、5 持久化

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

RDB作鏡像全量持久化、AOF作增量持久化,由於RDB會耗費較長時間,不夠實時,在停機的時候會致使大量有效數據丟失,因此須要AOF來配合使用,在redis實例重啓時,會使用RDB持久化文件從新構建內存,再使用AOF重放近期的操做指令來實現完整恢復重啓以前的狀態。

RDB機制

RDB持久化是指在指定的時間間隔內將內存中的數據集快照寫入磁盤。也是默認的持久化方式,這種方式是就是將內存中數據以快照的方式寫入到二進制文件中,默認的文件名爲dump.rdb。RDB提供了三種機制來觸發持久化

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

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

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

    執行該命令時,Redis會在後臺異步進行快照操做,快照同時還能夠響應客戶端請求

  • 三、自動觸發

    自動觸發是由咱們的配置文件來完成的,在redis.conf文件中配置,你們能夠去了解一下,這裏就不寫那麼多東西了

AOF機制

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

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

  • 一、always:同步持久化 每次發生數據變動會被當即記錄到磁盤 性能較差但數據完整性比較好

  • 二、everysec:每秒同步,異步操做,每秒記錄 若是一秒內宕機,有數據丟失

  • 三、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的數據。

結語

emmmm,第二篇文章也慢慢的寫完了,在寫文章的過程當中,真的是發現本身懂得越多,不懂越多,並且由於公司週末要加班,因此這將Redis分紅兩篇來完成,但願你們能夠喜歡~~ 同時須要思惟導圖的話,能夠聯繫我,畢竟知識越分享越香!

在這裏插入圖片描述
相關文章
相關標籤/搜索