【JAVA程序員進(jìn)階之路】Redis基礎(chǔ)知識兩篇就滿足(一)

導(dǎo)言

大家好,我是南橘,從接觸java到現(xiàn)在也有差不多兩年時間了,兩年時間,從一名連java有幾種數(shù)據(jù)結(jié)構(gòu)都不懂超級小白,到現(xiàn)在懂了一點點的進(jìn)階小白,學(xué)到了不少的東西。知識越分享越值錢,我這段時間總結(jié)(包括從別的大佬那邊學(xué)習(xí),引用)了一些平常學(xué)習(xí)和面試中的重點(自我認(rèn)為),希望給大家?guī)硪恍椭?/p>

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

這是這篇文章的思維導(dǎo)圖,因為用的是免費版的軟件,所以有不少水印,需要原版的可以問我要

image

1、Redis基礎(chǔ)知識

要學(xué)習(xí)Redis基礎(chǔ)知識,首先要知道Redis的五種基礎(chǔ)數(shù)據(jù)結(jié)構(gòu),我們先從這五種數(shù)據(jù)結(jié)構(gòu)的使用場景和一些工作中(面試中)容易出現(xiàn)的點來介紹

1、1 String 字符串類型

是redis中最基本的數(shù)據(jù)類型,一個key對應(yīng)一個value

適用情況:

1、緩存: 經(jīng)典使用場景,把常用信息,字符串,圖片或者視頻等信息放到redis中,redis作為緩存層,mysql做持久化層,降低mysql的讀寫壓力。

2.計數(shù)器:redis是單線程模型,一個命令執(zhí)行完才會執(zhí)行下一個,同時數(shù)據(jù)可以一步落地到其他的數(shù)據(jù)源。

3.session:通過redis實現(xiàn)session共享

1、2 Hash (哈希)

對于Java中的HashMap,本身是一種KV對結(jié)構(gòu),如 value={{field1,value1},......fieldN,valueN}},非常好理解

適用情況:

HashMap作為緩存,相比于string更節(jié)省空間的維護(hù)緩存信息,適合存儲如用戶信息,視頻信息等

底層用字典dict實現(xiàn)

1、3 List (鏈表)

Redis 的鏈表相當(dāng)于 Java 語言里面的 LinkedList

適用情況:

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

2、可以作為微博的時間軸,有人發(fā)布微博,用lpush加入時間軸,展示新的列表信息。     

3、可以實現(xiàn)阻塞隊列,左進(jìn)右出的隊列組完成設(shè)計

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

在早期的設(shè)計中, 當(dāng)列表對象中元素的長度比較小或者數(shù)量比較少的時候,采用ziplist來存儲,當(dāng)列表對象中元素的長度比較大或者數(shù)量比較多的時候,則會轉(zhuǎn)而使用雙向列表linkedlist來存儲。

這兩種存儲方式都各有優(yōu)缺點

  • 雙向鏈表linkedlist便于在表的兩端進(jìn)行push和pop操作,在插入節(jié)點上復(fù)雜度很低,但是它的內(nèi)存開銷比較大。首先,它在每個節(jié)點上除了要保存數(shù)據(jù)之外,還要額外保存兩個指針;其次,雙向鏈表的各個節(jié)點是單獨的內(nèi)存塊,地址不連續(xù),節(jié)點多了容易產(chǎn)生內(nèi)存碎片。
  • ziplist存儲在一段連續(xù)的內(nèi)存上,所以存儲效率很高。但是,它不利于修改操作,插入和刪除操作需要頻繁的申請和釋放內(nèi)存。特別是當(dāng)ziplist長度很長的時候,一次realloc可能會導(dǎo)致大批量的數(shù)據(jù)拷貝。

3.2版本更新之后,list的底層實現(xiàn)變成了quickList

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

1、4 Set 集合

Redis 的集合相當(dāng)于 Java 語言里面的 HashSet ,它內(nèi)部的鍵值對是無序的、唯一 的。
它的內(nèi)部實現(xiàn)相當(dāng)于一個特殊的字典,字典中所有的 value 都是一個值 NULL 當(dāng)集合中最后一個元素被移除之后,數(shù)據(jù)結(jié)構(gòu)被自動刪除,內(nèi)存被回收。

適用情況:

1、標(biāo)簽(tag),給用戶添加標(biāo)簽,或者用戶給消息添加標(biāo)簽,這樣有同一標(biāo)簽或者類似標(biāo)簽的可以給推薦關(guān)注的事或者關(guān)注的人。

2、點贊,或點踩,收藏等,可以放到set中實現(xiàn)

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

1、5 Zset 有序集合

它類似于 Java SortedSet HashMap 的結(jié)合體, 方面它是個 set ,保證 了內(nèi)部 value 的唯性,另方面它可 以給每個 value 賦予一個 score ,代表 這個 value 的排序權(quán)重。它的內(nèi)部實現(xiàn) 用的是一種叫作“跳躍表”的數(shù)據(jù) 結(jié)構(gòu)。

還是和上期一樣,從一位大佬那邊借過來一張圖片給大家展示,什么是跳躍表

image

從這張圖片,我們可以看出來:跳躍表的底層是一個順序鏈表,每隔一個節(jié)點有一個上層的指針指向下一個節(jié)點,并層層向上遞歸。這樣設(shè)計成類似樹形的結(jié)構(gòu),可以使得對于鏈表的查找可以到達(dá)二分查找的時間復(fù)雜度。

skiplist他不要求上下兩層鏈表之間個數(shù)的嚴(yán)格對應(yīng)關(guān)系,他為每個節(jié)點隨機出一個層數(shù)。比如上圖第三個節(jié)點的隨機出的層數(shù)是4,那么就把它插入到四層的空間上,而第四個節(jié)點隨機出的層數(shù)是1,那它就只存在第一層空間上。

  • 當(dāng)數(shù)據(jù)較少的時候,zset是由一個ziplist來實現(xiàn)的,就和list底層之前是一樣的

ziplist是由一系列特殊編碼的連續(xù)內(nèi)存塊組成的順序存儲結(jié)構(gòu),類似于數(shù)組,ziplist在內(nèi)存中是連續(xù)存儲的,但是不同于數(shù)組,為了節(jié)省內(nèi)存 ziplist的每個元素所占的內(nèi)存大小可以不同

ziplist將一些必要的偏移量信息記錄在了每一個節(jié)點里,使之能跳到上一個節(jié)點或下一個節(jié)點。

  • 當(dāng)數(shù)據(jù)較多的時候,zset是一個由dict 和一個 skiplist來實現(xiàn)的,dict用來查詢數(shù)據(jù)到分?jǐn)?shù)的對應(yīng)關(guān)系,而skiplist用來根據(jù)分?jǐn)?shù)查詢數(shù)據(jù)

除了這五大基礎(chǔ)數(shù)據(jù)結(jié)構(gòu),Redis還有更加專業(yè)的數(shù)據(jù)結(jié)構(gòu)
HyperLogLog(基數(shù)統(tǒng)計的算法)、Geo(地理位置系列)、Pub\Sub(消息隊列)、Pipeline(管道)、BloomFiler(布隆過濾器),都在不同的地方有用到,有些我會在下文向大家介紹,還有一些深入的我沒有了解這么多,大家可以去敖丙的文章中進(jìn)一步了解

Pipeline

可以將多次IO往返時間縮減為一次,前提是pipleline執(zhí)行的指令之間沒有因果關(guān)系

管道(pipeline)可以一次性發(fā)送多條命令并在執(zhí)行完后一次性將結(jié)果返回,pipeline 通過減少客戶端與redis的通信次數(shù)來實現(xiàn)降低往返延時時間,而且Pipeline 實現(xiàn)的原理是隊列,而隊列的原理是時先進(jìn)先出,這樣就保證數(shù)據(jù)的順序性。

注意:pipeline機制可以優(yōu)化吞吐量,但無法提供原子性/事務(wù)保障

2、進(jìn)階知識

2、1 Redis實現(xiàn)分布式鎖

隨著互聯(lián)網(wǎng)技術(shù)的飛速發(fā)展,越來越多的單體架構(gòu)已經(jīng)轉(zhuǎn)型成了分布式架構(gòu),,分布式架構(gòu)確實能帶來性能和效率上的提升,但是也會帶來數(shù)據(jù)一致性的問題。

分布式鎖,就是解決分布式架構(gòu)中數(shù)據(jù)一致性的專用武器,分布式鎖需要滿足一下三個方面方可放心使用:

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

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

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

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

基于Redis實現(xiàn)的鎖機制,主要是依賴redis自身的原子操作

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

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

但是,這樣的分布式鎖不是絕對安全的

首先,單點故障的問題不可避免
其次,因為使用鎖的客戶端,和redis服務(wù)器,不在一起?。r間是有延遲的,我們只能依靠redis的TTL命令來查詢鎖的剩余時間,然后根據(jù)這個剩余時間來判斷鎖是否超時。
然而在通常的計算機系統(tǒng)中,很難獲取到一個可靠的時間。

  • 系統(tǒng)可能因于時間服務(wù)器同步調(diào)整時間,
  • 虛擬機可能調(diào)整時間,
  • JVM GC可能導(dǎo)致時間停頓

怎么辦?

其實如果我們使用的場景不需要那么強的安全性精確性,這樣也足夠用了,至少我現(xiàn)在的公司夠用了(一個排名前列的第三方支付企業(yè))。但是,精益求精是程序員們的本質(zhì),RedLock的出現(xiàn)在一定程度上解決了這個問題。

我不是很了解RedLock,只能稍微給大家介紹一下,流程如下:

  1. 客戶端獲取當(dāng)前時間,生成一個隨機值作為鎖的值(目的是更加精確的獲得時間)
  2. 依次嘗試在所有5個redis上取得同一個鎖(使用類似單機redis鎖的方法, 使用同樣的key和同一個隨機值)
    獲取鎖的操作本身需要設(shè)定一個比較小的超時時間(如5-50ms), 防止在一個掛掉的redis上浪費太多時間
    如果一個redis不可用,要盡快開始嘗試下一個
  3. 客戶端計算獲取鎖一共用了多長時間,通過用當(dāng)前時間減去第1步得到的時間
    如果客戶端獲取了多數(shù)redis上的這個鎖(3到五個5),并且這時還沒有超過鎖的超時時間,
    這個鎖就算是獲取成功了
  4. 如果鎖獲取成功了,有效時間就按鎖超時時間-獲取鎖花費時間算
  5. 如果失敗,嘗試在所有redis上解除鎖
    (解除鎖的操作是一段lua script,刪除一個key如果key的value是第1步生成的隨機值)

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

2.2Redis集群

解決了分布式鎖的問題,但是還是沒有解決各種天災(zāi)人禍的問題,所以,這就需要Redis集群出馬了

集群同步機制

Redis中有主從機制,一個主節(jié)點對應(yīng)一個或多個從節(jié)點,主節(jié)點提供數(shù)據(jù)存取,從節(jié)點則是從主節(jié)點拉取數(shù)據(jù)備份,當(dāng)這個主節(jié)點掛掉后,就會有這個從節(jié)點選取一個來充當(dāng)主節(jié)點,從而保證集群不會掛掉。先講一下Redis主從同步的流程:

  • 1.第一次同步時,從服務(wù)器向主服務(wù)器發(fā)送一次SYNC命令,主服務(wù)器收到之后做一次bgsave、并同時將后續(xù)修改操作記錄到內(nèi)存buffer,待完成后將RDB文件全量同步到復(fù)制節(jié)點
  • 2.復(fù)制節(jié)點接收完成后將RDB鏡像加載到內(nèi)存中,加載完成后,再通知主節(jié)點
  • 3.后續(xù)的增量數(shù)據(jù)通過AOF日志同步即可,有點類似數(shù)據(jù)庫的binlog

同時,在2.8版本之后,Redis可以自動判斷是需要全量同步還是增量同步,效率比較高,增量同步其實就是在完成全量同步后,開始新復(fù)制時向主服務(wù)器發(fā)送PSYNC(<runid> <offset>)命令(runid是上次復(fù)制的主服務(wù)器id,offset是從服務(wù)器的復(fù)制偏移量),主服務(wù)器會根據(jù)這個兩個參數(shù)來決定做哪種同步,判斷服務(wù)器id是否和本機相同,復(fù)制偏移量是否在緩沖區(qū)中。

集群的高可用性

  • Redis Sentinal(哨兵模式)集群著眼于高可用,在master宕機時自動將slave提升為master,繼續(xù)提供服務(wù)
  • Redis Cluster集群著眼于擴展性,在單個redis內(nèi)存不足時,使用Cluster進(jìn)行分片存儲

關(guān)于這些集群的東西一章內(nèi)容肯定寫不完,我會在以后的文章里向大家介紹

2、3異步隊列

Redis的本職工作是緩存,但是由于它多才多藝,成為隊列也不錯,有一些阻塞式的API讓其有能力做消息隊列;另外,做消息隊列的其他特性例如FIFO(先入先出)也很容易實現(xiàn),只需要一個List對象從頭取數(shù)據(jù),從尾部塞數(shù)據(jù)即可

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

2、4延時隊列

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

zadd key score1 value1 score2 value2

  • socre為執(zhí)行時間,key為隊列名,value為數(shù)據(jù)
  • 消費隊列循環(huán)從sorted-set根據(jù)score獲?。▃rangebyscore)小于等于當(dāng)前時間的且score最小的一條數(shù)據(jù)輪詢處理
  • 如果沒有取到數(shù)據(jù),睡一會再去獲取

但是,Redis的延時隊列無法返回ACK,所以需要自己實現(xiàn)

2、5 持久化

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

RDB做鏡像全量持久化、AOF做增量持久化,因為RDB會耗費較長時間,不夠?qū)崟r,在停機的時候會導(dǎo)致大量有效數(shù)據(jù)丟失,所以需要AOF來配合使用,在redis實例重啟時,會使用RDB持久化文件重新構(gòu)建內(nèi)存,再使用AOF重放近期的操作指令來實現(xiàn)完整恢復(fù)重啟之前的狀態(tài)。

RDB機制

RDB持久化是指在指定的時間間隔內(nèi)將內(nèi)存中的數(shù)據(jù)集快照寫入磁盤。也是默認(rèn)的持久化方式,這種方式是就是將內(nèi)存中數(shù)據(jù)以快照的方式寫入到二進(jìn)制文件中,默認(rèn)的文件名為dump.rdb。RDB提供了三種機制來觸發(fā)持久化

  • 1、save觸發(fā)方式---客戶端發(fā)起save請求

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

  • 2、bgsave觸發(fā)方式--客戶端發(fā)起bgsave請求

    執(zhí)行該命令時,Redis會在后臺異步進(jìn)行快照操作,快照同時還可以響應(yīng)客戶端請求

  • 3、自動觸發(fā)

    自動觸發(fā)是由我們的配置文件來完成的,在redis.conf文件中配置,大家可以去了解一下,這里就不寫那么多東西了

AOF機制

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

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

  • 1、always:同步持久化 每次發(fā)生數(shù)據(jù)變更會被立即記錄到磁盤 性能較差但數(shù)據(jù)完整性比較好

  • 2、everysec:每秒同步,異步操作,每秒記錄 如果一秒內(nèi)宕機,有數(shù)據(jù)丟失

  • 3、no:從不同步

Redis本身的機制是AOF持久化開啟存在AOF文件時,優(yōu)先加載AOF文件。AOF文件不存在時候,加載RDB文件。加載AOF\RDB文件后,Redis啟動成功。AOF\RDB文件存在錯誤時,Redis啟動失敗并打印錯誤信息。

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

機器斷電對數(shù)據(jù)丟失的影響

AOF日志中可以進(jìn)行sync屬性的配置,如果不要求性能,在每條寫指令時都sync一下磁盤,就不會丟失數(shù)據(jù),但在高性能要求下每次都sync是不現(xiàn)實的,一般都使用定時sync,比如1s一次,這個時候就最多丟失1s的數(shù)據(jù)。

結(jié)語

emmmm,第二篇文章也慢慢的寫完了,在寫文章的過程中,真的是發(fā)現(xiàn)自己懂得越多,不懂越多,而且因為公司周末要加班,所以這將Redis分成兩篇來完成,希望大家可以喜歡~~
同時需要思維導(dǎo)圖的話,可以聯(lián)系我,畢竟知識越分享越香!


在這里插入圖片描述
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容