Redis基礎(chǔ)與應(yīng)用

Redis基本介紹

Remote Dictionary Server 高性能key-value數(shù)據(jù)庫,支持BSD協(xié)議

官網(wǎng)

Redis基本使用

數(shù)據(jù)類型
String

Redis使用SDS(Simple Dynamic String)動態(tài)字符串保存,可以根據(jù)不同的字符串長度使用不同的結(jié)構(gòu)體

使用場景:

  • 保存Session實現(xiàn)單點登錄
  • 計數(shù)器,記錄網(wǎng)站的瀏覽量或者點贊數(shù)等信息,然后按照一定的規(guī)則持久化到數(shù)據(jù)庫中
  • 緩存數(shù)據(jù),常用的是把緩存對象轉(zhuǎn)為Json格式的字符串保存,讀取的時候再反序列化
List 有序列表

Redis底層是使用QuickList保存,相當(dāng)于Java中的LinkedList,每個節(jié)點都是ZipList的雙向鏈表保存,所以從列表的兩端取數(shù)據(jù)效率很高,查詢數(shù)據(jù)的時間復(fù)雜度為O(n)

使用場景:

  • 粉絲列表
  • 文章評論列表等,可以使用lrange命令,進行分頁查詢,提高應(yīng)用分頁查詢的速度
  • 消息隊列,使用LPush和BRPop命令可以實現(xiàn)簡單的消息隊列功能

常用命令

命令 作用
LPUSH 從列表頭部插入一條數(shù)據(jù)
BRPOP 從列表尾部移除一條數(shù)據(jù),如果沒有數(shù)據(jù),則會一直阻塞等待到超時或者有元素為止
BRPOPLPUSH 從列表中彈出一個值,并且將他插入到另外一個列表的頭部,如果列表中沒有元素,則會一直阻塞等待到超時或者有元素為止
RPOP 從列表中取出指定下標(biāo)的元素
Set 集合

內(nèi)部使用value為空的Hashtable實現(xiàn),查詢的時間復(fù)雜度為O(1)

使用場景:

  • 去重
SortedSet 有序集合

基于跳躍表實現(xiàn),具體查看此文章,他給每一個元素設(shè)置一個score分數(shù),然后根據(jù)分數(shù)進行排序。

使用場景:

  • 各種排行榜
  • 帶權(quán)重的隊列,可以讓線程根據(jù)權(quán)重優(yōu)先執(zhí)行某些任務(wù)。
Hash

內(nèi)部使用ziplist或者Hashtable實現(xiàn),相當(dāng)于Java中的HashMap,使用數(shù)據(jù)+鏈表的方式解決Hash沖突的問題

使用場景:

  • 一般可以把java對象緩存到hash里面,然后通過key-value的方式取值,但是實際開發(fā)中的對象一般比較復(fù)雜,嵌套類型,所以使用hash較少
過期時間

Expire key secondskey設(shè)置過期時間

刪除策略

  • 消極方法
    • 當(dāng)用戶get獲取值的時候,判斷當(dāng)前key是否過期,如果過期就刪除,不返回給用戶
  • 積極方法
    • 周期性的從設(shè)置了過期時間的key中隨機的選擇20個進行檢查
    • 刪除已經(jīng)過期的鍵
    • 如果有25%的key過期,則重復(fù)一次該操作
pub/sub

publish channel message 發(fā)布一條消息到channel通道

subscribe channel[channel...] 訂閱一個或者多個通道的消息

發(fā)布消息后就刪除了,不能實現(xiàn)消息的持久化以及重發(fā)等功能,需要專門的消息隊列中間件來實現(xiàn)(Kafka、RocketMQ、RabbitMQ

自增

使用incr命令可以進行原子遞增

getset

設(shè)置一個key的value并獲取設(shè)置前的值,

Redis內(nèi)存回收策略

  • noeviction 不淘汰任何鍵值對,如果進行讀操作則正常工作,進行寫操作返回錯誤。Redis默認策略
  • allkeys-lru 淘汰最近最少使用的鍵值對
  • allkeys-random 對所有的鍵采用隨機刪除策略
  • volatile-lru 在設(shè)置了過期時間的鍵中采用最近最少使用策略刪除鍵
  • volatile-random 在設(shè)置了過期時間的鍵中采用隨機刪除的策略刪除鍵值對
  • volatile-ttl 在設(shè)置了過期時間的鍵中,具有更早過期時間的key優(yōu)先移除

JoinGroup過程

RDB方式:當(dāng)一定的條件觸發(fā)后,Redis會fork一個子進程來進行持久化操作,會把內(nèi)存中的數(shù)據(jù)集以快照形式寫入磁盤,采用二進制壓縮存儲,將所有的數(shù)據(jù)寫入到一個臨時文件中,等寫入完成后會替換上次持久化的文件。

觸發(fā)條件

  • 用戶配置(默認下面save seconds 操作次數(shù))
    • save 900 1
    • save 300 10
    • save 60 10000
  • bgsave、save 調(diào)用save方法或者bgsave方法
  • flushall 清空數(shù)據(jù)
  • replication 主從同步數(shù)據(jù)

AOF: 每隔一秒或者每次更改redis數(shù)據(jù)就將命令追加到AOF文件中

  • 默認不開啟,使用配置中appendonly yes打開AOF
  • AOF文件體積過大時,會自動的在后臺對AOF進行重寫,重寫后新的AOF文件包含了恢復(fù)當(dāng)前數(shù)據(jù)集所需要的最小命令集;主進程會fork一個子進程進行重寫,類似于RDB快照的方式。
  • auto-aof-rewrite-percentage 表示表示當(dāng)前的AOF文件大小 超過上一次重寫時的AOF文件大小的百分之多少時會再次進行重寫,如果之前沒有重寫過,則以啟動時AOF文件大小為依據(jù)。
  • Auto-aof-rewrite-min-size 表示限制了允許重寫的最小AOF文件大小

RDB和AOF的優(yōu)缺點:

  • 當(dāng)服務(wù)發(fā)生故障,RDB方式會丟失上次備份之后的所有數(shù)據(jù),AOF最多丟失1秒內(nèi)的數(shù)據(jù)
  • Redis重啟時,首先通過RDB加載數(shù)據(jù)到內(nèi)存中,速度比較快,然后通過AOF文件中的近期操作指令,將數(shù)據(jù)恢復(fù)到重啟之前的狀態(tài)
  • AOF文件可讀性比較好,但是相同內(nèi)容的數(shù)據(jù),會比RDB大很多

Redis 單線程

Redis為什么這么塊
  • Redis是在內(nèi)存中操作數(shù)據(jù),而且存儲的數(shù)據(jù)結(jié)構(gòu)類似于Java中的HashMap,查詢的時間復(fù)雜度為O(1)
  • 單線程,不需要在線程間切換,減少了上下文切換的資源消耗,單線程也不需要考慮鎖的使用,減少了獲取鎖和釋放鎖的資源消耗
  • 使用I/O多路復(fù)用,同步非阻塞IO

Lua腳本

Redis是單線程的,在內(nèi)部不會存在線程安全的問題,但是如果有多個客戶端同時訪問,就相當(dāng)于多線程,多個客戶端之間沒有請求的同步,實際順序不一樣就可能產(chǎn)生線程安全問題。使用Lua腳本可以滿足原子性。

基本使用

eval "redis.call(’set’,’hello’,’world')" 0不帶參數(shù)

eval “redis.call(’set’,KEYS[1],ARGV[1])” 1 hello world 帶參數(shù)

Lua腳本的好處
  • 減少網(wǎng)絡(luò)開銷,在Lua腳本中可以把多個命令放在同一個腳本中運行
  • 原子操作,Redis會將整個腳本作為一個整體執(zhí)行,中間不會被其他命令插入
  • 復(fù)用性,客戶端發(fā)送的腳本會存儲在Redis中,其他客戶端可以復(fù)用這一腳本完成同樣的邏輯。

Pinelining 管道

Redis是一種基于客戶端-服務(wù)端模型以及請求/響應(yīng)協(xié)議的TCP服務(wù),通常會遵循以下步驟

  • 客戶端發(fā)送一個查詢請求,并監(jiān)聽socket返回,通常是阻塞模式,等待服務(wù)端響應(yīng)
  • 服務(wù)端處理命令,然后將結(jié)果返回給客戶端

如果客戶端需要發(fā)送批量的命令的時候,往返時間就會變的很長(RTT Round Trip Time),使用pipeline可以減少RTT的時間,服務(wù)端也可以見減少I/O的調(diào)用次數(shù)(用戶態(tài)->內(nèi)核態(tài))

分布式鎖的實現(xiàn)

setnx命令可以設(shè)置一個key-value鍵值對,如果當(dāng)前Redis中已經(jīng)有該key,會返回失敗。多個進程同時進行setnx操作的時候,只會有一個可以設(shè)置成功。

  • setnx 需要設(shè)置一個超時時間,防止獲取鎖的進程掛掉后導(dǎo)致死鎖
  • 存在一種情況,當(dāng)一個客戶端A獲取鎖成功后,由于某種原因阻塞了,然后超時時間到了,自動釋放鎖了,然后另一個客戶端B獲取了鎖,此時客戶端A阻塞結(jié)束并且運行結(jié)束后,會嘗試釋放鎖,這時候可能會把客戶端A的鎖釋放了。解決辦法:setnx key randomValue設(shè)置一個隨機值,每個客戶端不同,釋放鎖的時候,首先判斷一下當(dāng)前value是否與自己客戶端相同,如果相同才能釋放鎖。

Redis集群

1.如何配置

master節(jié)點不用做任何修改,只需要在slave節(jié)點中,修改redis.conf文件中添加slaveof master-ip master-port

2.數(shù)據(jù)如何同步
  • slave初始化階段,Redis會觸發(fā)全量復(fù)制,slave需要將master節(jié)點上的所有數(shù)據(jù)
  • Master服務(wù)器執(zhí)行bgsave命令(子線程),生成快照,同時記錄在此期間的寫命令,快照發(fā)送到Slave節(jié)點并載入后,再把緩存的寫命令發(fā)送過來執(zhí)行命令
  • min-slaves-to-write 3 表示只有當(dāng)3個slave同步完成,master才是可寫的
  • min-slaves-max-lag 10 表示允許slave最長失去連接的時間是10秒,10秒還沒收到slave響應(yīng),master就認為slave已經(jīng)斷開
  • master node 會在內(nèi)存中創(chuàng)建一個backlogmasterslave都會保存要給replica offset還有一個master id,如果slave斷開連接,重連后slave會讓master從上次的replica offset開始繼續(xù)復(fù)制數(shù)據(jù),如果沒有對應(yīng)的offset,則會進行一次全量同步
image-20200408133241856.png

Redis哨兵(Sentinel)

Redis集群之后,如果master節(jié)點掛了,需要從slave節(jié)點中選舉masterRedis沒有提供相關(guān)的功能,需要哨兵來進行監(jiān)控。

哨兵是一個單獨的進程,一般使用三個哨兵集群,保證哨兵的高可用。此時哨兵不僅會監(jiān)控masterslave,還會互相監(jiān)控。

配置:在redis-sentinel.conf文件中配置 sentinel monitor name ip port quorum,只需要配置master節(jié)點即可

哨兵之間相互感知
  • 所有的sentinel向他們的監(jiān)視的master節(jié)點訂閱channel sentinel
  • 新加入的sentinel節(jié)點向master的這個節(jié)點發(fā)布一條消息,其他訂閱了這個channelsentinel會發(fā)現(xiàn)這個新的sentinel
  • 新加入的sentinel和其他sentinel節(jié)點建立長連接
故障發(fā)現(xiàn)

sentinel節(jié)點定期向master節(jié)點發(fā)送心跳包判斷是否存活,一旦發(fā)現(xiàn)master沒有正確響應(yīng),sentinel就會把master設(shè)為主觀不可用狀態(tài),然后把狀態(tài)發(fā)送給其他sentinel確認,當(dāng)確認的sentinel超過quorum時,就認為master節(jié)點客觀不可用,接著就進入選舉新的master的流程,這里使用到了Raft算法,基于投票的算法,只要保證過半數(shù)節(jié)點通過提議即可。

哨兵的主要功能
  • 集群監(jiān)控 哨兵可以監(jiān)控master節(jié)點和slave節(jié)點的運行情況
  • 消息通知 當(dāng)集群中有某一個節(jié)點掛了之后,可以通知管理員
  • 故障轉(zhuǎn)移 當(dāng)master節(jié)點掛了之后,哨兵會從slave節(jié)點中重新選舉一個做為master節(jié)點
  • 配置中心 如果發(fā)生故障轉(zhuǎn)移,通知客戶端新的master節(jié)點地址

Redis分片(Cluster)

image-20200402164755059.png

主從同步的集群中,每個節(jié)點都存有集群中的所有數(shù)據(jù),存在著單機存儲量的瓶頸問題,形成了木桶效應(yīng)。對Redis進行分片集群,可以提高Redis的性能和存儲能力。

集群結(jié)構(gòu)如上圖,一個Redis Cluster由多個Redis節(jié)點構(gòu)成,不同節(jié)點組服務(wù)沒有交集,也就是每一個節(jié)點組對應(yīng)數(shù)據(jù)sharding的一個分片。節(jié)點組內(nèi)部分為主備兩類節(jié)點,對應(yīng)master和slave節(jié)點。兩者數(shù)據(jù)準(zhǔn)實時一直,通過異步化的主備復(fù)制機制來保證。一個節(jié)點組有且只有一個master節(jié)點,同時可以有0到n個slave節(jié)點,在這個節(jié)點組中,只有master節(jié)點可以提供讀寫服務(wù),slave只能提供讀服務(wù)。

集群的數(shù)據(jù)分片

Redis 集群沒有使用一致性hash, 而是引入了 哈希槽的概念.

Redis 集群有16384個哈希槽,每個key通過CRC16校驗后對16384取模來決定放置哪個槽.集群的每個節(jié)點負責(zé)一部分hash槽,舉個例子,比如當(dāng)前集群有3個節(jié)點,那么:

  • 節(jié)點 A 包含 0 到 5500號哈希槽.
  • 節(jié)點 B 包含5501 到 11000 號哈希槽.
  • 節(jié)點 C 包含11001 到 16384號哈希槽.

這種結(jié)構(gòu)很容易添加或者刪除節(jié)點. 比如如果我想新添加個節(jié)點D, 我需要從節(jié)點 A, B, C中得部分槽到D上. 如果我想移除節(jié)點A,需要將A中的槽移到B和C節(jié)點上,然后將沒有任何槽的A節(jié)點從集群中移除即可. 由于從一個節(jié)點將哈希槽移動到另一個節(jié)點并不會停止服務(wù),所以無論添加刪除或者改變某個節(jié)點的哈希槽的數(shù)量都不會造成集群不可用的狀態(tài).

如何把一次操作的所有值保存到一個節(jié)點中

Redis中引入了HashTag的概念,可以是得數(shù)據(jù)分布算法可以根據(jù)key的某一個部分進行計算,

舉個簡單的例子,加入對于用戶的信息進行存儲, user:user1:id、user:user1:name/ 那么通過hashtag的方式, user:{user1}:id、user:{user1}.name; 表示

當(dāng)一個key包含 {} 的時候,就不對整個key做hash,而僅對 {} 包括的字符串做hash。

擴容

  • 槽位遷移
  • 數(shù)據(jù)遷移

Redis緩存問題(緩存擊穿、緩存穿透、緩存雪崩、緩存不一致)

緩存擊穿

當(dāng)Redis緩存中的某個熱點key失效,導(dǎo)致大量的請求穿過緩存,到達DB

緩存穿透

大量的無效請求到達數(shù)據(jù)庫,比如請求參數(shù)id = -1,redis和數(shù)據(jù)庫中都沒有數(shù)據(jù),但是會一直查詢數(shù)據(jù)庫。

解決辦法
  • 熱點數(shù)據(jù)設(shè)置永不失效
  • 多級緩存
  • 熔斷降級
  • nginx拉黑多次請求的同一ip
  • 參數(shù)校驗,永遠不要相信用戶
  • 當(dāng)查詢到數(shù)據(jù)庫中沒有的值,可以設(shè)置一個為null或其他提示的數(shù)據(jù)到緩存中,下次請求就不會到數(shù)據(jù)庫中了,緩存的失效時間設(shè)置短一點如30秒
  • 使用互斥鎖
  • 布隆過濾器
緩存雪崩

同一時間Redis緩存的key大面積失效,導(dǎo)致所有的請求都到達了數(shù)據(jù)庫

  • 一般這種情況是redis的過期時間到了,可以設(shè)置一個隨機值,讓key在一定范圍內(nèi)失效
  • Redis分片,將key保存到不同的節(jié)點上,防止出現(xiàn)大面積失效的情況
緩存更新方式

這是決定在使用緩存時就該考慮的問題。

緩存的數(shù)據(jù)在數(shù)據(jù)源發(fā)生變更時需要對緩存進行更新,數(shù)據(jù)源可能是 DB,也可能是遠程服務(wù)。更新的方式可以是主動更新。數(shù)據(jù)源是 DB 時,可以在更新完 DB 后就直接更新緩存。

當(dāng)數(shù)據(jù)源不是 DB 而是其他遠程服務(wù),可能無法及時主動感知數(shù)據(jù)變更,這種情況下一般會選擇對緩存數(shù)據(jù)設(shè)置失效期,也就是數(shù)據(jù)不一致的最大容忍時間。

這種場景下,可以選擇失效更新,key 不存在或失效時先請求數(shù)據(jù)源獲取最新數(shù)據(jù),然后再次緩存,并更新失效期。

但這樣做有個問題,如果依賴的遠程服務(wù)在更新時出現(xiàn)異常,則會導(dǎo)致數(shù)據(jù)不可用。改進的辦法是異步更新,就是當(dāng)失效時先不清除數(shù)據(jù),繼續(xù)使用舊的數(shù)據(jù),然后由異步線程去執(zhí)行更新任務(wù)。這樣就避免了失效瞬間的空窗期。另外還有一種純異步更新方式,定時對數(shù)據(jù)進行分批更新。實際使用時可以根據(jù)業(yè)務(wù)場景選擇更新方式。

數(shù)據(jù)不一致

第二個問題是數(shù)據(jù)不一致的問題,可以說只要使用緩存,就要考慮如何面對這個問題。緩存不一致產(chǎn)生的原因一般是主動更新失敗,例如更新 DB 后,更新 Redis 因為網(wǎng)絡(luò)原因請求超時;或者是異步更新失敗導(dǎo)致。

解決的辦法是,如果服務(wù)對耗時不是特別敏感可以增加重試;如果服務(wù)對耗時敏感可以通過異步補償任務(wù)來處理失敗的更新,或者短期的數(shù)據(jù)不一致不會影響業(yè)務(wù),那么只要下次更新時可以成功,能保證最終一致性就可以。

布隆過濾器

Redis使用中有哪些坑???

不能保證原子性
?著作權(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ù)。

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