分佈式存儲中間件(1):10000字把Redis扒個乾淨,一發入魂

前言

關於分佈式系列專題,總體是打算寫分佈式消息中間件、分佈式儲存中間件和分佈式框架的。 分佈式消息中間件選擇了兩個最經常使用的,以前已經寫了,感興趣的朋友能夠看看。java

這篇文章是寫分佈式存儲中間件種的Redis,關於Redis想必沒有任何一個程序員會感到陌生吧,其實網上寫Redis的文章已經不少了,甚至能夠說是氾濫,但總感受太碎片化了,因此仍是想系統的寫一下Redis,固然了,篇幅所限,這篇文章確定也是寫不盡Redis的,可是我會盡力把重要的東西都拎出來系統的講一下,讓不瞭解的朋友看完後對Redis可以簡單上手敲代碼,讓Redis有一些瞭解的朋友查漏補缺。mysql

我整理的一些相關學習資料能夠直接點擊領取git

1、Redis數據結構

Redis五種數據結構以下:

對redis來講,全部的key(鍵)都是字符串。spring

1.String 字符串類型

是redis中最基本的數據類型,一個key對應一個value。sql

String類型是二進制安全的,意思是 redis 的 string 能夠包含任何數據。如數字,字符串,jpg圖片或者序列化的對象。數據庫

使用:get 、 set 、 del 、 incr、 decr 等

127.0.0.1:6379> set hello world
OK
127.0.0.1:6379> get hello
"world"
127.0.0.1:6379> del hello
(integer) 1
127.0.0.1:6379> get hello
(nil)
127.0.0.1:6379> get counter
"2"
127.0.0.1:6379> incr counter
(integer) 3
127.0.0.1:6379> get counter
"3"
127.0.0.1:6379> incrby counter 100
(integer) 103
127.0.0.1:6379> get counter
"103"
127.0.0.1:6379> decr counter
(integer) 102
127.0.0.1:6379> get counter
"102"

實戰場景:

1.緩存: 經典使用場景,把經常使用信息,字符串,圖片或者視頻等信息放到redis中,redis做爲緩存層,mysql作持久化層,下降mysql的讀寫壓力。

2.計數器:redis是單線程模型,一個命令執行完纔會執行下一個,同時數據能夠一步落地到其餘的數據源。

3.session:常見方案spring session + redis實現session共享,

2.Hash (哈希)

是一個Mapmap,指值自己又是一種鍵值對結構,如 value={{field1,value1},......fieldN,valueN}}

使用:全部hash的命令都是  h   開頭的     hget  、hset 、  hdel 等

127.0.0.1:6379> hset user name1 hao
(integer) 1
127.0.0.1:6379> hset user email1 hao@163.com
(integer) 1
127.0.0.1:6379> hgetall user
1) "name1"
2) "hao"
3) "email1"
4) "hao@163.com"
127.0.0.1:6379> hget user user
(nil)
127.0.0.1:6379> hget user name1
"hao"
127.0.0.1:6379> hset user name2 xiaohao
(integer) 1
127.0.0.1:6379> hset user email2 xiaohao@163.com
(integer) 1
127.0.0.1:6379> hgetall user
1) "name1"
2) "hao"
3) "email1"
4) "hao@163.com"
5) "name2"
6) "xiaohao"
7) "email2"
8) "xiaohao@163.com"

實戰場景:

1.緩存: 能直觀,相比string更節省空間,的維護緩存信息,如用戶信息,視頻信息等。

 3.鏈表 

List 說白了就是鏈表(redis 使用雙端鏈表實現的 List),是有序的,value能夠重複,能夠經過下標取出對應的value值,左右兩邊都能進行插入和刪除數據。

使用列表的技巧

  • lpush+lpop=Stack(棧)
  • lpush+rpop=Queue(隊列)
  • lpush+ltrim=Capped Collection(有限集合)
  • lpush+brpop=Message Queue(消息隊列)

使用:

127.0.0.1:6379> lpush mylist 1 2 ll ls mem
(integer) 5
127.0.0.1:6379> lrange mylist 0 -1
1) "mem"
2) "ls"
3) "ll"
4) "2"
5) "1"
127.0.0.1:6379>

實戰場景:

1.timeline:例如微博的時間軸,有人發佈微博,用lpush加入時間軸,展現新的列表信息。

4.Set   集合

集合類型也是用來保存多個字符串的元素,但和列表不一樣的是集合中  1. 不容許有重複的元素,2.集合中的元素是無序的,不能經過索引下標獲取元素,3.支持集合間的操做,能夠取多個集合取交集、並集、差集。

使用:命令都是以s開頭的  sset 、srem、scard、smembers、sismember

127.0.0.1:6379> sadd myset hao hao1 xiaohao hao
(integer) 3
127.0.0.1:6379> SMEMBERS myset
1) "xiaohao"
2) "hao1"
3) "hao"
127.0.0.1:6379> SISMEMBER myset hao
(integer) 1

實戰場景;

1.標籤(tag),給用戶添加標籤,或者用戶給消息添加標籤,這樣有同一標籤或者相似標籤的能夠給推薦關注的事或者關注的人。

2.點贊,或點踩,收藏等,能夠放到set中實現

5.zset  有序集合

有序集合和集合有着必然的聯繫,保留了集合不能有重複成員的特性,區別是,有序集合中的元素是能夠排序的,它給每一個元素設置一個分數,做爲排序的依據。

(有序集合中的元素不能夠重複,可是score 分數 能夠重複,就和一個班裏的同窗學號不能重複,但考試成績能夠相同)。

使用: 有序集合的命令都是 以  z  開頭    zadd 、 zrange、 zscore

127.0.0.1:6379> zadd myscoreset 100 hao 90 xiaohao
(integer) 2
127.0.0.1:6379> ZRANGE myscoreset 0 -1
1) "xiaohao"
2) "hao"
127.0.0.1:6379> ZSCORE myscoreset hao
"100"

實戰場景:

1.排行榜:有序集合經典使用場景。例如小說視頻等網站須要對用戶上傳的小說視頻作排行榜,榜單能夠按照用戶關注數,更新時間,字數等打分,作排行。

2、Redis 持久化機制

Redis 有兩種持久化方案,RDB (Redis DataBase)和 AOF (Append Only File)。若是你想快速瞭解和使用RDB和AOF,能夠直接跳到文章底部看總結。本章節經過配置文件,觸發快照的方式,恢復數據的操做,命令操做演示,優缺點來學習 Redis 的重點知識持久化。

一、RDB 詳解

RDB 是 Redis 默認的持久化方案。在指定的時間間隔內,執行指定次數的寫操做,則會將內存中的數據寫入到磁盤中。即在指定目錄下生成一個dump.rdb文件。Redis 重啓會經過加載dump.rdb文件恢復數據。

從配置文件瞭解RDB 打開 redis.conf 文件,找到 SNAPSHOTTING 對應內容

1.1 RDB核心規則配置(重點)

save <seconds> <changes>
# save ""
save 900 1
save 300 10
save 60 10000

解說:save <指定時間間隔> <執行指定次數更新操做>,知足條件就將內存中的數據同步到硬盤中。官方出廠配置默認是 900秒內有1個更改,300秒內有10個更改以及60秒內有10000個更改,則將內存中的數據快照寫入磁盤。 若不想用RDB方案,能夠把 save "" 的註釋打開,下面三個註釋。

1.2 指定本地數據庫文件名,通常採用默認的 dump.rdb

dbfilename dump.rdb

1.3 指定本地數據庫存放目錄,通常也用默認配置

dir ./

1.4 默認開啓數據壓縮

rdbcompression yes

解說:配置存儲至本地數據庫時是否壓縮數據,默認爲yes。Redis採用LZF壓縮方式,但佔用了一點CPU的時間。若關閉該選項,但會致使數據庫文件變的巨大。建議開啓。

二、觸發RDB快照

2.1 在指定的時間間隔內,執行指定次數的寫操做 2.2 執行save(阻塞, 只管保存快照,其餘的等待) 或者是bgsave (異步)命令 2.3 執行flushall 命令,清空數據庫全部數據,意義不大。 2.4 執行shutdown 命令,保證服務器正常關閉且不丟失任何數據,意義...也不大。

三、經過RDB文件恢復數據

將dump.rdb 文件拷貝到redis的安裝目錄的bin目錄下,重啓redis服務便可。在實際開發中,通常會考慮到物理機硬盤損壞狀況,選擇備份dump.rdb 。能夠從下面的操做演示中能夠體會到。

四、RDB 的優缺點

優勢: 1 適合大規模的數據恢復。 2 若是業務對數據完整性和一致性要求不高,RDB是很好的選擇。

缺點: 1 數據的完整性和一致性不高,由於RDB可能在最後一次備份時宕機了。 2 備份時佔用內存,由於Redis 在備份時會獨立建立一個子進程,將數據寫入到一個臨時文件(此時內存中的數據是原來的兩倍哦),最後再將臨時文件替換以前的備份文件。 因此Redis 的持久化和數據的恢復要選擇在夜深人靜的時候執行是比較合理的。

操做演示

[root@itdragon bin]# vim redis.conf
save 900 1
save 120 5
save 60 10000
[root@itdragon bin]# ./redis-server redis.conf
[root@itdragon bin]# ./redis-cli -h 127.0.0.1 -p 6379
127.0.0.1:6379> keys *
(empty list or set)
127.0.0.1:6379> set key1 value1
OK
127.0.0.1:6379> set key2 value2
OK
127.0.0.1:6379> set key3 value3
OK
127.0.0.1:6379> set key4 value4
OK
127.0.0.1:6379> set key5 value5
OK
127.0.0.1:6379> set key6 value6
OK
127.0.0.1:6379> SHUTDOWN
not connected> QUIT
[root@itdragon bin]# cp dump.rdb dump_bk.rdb
[root@itdragon bin]# ./redis-server redis.conf
[root@itdragon bin]# ./redis-cli -h 127.0.0.1 -p 6379
127.0.0.1:6379> FLUSHALL 
OK
127.0.0.1:6379> keys *
(empty list or set)
127.0.0.1:6379> SHUTDOWN
not connected> QUIT
[root@itdragon bin]# cp dump_bk.rdb  dump.rdb
cp: overwrite `dump.rdb'? y
[root@itdragon bin]# ./redis-server redis.conf
[root@itdragon bin]# ./redis-cli -h 127.0.0.1 -p 6379
127.0.0.1:6379> keys *
1) "key5"
2) "key1"
3) "key3"
4) "key4"
5) "key6"
6) "key2"

第一步:vim 修改持久化配置時間,120秒內修改5次則持久化一次。 第二步:重啓服務使配置生效。 第三步:分別set 5個key,過兩分鐘後,在bin的當前目錄下會自動生產一個dump.rdb文件。(set key6 是爲了驗證shutdown有觸發RDB快照的做用) 第四步:將當前的dump.rdb 備份一份(模擬線上工做)。 第五步:執行FLUSHALL命令清空數據庫數據(模擬數據丟失)。 第六步:重啓Redis服務,恢復數據.....咦????( ′◔ ‸◔`)。數據是空的????這是由於FLUSHALL也有觸發RDB快照的功能。 第七步:將備份的 dump_bk.rdb 替換 dump.rdb 而後從新Redis。

PS:SHUTDOWN 和 FLUSHALL 命令都會觸發RDB快照,這是一個坑,請你們注意。

其餘命令

  • keys * 匹配數據庫中全部 key
  • save 阻塞觸發RDB快照,使其備份數據
  • FLUSHALL 清空整個 Redis 服務器的數據(幾乎不用)
  • SHUTDOWN 關機走人(不多用)

二、AOF 詳解

AOF :Redis 默認不開啓。它的出現是爲了彌補RDB的不足(數據的不一致性),因此它採用日誌的形式來記錄每一個寫操做,並追加到文件中。Redis 重啓的會根據日誌文件的內容將寫指令從前到後執行一次以完成數據的恢復工做。

一、從配置文件瞭解AOF

打開 redis.conf 文件,找到 APPEND ONLY MODE 對應內容 1.1 redis 默認關閉,開啓須要手動把no改成yes

appendonly yes

1.2 指定本地數據庫文件名,默認值爲 appendonly.aof

appendfilename "appendonly.aof"

1.3 指定更新日誌條件

# appendfsync always
appendfsync everysec
# appendfsync no

解說: always:同步持久化,每次發生數據變化會馬上寫入到磁盤中。性能較差當數據完整性比較好(慢,安全) everysec:出廠默認推薦,每秒異步記錄一次(默認值) no:不一樣步

1.4 配置重寫觸發機制

auto-aof-rewrite-percentage 100
auto-aof-rewrite-min-size 64mb

解說:當AOF文件大小是上次rewrite後大小的一倍且文件大於64M時觸發。通常都設置爲3G,64M過小了。

二、觸發AOF快照

根據配置文件觸發,能夠是每次執行觸發,能夠是每秒觸發,能夠不一樣步。

三、根據AOF文件恢復數據

正常狀況下,將appendonly.aof 文件拷貝到redis的安裝目錄的bin目錄下,重啓redis服務便可。但在實際開發中,可能由於某些緣由致使appendonly.aof 文件格式異常,從而致使數據還原失敗,能夠經過命令redis-check-aof --fix appendonly.aof 進行修復 。從下面的操做演示中體會。

四、AOF的重寫機制

前面也說到了,AOF的工做原理是將寫操做追加到文件中,文件的冗餘內容會愈來愈多。因此聰明的 Redis 新增了重寫機制。當AOF文件的大小超過所設定的閾值時,Redis就會對AOF文件的內容壓縮。

重寫的原理:Redis 會fork出一條新進程,讀取內存中的數據,並從新寫到一個臨時文件中。並無讀取舊文件(你都那麼大了,我還去讀你??? o(゚Д゚)っ傻啊!)。最後替換舊的aof文件。

觸發機制:當AOF文件大小是上次rewrite後大小的一倍且文件大於64M時觸發。這裏的「一倍」和「64M」 能夠經過配置文件修改。

五、AOF 的優缺點

優勢:數據的完整性和一致性更高 缺點:由於AOF記錄的內容多,文件會愈來愈大,數據恢復也會愈來愈慢。

操做演示

[root@itdragon bin]# vim appendonly.aof
appendonly yes
[root@itdragon bin]# ./redis-server redis.conf
[root@itdragon bin]# ./redis-cli -h 127.0.0.1 -p 6379
127.0.0.1:6379> keys *
(empty list or set)
127.0.0.1:6379> set keyAOf valueAof
OK
127.0.0.1:6379> FLUSHALL 
OK
127.0.0.1:6379> SHUTDOWN
not connected> QUIT
[root@itdragon bin]# ./redis-server redis.conf
[root@itdragon bin]# ./redis-cli -h 127.0.0.1 -p 6379
127.0.0.1:6379> keys *
1) "keyAOf"
127.0.0.1:6379> SHUTDOWN
not connected> QUIT
[root@itdragon bin]# vim appendonly.aof
fjewofjwojfoewifjowejfwf
[root@itdragon bin]# ./redis-server redis.conf
[root@itdragon bin]# ./redis-cli -h 127.0.0.1 -p 6379
Could not connect to Redis at 127.0.0.1:6379: Connection refused
not connected> QUIT
[root@itdragon bin]# redis-check-aof --fix appendonly.aof 
'x              3e: Expected prefix '*', got: '
AOF analyzed: size=92, ok_up_to=62, diff=30
This will shrink the AOF from 92 bytes, with 30 bytes, to 62 bytes
Continue? [y/N]: y
Successfully truncated AOF
[root@itdragon bin]# ./redis-server redis.conf
[root@itdragon bin]# ./redis-cli -h 127.0.0.1 -p 6379
127.0.0.1:6379> keys *
1) "keyAOf"

第一步:修改配置文件,開啓AOF持久化配置。 第二步:重啓Redis服務,並進入Redis 自帶的客戶端中。 第三步:保存值,而後模擬數據丟失,關閉Redis服務。 第四步:重啓服務,發現數據恢復了。(額外提一點:有教程顯示FLUSHALL 命令會被寫入AOF文件中,致使數據恢復失敗。我安裝的是redis-4.0.2沒有遇到這個問題)。 第五步:修改appendonly.aof,模擬文件異常狀況。 第六步:重啓 Redis 服務失敗。這同時也說明了,RDB和AOF能夠同時存在,且優先加載AOF文件。 第七步:校驗appendonly.aof 文件。重啓Redis 服務後正常。

補充點:aof 的校驗是經過 redis-check-aof 文件,那麼rdb 的校驗是否是能夠經過 redis-check-rdb 文件呢???

小結

  • Redis 默認開啓RDB持久化方式,在指定的時間間隔內,執行指定次數的寫操做,則將內存中的數據寫入到磁盤中。

  • RDB 持久化適合大規模的數據恢復但它的數據一致性和完整性較差。

  • Redis 須要手動開啓AOF持久化方式,默認是每秒將寫操做日誌追加到AOF文件中。

  • AOF 的數據完整性比RDB高,但記錄內容多了,會影響數據恢復的效率。

  • Redis 針對 AOF文件大的問題,提供重寫的瘦身機制。

  • 若只打算用Redis 作緩存,能夠關閉持久化。

  • 若打算使用Redis 的持久化。建議RDB和AOF都開啓。其實RDB更適合作數據的備份,留一後手。AOF出問題了,還有RDB。

  • Redis高頻面試題33道

3、Redis的四種模式

一、單機模式

這個最簡單,一看就懂。

就是安裝一個redis,啓動起來,業務調用便可。具體安裝步驟和啓動步驟就不贅述了,網上隨便搜一下就有了。

單機在不少場景也是有使用的,例如在一個並不是必須保證高可用的狀況下。

咳咳咳,其實咱們的服務使用的就是redis單機模式,因此來了就讓我改成哨兵模式。

說說單機的優缺點吧。

優勢:

  • 部署簡單,0成本。
  • 成本低,沒有備用節點,不須要其餘的開支。
  • 高性能,單機不須要同步數據,數據自然一致性。

缺點:

  • 可靠性保證不是很好,單節點有宕機的風險。
  • 單機高性能受限於CPU的處理能力,redis是單線程的。

單機模式選擇須要根據本身的業務場景去選擇,若是須要很高的性能、可靠性,單機就不太合適了。

二、主從複製

主從複製,是指將一臺Redis服務器的數據,複製到其餘的Redis服務器。

前者稱爲主節點(master),後者稱爲從節點(slave);數據的複製是單向的,只能由主節點到從節點。

主從模式配置很簡單,只須要在從節點配置主節點的ip和端口號便可。

slaveof <masterip> <masterport>
# 例如
# slaveof 192.168.1.214 6379

啓動主從節點的全部服務,查看日誌便可以看到主從節點之間的服務鏈接。

從上面很容易就想到一個問題,既然主從複製,意味着master和slave的數據都是同樣的,有數據冗餘問題。

在程序設計上,爲了高可用性和高性能,是容許有冗餘存在的。這點但願你們在設計系統的時候要考慮進去,不用爲公司節省這一點資源。

對於追求極用戶體驗的產品,是絕對不容許有宕機存在的。

主從模式在不少系統設計時都會考慮,一個master掛在多個slave節點,當master服務宕機,會選舉產生一個新的master節點,從而保證服務的高可用性。

主從模式的優勢:

  • 一旦 主節點宕機,從節點做爲主節點的備份能夠隨時頂上來。

  • 擴展主節點的讀能力,分擔主節點讀壓力。

  • 高可用基石:除了上述做用之外,主從複製仍是哨兵模式和集羣模式可以實施的基礎,所以說主從複製是Redis高可用的基石。

也有相應的缺點,好比我剛提到的數據冗餘問題:

  • 一旦主節點宕機,從節點晉升成主節點,同時須要修改應用方的主節點地址,還須要命令全部從節點去複製新的主節點,整個過程須要人工干預。
  • 主節點的 寫能力受到單機的限制。
  • 主節點的存儲能力受到單機的限制。

三、哨兵模式

剛剛提到了,主從模式,當主節點宕機以後,從節點是能夠做爲主節點頂上來,繼續提供服務的。

可是有一個問題,主節點的IP已經變更了,此時應用服務仍是拿着主節點的地址去訪問,這...

因而,在Redis 2.8版本開始引入,就有了哨兵這個概念。

複製的基礎上,哨兵實現了自動化的故障恢復。

如圖,哨兵節點由兩部分組成,哨兵節點和數據節點:

  • 哨兵節點:哨兵系統由一個或多個哨兵節點組成,哨兵節點是特殊的redis節點,不存儲數據。
  • 數據節點:主節點和從節點都是數據節點。

訪問redis集羣的數據都是經過哨兵集羣的,哨兵監控整個redis集羣。

一旦發現redis集羣出現了問題,好比剛剛說的主節點掛了,從節點會頂上來。可是主節點地址變了,這時候應用服務無感知,也不用更改訪問地址,由於哨兵纔是和應用服務作交互的。

Sentinel 很好的解決了故障轉移,在高可用方面又上升了一個臺階,固然Sentinel還有其餘功能。

好比 主節點存活檢測主從運行狀況檢測主從切換

Redis的Sentinel最小配置是 一主一從

說下哨兵模式監控的原理

每一個Sentinel以 每秒鐘 一次的頻率,向它全部的 主服務器從服務器 以及其餘Sentinel實例 發送一個PING 命令。

image

若是一個 實例(instance)距離最後一次有效回覆 PING命令的時間超過 down-after-milliseconds 所指定的值,那麼這個實例會被 Sentinel標記爲 主觀下線

若是一個 主服務器 被標記爲 主觀下線,那麼正在 監視 這個 主服務器 的全部 Sentinel 節點,要以 每秒一次 的頻率確認 該主服務器是否的確進入了 主觀下線 狀態。

若是一個 主服務器 被標記爲 主觀下線,而且有 足夠數量 的 Sentinel(至少要達到配置文件指定的數量)在指定的 時間範圍 內贊成這一判斷,那麼這個該主服務器被標記爲 客觀下線

在通常狀況下, 每一個 Sentinel 會以每 10秒一次的頻率,向它已知的全部 主服務器 和 從服務器 發送 INFO 命令。

當一個 主服務器 被 Sentinel標記爲 客觀下線 時,Sentinel 向 下線主服務器 的全部 從服務器 發送 INFO 命令的頻率,會從10秒一次改成 每秒一次。

Sentinel和其餘 Sentinel 協商 主節點 的狀態,若是 主節點處於 SDOWN`狀態,則投票自動選出新的主節點。將剩餘的 從節點 指向 新的主節點 進行 數據複製

當沒有足夠數量的 Sentinel 贊成 主服務器 下線時, 主服務器 的 客觀下線狀態 就會被移除。當 主服務器 從新向 Sentinel的PING命令返回 有效回覆 時,主服務器 的 主觀下線狀態 就會被移除。

哨兵模式的優缺點

優勢:

  • 哨兵模式是基於主從模式的,全部主從的優勢,哨兵模式都具備。
  • 主從能夠自動切換,系統更健壯,可用性更高。
  • Sentinel 會不斷的檢查 主服務器 和 從服務器 是否正常運行。當被監控的某個 Redis 服務器出現問題,Sentinel 經過API腳本向管理員或者其餘的應用程序發送通知。

缺點:

  • Redis較難支持在線擴容,對於集羣,容量達到上限時在線擴容會變得很複雜。

個人任務

我部署的redis服務就如上圖所示,三個哨兵節點,三個主從複製節點。

使用java的jedis去訪問個人redis服務,下面來一段簡單的演示代碼(並不是工程裏面的代碼):

public static void testSentinel() throws Exception {
     //mastername從配置中獲取或者環境變量,這裏爲了演示
         String masterName = "master";
         Set<String> sentinels = new HashSet<>();
     // sentinel的IP通常會從配置文件獲取或者環境變量,這裏爲了演示
         sentinels.add("192.168.200,213:26379");
         sentinels.add("192.168.200.214:26380");
         sentinels.add("192.168.200.215:26381");
 
     //初始化過程作了不少工做
         JedisSentinelPool pool = new JedisSentinelPool(masterName, sentinels); 
     //獲取到redis的client
         Jedis jedis = pool.getResource();
     //寫值到redis
         jedis.set("key1", "value1");
     //讀取數據
     jedis.get("key1");
}

具體部署的配置文件這裏太長了,須要的朋友能夠公衆號後臺回覆【redis配置】獲取。

聽起來是入職次日就部署了任務感受很難的樣子。

其實如今看來是個so easy的任務,申請一個redis集羣,本身配置下。在把工程裏面使用到redis的地方改一下,以前使用的是一個兩個單機節點。

幹完,收工。

雖然領導的任務完成了,但並不意味着學習redis的路結束了。愛學習的龍叔,繼續研究了下redis的集羣模式。

四、集羣模式

主從不能解決故障自動恢復問題,哨兵已經能夠解決故障自動恢復了,那到底爲啥還要集羣模式呢?

主從和哨兵都還有另一些問題沒有解決,單個節點的存儲能力是有上限,訪問能力是有上限的。

Redis Cluster 集羣模式具備 高可用可擴展性分佈式容錯 等特性。

Cluster 集羣模式的原理

經過數據分片的方式來進行數據共享問題,同時提供數據複製和故障轉移功能。

以前的兩種模式數據都是在一個節點上的,單個節點存儲是存在上限的。集羣模式就是把數據進行分片存儲,當一個分片數據達到上限的時候,就分紅多個分片。

數據分片怎麼分?

集羣的鍵空間被分割爲16384個slots(即hash槽),經過hash的方式將數據分到不一樣的分片上的。

HASH_SLOT = CRC16(key) & 16384

CRC16是一種循環校驗算法,這裏不是咱們研究的重點,有興趣能夠看看。

這裏用了位運算獲得取模結果,位運算的速度高於取模運算。

有一個很重要的問題,爲何是分割爲16384個槽?這個問題可能會被面試官隨口一問

數據分片以後怎麼查,怎麼寫?

讀請求分配給slave節點,寫請求分配給master,數據同步從master到slave節點。

讀寫分離提升併發能力,增長高性能。

如何作到水平擴展?

master節點能夠作擴充,數據遷移redis內部自動完成。

當你新增一個master節點,須要作數據遷移,redis服務不須要下線。

舉個栗子:上面的有三個master節點,意味着redis的槽被分爲三個段,假設三段分別是0~7000,7001~12000、12001~16383。

如今由於業務須要新增了一個master節點,四個節點共同佔有16384個槽。

槽須要從新分配,數據也須要從新遷移,可是服務不須要下線。

redis集羣的從新分片由redis內部的管理軟件redis-trib負責執行。redis提供了進行從新分片的全部命令,redis-trib經過向節點發送命令來進行從新分片。

如何作故障轉移?

假如途中紅色的節點故障了,此時master3下面的從節點會經過 選舉 產生一個主節點。替換原來的故障節點。

此過程和哨兵模式的故障轉移是同樣的。

4、Redis穿透、雪崩和失效

一、redis緩存穿透

理解

  • 重在穿透吧,也就是訪問透過redis直接通過mysql,一般是一個不存在的key,在數據庫查詢爲null。每次請求落在數據庫、而且高併發。數據庫扛不住會掛掉。

解決方案

  • 能夠將查到的null設成該key的緩存對象。
  • 固然,也能夠根據明顯錯誤的key在邏輯層就就行驗證
  • 同時,你也能夠分析用戶行爲,是否爲故意請求或者爬蟲、攻擊者。針對用戶訪問作限制。
  • 其餘等等,好比用布隆過濾器(超大型hashmap)先過濾。

二、redis緩存雪崩

理解

  • 雪崩,就是某東西蜂擁而至的意思,像雪崩同樣。在這裏,就是redis緩存集體大規模集體失效,在高併發狀況下忽然使得key大規模訪問mysql,使得數據庫崩掉。能夠想象下國家人口老年化。之後那天人集中在70-80歲,就沒人幹活了。國家勞動力就形成壓力。

解決方案

  • 一般的解決方案是將key的過時時間後面加上一個隨機數,讓key均勻的失效。
  • 考慮用隊列或者鎖讓程序執行在壓力範圍以內,固然這種方案可能會影響併發量。
  • 熱點數據能夠考慮不失效

三、redis緩存擊穿

理解

緩存擊穿,是指一個key很是熱點,在不停的扛着大併發,大併發集中對這一個點進行訪問,當這個key在失效的瞬間,持續的大併發就穿破緩存,直接請求數據庫,好像蠻力擊穿同樣。

  • 擊穿和穿透不一樣,穿透的意思是想法繞過redis去使得數據庫崩掉。而擊穿你能夠理解爲正面剛擊穿,這種一般爲大量併發對一個key進行大規模的讀寫操做。這個key在緩存失效期間大量請求數據庫,對數據庫形成太大壓力使得數據庫崩掉。就好比在秒殺場景下10000塊錢的mac和100塊的mac這個100塊的那個訂單確定會被搶到爆,不斷的請求(固然具體秒殺有本身處理方式這裏只是舉個例子)。因此緩存擊穿就是針對某個經常使用key大量請求致使數據庫崩潰。

解決方案

  • 可使用互斥鎖避免大量請求同時落到db。
  • 布隆過濾器,判斷某個容器是否在集合中
  • 能夠將緩存設置永不過時(適合部分狀況)
  • 作好熔斷、降級,防止系統崩潰。

5、布隆過濾器

一、布隆過濾器使用場景

  好比有以下幾個需求:

  ①、本來有10億個號碼,如今又來了10萬個號碼,要快速準確判斷這10萬個號碼是否在10億個號碼庫中?

  解決辦法一:將10億個號碼存入數據庫中,進行數據庫查詢,準確性有了,可是速度會比較慢。

  解決辦法二:將10億號碼放入內存中,好比Redis緩存中,這裏咱們算一下佔用內存大小:10億*8字節=8GB,經過內存查詢,準確性和速度都有了,可是大約8gb的內存空間,挺浪費內存空間的。

  ②、接觸過爬蟲的,應該有這麼一個需求,須要爬蟲的網站千千萬萬,對於一個新的網站url,咱們如何判斷這個url咱們是否已經爬過了?

  解決辦法仍是上面的兩種,很顯然,都不太好。

  ③、同理還有垃圾郵箱的過濾。

  那麼對於相似這種,大數據量集合,如何準確快速的判斷某個數據是否在大數據量集合中,而且不佔用內存,布隆過濾器應運而生了。

二、布隆過濾器簡介

  帶着上面的幾個疑問,咱們來看看到底什麼是布隆過濾器。

  布隆過濾器:一種數據結構,是由一串很長的二進制向量組成,能夠將其當作一個二進制數組。既然是二進制,那麼裏面存放的不是0,就是1,可是初始默認值都是0。

  以下所示:

  ①、添加數據

  介紹概念的時候,咱們說能夠將布隆過濾器當作一個容器,那麼如何向布隆過濾器中添加一個數據呢?

  以下圖所示:當要向布隆過濾器中添加一個元素key時,咱們經過多個hash函數,算出一個值,而後將這個值所在的方格置爲1。

  好比,下圖hash1(key)=1,那麼在第2個格子將0變爲1(數組是從0開始計數的),hash2(key)=7,那麼將第8個格子置位1,依次類推。

  ②、判斷數據是否存在?

  知道了如何向布隆過濾器中添加一個數據,那麼新來一個數據,咱們如何判斷其是否存在於這個布隆過濾器中呢?

  很簡單,咱們只須要將這個新的數據經過上面自定義的幾個哈希函數,分別算出各個值,而後看其對應的地方是否都是1,若是存在一個不是1的狀況,那麼咱們能夠說,該新數據必定不存在於這個布隆過濾器中。

  反過來講,若是經過哈希函數算出來的值,對應的地方都是1,那麼咱們可以確定的得出:這個數據必定存在於這個布隆過濾器中嗎?

  答案是否認的,由於多個不一樣的數據經過hash函數算出來的結果是會有重複的,因此會存在某個位置是別的數據經過hash函數置爲的1。

  咱們能夠獲得一個結論:布隆過濾器能夠判斷某個數據必定不存在,可是沒法判斷必定存在

  ③、布隆過濾器優缺點

  優勢:優勢很明顯,二進制組成的數組,佔用內存極少,而且插入和查詢速度都足夠快。

  缺點:隨着數據的增長,誤判率會增長;還有沒法判斷數據必定存在;另外還有一個重要缺點,沒法刪除數據。

三、Redis實現布隆過濾器

①、bitmaps

  咱們知道計算機是以二進制位做爲底層存儲的基礎單位,一個字節等於8位。

  好比「big」字符串是由三個字符組成的,這三個字符對應的ASCII碼分爲是9八、10五、103,對應的二進制存儲以下:

  

  在Redis中,Bitmaps 提供了一套命令用來操做相似上面字符串中的每個位。

  1、設置值

setbit key offset value

 

   咱們知道"b"的二進制表示爲0110 0010,咱們將第7位(從0開始)設置爲1,那0110 0011 表示的就是字符「c」,因此最後的字符 「big」變成了「cig」。

  2、獲取值

gitbit key offset

   3、獲取位圖指定範圍值爲1的個數

bitcount key [start end]

  若是不指定,那就是獲取所有值爲1的個數。

  注意:start和end指定的是字節的個數,而不是位數組下標。

②、Redisson

  Redis 實現布隆過濾器的底層就是經過 bitmap 這種數據結構,至於如何實現,這裏就不重複造輪子了,介紹業界比較好用的一個客戶端工具——Redisson。

  Redisson 是用於在 Java 程序中操做 Redis 的庫,利用Redisson 咱們能夠在程序中輕鬆地使用 Redis。

  下面咱們就經過 Redisson 來構造布隆過濾器。

package com.ys.rediscluster.bloomfilter.redisson;

import org.redisson.Redisson;
import org.redisson.api.RBloomFilter;
import org.redisson.api.RedissonClient;
import org.redisson.config.Config;

public class RedissonBloomFilter {

    public static void main(String[] args) {
        Config config = new Config();
        config.useSingleServer().setAddress("redis://192.168.14.104:6379");
        config.useSingleServer().setPassword("123");
        //構造Redisson
        RedissonClient redisson = Redisson.create(config);

        RBloomFilter<String> bloomFilter = redisson.getBloomFilter("phoneList");
        //初始化布隆過濾器:預計元素爲100000000L,偏差率爲3%
        bloomFilter.tryInit(100000000L,0.03);
        //將號碼10086插入到布隆過濾器中
        bloomFilter.add("10086");

        //判斷下面號碼是否在布隆過濾器中
        System.out.println(bloomFilter.contains("123456"));//false
        System.out.println(bloomFilter.contains("10086"));//true
    }
}

這是單節點的Redis實現方式,若是數據量比較大,指望的偏差率又很低,那單節點所提供的內存是沒法知足的,這時候可使用分佈式布隆過濾器,一樣也能夠用 Redisson 來實現,這裏我就不作代碼演示了,你們有興趣能夠試試。

四、guava 工具

  最後提一下不用Redis如何來實現布隆過濾器。

  guava 工具包相信你們都用過,這是谷歌公司提供的,裏面也提供了布隆過濾器的實現。

package com.ys.rediscluster.bloomfilter;

import com.google.common.base.Charsets;
import com.google.common.hash.BloomFilter;
import com.google.common.hash.Funnel;
import com.google.common.hash.Funnels;

public class GuavaBloomFilter {
    public static void main(String[] args) {
        BloomFilter<String> bloomFilter = BloomFilter.create(Funnels.stringFunnel(Charsets.UTF_8),100000,0.01);

        bloomFilter.put("10086");

        System.out.println(bloomFilter.mightContain("123456"));
        System.out.println(bloomFilter.mightContain("10086"));
    }
}

本文寫到這了,關於Redis平時能用到的地方文章裏基本都寫到了,但願對你學習Redis有所幫助。

給兄弟們準備的資料都放在這裏了

end

相關文章
相關標籤/搜索