手機(jī)企業(yè)網(wǎng)站制作企業(yè)網(wǎng)頁設(shè)計(jì)公司
目錄
Redis 是單線程的!為什么
Redis-Key(操作redis的key命令)
String
擴(kuò)展字符串操作命令
數(shù)字增長命令
字符串范圍range命令
設(shè)置過期時(shí)間命令
批量設(shè)置值
string設(shè)置對象,但最好使用hash來存儲對象
組合命令getset,先get然后在set
Hash
hash命令:
hash基礎(chǔ)的增刪查
hash擴(kuò)展命令
總結(jié)
List
Set
ZSet
Redis事務(wù)
redis的事務(wù)命令:
正常執(zhí)行事務(wù) exec
放棄事務(wù) discard
命令異常,事務(wù)回滾
1.類似于java編譯型異常(語法錯(cuò)誤)
2.類似于java運(yùn)行時(shí)異常(如1/0,除數(shù)不能為0錯(cuò)誤) (邏輯錯(cuò)誤)
監(jiān)控 Watch(相當(dāng)于java加鎖) (面試: redis的watch命令監(jiān)控實(shí)現(xiàn)秒殺系統(tǒng))
悲觀鎖
樂觀鎖
Redis用watch做樂觀鎖實(shí)現(xiàn)步驟
redis監(jiān)視watch測試案例
取錢正常執(zhí)行成功的流程
取錢出現(xiàn)并發(fā)問題的流程
Redis的Java客戶端
測試jedis
Springboot整合Redis
自定義RedisTemplate
緩存
緩存穿透
解決方案一:緩存空數(shù)據(jù)
解決方案二:布隆過濾器
緩存擊穿
解決方案一:互斥鎖(分布式鎖)
解決方案二:邏輯過期
緩存雪崩
持久化
1.RDB
RDB執(zhí)行原理
2.AOF
RDB與AOF對比
雙寫一致性
1.一致性要求高
1.延遲雙刪
1.無論第一步是先刪除緩存還是先修改數(shù)據(jù)庫都會導(dǎo)致臟數(shù)據(jù)的出現(xiàn)
2.刪除兩次緩存的原因
3.延時(shí)刪除的原因
總結(jié)
2.加讀寫鎖
2.允許延遲一致(較為主流)
1.異步通知保證數(shù)據(jù)的最終一致性
2.基于Canal的異步通知
數(shù)據(jù)過期策略
方案一惰性刪除:
方案二定期刪除:
總結(jié)
數(shù)據(jù)淘汰策略
八種不同策略
數(shù)據(jù)淘汰策略--使用建議
分布式鎖
場景
引入與基本介紹
redis分布式鎖實(shí)現(xiàn)原理
基本介紹
設(shè)置超時(shí)失效時(shí)間的原因(避免死鎖):
總結(jié)
缺陷
Redisson實(shí)現(xiàn)的分布式鎖
1.redisson實(shí)現(xiàn)的分布式鎖的執(zhí)行流程/合理地控制鎖的有效時(shí)長(失效時(shí)間)
2.可重入
3.主從一致性
Redis發(fā)布訂閱
訂閱/發(fā)布消息圖
命令
測試
Redis發(fā)布訂閱原理
總結(jié)
使用場景
Redis消息隊(duì)列
概念
基于List結(jié)構(gòu)模擬消息隊(duì)列(可實(shí)現(xiàn)阻塞隊(duì)列的效果)
基于PubSub(發(fā)布訂閱)的消息隊(duì)列
基于Stream的消息隊(duì)列
基本知識
消費(fèi)者組
Redis集群(分布式緩存)
單點(diǎn)Redis的問題
主從復(fù)制
主從數(shù)據(jù)同步原理
1.主從全量同步
2.主從增量同步(slave重啟或后期數(shù)據(jù)變化)
3.總結(jié)
哨兵模式
哨兵模式的結(jié)構(gòu)與作用
服務(wù)狀態(tài)監(jiān)控
哨兵選主規(guī)則(主節(jié)點(diǎn)宕機(jī)后,選從節(jié)點(diǎn)為主節(jié)點(diǎn)的規(guī)則)
Redis集群(哨兵模式)的腦裂問題
分片集群
redis集群環(huán)境部署(環(huán)境配置)
一主二從集群搭建(命令或文件配置)(這種方式的redis集群實(shí)際工作用不到,僅供基礎(chǔ)學(xué)習(xí))
命令方式配置
文件方式配置(一主二從,持久化的,對于哨兵模式,不建議使用這種)
一主兩從的第二種搭建方式(層層鏈路)哨兵模式的手動版
I/O多路復(fù)用模型
redis為什么這么快
用戶空間和內(nèi)核空間
阻塞IO
非阻塞IO
IO多路復(fù)用
Redis網(wǎng)絡(luò)模型
Redis 是單線程的!為什么
Redis是基于內(nèi)存實(shí)現(xiàn)的,使用Redis時(shí),CPU幾乎不會成為Redis性能瓶頸,Redis的瓶頸是機(jī)器的內(nèi)存和網(wǎng)絡(luò)帶寬(網(wǎng)絡(luò)),既然可以使用單線程來實(shí)現(xiàn),就使用單線程了!所有就使用了單線程了!
內(nèi)存訪問速度:由于Redis將數(shù)據(jù)存儲在內(nèi)存中,數(shù)據(jù)訪問速度非???/strong>,通常接近于CPU的緩存訪問速度。這意味著CPU在讀取或?qū)懭霐?shù)據(jù)時(shí)很少需要等待,從而減少了CPU的空閑時(shí)間。
計(jì)算密集度:Redis的操作通常是簡單的數(shù)據(jù)查找、插入、刪除和計(jì)算集合操作(如交集、并集等)。這些操作在CPU層面上的計(jì)算復(fù)雜度相對較低,因此不太可能使CPU成為瓶頸。
并發(fā)處理:Redis使用單線程模型來處理客戶端請求(盡管有IO多路復(fù)用技術(shù)來同時(shí)處理多個(gè)連接),但這并不意味著Redis不能利用多核CPU。通過部署多個(gè)Redis實(shí)例或使用Redis集群,可以水平擴(kuò)展以利用多核CPU的并行處理能力。
Redis 是C 語言寫的,官方提供的數(shù)據(jù)為 100000+ 的QPS,完全不比同樣是使用 key-vale的Memecache差!
Redis 為什么單線程還這么快?
1、高性能的服務(wù)器不一定是多線程的
2、多線程不一定比單線程效率高!(多線程CPU上下文會切換消耗資源!)
速度:CPU>內(nèi)存>硬盤
核心:redis 是將所有的數(shù)據(jù)全部放在內(nèi)存中的,所以說使用單線程去操作效率就是最高的,多線程(CPU上下文會切換:耗時(shí)的操作!)
對于內(nèi)存系統(tǒng)來說,如果沒有上下文切換效率就是最高的!
多次讀寫都是在一個(gè)CPU上的,在內(nèi)存情況下,這個(gè)就是最佳的方案!
Redis-Key(操作redis的key命令)
keys * # 查看所有的key?
set name123 kuangshen # set key
EXISTS name123 # 判斷當(dāng)前的key是否存在
move name123 # 移除當(dāng)前的key
EXPIRE name123 10 # 設(shè)置key的過期時(shí)間,單位是秒
ttl name123 # 查看當(dāng)前key的剩余時(shí)間
type name123? ??# 查看當(dāng)前key的一個(gè)類型
String
擴(kuò)展字符串操作命令
- APPEND key123 "hello" # 追加字符串,如果當(dāng)前key不存在,就相當(dāng)于setkey?
- STRLEN key123 # 獲取字符串的長度!
數(shù)字增長命令
- set views123 0 # 初始瀏覽量為0?
- incr views123 # 自增1 瀏覽量變?yōu)??
- decr views123 # 自減1 瀏覽量-1?
- INCRBY views123 10 # 可以設(shè)置步長,指定增量10!
字符串范圍range命令
- GETRANGE key123 0 3 # 截取字符串 [0,3]
- GETRANGE key123 0 -1 # 獲取全部的字符串 和 get key是一樣的
- SETRANGE key123 1 xx # 替換指定位置1開始的字符串!
設(shè)置過期時(shí)間命令
setex (set with expire) //設(shè)置過期時(shí)間
例:setex key123 30 "hello" //設(shè)置key123 的值為hello,30秒后過期
setnx (set if not exist) //key不存在在設(shè)置,key存在則回滾設(shè)置失敗(在分布式鎖中會常常使用)?
例:setnx mykey123 "MongoDB" //如果mykey不存在創(chuàng)建成功,存在,創(chuàng)建失敗不會替換值
批量設(shè)置值
- ?mset k1 v1 k2 v2 k3 v3 # 同時(shí)設(shè)置多個(gè)key和value值(k1:v1, k2:v2, k3:v3)
- mget k1 k2 k3 # 根據(jù)多個(gè)key同時(shí)獲取多個(gè)值
- msetnx k1 v1 k4 v4 # msetnx 是一個(gè)原子性的操作,要么一起成功,要么一起失敗!
-
- 由于k1已經(jīng)存在,所以setnx一定會失敗,由于是原子性操作k4也會跟著失敗
string設(shè)置對象,但最好使用hash來存儲對象
- set user:1 {name:zhangsan,age:3} # 設(shè)置一個(gè)key為user:1的對象,值為 json字符來保存一個(gè)對象!?
-
- key值為user:{id}, value值為json
- mset user:1:name zhangsan user:1:age 2 #批量創(chuàng)建對象
-
- 這里的key是一個(gè)巧妙的設(shè)計(jì): user:{id}:{filed} , 如此設(shè)計(jì)在Redis中是完全OK了!
- mget user:1:name user:1:age #批量獲取對象中的值
組合命令getset,先get然后在set
- getset db redis # 如果不存在值,則返回 nil?,但同時(shí)值被設(shè)置成了redis
- getset db mongodb # 如果存在值,獲取原來的值,并設(shè)置新的值
Hash
相當(dāng)于Map集合,key-value!,只是value存的是map,也就是key-map,值是map集合
- hash本質(zhì)和String類型沒有太大區(qū)別,還是一個(gè)簡單的key-value
- hset myhash field codeyuaiiao
-
- 命令含義:hset key mapkey mapvalue
hash命令:
hash基礎(chǔ)的增刪查
- hset myhash field1 yuaiiao # 添加或修改一個(gè)具體的值
- hget myhash field1 # 獲取一個(gè)字段值
- hmset myhash field4 hello field5 byebye # 添加多個(gè)字段值進(jìn)map集合
- hmget myhash field3 field4 # 獲取多個(gè)指定字段值
- hgetall myhash # 獲取hash全部字段值(包含了key和value)
- hdel myhash field1 field2 # 刪除一個(gè)或多個(gè)指定字段值
hash擴(kuò)展命令
- hlen myhash # 獲取hash表的字段數(shù)量
- hexists myhash field1 # 判斷hash表中指定字段是否存在
- hkeys myhash # 獲取所有field(相當(dāng)于key)
- hvals myhash # 獲取所有value
- hincrby myhash field3 2 # 指定增量
- hincrby myhash field3 -2 # 指定減量
- hsetnx myhash field4 yuaiiao # 如果不存在可以設(shè)置,如果存在則不可設(shè)置
總結(jié)
hash變更的用戶數(shù)據(jù)user表,name,age字段 ,尤其是用戶信息之類的,經(jīng)常變動的信息!
hash更適合于對象的存儲,String更加適合字符串存儲
List
Set
Redis的Set結(jié)構(gòu)與java中的HashSet類似,可以看做是一個(gè)value為null的HashMap.因?yàn)橐彩且粋€(gè)hash表,因此具備與hashset類似的特征:
無序
元素不可重復(fù)
查找快
支持交集,并集,差集等功能
ZSet
Redis事務(wù)
mysql事務(wù)本質(zhì)
- ACID特性,要么同時(shí)成功,要么同時(shí)失敗,保證原子性!
Redis事務(wù)本質(zhì)
- 一組命令的集合! 一個(gè)事務(wù)中的所有命令都會被序列化,在事務(wù)執(zhí)行過程中,會按照順序執(zhí)行!
- 一次性,順序性,排他性! 執(zhí)行一系列的命令!
------隊(duì)列 set get set 執(zhí)行-----
- Redis 事務(wù)沒有隔離級別的概念!
-
- 不會出現(xiàn)幻度,臟讀,不可重復(fù)讀等問題
- 所有的命令在事務(wù)中,并沒有直接被執(zhí)行,只有發(fā)起執(zhí)行命令的時(shí)候才會執(zhí)行! Exec
- Redis單條命令是保證原子性的,但是redis事務(wù)是一組命令的集合,所以不保證原子性!
redis的事務(wù)命令:
- 開啟事務(wù)(multi)
- 命令入隊(duì)(要執(zhí)行的命令) (事務(wù)隊(duì)列)
- 執(zhí)行事務(wù)(exec)
正常執(zhí)行事務(wù) exec
127.0.0.1:6379> multi # 開啟事務(wù)
OK
127.0.0.1:6379> set key hello # 執(zhí)行命令
QUEUED
127.0.0.1:6379> set key1 yuaiiao
QUEUED
127.0.0.1:6379> get key
QUEUED
127.0.0.1:6379> get key1
QUEUED
127.0.0.1:6379> exec # 執(zhí)行事務(wù)
1) OK
2) OK
3) "hello"
4) "yuaiiao"
127.0.0.1:6379>
放棄事務(wù) discard
127.0.0.1:6379> multi # 開啟事務(wù)
OK
127.0.0.1:6379> set key 1
QUEUED
127.0.0.1:6379> set key2 3
QUEUED
127.0.0.1:6379> discard # 放棄事務(wù)
OK
127.0.0.1:6379> get key2 #事務(wù)隊(duì)列中的命令都不會被執(zhí)行
(nil)
127.0.0.1:6379>
命令異常,事務(wù)回滾
1.類似于java編譯型異常(語法錯(cuò)誤)
命令語法導(dǎo)致執(zhí)行錯(cuò)誤,事務(wù)中所有的命令都不會被執(zhí)行。
127.0.0.1:6379> multi # 開啟事務(wù)
OK
127.0.0.1:6379> set k1 v1 # 執(zhí)行命令
QUEUED
127.0.0.1:6379> set k2 v2
QUEUED
127.0.0.1:6379> getset k3 v3
QUEUED
127.0.0.1:6379> getset k3 # 錯(cuò)誤的命令
(error) ERR wrong number of arguments for 'getset' command
127.0.0.1:6379> set k4 v4
QUEUED
127.0.0.1:6379> exec # 執(zhí)行事務(wù)報(bào)錯(cuò)
(error) EXECABORT Transaction discarded because of previous errors.
127.0.0.1:6379> get k2 # 事務(wù)執(zhí)行失敗,得不到值,所有的命令都不會被執(zhí)行
(nil)
127.0.0.1:6379>
2.類似于java運(yùn)行時(shí)異常(如1/0,除數(shù)不能為0錯(cuò)誤) (邏輯錯(cuò)誤)
命令邏輯執(zhí)行錯(cuò)誤 , 那么執(zhí)行命令的時(shí)候,其他的命令是可以正常執(zhí)行的,只是錯(cuò)誤命令拋出異常!
證明事務(wù)不保證原子性
127.0.0.1:6379> set k1 "v1" # 字符串
OK
127.0.0.1:6379> multi
OK
127.0.0.1:6379> incr k1 # 對字符串進(jìn)行 自增1 運(yùn)行時(shí)異常錯(cuò)誤
QUEUED
127.0.0.1:6379> set k2 v2
QUEUED
127.0.0.1:6379> set k3 v3
QUEUED
127.0.0.1:6379> get k2
QUEUED
127.0.0.1:6379> exec # 錯(cuò)誤的命令報(bào)錯(cuò)但是其余命令都能執(zhí)行
1) (error) ERR value is not an integer or out of range
2) OK
3) OK
4) "v2"
127.0.0.1:6379> get k2 # 其余命令正常執(zhí)行
"v2"
127.0.0.1:6379> get k3
"v3"
127.0.0.1:6379>
監(jiān)控 Watch(相當(dāng)于java加鎖) (面試: redis的watch命令監(jiān)控實(shí)現(xiàn)秒殺系統(tǒng))
悲觀鎖
很悲觀,認(rèn)為什么時(shí)候都會出問題,無論做什么都會加鎖,效率低下
樂觀鎖
很樂觀,認(rèn)為什么時(shí)候都不會出現(xiàn)問題,所以不會上鎖,更新數(shù)據(jù)的時(shí)候去判斷一下,在此期間是否有人修改過這個(gè)數(shù)據(jù),使用version字段比較。
Redis用watch做樂觀鎖實(shí)現(xiàn)步驟
1.獲取最新version
2.更新的的時(shí)候比較version,version沒變更新成功,version改變進(jìn)入自旋。
redis的樂觀鎖watch
- watch加鎖,記得用完需要unwatch解鎖
redis監(jiān)視watch測試案例
取錢正常執(zhí)行成功的流程
127.0.0.1:6379> set money 100 #存錢100
OK
127.0.0.1:6379> set out 0 #取錢0
OK
127.0.0.1:6379> watch money # 監(jiān)視money對象
OK
127.0.0.1:6379> multi
OK
127.0.0.1:6379> decrby money 20
QUEUED
127.0.0.1:6379> incrby out 20
QUEUED
127.0.0.1:6379> exec # 事務(wù)正常結(jié)束 , 期間數(shù)據(jù)沒有發(fā)生變動 ,這個(gè)時(shí)候就正常執(zhí)行成功了!
1) (integer) 80
2) (integer) 20
127.0.0.1:6379>
取錢出現(xiàn)并發(fā)問題的流程
- 測試多線程修改值,使用watch當(dāng)做redis 的樂觀鎖操作
-
- 在開一個(gè)redis-client客戶端,一共有兩個(gè)客戶端
- 客戶端1:開啟事務(wù),money取錢20
- 客戶端2:這時(shí)候直接把money修改成1000
-
- 客戶端1:繼續(xù)執(zhí)行,out存錢20,這時(shí)候執(zhí)行事務(wù)會
-
-
- 執(zhí)行返回nil,修改失敗
-
# 客戶端1:開啟事務(wù),監(jiān)視money對象,money取錢20
127.0.0.1:6379> watch money # 監(jiān)視money對象
OK
127.0.1:6379> multi
OK
127.0.0.1:6379> decrby money 20
QUEUED# 客戶端1還未提交,客戶端2:這時(shí)候直接把money修改成1000
# set money 10000# 客戶端1:繼續(xù)執(zhí)行,out存錢20,然后執(zhí)行事務(wù),樂觀鎖對比money版本號改動了,執(zhí)行失敗
# 執(zhí)行之前,另一個(gè)線程,修改了我們的值,就會導(dǎo)致事務(wù)執(zhí)行失敗!# 127.0.0.1:6379> get money"80"# 127.0.0.1:6379> set money 1000# OK
127.0.0.1:6379> incrby out 20 #out存錢20
QUEUED
127.0.0.1:6379> exec # 提交事務(wù),監(jiān)視money的version是否變化,有變化事務(wù)回滾,結(jié)果返回nil
(nil)
127.0.0.1:6379>
- redis事務(wù)執(zhí)行失敗后的自旋步驟
-
- 先釋放監(jiān)控鎖watch,在重新獲取鎖重復(fù)以上步驟,進(jìn)行自旋
127.0.0.1:6379> unwatch # 釋放鎖(監(jiān)控),如果發(fā)現(xiàn)事務(wù)執(zhí)行失敗,就先解鎖
OK
127.0.0.1:6379> watch money # 重新獲取鎖,獲取最新的值,再次監(jiān)視,select version
OK
127.0.0.1:6379> multi # 開啟事務(wù)執(zhí)行正常操作
OK
127.0.0.1:6379> decrby money 20
QUEUED
127.0.0.1:6379> incrby out 20
QUEUED
127.0.0.1:6379> exec # 在比對監(jiān)視的值是否發(fā)生了變化,如果沒有變化,可以執(zhí)行成功呢,如果變化了就執(zhí)行失敗,在重新以上步驟
1) (integer) 980
2) (integer) 40
127.0.0.1:6379>
- 如果修改失敗,獲取最新的值就好
Redis的Java客戶端
我們要使用java來操作redis
有springboot整合了,我們也要學(xué)習(xí)jedis
什么是jedis?
- jedis 是redis官方推薦的java連接開發(fā)工具! 使用java操作Redis的中間件 ! 如果你要使用java 操作redis, 那么一定要對jedis十分熟悉
測試jedis
- 導(dǎo)入對應(yīng)的依賴
<!--導(dǎo)入jedis-->
<dependencies><!-- https://mvnrepository.com/artifact/redis.clients/jedis --><dependency><groupId>redis.clients</groupId><artifactId>jedis</artifactId><version>3.3.0</version></dependency><!--導(dǎo)入 fastjson--><dependency><groupId>com.alibaba</groupId><artifactId>fastjson</artifactId><version>1.2.70</version></dependency>
</dependencies>
2.編碼測試
- 連接redis數(shù)據(jù)庫
- 操作命令
- 斷開連接
package com.kuang;
import redis.clients.jedis.Jedis;public class TestPing {public static void main(String[] args) {// 1、 new Jedis 對象即可Jedis jedis = new Jedis("127.0.0.1", 6379);// jedis 所有的命令就是我們之前學(xué)習(xí)的所有指令!所以之前的指令學(xué)習(xí)很重要!System.out.println(jedis.ping());}
}
結(jié)果輸出:
jedis 所有的命令就是我們之前學(xué)習(xí)的所有指令!所以之前的指令學(xué)習(xí)很重要!
Springboot整合Redis
說明 :在 SpringBoot2.x之后, 原來使用的jedis 被替換為了 lettuce ?
-
- jedis :底層采用的直連, 多個(gè)線程操作的話 ,是不安全的, 如果想要避免不安全, 使用jedis pool 連接池 ! 更像 BIO模式,阻塞的.
- lettuce : 采用netty , 實(shí)例可以再多個(gè)線程中進(jìn)行共享,不存在線程不安全的情況 ! 可以減少線程數(shù)量,不需要開連接池, 更像NIO模式非阻塞的
自定義RedisTemplate
redis關(guān)于對象的保存,對象需要序列化。
- 對象如果不序列化保存,則會報(bào)錯(cuò)
分析redisTemplate源碼為什么對象需要序列化
- 分析源碼序列化配置
-
-
新建config/RedisConfig
- 不使用JDK序列化,key使用哪個(gè)string序列化,value使用json序列化
package com.kuang.config;import com.fasterxml.jackson.annotation.JsonAutoDetect;
import com.fasterxml.jackson.annotation.PropertyAccessor;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.StringRedisSerializer;@Configuration
public class RedisConfig {// 這是我給大家寫好的一個(gè)固定模板,大家在企業(yè)中,拿去就可以直接使用!// 自己定義了一個(gè) RedisTemplate @Bean @SuppressWarnings("all")public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory factory) {// 我們?yōu)榱俗约洪_發(fā)方便,一般直接使用 <String, Object>//源碼是<Object,Object>類型,可以自定義把Object轉(zhuǎn)換成String類型RedisTemplate<String, Object> template = new RedisTemplate<String, Object>();template.setConnectionFactory(factory);// Json序列化配置Jackson2JsonRedisSerializer jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer(Object.class);ObjectMapper om = new ObjectMapper(); om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY); om.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);jackson2JsonRedisSerializer.setObjectMapper(om);// String 的序列化,解決redis存儲字符串是轉(zhuǎn)義字符,看著像亂碼StringRedisSerializer stringRedisSerializer = new StringRedisSerializer();// key采用String的序列化方式template.setKeySerializer(stringRedisSerializer);// hash的key也采用String的序列化方式template.setHashKeySerializer(stringRedisSerializer);// value 序 列 化 方 式 采 用 jacksontemplate.setValueSerializer(jackson2JsonRedisSerializer);// hash 的 value 序 列 化 方 式 采 用 jacksontemplate.setHashValueSerializer(jackson2JsonRedisSerializer); template.afterPropertiesSet();return template;}}
StringRedisTemplate:
在使用String類型存儲自定義對象時(shí):
存入到Redis的數(shù)據(jù)會存儲一個(gè)該類對象的位置:
比如:
"@class": "com.sky.test.User",
這種方法更麻煩一點(diǎn),需要每次手動地序列化,反序列化。
緩存
緩存就是數(shù)據(jù)交換的緩沖區(qū)(稱作Cache),是存貯數(shù)據(jù)的臨時(shí)地方,一般讀寫性能較高。
緩存:緩存穿透,緩存擊穿,緩存雪崩
雙寫一致性,緩存的持久化
數(shù)據(jù)過期策略,數(shù)據(jù)淘汰策略
緩存穿透
緩存穿透:查詢一個(gè)不存在的數(shù)據(jù)(在緩存中和數(shù)據(jù)庫中都不存在),mysql查詢不到數(shù)據(jù)也不會直接寫入緩存,就會導(dǎo)致每次請求都查數(shù)據(jù)庫。
危害:如果有人惡意地讓很多線程并發(fā)地請求訪問這些不存在的數(shù)據(jù),那么這些所有的請求都會到達(dá)數(shù)據(jù)庫。而數(shù)據(jù)庫的并發(fā)不會很高,請求到達(dá)一定的量則會把數(shù)據(jù)庫搞垮,導(dǎo)致數(shù)據(jù)庫宕機(jī)。
解決方案一:緩存空數(shù)據(jù)
緩存空數(shù)據(jù),查詢返回的數(shù)據(jù)為空,仍把這個(gè)空結(jié)果進(jìn)行緩存 。即{key:1,value:null}
優(yōu)點(diǎn):簡單
缺點(diǎn):消耗內(nèi)存,可能會發(fā)生不一致的問題
解決方案二:布隆過濾器
優(yōu)點(diǎn):內(nèi)存占用較少,沒有多余key
缺點(diǎn):實(shí)現(xiàn)復(fù)雜,存在誤判
首先是緩存預(yù)熱時(shí)往布隆過濾器中添加數(shù)據(jù)(存儲數(shù)據(jù)過程)。
布隆過濾器主要是用于檢索一個(gè)元素是否在一個(gè)集合中。
我們當(dāng)時(shí)使用的是redisson實(shí)現(xiàn)的布隆過濾器。
它的底層主要是先去初始化一個(gè)比較大數(shù)組(bitmap),里面存放的二進(jìn)制0或1。在一開始都是0,當(dāng)一個(gè)key來了之后經(jīng)過3次hash計(jì)算,模于數(shù)組長度找到數(shù)據(jù)的下標(biāo)然后把數(shù)組中原來的0改為1,這樣的話,三個(gè)數(shù)組的位置就能標(biāo)明一個(gè)key的存在。
查找的過程也是一樣的。
缺點(diǎn):
布隆過濾器有可能會產(chǎn)生一定的誤判,我們一般可以設(shè)置這個(gè)誤判率,大概不會超過5%,其實(shí)這個(gè)誤判是必然存在的,要不就得增加數(shù)組的長度,其實(shí)已經(jīng)算是很劃算了,5%以內(nèi)的誤判率一般的項(xiàng)目也能接受,不至于高并發(fā)下壓倒數(shù)據(jù)庫。
誤判示例如下圖:
誤判率:數(shù)組越小誤判率就越大,數(shù)組越大誤判率就越小,但是同時(shí)帶來了更多的內(nèi)存消耗。
緩存擊穿
緩存擊穿的意思是對于設(shè)置了過期時(shí)間的key,緩存在某個(gè)時(shí)間點(diǎn)過期的時(shí)候,恰好這時(shí)間點(diǎn)對這個(gè)key有大量的并發(fā)請求過來,這些請求發(fā)現(xiàn)緩存過期,一般都會從后端DB加載數(shù)據(jù)并回設(shè)到緩存,這個(gè)時(shí)候大并發(fā)的請求可能會瞬間把DB壓垮。
解釋:對于Redis中正好過期的數(shù)據(jù)(Redis不存在數(shù)據(jù)了),此時(shí)如果有請求來訪問這些數(shù)據(jù),正常來說是會去查DB,同時(shí)DB把數(shù)據(jù)更新到Redis,再把數(shù)據(jù)返回。那么Redis也就得到了刷新,后續(xù)redis也可以繼續(xù)為DB分擔(dān)壓力。
但是把DB數(shù)據(jù)更新到Redis的過程中,可能會花費(fèi)過多的時(shí)間(可能是因?yàn)镈B刷新到redis的數(shù)據(jù)是多表的,多表統(tǒng)計(jì)費(fèi)時(shí)間),在這個(gè)時(shí)間段內(nèi)redis的數(shù)據(jù)未重建完成,大量的并發(fā)請求過來的話則會全部走DB,會瞬間把DB壓垮。
如圖所示:
解決方案一:互斥鎖(分布式鎖)
使用互斥鎖:當(dāng)緩存過期失效時(shí),不立即去load db,先使用如redis的setnx去設(shè)置一個(gè)互斥鎖,當(dāng)操作成功返回時(shí)再進(jìn)行l(wèi)oad db的操作并回設(shè)緩存,否則重試get緩存的方法。
如圖所示:
互斥鎖保證了同時(shí)只能有一個(gè)線程獲得鎖去查詢數(shù)據(jù)庫并重建redis緩存數(shù)據(jù)。保證了數(shù)據(jù)的強(qiáng)一致性。
缺點(diǎn):性能較低。
解決方案二:邏輯過期
Redis中的熱點(diǎn)數(shù)據(jù)的key不設(shè)置過期時(shí)間,設(shè)置邏輯過期字段。
1、在設(shè)置key的時(shí)候,設(shè)置一個(gè)過期時(shí)間字段一塊存入緩存中,不給當(dāng)前key設(shè)置過期時(shí)間
2、當(dāng)查詢的時(shí)候,從redis取出數(shù)據(jù)后判斷時(shí)間是否過期
3、如果過期則開通另外一個(gè)線程進(jìn)行數(shù)據(jù)同步,當(dāng)前線程正常返回?cái)?shù)據(jù),這個(gè)數(shù)據(jù)不是最新
如:
key:1 value:{"id":"123", "title":"張三", "expire":153213455}
這種方案也是在查詢DB和重置邏輯過期時(shí)間時(shí)加上互斥鎖,其它線程來查詢緩存時(shí)要不就是得到還未更新的過期數(shù)據(jù),要不就得到更新后的數(shù)據(jù)。保證了在多個(gè)線程并發(fā)訪問時(shí)不把其它線程全部攔截住(就是不會讓它們一遍又一遍地重試獲取數(shù)據(jù))。
相比方案一更為高可用、性能優(yōu)。但由于可能會得到邏輯過期數(shù)據(jù),導(dǎo)致數(shù)據(jù)并不是絕對一致的。
特點(diǎn):邏輯過期,更注重用戶體驗(yàn),高可用,性能優(yōu)。但不能保證數(shù)據(jù)絕對一致。
緩存雪崩
緩存雪崩是指設(shè)置緩存時(shí)采用了相同的過期時(shí)間,導(dǎo)致緩存在某一時(shí)刻同時(shí)失效,請求全部轉(zhuǎn)發(fā)到DB,DB瞬時(shí)壓力過重雪崩?;蛘呤?strong>Redis服務(wù)宏機(jī),導(dǎo)致大量請求到達(dá)數(shù)據(jù)庫,帶來巨大壓力。
緩存雪崩與緩存擊穿的區(qū)別:
雪崩是很多key,擊穿是某一個(gè)key緩存。
第一種情況的解決方案是將緩存失效時(shí)間分散開,比如可以在原有的失效時(shí)間(TTL)基礎(chǔ)上增加一個(gè)隨機(jī)值,比如1-5分鐘隨機(jī),這樣每一個(gè)緩存的過期時(shí)間的重復(fù)率就會降低,就很難引發(fā)集體失效的事件。
其余解決方案:
1.利用Redis集群提高服務(wù)的可用性(哨兵模式,集群模式)
2.給緩存業(yè)務(wù)添加降級限流策略(ngxin或spring cloud gateway) (降級限流策略可做為系統(tǒng)的保底策略,適用于穿透,擊穿,雪崩)
3.給業(yè)務(wù)添加多級緩存 (Guava或Caffeine)
持久化
Redis是內(nèi)存數(shù)據(jù)庫,如果不將內(nèi)存中的數(shù)據(jù)庫狀態(tài)保存到磁盤,那么一旦服務(wù)器進(jìn)程退出,服務(wù)器中的數(shù)據(jù)庫狀態(tài)也會消失(斷電即失),所以redis提供了持久化功能。
1.RDB
RDB執(zhí)行原理
這里存在一個(gè)問題:就是如果子進(jìn)程在讀共享的內(nèi)存數(shù)據(jù)時(shí),主進(jìn)程正在對共享的內(nèi)存數(shù)據(jù)進(jìn)行更改。那么子進(jìn)程就可能會得到一些臟數(shù)據(jù)。
解決方法:
主進(jìn)程執(zhí)行寫操作時(shí),共享數(shù)據(jù)會改成只讀數(shù)據(jù)。且會拷貝一份數(shù)據(jù)去執(zhí)行主進(jìn)程的寫操作。
2.AOF
AOF全稱為Append Only File(追加文件)Redis處理的每一個(gè)寫命令都會記錄在AOF文件,可以看做是命令日志文件。
bgrewriteaof命令
因?yàn)槭怯涗?strong>命令,AOF文件會比RDB文件大的多。而且aof會記錄對同一個(gè)key的多次寫操作,但只有最后一次寫操作才有意義。通過執(zhí)行bgrewriteaof命令,可以讓AOF文件執(zhí)行重寫功能,用最少的命令達(dá)到相同效果。
RDB與AOF對比
RDB和AOF各有自己的優(yōu)缺點(diǎn),如果對數(shù)據(jù)安全性要求較高,在實(shí)際開發(fā)中往往會結(jié)合兩者來使用。
雙寫一致性
redis做為緩存,mysql的數(shù)據(jù)如何與redis進(jìn)行同步呢? (雙寫一致性)
這個(gè)雙寫一致性最好要根據(jù)項(xiàng)目實(shí)際業(yè)務(wù)背景來說,一般分為兩種情況:
1.一致性要求高
2.允許延遲一致
1.一致性要求高
1.延遲雙刪
讀操作:緩存命中,直接返回;緩存未命中查詢數(shù)據(jù)庫,寫入緩存,設(shè)定超時(shí)時(shí)間
寫操作:延遲雙刪
1.無論第一步是先刪除緩存還是先修改數(shù)據(jù)庫都會導(dǎo)致臟數(shù)據(jù)的出現(xiàn)
所以重點(diǎn)是進(jìn)行二次刪除緩存以及第二次刪除時(shí)做到延時(shí)刪除。
2.刪除兩次緩存的原因
如果在第一次刪除緩存和修改數(shù)據(jù)庫之間的時(shí)間里,另一個(gè)線程此時(shí)來查詢緩存了(未命中,查詢數(shù)據(jù)庫),那么此時(shí)寫入緩存的則是未更新的數(shù)據(jù)庫的數(shù)據(jù),為臟數(shù)據(jù)。如下圖所示:
所以在更新完數(shù)據(jù)庫后再次刪除緩存可以將這種情況下的臟數(shù)據(jù)盡量消除。
所以對緩存進(jìn)行兩次刪除可以降低臟數(shù)據(jù)的出現(xiàn),但是不能杜絕。
3.延時(shí)刪除的原因
因?yàn)閿?shù)據(jù)庫一般是主從模式的,讀寫分離了,主庫的數(shù)據(jù)同步到從庫需要一定的時(shí)間,故先要延遲一會。
問題:因?yàn)檠舆t的時(shí)間不好控制,所以還是可能會出現(xiàn)臟數(shù)據(jù)。
總結(jié)
延遲雙刪極大地控制了臟數(shù)據(jù)的風(fēng)險(xiǎn),但不可杜絕臟數(shù)據(jù)的風(fēng)險(xiǎn)。
2.加讀寫鎖
能保證強(qiáng)一致性,但性能低。
強(qiáng)一致性的,采用redisson提供的讀寫鎖
共享鎖:讀鎖readLock,加鎖之后,其他線程可以共享讀操作
排他鎖:獨(dú)占寫鎖writeLock,加鎖之后,阻塞其他線程讀寫操作
2.允許延遲一致(較為主流)
能保證最終一致性,會有短暫延遲。
1.異步通知保證數(shù)據(jù)的最終一致性
2.基于Canal的異步通知
數(shù)據(jù)過期策略
Redis對有些數(shù)據(jù)設(shè)置有效時(shí)間,數(shù)據(jù)過期以后,就需要將數(shù)據(jù)從內(nèi)存中刪除掉??梢园凑詹煌囊?guī)則進(jìn)行刪除,這種刪除規(guī)則就被稱之為數(shù)據(jù)的刪除策略(數(shù)據(jù)過期策略)。
方案一惰性刪除:
惰性刪除,在設(shè)置該key過期時(shí)間后,我們不去管它,當(dāng)需要該key時(shí),我們在檢查其是否過期,如果過期,我們就刪掉它,反之返回該key。
優(yōu)點(diǎn):對CPU友好,只會在使用該key時(shí)才會進(jìn)行過期檢查,對于很多用不到的key不用浪費(fèi)時(shí)間進(jìn)行過期檢查
缺點(diǎn):對內(nèi)存不友好,如果一個(gè)key已經(jīng)過期。但是一直沒有使用,那么該key就會一直存在內(nèi)存中,內(nèi)存永遠(yuǎn)不會釋放
方案二定期刪除:
定期刪除,就是說每隔一段時(shí)間,我們就對一些key進(jìn)行檢查,刪除里面過期的key。
?
定期清理的兩種模式:
1.SLOW模式是定時(shí)任務(wù),執(zhí)行頻率默認(rèn)為10hz,每次不超過25ms,以通過修改配置文件redis.conf的hz選項(xiàng)來調(diào)整這個(gè)次數(shù)
2.FAST模式執(zhí)行頻率不固定,每次事件循環(huán)會嘗試執(zhí)行,但兩次間隔不低于2ms,每次耗時(shí)不超過1ms
優(yōu)點(diǎn):可以通過限制刪除操作執(zhí)行的時(shí)長和頻率來減少刪除操作對CPU的影響。另外定期劇除,也能有效釋放過期鍵占用的內(nèi)存。
缺點(diǎn):難以確定刪除操作執(zhí)行的時(shí)長和頻率。
定期清理控制時(shí)長和頻率--->盡量少占用主進(jìn)程的操作--->減少對CPU的影響
總結(jié)
Redis的過期刪除策略:情性刪除+定期刪除兩種策略進(jìn)行配合使用。
數(shù)據(jù)淘汰策略
數(shù)據(jù)的淘汰策略:當(dāng)Redis中的內(nèi)存不夠用時(shí),此時(shí)在向Redis中添加新的key,那么Redis就會按照某一種規(guī)則將內(nèi)存中的數(shù)據(jù)刪除掉,這種數(shù)據(jù)的刪除規(guī)則被稱之為內(nèi)存的淘汰策略。
Redis支持8種不同策略來選擇要刪除的key:
八種不同策略
noeviction:不淘汰任何key,但是內(nèi)存滿時(shí)不允許寫入新數(shù)據(jù),默認(rèn)就是這種策略。
volatile-ttl: 對設(shè)置了TTL的key,比較key的剩余TTL值,TTL越小越先被淘汰。 allkeys-random:對全體key,隨機(jī)進(jìn)行淘汰
volatile-random:對設(shè)置了TTL的key,隨機(jī)進(jìn)行淘汰
allkeys-lru:對全體key,基于LRU算法進(jìn)行淘汰
volatile-lru:對設(shè)置了TTL的key,基于LRU算法進(jìn)行淘汰
allkeys-lfu:對全體key,基于LFU算法進(jìn)行淘汰
volatile-lfu:對設(shè)置了TTL的key,基于LFU算法進(jìn)行淘汰
LRU(Least Recently Used)算法:最近最少使用。用當(dāng)前時(shí)間減去最
后一次訪問時(shí)間,這個(gè)值越大則淘汰優(yōu)先級越高。
例:key1是在6s之前訪問的,key2是在9s之前訪問的,刪除的就是key2
LFU(Least Frequently Used)算法:最少頻率使用。會統(tǒng)計(jì)每個(gè)key的
訪問頻率,值越小淘汰優(yōu)先級越高。
例:key1最近5s訪問了6次,key2最近5s訪問了9次,刪除的就是key1
數(shù)據(jù)淘汰策略--使用建議
1.優(yōu)先使用alkeys-lru策略。充分利用LRU算法的優(yōu)勢,把最近最常訪問的數(shù)據(jù)留在緩存中。如果業(yè)務(wù)有明顯的冷熱數(shù)據(jù)區(qū)分,建議使用這種策略。
2.如果業(yè)務(wù)中數(shù)據(jù)訪問頻率差別不大,沒有明顯冷熱數(shù)據(jù)區(qū)分,建議使用allkeys-random,隨機(jī)選擇淘汰。
3.如果業(yè)務(wù)中有置頂?shù)男枨?#xff0c;可以使用volatile-lru策略,同時(shí)置頂數(shù)據(jù)不設(shè)置過期時(shí)間,這些數(shù)據(jù)就一直不被刪除
會淘汰其他設(shè)置過期時(shí)間的數(shù)據(jù)。
4.如果業(yè)務(wù)中有短時(shí)高頻訪問的數(shù)據(jù),可以使用allkeys-lfu 或volatile-lfu 策略。
常見問題:
1.數(shù)據(jù)庫有1000萬數(shù)據(jù),Redis只能緩存2ow數(shù)據(jù),如何保證Redis中的數(shù)據(jù)都是熱點(diǎn)數(shù)據(jù)?
使用allkeys-lru(挑選最近最少使用的數(shù)據(jù)淘汰)淘汰策略,留下來的都是經(jīng)常訪問的熱點(diǎn)數(shù)據(jù)
2.Redis的內(nèi)存用完了會發(fā)生什么?
默認(rèn)的配置(noeviction):會直接報(bào)錯(cuò)
分布式鎖
場景
通常情況下,分布式鎖使用的場景:
集群情況下的定時(shí)任務(wù),搶單搶券,秒殺,冪等性場景
引入與基本介紹
如果項(xiàng)目是單體項(xiàng)目,只啟動了一臺服務(wù),那遇到這類搶單問題時(shí)(防止超賣),可以加synchronized鎖解決。(解決多線程并發(fā)環(huán)境下的問題)
但是項(xiàng)目服務(wù)是集群部署的話,那么synchronized鎖這種本地鎖(只能保證單個(gè)JVM內(nèi)部的多個(gè)線程之間互斥,不能讓集群下的多個(gè)JVM下的多個(gè)線程互斥)(只對本服務(wù)器有效)會失效,需要使用外部鎖,也就是分布式鎖。
例1(搶券場景):
例2:
分布式鎖:滿足分布式系統(tǒng)或集群模式下多進(jìn)程可見并且互斥的鎖。
實(shí)現(xiàn)分布式鎖的方式有很多,常見的有三種:
redis分布式鎖實(shí)現(xiàn)原理
基本介紹
設(shè)置超時(shí)失效時(shí)間的原因(避免死鎖):
如果某個(gè)線程拿到鎖在執(zhí)行業(yè)務(wù)時(shí),服務(wù)器突然宕機(jī),此時(shí)這個(gè)線程還沒來得及釋放鎖,而如果沒有設(shè)置過期時(shí)間的話,這個(gè)鎖就沒辦法得到釋放了,別的線程怎么也獲取不到這個(gè)鎖了,就造成了死鎖。而設(shè)置了過期時(shí)間的話,鎖到時(shí)間了就會自動釋放。
總結(jié)
缺陷
以上的問題是比較小的可能出現(xiàn)的,但是我們用Redis實(shí)現(xiàn)的分布式鎖去解決又顯得尤為困難,所以我們可以去使用Redisson框架,它底層提供了以上問題的解決方案,方便了我們?nèi)ソ鉀Q問題。
Redisson實(shí)現(xiàn)的分布式鎖
redisson是Redis的一個(gè)框架。
Redisson是一個(gè)在Redis的基礎(chǔ)上實(shí)現(xiàn)的Java駐內(nèi)存數(shù)據(jù)網(wǎng)格。它不僅提供了一系列的分布式的Java常用對象,還提供了許多分布式服務(wù),其中就包含了各種分布式鎖的實(shí)現(xiàn)。
快速入門:
在Redisson中需要手動加鎖,并且可以控制鎖的失效時(shí)間和等待時(shí)間,當(dāng)鎖住的一個(gè)業(yè)務(wù)還沒有執(zhí)行完成的時(shí)候,在redisson中引入了一個(gè)看門狗機(jī)制,就是說每隔一段時(shí)間就檢查當(dāng)前業(yè)務(wù)是否還持有鎖,如果持有就增加鎖的持有時(shí)間,當(dāng)業(yè)務(wù)執(zhí)行完成之后需要釋放鎖。
在高并發(fā)下,一個(gè)業(yè)務(wù)有可能會執(zhí)行很快,先客戶1持有鎖的時(shí)候,客戶2來了以后并不會馬上拒絕,它會自旋不斷嘗試獲取鎖(while循環(huán)獲取),如果客戶1釋放之后,客戶2就可以馬上持有鎖,性能也得到了提升。
1.redisson實(shí)現(xiàn)的分布式鎖的執(zhí)行流程/合理地控制鎖的有效時(shí)長(失效時(shí)間)
原因:如果鎖的有效時(shí)間設(shè)置的不合理,可能業(yè)務(wù)還沒執(zhí)行完鎖就釋放了,那此時(shí)其它線程來也可以獲取到鎖,就破壞了業(yè)務(wù)執(zhí)行的原子性,業(yè)務(wù)數(shù)據(jù)會受到影響。
方法:根據(jù)業(yè)務(wù)所需時(shí)間實(shí)時(shí)給鎖續(xù)期。
//可重試:利用信號量和PubSub功能實(shí)現(xiàn)等待,喚醒,獲取鎖失敗的重試機(jī)制。
releaseTime默認(rèn)是30s。
另外開一個(gè)線程“看門狗”來監(jiān)視持有鎖的線程并做續(xù)期任務(wù)(每隔releaseTime/3的時(shí)間做一次續(xù)期)。
public void redislock() thr throws interruptedexception {//獲取鎖(重入鎖),執(zhí)行鎖的名稱RLock lock = redissonClient.getLock("lock");//嘗試獲取鎖,//參數(shù)分別是:獲取鎖的最大等待時(shí)間(期間會重試),鎖自動釋放時(shí)間(鎖失效時(shí)間),時(shí)間單位//boolean islock = lock.tryLock(10,30,TimeUnit.SECONDS);boolean isLock = lock.tryLock(10,TimeUnit.SECONDS);//參數(shù):1.鎖的最大等待時(shí)間:鎖通過while循環(huán)來不斷嘗試獲取鎖的最大等待時(shí)間,如果這個(gè)時(shí)間內(nèi)沒有獲取到鎖則放棄獲取鎖。// 2.鎖自動釋放時(shí)間:最好不要設(shè)置或者設(shè)置為-1,否則不會啟動看門狗線程進(jìn)行續(xù)期任務(wù)。// 3.時(shí)間單位//加鎖,釋放鎖,設(shè)置過期時(shí)間,給鎖續(xù)期等操作都是基于lua腳本完成。//Lua腳本可以調(diào)用Redis命令來保證多條命令執(zhí)行的原子性。//判斷是否獲取成功 if(isLock){try{System.out.println("執(zhí)行業(yè)務(wù)");} finally {//釋放鎖lock.unlock();}}
}
原子性問題:
Redis提供了Lua腳本功能,在一個(gè)腳本中編寫多條Redis命令,確保多條命令執(zhí)行時(shí)的原子性。
2.可重入
Redis實(shí)現(xiàn)的鎖是不可重入的,但redisson實(shí)現(xiàn)的鎖是可重入的。
作用:避免死鎖的產(chǎn)生。
這個(gè)重入其實(shí)在內(nèi)部就是判斷是否是當(dāng)前線程持有的鎖。如果是當(dāng)前線程持有的鎖就會計(jì)數(shù),如果釋放鎖就會在計(jì)算上減一。
存儲數(shù)據(jù)的時(shí)候采用的hash結(jié)構(gòu),大key可以按照自己的業(yè)務(wù)進(jìn)行定制,其中小key是當(dāng)前線程的唯一標(biāo)識(線程id),value是當(dāng)前線程重入的次數(shù)。
public void add1(){RLock lock = redissonClient.getLock("heimalock");boolean islock = lock.tryLock();//執(zhí)行業(yè)務(wù)add2();//釋放鎖lock.unlock();
}
public void add2(){RLock lock = redissonClient.getLock("heimalock");boolean islock = lock.trylock();//執(zhí)行業(yè)務(wù)...//釋放鎖lock.unlock();
}
底層獲取鎖和釋放鎖等操作都很復(fù)雜,都是有多個(gè)步驟,所以是用Lua腳本寫確保各個(gè)操作的原子性。
3.主從一致性
redisson實(shí)現(xiàn)的分布式鎖不能解決主從一致性問題。
比如,當(dāng)線程1加鎖成功后,Master節(jié)點(diǎn)數(shù)據(jù)會異步復(fù)制到Slave節(jié)點(diǎn),當(dāng)數(shù)據(jù)還沒來得及同步到Slave節(jié)點(diǎn)時(shí),當(dāng)前持有Redis鎖的Master節(jié)點(diǎn)宕機(jī),Slave節(jié)點(diǎn)被提升為新的Master節(jié)點(diǎn)。(按道理主節(jié)點(diǎn)和從節(jié)點(diǎn)的數(shù)據(jù)應(yīng)該要是一模一樣的,加鎖的信息也要一模一樣(其實(shí)就是一個(gè)setnx數(shù)據(jù)而已))
假如現(xiàn)在來了一個(gè)線程2,再次加鎖,因?yàn)镸aster節(jié)點(diǎn)數(shù)據(jù)還沒來得及同步過來(從節(jié)點(diǎn)已經(jīng)被這把鎖鎖住且線程一已經(jīng)拿到了這把鎖的信息還未更新過來),所以會在新的Master節(jié)點(diǎn)上加鎖成功,這個(gè)時(shí)候就會出現(xiàn)兩個(gè)線程同時(shí)持有一把鎖的問題。
兩個(gè)線程同時(shí)獲取一把鎖--->違背了鎖的互斥性(鎖失效了)。
紅鎖:
紅鎖算法的基本思想是,當(dāng)需要鎖定多個(gè)資源時(shí),可以在多個(gè)Redis節(jié)點(diǎn)上分別獲取鎖,只有當(dāng)大多數(shù)節(jié)點(diǎn)上
的鎖都被成功獲取時(shí),整個(gè)鎖才算獲取成功。這樣可以提高系統(tǒng)的容錯(cuò)性和可用性。
我們可以利用Redisson提供的紅鎖來解決這個(gè)問題,它的主要作用是,不能只在一個(gè)redis實(shí)例上創(chuàng)建鎖,應(yīng)該是在多個(gè)redis實(shí)例上創(chuàng)建鎖,并且要求在大多數(shù)Redis節(jié)點(diǎn)上都成功創(chuàng)建鎖,紅鎖中要求是Redis的節(jié)點(diǎn)數(shù)量要過半。這樣就能避免線程1加鎖成功后Master節(jié)點(diǎn)宕機(jī)導(dǎo)致線程2成功加鎖到新的Master節(jié)點(diǎn)上的問題了。
意思就是線程來的時(shí)候要獲取多個(gè)Redis節(jié)點(diǎn)的鎖才算成功,才可以執(zhí)行代碼。
如果一個(gè)主節(jié)點(diǎn)宕機(jī)(主節(jié)點(diǎn)的數(shù)據(jù)還沒來得及同步到從節(jié)點(diǎn),與以上同理),它的從節(jié)點(diǎn)變成主節(jié)點(diǎn),那么此時(shí)另一個(gè)線程來是不可以獲取到鎖的,因?yàn)檫@個(gè)線程必須要獲取到所有的節(jié)點(diǎn)的鎖才能成功獲取到鎖,它只能拿到宕機(jī)的那個(gè)主節(jié)點(diǎn)的從節(jié)點(diǎn)的鎖(因?yàn)橹鞴?jié)點(diǎn)的數(shù)據(jù)還沒來得及同步到從節(jié)點(diǎn)),所以會獲取鎖失敗。
只要有一個(gè)節(jié)點(diǎn)是存活的,其它線程就不可以拿到鎖,鎖就不會失效。
缺點(diǎn)
如果使用了紅鎖,因?yàn)樾枰瑫r(shí)在多個(gè)節(jié)點(diǎn)上都添加鎖,性能就變的很低了,并且運(yùn)維維護(hù)成本也非常高,所以,我們一般在項(xiàng)目中也不會直接使用紅鎖,并且官方也暫時(shí)廢棄了這個(gè)紅鎖。
所以強(qiáng)一致性要求高的業(yè)務(wù),建議使用zookeeper實(shí)現(xiàn)的分布式鎖,它是可以保證強(qiáng)一致性的。
Redis發(fā)布訂閱
- Redis 發(fā)布訂閱(pub/sub)是一種消息通信模式: 發(fā)送者(pub)發(fā)送消息,訂閱者(sub)接受消息.微博,微信,關(guān)注系統(tǒng)
- Redis 客戶端可以訂閱任意數(shù)量的頻道。
訂閱/發(fā)布消息圖
- 第一個(gè): 消息發(fā)送者, 第二個(gè) :頻道 第三個(gè) :消息訂閱者!
- 下圖展示了頻道 channel1 , 以及訂閱這個(gè)頻道的三個(gè)客戶端 —— client2 、 client5 和 client1 之間的關(guān)系:
- 當(dāng)有新消息通過 PUBLISH 命令發(fā)送給頻道 channel1 時(shí), 這個(gè)消息就會被發(fā)送給訂閱它的三個(gè)客戶端:
命令
- 這些命令被廣泛用于構(gòu)建即時(shí)通信應(yīng)用,比如網(wǎng)絡(luò)聊天室和實(shí)時(shí)廣播,實(shí)時(shí)提醒
測試
- 訂閱端(消費(fèi)者)
-
- 開啟客戶端1
-
127.0.0.1:6379> subscribe codeyuaiiao //訂閱一個(gè)頻道,頻道名稱:codeyuaiiao 訂閱的時(shí)候頻道就建立了
Reading messages... (press Ctrl-C to quit)
1) "subscribe"
2) "codeyuaiiao"
3) (integer) 1# 等待讀取推送(客戶端2發(fā)送消息,客戶端1這邊接收消息)
1) "message" # 消息
2) "codeyuaiiao" # 消息來自哪個(gè)頻道
3) "hello world" # 消息的具體內(nèi)容1) "message"
2) "codeyuaiiao"
3) "hello yuhaijiao"
- 發(fā)送端:(生產(chǎn)者)
-
- 再開啟一個(gè)客戶端2
-
127.0.0.1:6379> publish codeyuaiiao "hello world" # 發(fā)布者發(fā)布消息到頻道
(integer) 1
127.0.0.1:6379> publish codeyuaiiao "hello yuhaijiao" # 發(fā)布者發(fā)布消息到頻道
(integer) 1
127.0.0.1:6379>
Redis發(fā)布訂閱原理
- Redis是使用C實(shí)現(xiàn)的,通過分析 Redis 源碼里的 pubsub.c 文件,了解發(fā)布和訂閱機(jī)制的底層實(shí)現(xiàn),籍此加深對 Redis 的理解。
- Redis 通過 PUBLISH(發(fā)送消息) 、SUBSCRIBE(訂閱頻道) 和 PSUBSCRIBE(訂閱多個(gè)頻道) 等命令實(shí)現(xiàn)發(fā)布和訂閱功能。
例如微信訂閱公眾號:
- 通過 SUBSCRIBE 命令訂閱某頻道后
-
- Redis-server 里維護(hù)了一個(gè)字典
- 字典的鍵就是一個(gè)個(gè)頻道
- 字典的值則是一個(gè)鏈表,鏈表中保存了所有訂閱這個(gè) channel 的客戶端
- SUBSCRIBE 命令的關(guān)鍵, 就是將客戶端添加到給定 channel 的訂閱鏈表中
- 通過publish命令向訂閱者發(fā)送消息,redis-server會使用給定的頻道作為鍵,在它所維護(hù)的channel字典中查找記錄了訂閱這個(gè)頻道的所有客戶端的鏈表,遍歷這個(gè)鏈表,將消息發(fā)布給所有訂閱者.
總結(jié)
Pub/Sub 從字面上理解就是發(fā)布(Publish)與訂閱(Subscribe),在Redis中,你可以設(shè)定對某一個(gè)key值進(jìn)行消息發(fā)布及消息訂閱,當(dāng)一個(gè)key值上進(jìn)行了消息發(fā)布后,所有訂閱它的客戶端都會收到相應(yīng)的消息。這一功能最明顯的用法就是用作實(shí)時(shí)消息系統(tǒng),比如普通的即時(shí)聊天,群聊等功能。
使用場景
- 實(shí)時(shí)消息系統(tǒng)
- 實(shí)時(shí)聊天! (頻道當(dāng)做聊天室,將信息回顯給所有人即可! )
- 訂閱,關(guān)注系統(tǒng)都是可以的
稍微復(fù)雜的場景我們就會使用 消息中間件MQ
Redis消息隊(duì)列
概念
消息隊(duì)列(Message Queue),字面意思就是存放消息的隊(duì)列。最簡單的消息隊(duì)列模型包括3個(gè)角色:
消息隊(duì)列:存儲和管理消息,也被稱為消息代理(message broker)
生產(chǎn)者:發(fā)送消息到消息隊(duì)列
消費(fèi)者:從消息隊(duì)列獲取消息并處理消息
例(秒殺搶券業(yè)務(wù)):
生產(chǎn)者:判斷是否有資格搶券(券的剩余數(shù)量大于0且當(dāng)前用戶之前未搶到券),如果有資格則將訂單相關(guān)信息寫入消息隊(duì)列。
消費(fèi)者:開啟一個(gè)獨(dú)立的線程去接收消息,完成下單(把訂單信息寫入Mysql數(shù)據(jù)庫)
這樣秒殺搶單的業(yè)務(wù)和真正寫數(shù)據(jù)庫的業(yè)務(wù)就實(shí)現(xiàn)了分離,變成了異步操作,解耦合了。
秒殺搶單的業(yè)務(wù):秒殺這里因?yàn)椴挥脤憯?shù)據(jù)庫(比較耗時(shí)),并發(fā)能力大大提高。
寫數(shù)據(jù)庫的業(yè)務(wù):可以根據(jù)自己的節(jié)奏慢慢地去取訂單寫數(shù)據(jù)庫,不會讓數(shù)據(jù)庫有太大的壓力,保證數(shù)據(jù)庫抗得住。
Redis提供了三種不同的方式來實(shí)現(xiàn)消息隊(duì)列:
list結(jié)構(gòu):基于List結(jié)構(gòu)模擬消息隊(duì)列
PubSub:基本的點(diǎn)對點(diǎn)消息模型
Stream:比較完善的消息隊(duì)列模型
基于List結(jié)構(gòu)模擬消息隊(duì)列(可實(shí)現(xiàn)阻塞隊(duì)列的效果)
支持持久化:因?yàn)閘ist類型redis本身是用鏈表做存儲數(shù)據(jù)的,只是我們把它當(dāng)成消息隊(duì)列來用,故對數(shù)據(jù)可以持久化
基于PubSub(發(fā)布訂閱)的消息隊(duì)列
主要內(nèi)容就是上面學(xué)習(xí)的Redis發(fā)布訂閱
優(yōu)點(diǎn):
采用發(fā)布訂閱模型,支持多生產(chǎn),多消費(fèi)
缺點(diǎn):
不支持?jǐn)?shù)據(jù)持久化
無法避免消息丟失
消息堆積有上限,超出時(shí)數(shù)據(jù)丟失
不支持持久化:因?yàn)镻ubSub本身就只是用來做發(fā)布訂閱功能的,如果沒有人訂閱某個(gè)頻道,那么往這個(gè)頻道發(fā)布數(shù)據(jù)后,數(shù)據(jù)會丟失,Redis不會保存這個(gè)數(shù)據(jù)。
基于Stream的消息隊(duì)列
基本知識
Stream是Redis 5.0引入的一種新數(shù)據(jù)類型,可以實(shí)現(xiàn)一個(gè)功能非常完善的消息隊(duì)列。
例:
注意
當(dāng)我們指定起始id為$時(shí),代表讀取最新的消息,如果我們處理一條消息的過程中,又有超過一條以上的消息到達(dá)隊(duì)列,則下次獲取時(shí)也只能獲取到最新的一條,會出現(xiàn)漏讀消息的問題。
Stream類型消息隊(duì)列的XREAD命令特點(diǎn):
1.消息可回溯
2.一個(gè)消息可以被多個(gè)消費(fèi)者讀取
3.可以阻塞讀取
4.有消息漏讀的風(fēng)險(xiǎn)
消費(fèi)者組
消費(fèi)者組:將多個(gè)消費(fèi)者劃分到一個(gè)組中,監(jiān)聽同一個(gè)隊(duì)列。
具備下列特點(diǎn):
Stream類型消息隊(duì)列的XREADGROUP命令特點(diǎn):
消息可回溯
可以多消費(fèi)者爭搶消息,加快消費(fèi)速度
可以阻塞讀取
沒有消息漏讀的風(fēng)險(xiǎn)
有消息確認(rèn)機(jī)制,保證消息至少被消費(fèi)一次
Redis集群(分布式緩存)
單點(diǎn)Redis的問題
1.并發(fā)能力問題
解決方法:搭建主從集群,實(shí)現(xiàn)讀寫分離。實(shí)現(xiàn)高并發(fā)。
2.故障恢復(fù)問題
解決方法:利用Redis哨兵,實(shí)現(xiàn)健康檢測和自動恢復(fù)。保障高可用。
3.存儲能力問題
解決方法:搭建分片集群,利用插槽機(jī)制實(shí)現(xiàn)動態(tài)擴(kuò)容。
主從復(fù)制
單節(jié)點(diǎn)Redis的并發(fā)能力是有上限的,要進(jìn)一步提高Redis的并發(fā)能力,就需要搭建主從集群,實(shí)現(xiàn)讀寫分離。
主從數(shù)據(jù)同步原理
1.主從全量同步
注:
1.判斷是否第一次同步:
從節(jié)點(diǎn)的replid與主節(jié)點(diǎn)的不一樣則說明這個(gè)從節(jié)點(diǎn)是第一次同步。
2.只有第一次同步的時(shí)候主節(jié)點(diǎn)才會生成RDB文件,第一次之后的同步會根據(jù)偏移量利用repl_baklog日志文件進(jìn)行同步數(shù)據(jù)。
2.主從增量同步(slave重啟或后期數(shù)據(jù)變化)
3.總結(jié)
全量同步:
1.從節(jié)點(diǎn)請求主節(jié)點(diǎn)同步數(shù)據(jù)(replication id, offset)
2.主節(jié)點(diǎn)判斷是否是第一次請求,是第一次就與從節(jié)點(diǎn)同步版本信息(replicationid和offset)
3.主節(jié)點(diǎn)執(zhí)行bgsave,生成rdb文件后,發(fā)送給從節(jié)點(diǎn)去執(zhí)行
4.在rdb生成執(zhí)行期間,主節(jié)點(diǎn)會以命令的方式記錄到緩沖區(qū)(一個(gè)日志文件)
5.把生成之后的命令日志文件發(fā)送給從節(jié)點(diǎn)進(jìn)行同步
增量同步:
1.從節(jié)點(diǎn)請求主節(jié)點(diǎn)同步數(shù)據(jù),主節(jié)點(diǎn)判斷不是第一次請求,不是第一次就獲取從節(jié)點(diǎn)的offset值
2.主節(jié)點(diǎn)從命令日志中獲取offset值之后的數(shù)據(jù),發(fā)送給從節(jié)點(diǎn)進(jìn)行數(shù)據(jù)同步
哨兵模式
redis提供了哨兵模式來實(shí)現(xiàn)主從集群的自動故障恢復(fù),從而極大地保障了Redis主從的高可用。
哨兵模式的結(jié)構(gòu)與作用
redis提供了哨兵 (Sentinel)機(jī)制來實(shí)現(xiàn)主從集群的自動故障恢復(fù)。
結(jié)構(gòu):
作用:
監(jiān)控:Sentinel會不斷檢查您的master和slave是否按預(yù)期工作
自動故障恢復(fù):如果master故障, Sentinel會將一個(gè)slave提升為master。當(dāng)故障實(shí)例恢復(fù)后也以新的master為主
通知:Sentinel充當(dāng)redis客戶端的服務(wù)發(fā)現(xiàn)來源,當(dāng)集群發(fā)生故障轉(zhuǎn)移時(shí),會將最新信息推送給redis的客戶端
服務(wù)狀態(tài)監(jiān)控
Sentinel基于心跳機(jī)制監(jiān)測服務(wù)狀態(tài),每隔1秒向集群的每個(gè)實(shí)例發(fā)送ping命令:
主觀下線:如果某sentinel節(jié)點(diǎn)發(fā)現(xiàn)某實(shí)例未在規(guī)定時(shí)間響應(yīng),則認(rèn)為該實(shí)例主觀下線。
客觀下線:若超過指定數(shù)量(quorum)的sentinel都認(rèn)為該實(shí)例主觀下線,則該實(shí)例客觀下線。quorum值最好
超過sentinel實(shí)例數(shù)量的一半。
哨兵選主規(guī)則(主節(jié)點(diǎn)宕機(jī)后,選從節(jié)點(diǎn)為主節(jié)點(diǎn)的規(guī)則)
1.首先判斷主與從節(jié)點(diǎn)斷開時(shí)間長短,如超過指定值就排除該從節(jié)點(diǎn)
2.然后判斷從節(jié)點(diǎn)的slave-priority值,越小優(yōu)先級越高
3.如果slave-prority一樣,則判斷slave節(jié)點(diǎn)的offset值,越大優(yōu)先級越高.
4.最后是判斷slave節(jié)點(diǎn)的運(yùn)行id大小,越小優(yōu)先級越高。
第三條最重要!!!
Redis集群(哨兵模式)的腦裂問題
有的時(shí)候由于網(wǎng)絡(luò)等原因可能會出現(xiàn)腦裂的情況,就是說,由于redis的master節(jié)點(diǎn)和redis的salve節(jié)點(diǎn)和sentinel處于不同的網(wǎng)絡(luò)分區(qū),使得sentinel沒有能夠心跳感知到主節(jié)點(diǎn),所以通過選舉的方式提升了一個(gè)salve為master,這樣就存在了兩個(gè)master,就像大腦分裂了一樣,這樣會導(dǎo)致客戶端還在old master那里寫入數(shù)據(jù),新節(jié)點(diǎn)無法同步數(shù)據(jù),當(dāng)網(wǎng)絡(luò)恢復(fù)后,sentinel會將old master降為salve,這時(shí)再從新master同步數(shù)據(jù),就會導(dǎo)致old master中的大量數(shù)據(jù)丟失。
----------->
------------>
解決方法
在redis的配置中設(shè)置兩個(gè)配置參數(shù)
1.(min-replicas-to-write 1)設(shè)置最少的salve節(jié)點(diǎn)個(gè)數(shù)為1,設(shè)置至少要有一個(gè)從節(jié)點(diǎn)才能同步數(shù)據(jù)
2.(min-replicas-max-lag 5)設(shè)置主從數(shù)據(jù)復(fù)制和同步的延遲時(shí)間不能超過5秒
達(dá)不到要求就拒絕請求,就可以避免大量的數(shù)據(jù)丟失。
總結(jié):
我們可以修改redis的配置,可以設(shè)置最少的從節(jié)點(diǎn)數(shù)量至少為一個(gè)以及縮短主從數(shù)據(jù)同步的延遲時(shí)間(不能超過5秒),達(dá)不到要求就拒絕Redis客戶端的請求(不讓客戶端寫入數(shù)據(jù)到老的主節(jié)點(diǎn)),這樣就可以避免大量的數(shù)據(jù)丟失。
分片集群
主從和哨兵可以解決高可用,高并發(fā)讀的問題。但是依然有兩個(gè)問題沒有解決;
1.海量數(shù)據(jù)存儲問題
2.高并發(fā)寫的問題
使用分片集群可以解決上述問題,分片集群特征:
1.集群中有多個(gè)master,每個(gè)master保存不同數(shù)據(jù)
2.每個(gè)master都可以有多個(gè)slave節(jié)點(diǎn)
3.master之間通過ping監(jiān)測彼此健康狀態(tài) (這點(diǎn)類似于之前的哨兵模式)
4.客戶端請求可以訪問集群任意節(jié)點(diǎn),最終都會被轉(zhuǎn)發(fā)到正確節(jié)點(diǎn) (路由:客戶端請求可以訪問集群任意節(jié)點(diǎn),最終都會被轉(zhuǎn)發(fā)到正確節(jié)點(diǎn)。)
具體的路由規(guī)則:
Redis分片集群引入了哈希槽的概念,Redis集群有16384個(gè)哈希槽,每個(gè)key通過 CRC16 校驗(yàn)后對 16384 取模來
決定放置哪個(gè)槽,集群的每個(gè)節(jié)點(diǎn)負(fù)責(zé)一部分hash槽。
Redis分片集群中數(shù)據(jù)的存儲和讀取:
redis分片集群引入了哈希槽的概念,redis集群有16384個(gè)哈希槽,將16384個(gè)插槽分配到不同的實(shí)例
讀寫數(shù)據(jù):根據(jù)key的有效部分計(jì)算哈希值。對16384取余(有效部分,如果key前面有大括號的
內(nèi)容就是有效部分,如果沒有,則以key本身做為有效部分)余數(shù)做為插槽,尋找插槽所在的實(shí)例
redis集群環(huán)境部署(環(huán)境配置)
只配置從庫,不用配置主庫!
- 原因:redis默認(rèn)都是主庫
查看當(dāng)前redis庫的信息,分析是否是主庫
- 命令:info replication
127.0.0.1:6379> info replication # 查看當(dāng)前庫的信息
# Replication
role:master # 角色 master
master connected_slaves:0 # 沒有從機(jī)
master_replid:b63c90e6c501143759cb0e7f450bd1eb0c70882a
master_replid2:0000000000000000000000000000000000000000
master_repl_offset:0
second_repl_offset:-1
repl_backlog_active:0
repl_backlog_size:1048576
repl_backlog_first_byte_offset:0
repl_backlog_histlen:0
搭建redis集群準(zhǔn)備工作
- 復(fù)制3個(gè)redis.conf配置文件,然后修改對應(yīng)的集群信息
- 分別修改3個(gè)redis.conf對應(yīng)的以下4個(gè)屬性配置
- port端口修改
- pid名字
- log文件名字
- dump.rdb名字
- 修改完畢之后,啟動我們的3個(gè)redis服務(wù)器
-
- 分別啟動3個(gè)redis服務(wù)命令
- 啟動完畢,通過進(jìn)程查看信息
一主二從集群搭建(命令或文件配置)(這種方式的redis集群實(shí)際工作用不到,僅供基礎(chǔ)學(xué)習(xí))
命令方式配置
默認(rèn)情況下, 每臺Redis 服務(wù)器都是主節(jié)點(diǎn)?; 我們一般情況下只用配置從機(jī)就好了!
- 認(rèn)老大! 一主 (6379)二從(6380,6381)
- 配置從機(jī),去6380和6381配置,命令:
127.0.0.1:6380> SLAVEOF 127.0.0.1 6379 # SLAVEOF host 6379 登錄6380找6379主機(jī)當(dāng)自己的老大!
OK
127.0.0.1:6380> info replication # 查詢信息redis6380的服務(wù)信息
role:slave # 查看當(dāng)前角色是從機(jī)
master_host:127.0.0.1 # 可以的看到主機(jī)的信息和端口號
master_port:6379
- 配置結(jié)果查詢:6380,6381的角色role變成了slave從機(jī)
- 如果兩個(gè)都配置完了,就有兩個(gè)從機(jī)了
-
- 登錄6379主機(jī),查看主機(jī)下的兩個(gè)從節(jié)點(diǎn)
- 真實(shí)的主從配置應(yīng)該在配置文件中配置,這樣的話是永久的, 我們這里使用的是命令,暫時(shí)的!
文件方式配置(一主二從,持久化的,對于哨兵模式,不建議使用這種)
- 登錄redis從機(jī),進(jìn)入redis.conf配置文件配置replicaof
- 如果主機(jī)有密碼,則配置主機(jī)密碼
一主二從細(xì)節(jié)
- 主機(jī)可以寫, 從機(jī)不能寫只能讀! 主機(jī)中的所有信息和數(shù)據(jù),都會自動被從機(jī)保存.
測試主機(jī)寫,從機(jī)讀
- 主機(jī)寫:
- 從機(jī)讀:
- 從機(jī)寫,會報(bào)錯(cuò)
測試: 主機(jī)宕機(jī)斷開連接,從機(jī)會有什么變化
-
- 從機(jī)沒有變化,依然指向主機(jī),并且只能讀不能寫
- 如果想把從機(jī)改為主機(jī),只能手動去設(shè)置,或者配置哨兵通過選舉,將從機(jī)變?yōu)橹鳈C(jī)
測試2:這個(gè)時(shí)候, 主機(jī)如果回來了,從機(jī)有什么變化
- 從機(jī)依舊可以直接獲取到主機(jī)寫的信息!保證高可用性
測試3:如果從機(jī)斷了,會有什么后果
- 由于是使用命令行來配置的從機(jī),這個(gè)時(shí)候如果從機(jī)重啟了,就會變成主機(jī) (所以建議在redis.conf配置文件中配置從機(jī))!
- 但只要重新將主機(jī)變?yōu)閺臋C(jī), 立馬就會從主機(jī)中獲取值!
主從復(fù)制原理
- Slave啟動成功連接到master后會發(fā)送一個(gè)sync同步命令
- Master接到命令,啟動后臺的存盤進(jìn)程,同時(shí)收集所有接收到的用于修改數(shù)據(jù)集命令, 在后臺進(jìn)程完畢之后,master將傳送整個(gè)數(shù)據(jù)文件到slave,并完成一次完全同步.
- 全量復(fù)制: 而slave服務(wù)在接收到數(shù)據(jù)庫文件數(shù)據(jù)后, 將其存盤并加載到內(nèi)存中.
- 增量復(fù)制: Master繼續(xù)將新的所有收集到的修改命令依次傳給slave,完成同步.
- 但是只要是重新連接master, 一次完全同步(全量復(fù)制)將被自動執(zhí)行! 我們的數(shù)據(jù)一定可以在從機(jī)中看到!
一主兩從的第二種搭建方式(層層鏈路)哨兵模式的手動版
層層鏈路
- 79是主節(jié)點(diǎn)
- 80是79的從節(jié)點(diǎn)
- 81是79的從節(jié)點(diǎn)
- 上一個(gè)M連接下一個(gè)S!
- 這時(shí)候也可以完成我們的主從復(fù)制!
如果沒有老大了,這個(gè)時(shí)候能不能選擇一個(gè)老大出來呢? 手動!
- 謀朝篡位
-
- 如果主機(jī)斷開了連接, 我們可以使用
slaveof no one
?讓自己變成主機(jī)! 其他的節(jié)點(diǎn)就可以手動連接到最新的這個(gè)主節(jié)點(diǎn)(手動)! 如果這個(gè)時(shí)候老大修復(fù)了, 那就只能重新配置連接! - 所以建議使用命令配置集群,方便將從節(jié)點(diǎn)改為主節(jié)點(diǎn)后,不用在去改配置文件
- 如果主機(jī)斷開了連接, 我們可以使用
I/O多路復(fù)用模型
redis為什么這么快
用戶空間和內(nèi)核空間
Linux系統(tǒng)中一個(gè)進(jìn)程使用的內(nèi)存情況劃分兩部分:內(nèi)核空間,用戶空間。
用戶空間只能執(zhí)行受限的命令(Ring3),而且不能直接調(diào)用系統(tǒng)資源(比如網(wǎng)卡數(shù)據(jù)),必須通過內(nèi)核提供的接口來訪問。
內(nèi)核空間可以執(zhí)行特權(quán)命令(Ring0),調(diào)用一切系統(tǒng)資源。
Linux系統(tǒng)為了提高IO效率,會在用戶空間和內(nèi)核空間都加入緩沖區(qū):
寫數(shù)據(jù)時(shí),要把用戶緩沖數(shù)據(jù)拷貝到內(nèi)核緩沖區(qū),然后寫入設(shè)備
讀數(shù)據(jù)時(shí),要從設(shè)備讀取數(shù)據(jù)到內(nèi)核緩沖區(qū),然后拷貝到用戶緩沖區(qū)
如圖所示
阻塞IO
顧名思義,阻塞IO就是兩個(gè)階段都必須阻塞等待
階段一:
1.用戶進(jìn)程嘗試讀取數(shù)據(jù)(比如網(wǎng)卡數(shù)據(jù))
2.此時(shí)數(shù)據(jù)尚未到達(dá),內(nèi)核需要等待數(shù)據(jù)
3.此時(shí)用戶進(jìn)程也處于阻塞狀態(tài)
階段二:
1.數(shù)據(jù)到達(dá)并拷貝到內(nèi)核緩沖區(qū),代表已就緒
2.將內(nèi)核數(shù)據(jù)拷貝到用戶緩沖區(qū)
3.拷貝過程中,用戶進(jìn)程依然阻塞等待
4.拷貝完成,用戶進(jìn)程解除阻塞,處理數(shù)據(jù)
可以看到,阻塞IO模型中,用戶進(jìn)程在兩個(gè)階段都是阻塞狀態(tài)。
非阻塞IO
顧名思義,非阻塞IO的recvfrom操作會立即返回結(jié)果而不是阻塞用戶進(jìn)程。
階段一:
1.用戶進(jìn)程嘗試讀取數(shù)據(jù)(比如網(wǎng)卡數(shù)據(jù))
2.此時(shí)數(shù)據(jù)尚未到達(dá),內(nèi)核需要等待數(shù)據(jù)
3.返回異常給用戶進(jìn)程
4.用戶進(jìn)程拿到error后,再次嘗試讀取
5.循環(huán)往復(fù),直到數(shù)據(jù)就緒
階段二:
將內(nèi)核數(shù)據(jù)拷貝到用戶緩沖區(qū)
拷貝過程中,用戶進(jìn)程依然阻塞等待
拷貝完成,用戶進(jìn)程解除阻塞,處理數(shù)據(jù)
可以看到,非阻塞IO模型中,用戶進(jìn)程在第一個(gè)階段是非阻露,第二個(gè)階段是阻塞狀態(tài)。雖然是非阻塞,但性能并沒有得到提高。而且忙等機(jī)制會導(dǎo)致CPU空轉(zhuǎn),CPU使用率暴增。
IO多路復(fù)用
Redis網(wǎng)絡(luò)模型
Redis通過IO多路復(fù)用來提高網(wǎng)絡(luò)性能,并且支持各種不同的多路復(fù)用實(shí)現(xiàn),并且將這些實(shí)現(xiàn)進(jìn)行封裝,提供了統(tǒng)一的高性能事件庫。
主要是IO多路復(fù)用+事件派發(fā)機(jī)制:
Redis 6.0之后,為了提升性能,引入了多線程處理: