Redis 可以說是現(xiàn)在軟件開發(fā)中最常用的中間件了,可以用來做 Cache、分布式鎖锈颗、隊(duì)列等用途,高級特性里面的 Geo咪惠、Bitmap击吱、HyperLogLog 等更是具有“神奇”的能力,合理的應(yīng)用可以大大提升我們開發(fā)系統(tǒng)的能力遥昧。同時(shí) Redis 提供了完善的高可用方案覆醇,可以很好的支持 AP。但是 Redis 使用的不當(dāng)也會造成一些問題炭臭,所以想要馴服 Redis永脓,需要注意一些常見的問題。
基本數(shù)據(jù)類型
String
String 類型應(yīng)該是我們最常用的數(shù)據(jù)類型鞋仍,其底層實(shí)現(xiàn)是簡單動態(tài)字符串 SDS(Simple Dynamic String)常摧。當(dāng)字符串長度小于 1M 時(shí),擴(kuò)容都是加倍現(xiàn)有的空間,如果超過 1M落午,擴(kuò)容時(shí)一次只會多擴(kuò) 1M 的空間谎懦,最大長度為 512M。
應(yīng)用場景
字符串類型
當(dāng)作為字符串類型時(shí)候經(jīng)常用于緩存或者共享對象使用溃斋,比如 Session党瓮、分布式鎖等。String 類型也是二進(jìn)制安全的盐类,也可以用來存放小文件寞奸。
整數(shù)類型
在 SET 的時(shí)候,如果值為整數(shù)值在跳,那么 redisObject 的 encoding 則為 int(可以使用 OBJECT ENCODING key 命令來查看)枪萄。可以應(yīng)用于秒殺猫妙、限流瓷翻、計(jì)數(shù)等場景。
使用示例
字符串類型
## 普通字符串使用
127.0.0.1:6379> SET abc def
OK
127.0.0.1:6379> GET abc
"def"
## 鎖使用使用
## 當(dāng)已經(jīng)存在 key 將返回 0
127.0.0.1:6379> SETNX abc edf
(integer) 0
127.0.0.1:6379> SETNX def def
(integer) 1
整數(shù)類型
## 整數(shù)類型使用
127.0.0.1:6379> SET abc 123
OK
## 原子加 1
127.0.0.1:6379> INCR abc
(integer) 124
### 原子減1
127.0.0.1:6379> DECR abc
(integer) 123
## 原子加 5
127.0.0.1:6379> INCRBY abc 5
(integer) 128
List
List 是簡單的字符串列表割坠,按照插入順序排序齐帚。一個(gè)列表最多可以包含 個(gè)元素(大約40億)。
應(yīng)用場景
List 根據(jù)其特性可以很容易實(shí)現(xiàn)隊(duì)列彼哼、棧數(shù)據(jù)結(jié)構(gòu)对妄。當(dāng) LPUSH + RPOP 就實(shí)現(xiàn)了隊(duì)列數(shù)據(jù)結(jié)構(gòu),而使用 LPUSH + LPOP 就可以實(shí)現(xiàn)棧數(shù)據(jù)結(jié)構(gòu)敢朱。
BLPOP剪菱、BRPOP 會移出并獲取列表的第一個(gè)或最后一個(gè)元素, 如果列表沒有元素會阻塞列表直到等待超時(shí)或發(fā)現(xiàn)可彈出元素為止拴签,可以用來實(shí)現(xiàn)消息隊(duì)列功能孝常。
使用示例
## 隊(duì)列右邊加入元素
127.0.0.1:6379> RPUSH list 1 2
(integer) 2
## 獲取隊(duì)列中所有元素
127.0.0.1:6379> LRANGE list 0 -1
1) "1"
2) "2"
## 隊(duì)列左邊加入元素
127.0.0.1:6379> LPUSH list 0
(integer) 3
127.0.0.1:6379> LRANGE list 0 -1
1) "0"
2) "1"
3) "2"
## 移除列表最后一個(gè)元素,并返回
127.0.0.1:6379> RPOP list
"2"
127.0.0.1:6379> LRANGE list 0 -1
1) "0"
2) "1"
## 移除列表第一個(gè)元素蚓哩,并返回
127.0.0.1:6379> LPOP list
"0"
127.0.0.1:6379> LRANGE list 0 -1
1) "1"
## 使用 LTRIM 可以對列表進(jìn)行裁剪构灸,不在指定區(qū)間之內(nèi)的元素都將被刪除
127.0.0.1:6379> RPUSH list 2 3 4
(integer) 4
127.0.0.1:6379> LRANGE list 0 -1
1) "1"
2) "2"
3) "3"
4) "4"
127.0.0.1:6379> LTRIM list 1 2
OK
127.0.0.1:6379> LRANGE list 0 -1
1) "2"
2) "3"
Hash
使用場景
Hash 也是最常用到的數(shù)據(jù)結(jié)構(gòu)之一了,主要用來做數(shù)據(jù)歸并岸梨。比如當(dāng)緩存用戶信息的時(shí)候喜颁,可以將用戶ID、姓名盛嘿、性別等其他信息洛巢,歸并到同一個(gè) key括袒,在取用的時(shí)候也很方便次兆,可以統(tǒng)一取出,也可以指定字段取出锹锰。
Hash 也支持 HSETNX 對指定字段設(shè)置值(當(dāng)字段不存在時(shí)候)芥炭,與普通的 SETNX 類似漓库。但是 Hash 不支持字段級別過期時(shí)間,只能給整個(gè) key 加過期時(shí)間园蝠。
使用示例
## 使用 HMSET 一次設(shè)置多個(gè)字段
127.0.0.1:6379> HMSET hash id 123 name jack sex man login_times 5
OK
127.0.0.1:6379> HGETALL hash
1) "name"
2) "jack"
3) "id"
4) "123"
5) "sex"
6) "man"
7) "login_times"
8) "5"
## Hash 也支持使用 HINCRBY 對字段進(jìn)行加值
127.0.0.1:6379> HINCRBY hash login_times 1
(integer) 6
## Hash 也支持 HSETNX 對指定字段設(shè)置值(當(dāng)字段不存在時(shí)候)渺蒿,與普通的 SETNX 類似
127.0.0.1:6379> HSETNX hash lock_key lock_value
(integer) 1
127.0.0.1:6379> HSETNX hash lock_key lock_value
(integer) 0
Set
Set 是 String 類型的去重?zé)o序集合,集合中成員是唯一的彪薛。
使用場景
Set 提供了隨機(jī)移除茂装、集合交集、集合并集善延、集合差集等操作少态。結(jié)合這些特性,可以用于如抽獎這樣的隨機(jī)事件易遣,可以實(shí)現(xiàn)查找共同好友彼妻、可能認(rèn)識的人、獲取人脈等操作豆茫。
使用示例
## 向集合中添加對象
127.0.0.1:6379> SADD set1 a b c d
(integer) 4
127.0.0.1:6379> SADD set2 c d e f
(integer) 4
## 查看在 set1 沒在 set2 的元素侨歉,差集
127.0.0.1:6379> SDIFF set1 set2
1) "a"
2) "b"
## 查看既在 set1 又在 set2 的元素,交集
127.0.0.1:6379> SINTER set1 set2
1) "c"
2) "d"
## 查看在 set1 或在 set2 的元素揩魂,并集
127.0.0.1:6379> SUNION set1 set2
1) "c"
2) "d"
3) "b"
4) "a"
5) "e"
6) "f"
## 隨機(jī)獲取指定數(shù)量的元素幽邓,不會從集合中刪除元素
127.0.0.1:6379> SRANDMEMBER set1 2
1) "a"
2) "b"
127.0.0.1:6379> SRANDMEMBER set1 2
1) "a"
2) "d"
## 隨機(jī)移除指定數(shù)量的元素,會從集合中刪除元素
127.0.0.1:6379> SPOP set1 2
1) "b"
2) "c"
127.0.0.1:6379> SMEMBERS set1
1) "a"
2) "d"
## SDIFF火脉、SINTER颊艳、SUNION 都支持后面加上 STORE,將計(jì)算結(jié)果存入指定的 key
127.0.0.1:6379> SDIFFSTORE set3 set1 set2
(integer) 1
127.0.0.1:6379> SMEMBERS set3
1) "a"
Sorted Set
Sorted Set 和 Set 一樣也是 string 類型元素的集合忘分,且不允許重復(fù)的成員棋枕。不同的是每個(gè)元素都會關(guān)聯(lián)一個(gè) double 類型的分?jǐn)?shù),通過分?jǐn)?shù)來為集合中的成員進(jìn)行從小到大的排序妒峦。
壓縮表
在鍵值對少于 128 且每個(gè)元素小于 64 字節(jié)時(shí)候重斑,使用壓縮表(ziplist)數(shù)據(jù)結(jié)構(gòu)來存儲數(shù)據(jù)。數(shù)據(jù)結(jié)構(gòu)如下:
- zlbytes:記錄整個(gè)壓縮列表占用的字節(jié)數(shù)
- zltail:記錄壓縮列表表尾節(jié)點(diǎn)距離壓縮列表起始地址有多少字節(jié)
- zllen:記錄了壓縮列表包含的節(jié)點(diǎn)數(shù)量
- entryN:壓縮列表的節(jié)點(diǎn)肯骇,節(jié)點(diǎn)長度由節(jié)點(diǎn)保存的內(nèi)容決定
- zlend:特殊值 0xFF窥浪,用于標(biāo)記壓縮列表的末端
- previous_entry_length:記錄了壓縮表中前一個(gè)節(jié)點(diǎn)的長度
- encoding:記錄了節(jié)點(diǎn) content 屬性所保存數(shù)據(jù)的類型以及長度,最兩位標(biāo)識類型笛丙,其余為長度
- content:保存節(jié)點(diǎn)的值,節(jié)點(diǎn)值可以是一個(gè)字節(jié)數(shù)組或者整數(shù)
跳表
在鍵值較多的時(shí)候?qū)⑹褂锰恚╯kiplist)數(shù)據(jù)結(jié)構(gòu)來存儲數(shù)據(jù)漾脂,跳表結(jié)構(gòu)簡單,結(jié)構(gòu)如下所示胚鸯。跳表與二叉查找樹類似骨稿,都是為了提高查詢效率,將 O(n) 的查詢效率降低到 O(logn)。
使用場景
由于 Sorted Set 是一個(gè)有序集合坦冠,所以天然的適合進(jìn)行排序相關(guān)的場景形耗。如排行榜、具有優(yōu)先級特性的隊(duì)列功能等辙浑。
使用示例
## 向集合中添加元素
127.0.0.1:6379> ZADD sortedset 100 a 200 b 300 c 250 d
(integer) 4
127.0.0.1:6379> ZRANGE sortedset 0 -1 WITHSCORES
1) "a"
2) "100"
3) "b"
4) "200"
5) "d"
6) "250"
7) "c"
8) "300"
## 獲取指定區(qū)間分?jǐn)?shù)的成員數(shù)
127.0.0.1:6379> ZCOUNT sortedset 200 300
(integer) 3
## 對指定成員的分?jǐn)?shù)加上增量值
127.0.0.1:6379> ZINCRBY sortedset 100 b
"300"
127.0.0.1:6379> ZRANGE sortedset 0 -1 WITHSCORES
1) "a"
2) "100"
3) "d"
4) "250"
5) "b"
6) "300"
7) "c"
8) "300"
## 返回集合中指定成員的排行激涤,從 0 開始
127.0.0.1:6379> ZRANK sortedset d
(integer) 1
## 按照排行移除排行 0-1 的元素,利用此命令可以實(shí)現(xiàn)優(yōu)先級隊(duì)列
## ZREMRANGEBYSCORE 按照分?jǐn)?shù)移除
## ZREMRANGEBYLEX 按照 key 字典區(qū)間移除
## ZREM 按照元素 key 移除
127.0.0.1:6379> ZREMRANGEBYRANK sortedset 0 1
(integer) 2
127.0.0.1:6379> ZRANGE sortedset 0 -1 WITHSCORES
1) "b"
2) "300"
3) "c"
4) "300"
高級特性
Geo
應(yīng)用場景
在 Redis 3.2 版本中加入 Geo 相關(guān)功能判呕,主要用于對地理數(shù)據(jù)的一些處理倦踢,使用 Geo 可以很方便的構(gòu)建出周邊查詢功能。
使用示例
## 往指定 key 中增加帶坐標(biāo)的 Geo 元素
127.0.0.1:6379> GEOADD geo 116.397466 39.908628 tiananmen 116.411246 39.913171 wangfujing 116.44401 39.915557 ritan
(integer) 3
## 獲取兩個(gè)元素的距離侠草,支持 m,km 等單位
127.0.0.1:6379> GEODIST geo tiananmen wangfujing m
"1279.6965"
## 獲取指定坐標(biāo)周邊的點(diǎn)硼一,可以指定返回的數(shù)量,及排序方式
127.0.0.1:6379> GEORADIUS geo 116.397466 39.908628 1300 m WITHDIST WITHCOORD COUNT 1000 ASC
1) 1) "tiananmen"
2) "0.0875"
3) 1) "116.39746695756912231"
2) "39.90862828249731109"
2) 1) "wangfujing"
2) "1279.7840"
3) 1) "116.41124814748764038"
2) "39.91317050281484313"
## 獲取指定元素周邊的點(diǎn)梦抢,與上面的命令類似
127.0.0.1:6379> GEORADIUSBYMEMBER geo wangfujing 3 km WITHDIST WITHCOORD
1) 1) "wangfujing"
2) "0.0000"
3) 1) "116.41124814748764038"
2) "39.91317050281484313"
2) 1) "ritan"
2) "2.8074"
3) 1) "116.44400864839553833"
2) "39.91555821014694772"
3) 1) "tiananmen"
2) "1.2797"
3) 1) "116.39746695756912231"
2) "39.90862828249731109"
Bitmap
應(yīng)用場景
Bitmap 本質(zhì)還是使用了 String 類型進(jìn)行數(shù)據(jù)存儲般贼,使用 Bitmap 經(jīng)常可以以很小的成本實(shí)現(xiàn)一些復(fù)雜的需求奥吩。如:統(tǒng)計(jì)任意時(shí)間窗口哼蛆,用戶登錄次數(shù),每個(gè)用戶一個(gè) 365 位的 Bitmap霞赫,登錄則天數(shù)位置為 1腮介;預(yù)估活躍用戶,按天存儲用戶的活躍情況端衰,按照用戶 ID 設(shè)置對應(yīng) bit 位叠洗;OA 系統(tǒng)的權(quán)限、Linux 的文件操作等旅东。
使用示例
## 設(shè)置 key 指定 bit 位值
127.0.0.1:6379> SETBIT sign_in_week_12 5 1
(integer) 0
127.0.0.1:6379> SETBIT sign_in_week_12 1 1
(integer) 0
127.0.0.1:6379> SETBIT sign_in_week_12 3 1
(integer) 0
127.0.0.1:6379> SETBIT sign_in_week_12 4 1
(integer) 0
127.0.0.1:6379> SETBIT sign_in_week_12 5 1
## 獲取指定 key 指定區(qū)間的值為 1 的數(shù)量
## bitmap 也是采用 string 類型灭抑,一個(gè)字節(jié)可以存儲 8 位,這里的 start抵代,end 是指字節(jié)的
127.0.0.1:6379> BITCOUNT sign_in_week_12 0 0
(integer) 4
## 獲取 bitmap 里面 bit 值為指定值的第一個(gè)位置
127.0.0.1:6379> SETBIT bit2 0 1
(integer) 0
127.0.0.1:6379> BITPOS bit2 0
(integer) 1
127.0.0.1:6379> BITPOS bit2 1
(integer) 0
## 對 bitma p進(jìn)行操作腾节,支持 AND、OR荤牍、NOT案腺、XOR
127.0.0.1:6379> SETBIT bitop1 0 1
(integer) 0
127.0.0.1:6379> SETBIT bitop1 1 1
(integer) 0
127.0.0.1:6379> SETBIT bitop2 0 0
(integer) 0
127.0.0.1:6379> SETBIT bitop2 1 1
(integer) 0
127.0.0.1:6379> BITOP OR bitor bitop1 bitop2
(integer) 1
127.0.0.1:6379> BITOP AND bitand bitop1 bitop2
(integer) 1
127.0.0.1:6379> GET bitor
"\xc0"
127.0.0.1:6379> GET bitand
"@"
HyperLogLog
使用場景
HyperLogLog 是用來做基數(shù)統(tǒng)計(jì)的算法,HyperLogLog 的優(yōu)點(diǎn)是康吵,在輸入元素的數(shù)量或者體積非常非常大時(shí)劈榨,計(jì)算基數(shù)所需的空間總是固定的、并且是很小的晦嵌,每個(gè) HyperLogLog 鍵只需要花費(fèi) 12 KB 內(nèi)存同辣,就可以計(jì)算接近 2^64 個(gè)不同元素的基數(shù)拷姿,但存在 0.81% 的誤差。
基數(shù)統(tǒng)計(jì)是在伯努利實(shí)驗(yàn)基礎(chǔ)上邑闺,指的是在同樣的條件下重復(fù)地、相互獨(dú)立地進(jìn)行的一種隨機(jī)試驗(yàn)棕兼,其特點(diǎn)是該隨機(jī)試驗(yàn)只有兩種可能結(jié)果:發(fā)生或者不發(fā)生陡舅。
以最容易理解的拋硬幣舉例,每次試驗(yàn)拋到正面的硬幣就停止伴挚,否則就繼續(xù)靶衍。那么可以知道:拋 1 次停止概率是 1/2;拋 2 次停止的概率是 1/22茎芋;拋 k 次停止的概率是1/2k颅眶。對每次試驗(yàn),拋擲 k 次停止的概率都是 1/2k田弥。
假設(shè)做了 n 次試驗(yàn)涛酗,其中最大的拋擲次數(shù)是 4,那我們可以計(jì)算出做的試驗(yàn)次數(shù) n 嗎偷厦?結(jié)合上面的基礎(chǔ)商叹,發(fā)現(xiàn)在 n 和最大拋擲次數(shù)(k_max)之間存在估算關(guān)聯(lián):n = 2k_max,那么我們也就知道應(yīng)該是做了 16 次試驗(yàn)只泼。
因?yàn)榇髷?shù)定理對于數(shù)據(jù)量小的時(shí)候剖笙,會有很大的誤差。而為了解決這個(gè)問題请唱,HyperLogLog 引入了分桶算法和調(diào)和平均數(shù)來使這個(gè)算法更接近真實(shí)情況弥咪。
基于 HyperLogLog 的特性,非常適合做不需要很精確的網(wǎng)站 UV 統(tǒng)計(jì)十绑。
## 向 HyperLogLog 中添加元素
127.0.0.1:6379> PFADD hyperloglog 1 2 3 4 5 5
(integer) 1
127.0.0.1:6379> PFCOUNT hyperloglog
(integer) 5
127.0.0.1:6379> PFADD hyperloglog 6 7 8 9
(integer) 1
127.0.0.1:6379> PFCOUNT hyperloglog
(integer) 9
## 支持兩個(gè) HyperLogLog 的合并操作
127.0.0.1:6379> PFADD hyperloglog2 8 9 10 11 12
(integer) 1
127.0.0.1:6379> PFMERGE hyper_merge hyperloglog hyperloglog2
OK
127.0.0.1:6379> PFCOUNT hyper_merge
(integer) 12
發(fā)布訂閱
使用場景
Redis 發(fā)布訂閱是一種消息通信模式:發(fā)送者 (pub) 發(fā)送消息聚至,訂閱者 (sub) 接收消息。與現(xiàn)在經(jīng)常使用的 MQ 機(jī)制很像本橙,如果想實(shí)現(xiàn) MQ 的發(fā)布訂閱功能晚岭,又不想引入過多的中間件,可以嘗試使用 Redis 的發(fā)布訂閱功能勋功。不過由于不具備傳統(tǒng) MQ 的高可用坦报、消息堆積、重試等功能狂鞋,所以并不能完全替代傳統(tǒng) MQ片择。
使用示例
監(jiān)聽某個(gè) channel。
當(dāng)監(jiān)聽的 channl 發(fā)布了消息后骚揍,能自動感知字管。
事務(wù)
使用場景
Redis 的事務(wù)啰挪,并不是預(yù)先執(zhí)行的,而是先放入隊(duì)列中緩存嘲叔,當(dāng) EXEC 的時(shí)候才執(zhí)行亡呵。當(dāng)其中語句有語法錯誤(不需執(zhí)行就知道錯誤)的時(shí)候是可以回滾的,否則并不會回滾硫戈,所以不能完全當(dāng)成數(shù)據(jù)庫事務(wù)一樣使用锰什。
Redis 事務(wù)能保證的是在事務(wù)執(zhí)行過程,其他客戶端提交的命令不會插入到事務(wù)執(zhí)行命令中間丁逝。
使用示例
## 開啟事務(wù)汁胆,中間有非法語句,事務(wù)將失敗
127.0.0.1:6379> MULTI
OK
127.0.0.1:6379> SET trans1 abc
QUEUED
127.0.0.1:6379> SET1 trans2 cde
(error) ERR unknown command `set1`, with args beginning with: `trans2`, `cde`,
127.0.0.1:6379> exec
(error) EXECABORT Transaction discarded because of previous errors.
## 開啟事務(wù)霜幼,中間有錯誤類型使用嫩码,正常的語句將提交,不會回滾
127.0.0.1:6379> MULTI
OK
127.0.0.1:6379> SET trans3 abc
QUEUED
127.0.0.1:6379> SADD trans3 def
QUEUED
127.0.0.1:6379> EXEC
1) OK
2) (error) WRONGTYPE Operation against a key holding the wrong kind of value
127.0.0.1:6379> get trans3
"abc"
Pipeline
使用場景
Redis 的 Pipeline(管道)功能在命令行中沒有罪既,但 Redis 是支持 Pipeline 的铸题,而且在各個(gè)語言版的 Client 中都有相應(yīng)的實(shí)現(xiàn)。 Pipeline 與 SQL 中的批處理過程類似琢感,所有命令會先在 Client 端“打包”回挽,統(tǒng)一發(fā)送至 Redis,Redis 將部分請求放到隊(duì)列中(使用內(nèi)存)猩谊,執(zhí)行完畢后一次性發(fā)送結(jié)果千劈。當(dāng) Client 與 Redis 之間的網(wǎng)絡(luò)延時(shí)比較大,Pipeline 將非常有用牌捷。整個(gè)的處理過程改變?nèi)缦拢?/p>
不過墙牌,Pipeline 期間將獨(dú)占鏈接,期間內(nèi)將不能進(jìn)行非“管道”類型的其他操作暗甥,直到 Pipeline 關(guān)閉喜滨;如果你的 Pipeline 的指令集很龐大,為了不干擾鏈接中的其他操作撤防,可以為 Pipeline 操作新建 Client 鏈接虽风。
高可用
Redis 作為 AP 系統(tǒng),高可用的考慮是必不可少的寄月。Redis 主要使用了主從復(fù)制加以 Sentinel 監(jiān)控辜膝、自動切換來保證集群的可用性,同時(shí)使用 RDB漾肮、AOF 等機(jī)制保證數(shù)據(jù)持久化能力〕Ф叮現(xiàn)在,Redis 更多的使用 Redis Cluster 模式克懊,即保證了集群的高可用忱辅,又可以突破單機(jī)存儲瓶頸七蜘,理論上可以做到無限擴(kuò)展。
Sentinel
Redis 可以用 Sentinel 系統(tǒng)管理多個(gè) Redis 服務(wù)示例墙懂,Sentinel 主要執(zhí)行以下三個(gè)任務(wù):
- 監(jiān)控(Monitoring):Sentinel 會不斷地檢查主服務(wù)器和從服務(wù)器是否運(yùn)作正常
- 提醒(Notification):當(dāng)被監(jiān)控的某個(gè) Redis 服務(wù)器出現(xiàn)問題時(shí)橡卤,Sentinel 可以通過 API 向管理員或者其他應(yīng)用程序發(fā)送通知
- 自動故障遷移(Automatic failover):當(dāng)一個(gè)主服務(wù)器不能正常工作時(shí),Sentinel 會開始一次自動故障遷移操作损搬,它會將失效主服務(wù)器的其中一個(gè)從服務(wù)器升級為新的主服務(wù)器碧库,并讓失效主服務(wù)器的其他從服務(wù)器改為復(fù)制新的主服務(wù)器; 當(dāng)客戶端試圖連接失效的主服務(wù)器時(shí)场躯,集群也會向客戶端返回新主服務(wù)器的地址谈为,使得集群可以使用新主服務(wù)器代替失效服務(wù)器旅挤。
一個(gè) Sentinel 可以與其他多個(gè) Sentinel 進(jìn)行連接踢关,Sentinel 之間可以互相檢查對方的可用性,并進(jìn)行信息交換粘茄。Sentinel 可以通過發(fā)布與訂閱功能來自動發(fā)現(xiàn)正在監(jiān)視相同主服務(wù)器的其他 Sentinel签舞,Sentinel 也可以通過詢問主服務(wù)器來獲得所有從服務(wù)器的信息。
一次故障轉(zhuǎn)移操作由以下步驟組成:
- 發(fā)現(xiàn)主服務(wù)器已經(jīng)進(jìn)入客觀下線狀態(tài)
- 對我們的當(dāng)前任期進(jìn)行自增(Raft Leader Election 過程)柒瓣,并嘗試在這個(gè)任期中當(dāng)選
- 如果當(dāng)選失敗儒搭,那么在設(shè)定的故障遷移超時(shí)時(shí)間的兩倍之后,重新嘗試當(dāng)選芙贫。如果當(dāng)選成功搂鲫,那么執(zhí)行以下步驟
- 選出一個(gè)從服務(wù)器,并將它升級為主服務(wù)器
- 向被選中的從服務(wù)器發(fā)送 SLAVEOF NO ONE 命令磺平,讓它轉(zhuǎn)變?yōu)橹鞣?wù)器
- 通過發(fā)布與訂閱功能魂仍, 將更新后的配置傳播給所有其他 Sentinel,其他 Sentinel 對它們自己的配置進(jìn)行更新
- 向已下線主服務(wù)器的從服務(wù)器發(fā)送 SLAVEOF 命令拣挪,讓它們?nèi)?fù)制新的主服務(wù)器
- 當(dāng)所有從服務(wù)器都已經(jīng)開始復(fù)制新的主服務(wù)器時(shí)擦酌,領(lǐng)頭 Sentinel 終止這次故障遷移操作
持久化
Redis 的持久化主要支持 RDB 跟 AOF 兩種方式:
- RDB持久化:在指定的時(shí)間間隔能對 Redis 的數(shù)據(jù)進(jìn)行快照存儲
- AOF持久化:記錄每次對服務(wù)器寫的操作,當(dāng)服務(wù)器重啟的時(shí)候會重新執(zhí)行這些命令來恢復(fù)原始的數(shù)據(jù)菠劝,AOF 命令以 Redis 協(xié)議追加保存每次寫的操作到文件末尾赊舶。Redis 還能對 AOF 文件進(jìn)行后臺重寫,使得AOF 文件的體積不至于過大
RDB
RDB 是一個(gè)非常緊湊的文件赶诊,它保存了某個(gè)時(shí)間點(diǎn) Redis 服務(wù)的數(shù)據(jù)集笼平,非常適用于數(shù)據(jù)集的備份,與 AOF 相比舔痪,在恢復(fù)大的數(shù)據(jù)集的時(shí)候出吹,RDB 方式會更快一些。
在保存 RDB 文件時(shí)父進(jìn)程唯一需要做的就是 fork 出一個(gè)子進(jìn)程辙喂,接下來的工作全部由子進(jìn)程來做捶牢,父進(jìn)程不需要再做其他 IO 操作,所以 RDB 持久化方式可以最大化 Redis 的性能渐排。
RDB 雖然可以配置不同的保存時(shí)間點(diǎn)(默認(rèn)是 5 分鐘)可缚,但如果配置的時(shí)間較短瓤帚,那么這個(gè)任務(wù)將是一個(gè)比較繁重的工作。因此在權(quán)衡性能后,使用 RDB 方式可能會丟失幾分鐘的數(shù)據(jù)搂捧。
RDB 需要經(jīng)常 fork 子進(jìn)程來保存數(shù)據(jù)集到硬盤上聋丝,當(dāng)數(shù)據(jù)集比較大的時(shí)候垒拢,fork 的過程是非常耗時(shí)的尸疆,可能會導(dǎo)致 Redis 在一些毫秒級內(nèi)不能響應(yīng)客戶端的請求症革。
AOF
AOF 為 Redis 提供了更實(shí)時(shí)的持久方案嗜浮,通過使用不同的 fsync 策略:①無 fsync(交由操作系統(tǒng)決定)羡亩;②每秒 fsync摩疑;③每次寫的時(shí)候 fsync。在默認(rèn)使用每秒 fsync 情況下畏铆,Redis 依然能保持很好的性能雷袋,一旦出現(xiàn)故障也最多丟失 1 秒的數(shù)據(jù)。
Redis 在 AOF 文件體積變得過大時(shí)辞居,會自動在后臺對 AOF 進(jìn)行 rewrite 操作(合并重復(fù)命令)楷怒,rewrite 后的新 AOF 文件僅包含恢復(fù)當(dāng)前數(shù)據(jù)集所需的最小命令集合。AOF 文件有序地保存了對數(shù)據(jù)庫執(zhí)行的所有寫入操作瓦灶,這些寫入操作以 Redis 協(xié)議的格式保存鸠删,因此 AOF 文件的內(nèi)容非常容易被讀懂。
但對于相同的數(shù)據(jù)集來說贼陶,AOF 文件的體積通常要大于 RDB 文件的體積刃泡。根據(jù)所使用的 fsync 策略,AOF 的速度可能會慢于 RDB碉怔。在一般情況下烘贴,每秒 fsync 的性能依然非常高,而關(guān)閉 fsync 可以讓 AOF 的速度和 RDB 一樣快撮胧。
混合模式
在 Redis 4.0 之后支持了 RDB + AOF 的混合持久化方案桨踪,混合持久化結(jié)合了 RDB 持久化和 AOF 持久化的優(yōu)點(diǎn):RDB 文件小易于故障快速恢復(fù),AOF 能夠保障數(shù)據(jù)丟失最小化芹啥。
混合持久化是通過 rewrite 完成的锻离,與普通 AOF 不同的是:當(dāng)開啟混合持久化時(shí)铺峭,fork 出的子進(jìn)程先將共享內(nèi)存副本全量的以 RDB 方式寫入 AOF 文件,然后再將 rewrite 緩沖區(qū)的增量命令以 AOF 方式寫入到文件汽纠,并將新的含有 RDB 格式和 AOF 格式的 AOF 文件替換舊的的 AOF 文件逛薇。簡單的說:混合模式下,AOF 文件前半段是 RDB 格式的全量數(shù)據(jù)疏虫,后半段是 AOF 格式的增量數(shù)據(jù)永罚。
Redis 常見問題
過期策略
Redis 淘汰過期的 key 的方式只要有兩種,主動和被動卧秘。
- 主動方式:當(dāng)客戶端嘗試訪問某個(gè) key 時(shí)呢袱,如果 key 已經(jīng)過期將主動進(jìn)行 key 的過期刪除,這種方式也叫惰性淘汰
- 被動方式:僅依靠訪問時(shí)刪除是遠(yuǎn)遠(yuǎn)不夠的翅敌,因?yàn)闀写罅?key 訪問頻率不是那么高羞福,而導(dǎo)致無法刪除。Redis 將定時(shí)檢測 key 是否過期而進(jìn)行刪除蚯涮,Redis 每秒將進(jìn)行 10 次的過期檢測治专,具體步驟是:
a. 隨機(jī)抽取 20 個(gè) keys 進(jìn)行相關(guān)過期檢測
b. 刪除所有已經(jīng)過期的 keys
c. 如果有多于 25% 的 keys 過期,重復(fù)步奏 a
淘汰策略
當(dāng) Redis 的數(shù)據(jù)集達(dá)到了 maxmemory 的限制時(shí)候遭顶,需要一些策略來拒絕或者清理 Redis 來保證系統(tǒng)可以正常運(yùn)行下去张峰。
可以使用 maxmemory-policy 配置項(xiàng)來指定相應(yīng)的策略:
- noeviction:返回錯誤當(dāng)內(nèi)存限制達(dá)到并且客戶端嘗試執(zhí)行會讓更多內(nèi)存被使用的命令(大部分的寫入指令,但DEL和幾個(gè)例外)
- allkeys-lru:嘗試回收最少使用的鍵(LRU)棒旗,使得新添加的數(shù)據(jù)有空間存放
- volatile-lru:嘗試回收最少使用的鍵(LRU)喘批,但僅限于在過期集合的鍵,使得新添加的數(shù)據(jù)有空間存放
- allkeys-random:回收隨機(jī)的鍵使得新添加的數(shù)據(jù)有空間存放
- volatile-random:回收隨機(jī)的鍵使得新添加的數(shù)據(jù)有空間存放铣揉,但僅限于在過期集合的鍵
- volatile-ttl:回收在過期集合的鍵饶深,并且優(yōu)先回收存活時(shí)間(TTL)較短的鍵,使得新添加的數(shù)據(jù)有空間存放
Redis 的 LRU 算法并非完整的實(shí)現(xiàn),這意味著 Redis 無法選擇最佳候選 keys 進(jìn)行回收逛拱。Redis 嘗試運(yùn)行一個(gè)近似LRU的算法敌厘,通過對少量 keys 進(jìn)行取樣,然后回收其中一個(gè)最好的 key(被訪問時(shí)間較早的)朽合。
不使用真實(shí)的 LRU 實(shí)現(xiàn)是因?yàn)樾枰嗟膬?nèi)存俱两,而近似的 LRU 算法對于應(yīng)用而言也完全夠用。
常見問題
緩存雪崩
緩存雪崩是指緩存在同一時(shí)間失效旁舰,導(dǎo)致大量的 Cache Miss锋华,可以通過隨機(jī)過期時(shí)間來避免,也可以通過延期 key 過期時(shí)間來避免箭窜。
緩存擊穿
緩存擊穿主要是說熱點(diǎn) key 失效毯焕,當(dāng)熱點(diǎn) key 失效時(shí)候,大量請求將達(dá)到后端,并嘗試更新緩存纳猫。解決辦法是熱點(diǎn) key 不失效婆咸,或者更新緩存時(shí)候需要加鎖。
緩存穿透
緩存穿透是支持訪問數(shù)據(jù)庫中不存在的 key芜辕,導(dǎo)致數(shù)據(jù)無法被緩存尚骄,全量請求到后端。解決辦法是:首先需要增加查詢校驗(yàn)侵续,其次可以將 null 或指定值進(jìn)行緩存倔丈,或者使用 BloomFilter 進(jìn)行異常 key 過濾。
雙寫一致性
在單機(jī)情況下状蜗,經(jīng)常會因?yàn)槎嗑€程對同一個(gè)數(shù)據(jù)處理需五,導(dǎo)致數(shù)據(jù)不一致性。在分布式情況下轧坎,這種情況將更容易發(fā)生宏邮。如上圖所示,當(dāng)發(fā)生這種處理過程的時(shí)候缸血,將導(dǎo)致最新的數(shù)據(jù)更新丟失蜜氨,因此提出了“延時(shí)雙刪”的概念。
延時(shí)雙刪捎泻,顧名思義即在第一次刪除后飒炎,根據(jù)業(yè)務(wù)情況延遲一定時(shí)間再進(jìn)行一次刪除,以求最大程度的保證數(shù)據(jù)一致性族扰。因?yàn)槿绻舆t的時(shí)間較大厌丑,那么延時(shí)期間內(nèi)請求到的數(shù)據(jù)可能是失效的定欧,而如果延遲時(shí)間較小渔呵,又可能導(dǎo)致雙刪失效。
Bigkey
Bigkey 即數(shù)據(jù)量大的 key 砍鸠,由于其數(shù)據(jù)大小遠(yuǎn)大于其他 key扩氢,導(dǎo)致經(jīng)過分片之后,某個(gè)具體存儲這個(gè) Bigkey 的實(shí)例內(nèi)存使用量遠(yuǎn)大于其他實(shí)例造成內(nèi)存分配不均爷辱。在請求的時(shí)候录豺,由于數(shù)據(jù)量過大很可能會造成阻塞,甚至被判定為下線進(jìn)而進(jìn)行主從切換饭弓。如果這個(gè) key 同時(shí)是熱點(diǎn) key双饥,非常容易造成網(wǎng)絡(luò)阻塞。
發(fā)現(xiàn) Bigkey 可以通過 Redis 自帶的 --bigkeys 命令弟断,或者使用 scan 命令對 key 逐個(gè)進(jìn)行分析咏花。在發(fā)現(xiàn) Bigkey 之后,主要原則就是刪除、拆分昏翰、裁剪苍匆。
熱點(diǎn) key
熱點(diǎn) key 主要是對高熱訪問的一些 key 做處理時(shí)候需要的一些注意事項(xiàng)。
首先在構(gòu)建過程中要保證同時(shí)只有一個(gè)線程會去訪問數(shù)據(jù)庫獲取值棚菊,過期策略可以根據(jù)情況適當(dāng)加長甚至永久有效浸踩。
在使用的時(shí)候需要提前進(jìn)行預(yù)熱,同時(shí)利用 Java 本地緩存緩解壓力统求,必要時(shí)候需要有服務(wù)降級預(yù)案检碗。