導言
大家好,我是南橘,一名練習時常兩年半的java練習生,這是我在博客園的第一篇文章,當然,都是要從別處搬運過來的,不過以后新的文章也會在博客園同步發布,希望大家能多多支持^_^
這篇文章的出現,首先要感謝一個人三太子敖丙 ,就是他的文章讓我發現,原來Redis的知識如此的多姿多彩,恩恩,他的文章,我是期期都看
這是這篇文章的思維導圖,因為用的是免費版的軟體,所以有不少水印,需要原版的可以問我要
Redis篇,因為時間和篇幅的原因,并沒有一次性寫完,于是乎,分成了上下兩篇,沒有看過上半部分的小伙伴可以去看一下~
- 索引中一些易忽視的點
- Redis基礎知識兩篇就滿足(一)
- Redis基礎知識兩篇就滿足(二)
- 包羅萬象——JAVA中的鎖
- 攻克JVM——JVM物件及物件的訪問定位(一)
- 攻克JVM——JVM的垃圾回識訓制(二)
- 攻克JVM——JVM的垃圾收集器(三)
1、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=https://www.cnblogs.com/nanjuryc/p/{{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(布隆過濾器),都在不同的地方有用到,有些我會在下文向大家介紹,還有一些深入的我沒有了解這么多,大家可以去敖丙的文章中進一步了解
Pipeline
可以將多次IO往返時間縮減為一次,前提是pipleline執行的指令之間沒有因果關系
管道(pipeline)可以一次性發送多條命令并在執行完后一次性將結果回傳,pipeline 通過減少客戶端與redis的通信次數來實作降低往返延時時間,而且Pipeline 實作的原理是佇列,而佇列的原理是時先進先出,這樣就保證資料的順序性,
注意:pipeline機制可以優化吞吐量,但無法提供原子性/事務保障
2、進階知識
2、1 Redis實作分布式鎖
隨著互聯網技術的飛速發展,越來越多的單體架構已經轉型成了分布式架構,,分布式架構確實能帶來性能和效率上的提升,但是也會帶來資料一致性的問題,
分布式鎖,就是解決分布式架構中資料一致性的專用武器,分布式鎖需要滿足一下三個方面方可放心使用:
-
排他性:在同一時間只會有一個客戶端能獲取到鎖,其它客戶端無法同時獲取
-
避免死鎖:這把鎖在一段有限的時間之后,一定會被釋放(正常釋放或例外釋放)
-
高可用:獲取或釋放鎖的機制必須高可用且性能佳
目前,我所知道的分布式鎖大概有三種主流方式實作,分別是zookpeer,redis,還有本地資料庫,今天我就介紹一下如何用redis實作分布式鎖,
基于Redis實作的鎖機制,主要是依賴redis自身的原子操作
setnx爭搶鎖,再用expire添加過期時間
你沒有看錯,就是這么簡單,如果害怕不妥,比如爭搶鎖的時候還沒有設定過期時間就突然宕機之類的問題,可以直接用jedis等封裝好的RedisTemplate把setnx和expire合成一條指令使用,
但是,這樣的分布式鎖不是絕對安全的
首先,單點故障的問題不可避免
其次,因為使用鎖的客戶端,和redis服務器,不在一起啊!時間是有延遲的,我們只能依靠redis的TTL命令來查詢鎖的剩余時間,然后根據這個剩余時間來判斷鎖是否超時,
然而在通常的計算機系統中,很難獲取到一個可靠的時間,
- 系統可能因于時間服務器同步調整時間,
- 虛擬機可能調整時間,
- JVM GC可能導致時間停頓
怎么辦?
其實如果我們使用的場景不需要那么強的安全性精確性,這樣也足夠用了,至少我現在的公司夠用了(一個排名前列的第三方支付企業),但是,精益求精是程式員們的本質,RedLock的出現在一定程度上解決了這個問題,
我不是很了解RedLock,只能稍微給大家介紹一下,流程如下:
- 客戶端獲取當前時間,生成一個隨機值作為鎖的值(目的是更加精確的獲得時間)
- 依次嘗試在所有5個redis上取得同一個鎖(使用類似單機redis鎖的方法, 使用同樣的key和同一個隨機值)
獲取鎖的操作本身需要設定一個比較小的超時時間(如5-50ms), 防止在一個掛掉的redis上浪費太多時間
如果一個redis不可用,要盡快開始嘗試下一個
- 客戶端計算獲取鎖一共用了多長時間,通過用當前時間減去第1步得到的時間
如果客戶端獲取了多數redis上的這個鎖(3到五個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(
集群的高可用性
- Redis Sentinal(哨兵模式)集群著眼于高可用,在master宕機時自動將slave提升為master,繼續提供服務
- Redis Cluster集群著眼于擴展性,在單個redis記憶體不足時,使用Cluster進行分片存盤
關于這些集群的東西一章內容肯定寫不完,我會在以后的文章里向大家介紹
2、3異步佇列
Redis的本職作業是快取,但是由于它多才多藝,成為佇列也不錯,有一些阻塞式的API讓其有能力做訊息佇列;另外,做訊息佇列的其他特性例如FIFO(先入先出)也很容易實作,只需要一個List物件從頭取資料,從尾部塞資料即可
- 在Redis中,如果讓List結構作為佇列、rpush生產訊息、lpop消費訊息、當lpop沒有訊息的時候,可以當sleep一會再重試,這就相當于生產者消費模式模式了,同時List有個指令叫blpop,在沒有訊息的時候,它會阻塞住直到訊息到來,
- pub\sub主題訂閱者模式、可以實作1對N的訊息佇列,實作生產一次,消費多次,但是,它也有不足之處,如果讓pub\sub主題訂閱者模式、消費者下線的情況下,訊息會丟失、不如直接用MQ
2、4延時佇列
在Redis中,可以利用 sorted-set 來做延時佇列
zadd key score1 value1 score2 value2
- socre為執行時間,key為佇列名,value為資料
- 消費佇列回圈從sorted-set根據score獲取(zrangebyscore)小于等于當前時間的且score最小的一條資料輪詢處理
- 如果沒有取到資料,睡一會再去獲取
但是,Redis的延時佇列無法回傳ACK,所以需要自己實作
2、5 持久化
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的資料,
結語
emmmm,第二篇文章也慢慢的寫完了,在寫文章的程序中,真的是發現自己懂得越多,不懂越多,而且因為公司周末要加班,所以這將Redis分成兩篇來完成,希望大家可以喜歡~~
同時需要思維導圖的話,可以聯系我,畢竟知識越分享越香!
轉載請註明出處,本文鏈接:https://www.uj5u.com/houduan/199668.html
標籤:Java
下一篇:Helm安裝檔案
