在 Web 應用發展的初期階段,一個網站的訪問量本身就不是很高,直接使用關係型數據庫就可以應付絕大部分場景。但是隨着互聯網時代的崛起,人們對於網站訪問速度有着越來越高的要求,直接使用關係型數據庫的方案在性能上就出現了瓶頸。因此在客戶端與數據層之間就需要一個緩存層來分擔請求壓力,而 Redis 作爲一款優秀的緩存中間件,在企業級架構中佔有重要的地位,因此 Redis 也作爲面試的必問項。
Redis(Remote Dictionary Server)是一個開源的、鍵值對型的數據存儲系統。使用C語言編寫,遵守BSD協議,可基於內存也可持久化的日誌型數據庫,提供了多種語言的API,被廣泛用於數據庫、緩存和消息中間件。並且支持多種類型的數據結構,用於應對各種不同場景。可以存儲多種不同類型值之間的映射,支持事務,持久化,LUA 腳本以及多種集羣方案等。
優點:
- 完全基於內存操作,性能極高,讀寫速度快,Redis 能夠支持超過 100KB/s 的讀寫速率
- 支持高併發,支持10萬級別的併發讀寫
- 支持主從模式,支持讀寫分離與分佈式
- 具有豐富的數據類型與豐富的特性(發佈訂閱模式)
- 支持持久化操作,不會丟失數據
缺點:
- 數據庫容量受到物理內存的限制,不能實現海量數據的高性能讀寫
- 相比關係型數據庫,不支持複雜邏輯查詢,且存儲結構相對簡單
- 雖然提供持久化能力,但實際更多是一個 disk-backed 功能,與傳統意義上的持久化有所區別
Memcache 也是一個開源、高性能、分佈式內存對象緩存系統。所有數據均存儲在內存中,在服務器重啓之後就會消失,需要重新加載數據,採用 hash 表的方式將所有數據緩存在內存中,採用 LRU 算法來逐漸把過期的數據清除掉。
- 數據類型:Memcache 僅支持字符串類型,Redis 支持 5 種不同的數據類型
- 數據持久化:Memcache 不支持持久化,Redis 支持兩種持久化策略,RDB 快照 和 AOF 日誌
- 分佈式:Memcache 不支持分佈式,只能在客戶端使用一致性哈希的方式來實現分佈式存儲,Redis3.0 之後可在服務端構建分佈式存儲,Redis集羣沒有中心節點,各個節點地位平等,具有線性可伸縮的功能。
- 內存管理機制:Memcache數據量不能超出系統內存,但可以調整內存大小,淘汰策略採用LRU算法。Redis增加了 VM 特性,實現了物理內存的限制,它們之間底層實現方式以及客戶端之間通信的應用協議不一樣。
- 數據大小限制:Memcache 單個 key-value 大小有限制,一個Value最大容量爲 1MB,Redis 最大容量爲512 MB
基本數據類型:
- String(字符串)
- Hash(哈希)
- List(列表)
- Set(集合)
- ZSet(Sorted Set 有序集合)
高級數據類型:
- HyperLogLog:用來做基數統計的算法,在輸入元素的數量或體積非常大時,計算基數所需的空間總是固定的,並且是很小的。HyperLogLog 只會根據輸入元素來計算基數,而不會存儲輸入元素本身
- Geo:用來地理位置的存儲和計算
- BitMap:實際上不是特殊的存儲結構,本質上是二進制字符串,可以進行位操作,常用於統計日活躍用戶等
擴展: geohash通過算法將1個定位的經度和緯度2個數值,轉換成1個hash字符串。如果2個地方距離越近,那麼他們的hash值的前綴越相同。
Redis 底層實現了簡單動態字符串的類型(Simple Dynamic String,SDS)來表示 String 類型。沒有直接使用C語言定義的字符串類型。
SDS 實現相對於C語言String方式的提升
- 避免緩衝區移除。對字符修改時,可以根據 len 屬性檢查空間是否滿足要求
- 獲取字符串長度的複雜度較低
- 減少內存分配次數
- 兼容C字符串函數,可以重用C語言庫的一部分函數
Redis 直接以內存的方式存儲可以達到最快的讀寫速度,如果開啓了持久化則通過異步的方式將數據寫入磁盤,因此Redis 具有快速和數據持久化的特徵。
在內存中操作本身就比從磁盤操作更快,且不受磁盤I/O速度的影響。如果不將數據放在內存中而是保存到磁盤,磁盤I/O速度會嚴重影響到Redis 的性能,而數據集大小如果達到了內存的最大限定值則不能繼續插入新值。
如果打開了虛擬內存功能,當內存用盡時,Redis就會把那些不經常使用的數據存儲到磁盤,如果Redis中的虛擬內存被禁了,它就會操作系統的虛擬內存(交換內存),但這時Redis的性能會急劇下降。如果配置了淘汰機制,會根據已配置的數據淘汰機制來淘汰舊數據。
1、儘可能使用哈希表(hash 數據結構):Redis 在儲存小於100個字段的Hash結構上,其存儲效率是非常高的。所以在不需要集合(set)操作或 list 的push/pop 操作的時候,儘可能使用 hash 結構。
2、根據業務場景,考慮使用 BitMap
3、充分利用共享對象池:Redis 啓動時會自動創建【0-9999】的整數對象池,對於 0-9999的內部整數類型的元素,整數值對象都會直接引用整數對象池中的對象,因此儘量使用 0-9999 整數對象可節省內存。
4、合理使用內存回收策略:過期數據清除、expire 設置數據過期時間等
Redis 能夠用來實現分佈式鎖的命令有 INCR、SETNX、SET,並利用過期時間命令 expire 作爲輔助
方式1:利用 INCR
如果 key 不存在,則初始化值爲 0,然後再利用 INCR
進行加 1 操作。後續用戶如果獲取到的值大於等於 1,說明已經被其他線程加鎖。當持有鎖的用戶在執行完任務後,利用 DECR
命令將 key 的值減 1,則表示釋放鎖。
方式2:利用 SETNX
先使用 setnx
來爭搶鎖,搶到之後利用 expire
設置一個過期時間防止未能釋放鎖。setnx
的意義是如果 key 不存在,則將key設置爲 value,返回 1。如果已存在,則不做任何操作,返回 0。
方式3:利用 SET
set
指令有非常複雜的參數,相當於合成了 setnx
和 expire
兩條命令的功能。其命令格式如:set($Key,$value, array('nx', 'ex'=>$ttl))
。
-
完全基於內存
-
數據結構簡單,操作方便,並且不同數據結構能夠應對於不同場景
-
採用單線程(網絡請求模塊使用單線程,其他模塊仍用了多線程),避免了不必要的上下文切換和競爭條件,也不存在多進程或多線程切換導致CPU消耗,不需要考慮各種鎖的問題。
-
使用多路I/O複用模型,爲非阻塞I/O
-
Redis 本身設定了 VM 機制,沒有使用 OS 的Swap,可以實現冷熱數據分離,避免因爲內存不足而造成訪問速度下降的問題
1、RDB(Redis DataBase)持久化
RDB 是 Redis 中默認的持久化機制,按照一定的時間將內存中的數據以快照的方式保存到磁盤中,它會產生一個特殊類型的文件 .rdb
文件,同時可以通過配置文件中的 save
參數來定義快照的週期
在 RDB 中有兩個核心概念 fork
和 cow
,在執行備份的流程如下:
在執行bgsave
的時候,Redis 會 fork 主進程得到一個新的子進程,子進程是共享主進程內存數據的,會將數據寫到磁盤上的一個臨時的 .rdb
文件中,當子進程寫完臨時文件後,會將原來的 .rdb
文件替換掉,這個就是 fork 的概念。那 cow 全稱是 copy-on-write ,當主進程執行讀操作的時候是訪問共享內存的,而主進程執行寫操作的時候,則會拷貝一份數據,執行寫操作。
優點
- 只有一個文件 dump.rdb ,方便持久化
- 容錯性好,一個文件可以保存到安全的磁盤
- 實現了性能最大化,fork 單獨子進程來完成持久化,讓主進程繼續處理命令,主進程不進行任何 I/O 操作,從而保證了Redis的高性能
- RDB 是一個緊湊壓縮的二進制文化,RDB重啓時的加載效率比AOF持久化更高,在數據量大時更明顯
缺點
- 可能出現數據丟失,在兩次RDB持久化的時間間隔中,如果出現宕機,則會丟失這段時間中的數據
- 由於RDB是通過fork子進程來協助完成數據持久化,如果當數據集較大時,可能會導致整個服務器間歇性暫停服務
2、AOF(Append Only File)持久化
AOF 全稱是 Append Only File(追加文件)。當 Redis 處理每一個寫命令都會記錄在 AOF 文件中,可以看做是命令日誌文件。該方式需要設置 AOF 的同步選項,因爲對文件進行寫入並不會馬上將內容同步到磁盤上,而是先存儲到緩衝區中,同步選項有三種配置項選擇:
always
:同步刷盤,可靠性高,但性能影響較大everysec
:每秒刷盤,性能適中,最多丟失 1 秒的數據no
:操作系統控制,性能最好,可靠性最差
爲了解決 AOF 文件體檢不斷增大的問題,用戶可以向 Redis 發送 bgrewriteaof
命令,可以將 AOF 文件進行壓縮,也可以選擇自動觸發,在配置文件中配置
auto-aof-rewrite-precentage 100
auto-aof-rewrite-min-zise 64mb
優點
- 實現持久化,數據安全,AOF持久化可以配置 appendfsync 屬性爲 always,每進行一次命令操作就記錄到AOF文件中一次,數據最多丟失一次
- 通過 append 模式寫文件,即使中途服務器宕機,可以通過 Redis-check-aof 工具解決數據一致性問題
- AOF 機制的 rewrite 模式。AOF 文件的文件大小觸碰到臨界點時,rewrite 模式會被運行,重寫內存中的所有數據,從而縮小文件體積
缺點
- AOF 文件大,通常比 RDB 文件大很多
- 比 RDB 持久化啓動效率低,數據集大的時候較爲明顯
- AOF 文件體積可能迅速變大,需要定期執行重寫操作來降低文件體積
方式1:定時刪除
在設置 Key 的過期時間的同時,會創建一個定時器 timer,定時器在 Key 過期時間來臨時,會立即執行對 Key 的刪除操作
特點: 對內存友好,對 CPU 不友好。存在較多過期鍵時,利用定時器刪除過期鍵會佔用相當一部分CPU
方式2:惰性刪除
key 不使用時不管 key 過不過期,只會在每次使用的時候再檢查 Key 是否過期,如果過期的話就會刪除該 Key。
特點: 對 CPU 友好,對內存不友好。不會花費額外的CPU資源來檢測Key是否過期,但如果存在較多未使用且過期的Key時,所佔用的內存就不會得到釋放
方式3:定期刪除
每隔一段時間就會對數據庫進行一次檢查,刪除裏面的過期Key,而檢查多少個數據庫,則由算法決定
特點: 定期刪除是對上面兩種過期策略的折中,也就是對內存友好和CPU友好的折中方法。每隔一段時間執行一次刪除過期鍵任務,並通過限制操作的時長和頻率來減少對CPU時間的佔用。
Redis 主從同步分爲增量同步和全量同步Redis 會先嚐試進行增量同步,如果不成功則會進行全量同步。
增量同步:
Slave 初始化後開始正常工作時主服務器發生的寫操作同步到從服務器的過程。增量同步的過程主要是主服務器每執行一個寫命令就會向從服務器發送相同的寫命令。
全量同步:
Slave 初始化時它會發送一個 psync
命令到主服務器,如果是第一次同步,主服務器會做一次bgsave
,並同時將後續的修改操作記錄到內存 buffer 中,待 bgsave
完成後再將 RDB 文件全量同步到從服務器,從服務器接收完成後會將 RDB 快照加載到內存然後寫入到本地磁盤,處理完成後,再通知主服務器將期間修改的操作記錄同步到複製節點進行重放就完成了整個全量同步過程。
在Redis中,最大使用內存大小由Redis.conf中的參數maxmemory決定,默認值爲0,表示不限制,這時實際相當於當前系統的內存。但如果隨着數據的增加,如果對內存中的數據沒有管理機制,那麼數據集大小達到或超過最大內存的大小時,則會造成Redis崩潰。因此需要內存數據淘汰機制。
設有過期時間
volatile-lru
:嘗試回收最少使用的鍵volatile-random
:回收隨機的鍵volatile-ttl
:優先回收存活時間較短的鍵
沒有過期時間
allkey-lru
:嘗試回收最少使用的鍵allkeys-random
:回收隨機的鍵noeviction
:當內存達到限制並且客戶端嘗試執行新增,會返回錯誤
淘汰策略的規則
- 如果數據呈現冪律分佈,也就是一部分數據訪問頻率高,一部分數據訪問頻率低,則使用 allKeys-lru
- 如果數據呈現平等分佈,也就是所有的數據訪問頻率大體相同,則使用 allKeys-random
- 關於 lru 策略,Redis中並不會準確的刪除所有鍵中最近最少使用的鍵,而是隨機抽取5個鍵(個數由參數maxmemory-samples決定,默認值是5),刪除這5個鍵中最近最少使用的鍵。
問題1:緩存穿透
緩存穿透是指緩存和數據庫上都沒有的數據,導致所有請求都落到數據庫上,造成數據庫短時間內承受大量的請求而導致宕機
解決:
- 使用布隆過濾器:將查詢的參數都存儲到一個 bitmap 中,在查詢緩存前,如果 bitmap 存在則進行底層緩存的數據查詢,如果不存在則進行攔截,不再進行緩存的數據查詢
- 緩存空對象:如果數據庫查詢的爲空,則依然把這個數據緩存並設置過期時間,當多次訪問的時候可以直接返回結果,避免造成多次訪問數據庫,但要保證當數據庫有數據時及時更新緩存。
問題2:緩存擊穿
緩存擊穿是指緩存中沒有但數據庫中有的數據(一般是緩存時間到期),就會導致所有請求都落到數據庫上,造成數據庫段時間內承受大量的請求而宕機
解決:
- 設置熱點數據永不過期
- 可以使用互斥鎖更新,保證同一進程中針對同一個數據不會併發請求到 DB,減小DB的壓力
- 使用隨機退避方式,失效時隨機 sleep 一個很短的時間,再次查詢,如果失敗再執行更新
問題3:緩存雪崩
緩存雪崩是指大量緩存同一時間內大面積失效,後面的請求都會落到數據庫上,造成數據庫段時間無法承受大量的請求而宕掉
解決:
- 在緩存失效後,通過加鎖或者隊列來控制讀數據庫寫緩存的線程數量。比如對某個Key只允許一個線程查詢和寫緩存,其他線程等待
- 通過緩存 reload 機制,預先去更新緩存,在即將發生高併發訪問前手動觸發加載緩存
- 對於不同的key設置不同的過期時間,讓緩存失效的時間點儘量均勻,比如我們可以在原有的失效時間基礎上增加一個隨機值,比如1~5分鐘隨機,這樣每一個緩存的過期時間的重複率就會降低。
- 設置二級緩存,或者雙緩存策略。
緩存降級,其實都應該是指服務降級。在訪問量劇增、服務響應出現問題(如響應延遲或不響應)或非核心服務影響到核心流程的性能的情況下,仍然需要保證核心服務可用,儘管可能一些非主要服務不可用,這時就可以採取服務降級策略。
服務降級的最終目的是保證核心服務可用,即使是有損的。服務降級應當事先確定好降級方案,確定哪些服務是可以降級的,哪些服務是不可降級的。根據當前業務情況及流量對一些服務和頁面有策略的降級,以此釋放服務器資源以保證核心服務的正常運行。
降級往往會指定不同的級別,面臨不同的異常等級執行不同的處理。根據服務方式:可以拒接服務,可以延遲服務,也可以隨機提供服務。根據服務範圍:可以暫時禁用某些功能或禁用某些功能模塊。總之服務降級需要根據不同的業務需求採用不同的降級策略。主要的目的就是服務雖然有損但是總比沒有好。
- 據實時同步失效或更新。這是一種增量主動型的方案,能保證數據強一致性,在數據庫數據更新之後,主動請求緩存更新
- 數據異步更新。這是一種增量被動型方案,數據一致性稍弱,數據更新會有延遲,更新數據庫數據後,通過異步方式,用多線程方式或消息隊列來實現更新
- 定時任務更新。這是一種增/全量被動型方案,通過定時任務按一定頻率調度更新,數據一致性最差
- 直接寫個緩存刷新頁面,上線時手工操作
- 數據量不大,可以在項目啓動時自動進行加載
- 定時刷新緩存
Sentinel(哨兵)適用於監控 Redis 集羣中 Master 和 Slave 狀態的工具,是Redis的高可用性解決方案
主要作用
- 監控。哨兵會不斷檢查用戶的Master和Slave是否運作正常
- 提醒。當被監控的某個Redis節點出現問題時,哨兵可以通過API向管理員或其他應用程序發送通知
- 自動故障遷移。當一個Master不能正常工作時,哨兵會開始一次自動故障遷移操作,它會將集羣中一個Slave提升爲新的Master,並讓其他Slave改爲與新的Master進行同步。當客戶端試圖連接失敗的Master時,集羣也會想客戶端返回新的Master地址。當主從服務器切換後,新Master的Redis.conf,Slave的Redis.conf和Sentinel的Redis.conf三者配置文件都會發生相應的改變。
問題背景
Redis 是基於TCP協議的請求/響應服務器,每次通信都要經過TCP協議的三次握手,所以當需要執行的命令足夠複雜時,會產生很大的網絡延遲,並且網絡的傳輸時間成本和服務器開銷沒有計入其中,總的延遲可能更大。
Pipeline解決
-
Pipeline 主要就是爲了解決存在這種情況的場景,使用Pipeline模式,客戶端可以一次性發送多個命令,無需等待服務端返回,這樣可以將多次I/O往返的時間縮短爲一次,大大減少了網絡往返時間,提高了系統性能。
-
Pipeline 是基於隊列實現,基於先進先出的原理,滿足了數據順序性。同時一次提交的命令很多的話,隊列需要非常大量的內存來組織返回數據內容,如果大量使用Pipeline的話,應當合理分批次提交命令。
-
Pipeline的默認同步個數爲
53
個,累加到 53 條數據時會把數據提交
注意: Redis 集羣中使用不了 Pipeline,對可靠性要求很高,每次操作都需要立即獲取本次操作結果的場景都不適合用 Pipeline
-
Master 最好不要做 RDB 持久化,因爲這時 save 命令調度 rdbSave 函數,會阻塞主線程的工作,當數據集比較大時可能造成主線程間斷性暫停服務
-
如果數據比較重要,將某個 Slave 節點開啓AOF數據備份,策略設置爲每秒一次
-
爲了主從複製速度和連接的穩定性,Master 和 Slave 最好在同一個局域網中
-
儘量避免在運行壓力很大的主庫上增加從庫
-
主從複製不要用圖狀結構,用單向鏈表結構更爲穩定,
Mater->Slave1->Slave2->Slave3...
這樣的結構方便解決單點故障問題,實現 Slave 對 Master 的替換,如果 Master 崩潰,可以立即啓用 Slave1替換Mater,而其他依賴關係則保持不變。
方式1:先更新數據庫,再更新緩存
這種是常規的做法,但是如果更新緩存失敗,將會導致緩存是舊數據,數據庫是新數據
方式2:先刪除緩存,再寫入數據庫
這種方式能夠解決方式1的問題,但是僅限於低併發的場景,不然如果有新的請求在刪完緩存之後,寫數據庫之前進來,那麼緩存就會馬上更新數據庫更新之前數據,造成數據不一致的問題
方式3:延時雙刪策略
這種方式是先刪除緩存,然後更新數據庫,最後延遲個幾百毫秒再刪除緩存
方式4:直接操作緩存,定期寫入數據庫
雖然Redis的Transactions 提供的並不是嚴格的 ACID的事務(如一串用EXEC提交執行的命令,如果在執行中服務器宕機,那麼會有一部分命令執行一部分命令未執行),但這些Transactions還是提供了基本的命令打包執行的功能(在服務器不出問題的情況下,可以保證一連串的命令是順序在一起執行的。
Redis 事務的本質就是四個原語:
multi
:用於開啓一個事務,它總是返回 OK,當 multi 執行之後,客戶端可以繼續向服務器發送任意多條命令,這些命令不會被立即執行,而是放到一個隊列中,當 exec 命令被調用的時候,所有隊列d 命令纔會執行exec
:執行所有事務隊列內的命令,返回事務內所有命令的返回值,當操作被打斷的時候,返回空值 nilwatch
:是一個樂觀鎖。可以爲 redis 事務提供 CAS 操作,可以監控一個或多個鍵。一旦其中有一個鍵被修改(刪除),之後的事務就不會執行,監控一直持續到 exec 命令執行之後discard
:調用 discard,客戶端可以清空事務隊列中的命令,並放棄執行事務
事務支持一次執行多個命令,一個事務中的所有命令都會被序列化。在事務執行的過程中,會按照順序串行化執行隊列中的命令,其他客戶端提交的命令請求不會插入到事務執行命令隊列中。Redis 不支持回滾事務,在事務失敗的時候不會回滾,而是繼續執行餘下的命令。
方式1:Cluster 3.0
這是Redis 自帶的集羣功能,它採用的分佈式算法是哈希槽,而不是一致性Hash。支持主從結構,可以擴展多個從服務器,當主節點掛了可以很快切換到一個從節點作主節點,然後其他從節點都會切換讀取最新的主節點。
方式2:Twemproxy
Twitter 開源的一個輕量級後端代理。可以管理 Redis 或 Memcache 集羣。相對於 Redis 集羣來說,易於管理。它的使用方法和Redis集羣沒有任何區別,只需要設置多個Redis實例後,在本需要連接 Redis 的地方改爲連接 Twemproxy ,它就會以一個代理的身份接收請求並使用一致性Hash算法,將請求連接到具體的Redis節點上,將結果再返回Twemproxy。對於客戶端來說,Twemproxy 相當於是緩存數據庫的總入口,它不需要知道後端如何部署的。Twemproxy 會檢測與每個節點的連接是否正常,如果存在異常節點就會將其剔除,等一段時間後,Twemproxy 還會再次嘗試連接被剔除的節點。
方式3:Codis
它是一個 Redis 分佈式的解決方法,對於應用使用 Codis Proxy 的連接和使用Redis的服務沒有明顯區別,應用能夠像使用單機 Redis 一樣,讓 Codis 底層處理請求轉發,實現不停機實現數據遷移等工作。
什麼是腦裂問題
腦裂問題通常是因爲網絡問題導致的。讓 master、slave 和 sentinel 三類節點處於不同的網絡分區。此時哨兵無法感知到 master 的存在,會將 slave 提升爲 master 節點。此時就會存在兩個 master,就像大腦分裂,那麼原來的客戶端往繼續往舊的 master 寫入數據,而新的master 就會丟失這些數據
如何解決
通過配置文件修改兩個參數
min-slaves-to-write 3 # 表示連接到 master 最少 slave 的數量
min-slaves-max-lag 10 # 表示slave連接到master最大的延遲時間
--------------------新版本寫法-----------------
min-replicas-to-write 3
min-replicas-max-lag 10
配置這兩個參數之後,如果發生集羣腦裂,原先的master節點接收到寫入請求就會拒絕,就會減少數據同步之後的數據丟失
一般使用 List
結構作爲隊列。Rpush
生產消息,Lpop
消費消息。當 Lpop
沒有消費的時候,需要適當 sleep 一會再重試。但是重複 sleep 會耗費性能,所以我們可以利用 list 的 blpop
指令,在還沒有消息到來時,它會阻塞直到消息到來。
我們也可以使用 pub/sub
主題訂閱者模式,實現 1:N 的消費隊列,但是在消費者下線的時候,生產的消息會丟失
可以使用 zset
結構,可以拿時間戳作爲 score,消息的內容作爲key,通過調用 zadd
來生產消息,消費者使用 zrangebyscore
指令輪詢獲取 N 秒之前的數據進行處理
Redis Cluster提供了自動將數據分散到各個不同節點的能力,但採用的策略並不是一致性Hash,而是哈希槽。Redis 集羣將整個Key的數值域分成16384個哈希槽,每個Key通過 CRC16檢驗後對16384驅魔來決定放置到那個槽中,集羣的每個節點都負責其中一部分的哈希槽。
1、數據緩存
經典的場景,現在幾乎是所有中大型網站都在用的提升手段,合理地利用緩存能夠提升網站訪問速度
2、排行榜
可以藉助Redis提供的有序集合(sorted set
)能力實現排行榜的功能
3、計數器
可以藉助Redis提供的 incr
命令來實現計數器功能,因爲是單線程的原子操作,保證了統計不會出錯,而且速度快
4、分佈式session共享
集羣模式下,可以基於 Redis 實現 session 共享
5、分佈式鎖
在分佈式架構中,爲了保證併發訪問時操作的原子性,可以利用Redis來實現分佈式鎖的功能
6、最新列表
可以藉助Redis列表結構,LPUSH
、LPOP
、LTRIM
等命令來完成內容的查詢
7、位操作
可以藉助Redis中 setbit
、getbit
、bitcount
等命令來完成數量上千萬甚至上億的場景下,實現用戶活躍度統計
8、消息隊列
Redis 提供了發佈(Publish
)與訂閱(Subscribe
)以及阻塞隊列能力,能夠實現一個簡單的消息隊列系統
方式1:Set 結構
以日期爲 key,以用戶 ID(對應數據庫的 Primary Id)組成的集合爲 value
-
查詢某個用戶的簽到狀態
sismember key member
-
插入簽到狀態
sadd key member
-
統計某天用戶的簽到人數
scard key
2、bitMap 結構
Key的格式爲u:sign:uid:yyyyMM
,Value則採用長度爲4個字節(32位)的位圖(最大月份只有31天)。位圖的每一位代表一天的簽到,1表示已籤,0表示未籤。
# 用戶2月17號簽到
SETBIT u:sign:1000:201902 16 1 # 偏移量是從0開始,所以要把17減1
# 檢查2月17號是否簽到
GETBIT u:sign:1000:201902 16 # 偏移量是從0開始,所以要把17減1
# 統計2月份的簽到次數
BITCOUNT u:sign:1000:201902
# 獲取2月份前28天的簽到數據
BITFIELD u:sign:1000:201902 get u28 0
# 獲取2月份首次簽到的日期
BITPOS u:sign:1000:201902 1 # 返回的首次簽到的偏移量,加上1即爲當月的某一天
兩者對比
- 使用 set 的方式所佔用的內存只與數量相關,和存儲哪些 ID 無關
- 使用 bitmap 的方式所佔用的內存與數量沒有絕對的關係,而是與最高位有關,比如假設 ID 爲 500 W的用戶簽到了,那麼從 1~4999999 用戶不管是否簽到,所佔用的內存都是 500 w個bit,這邊是最壞的情況
- 使用 bitmap 最大可以存儲 2^32-1也就是 512M 數據
- 使用 bitmap 只適用存儲只有兩個狀態的數據,比如用戶簽到,資源(視頻、文章、商品)的已讀或未讀狀態
Redis中 ZSet 是選擇使用 跳錶
而不是紅黑樹
什麼是跳錶
- 跳錶是一個隨機化的數據結構,實質上就是一種可以進行二分查找的有序鏈表。
- 跳錶在原有的有序鏈表上增加了多級索引,通過索引來實現快速查找
- 跳錶不僅能提高搜索性能,同時也可以提高插入和刪除操作的性能
總結:
- 跳錶是可以實現二分查找的有序鏈表
- 每個元素插入時隨機生成它的 level
- 最底層包含所有的元素
- 如果一個元素出現在 level(x),那麼它肯定出現在 x 以下的 level 中
- 每個索引節點包含兩個指針,一個向下,一個向右
- 跳錶查詢、插入、刪除的時間複雜度爲 O(log n),與平衡二叉樹接近
爲什麼不選擇紅黑樹來實現
首先來分析下 Redis 的有序集合支持的操作:
- 插入元素
- 刪除元素
- 查找元素
- 有序輸出所有元素
- 查找區間內的所有元素
其中前 4 項紅黑樹都可以完成,且時間複雜度與跳錶一致,但是最後一個紅黑樹的效率就沒有跳錶高了。在跳錶中,要查找區間的元素,只要定位到兩個區間端點在最低層級的位置,然後按順序遍歷元素就可以了,非常高效。
參考: