1. 程式人生 > 程式設計 >?從零開始學Redis之半步神遊

?從零開始學Redis之半步神遊

前言

文字已收錄至我的GitHub倉庫,歡迎Star:github.com/bin39232820…
種一棵樹最好的時間是十年前,其次是現在
我知道很多人不玩qq了,但是懷舊一下,歡迎加入六脈神劍Java菜鳥學習群,群聊號碼:549684836 鼓勵大家在技術的路上寫部落格

絮叨

半步神遊,神遊之下,天下無敵。一夢一遊 便是天下。
Redis前面幾篇的文章連結:
?從零開始學Redis之金剛凡境
?從零開始學Redis之自在地境
?從零開始學Redis之逍遙天境
上一篇的逍遙天境 講的是Redis的記憶體淘汰策略 和持久化方式。那這半步神遊就是帶你們遨遊Redis的主從HA,哨兵,和Lua指令碼

Redis主從和哨兵模式

Redis 主從搭建(有興趣的小夥伴自己用虛擬機器器搭一個玩玩)

1、環境說明

主機名稱 IP地址 redis版本和角色說明
redis-master 192.168.56.11 redis 5.0.3(主)
redis-slave01 192.168.56.12 redis 5.0.3(從)
redis-slave02 192.168.56.13 redis 5.0.3(從)

2、修改主從的redis配置檔案

[root@redis-master ~]# grep -Ev "^$|#" /usr/local/redis/redis.conf 
bind
192.168.56.11 protected-mode yes port 6379 daemonize yes pidfile /var/run/redis_6379.pid logfile "/var/log/redis.log" dir /var/redis/ [root@redis-slave01 ~]# grep -Ev "^$|#" /usr/local/redis/redis.conf bind 192.168.56.12 protected-mode yes port 6379 daemonize yes pidfile /var/run/redis_6379.pid logfile "/var/log/redis.log"
dir /var/redis/ replicaof 192.168.56.11 6379 #配置為master的從,如果master上有密碼配置,還需要增加下面一項密碼配置 masterauth 123456 #配置主的密碼 [root@redis-slave02 ~]# grep -Ev "^$|#" /usr/local/redis/redis.conf bind 192.168.56.13 protected-mode yes port 6379 daemonize yes pidfile /var/run/redis_6379.pid logfile "/var/log/redis.log" dir /var/redis/ replicaof 192.168.56.11 6379 #配置為master的從 masterauth 123456 #配置主的密碼 複製程式碼

3、啟動主從redis 這裡需要注意的是:redis主從和mysql主從不一樣,redis主從不用事先同步資料,它會自動同步過去

[root@redis-master ~]# systemctl start redis
[root@redis-slave01 ~]# systemctl start redis
[root@redis-slave02 ~]# systemctl start redis
[root@redis-master ~]# netstat -tulnp |grep redis
tcp        0      0 192.168.56.11:6379      0.0.0.0:*               LISTEN      1295/redis-server 1 
[root@redis-slave01 ~]# netstat -tulnp |grep redis
tcp        0      0 192.168.56.12:6379      0.0.0.0:*               LISTEN      1625/redis-server 1 
[root@redis-slave02 ~]# netstat -tulnp |grep redis
tcp        0      0 192.168.56.13:6379      0.0.0.0:*               LISTEN      1628/redis-server 1 
複製程式碼

4、資料同步驗證

[root@redis-master ~]# redis-cli -h 192.168.56.11   #主上寫入資料
192.168.56.11:6379> KEYS *
(empty list or set)
192.168.56.11:6379> set k1 123
OK
192.168.56.11:6379> set k2 456
OK

[root@redis-slave01 ~]# redis-cli -h 192.168.56.12  #slave01上檢視是否資料同步
192.168.56.12:6379> KEYS *
1) "k2"
2) "k1"
192.168.56.12:6379> get k1
"123"
192.168.56.12:6379> get k2
"456"

[root@redis-slave02 ~]# redis-cli -h 192.168.56.13  #slave02上檢視是否資料同步
192.168.56.13:6379> KEYS *
1) "k2"
2) "k1"
192.168.56.13:6379> get k1
"123"
192.168.56.13:6379> get k2
"456"
複製程式碼

主從架構

主從架構的特點

  • 主伺服器負責接收寫請求
  • 從伺服器負責接收讀請求
  • 從伺服器的資料由主伺服器複製過去。主從伺服器的資料是一致的

主從架構的好處

  • 讀寫分離(主伺服器負責寫,從伺服器負責讀)
  • 高可用(某一臺從伺服器掛了,其他從伺服器還能繼續接收請求,不影響服務)
  • 處理更多的併發量(每臺從伺服器都可以接收讀請求,讀QPS就上去了)

主從同步

主從架構的特點之一:主伺服器和從伺服器的資料是一致的。
主從同步的2種情況

  • 同步(sync)
    • 將從伺服器的資料庫狀態更新至主伺服器的資料庫狀態
  • 命令傳播(command propagate)
    • 主伺服器的資料庫狀態被修改,導致主從伺服器的資料庫狀態不一致,讓主從伺服器的資料庫狀態重新回到一致狀態。

完整的同步

  • 從伺服器向主伺服器傳送PSYNC命令
  • 收到PSYNC命令的主伺服器執行BGSAVE命令,在後臺生成一個RDB檔案。並用一個緩衝區來記錄從現在開始執行的所有寫命令。
  • 當主伺服器的BGSAVE命令執行完後,將生成的RDB檔案傳送給從伺服器,從伺服器接收和載入RBD檔案。將自己的資料庫狀態更新至與主伺服器執行BGSAVE命令時的狀態。
  • 主伺服器將所有緩衝區的寫命令傳送給從伺服器,從伺服器執行這些寫命令,達到資料最終一致性。

部分重同步

  • 主從伺服器的複製偏移量 主伺服器每次傳播N個位元組,就將自己的複製偏移量加上N
  • 從伺服器每次收到主伺服器的N個位元組,就將自己的複製偏移量加上N
  • 通過對比主從複製的偏移量,就很容易知道主從伺服器的資料是否處於一致性的狀態!

Redis HA 方案

HA(High Available,高可用性群集)機集群系統簡稱,是保證業務連續性的有效解決方案,一般有兩個或兩個以上的節點,且分為活動節點及備用節點。通常把正在執 行業務的稱為活動節點,而作為活動節點的一個備份的則稱為備用節點。當活動節點出現問題,導致正在執行的業務(任務)不能正常執行時,備用節點此時就會偵測到,並立即接續活動節點來執行業務。從而實現業務的不中斷或短暫中斷。

Redis 一般以主/從方式部署(這裡討論的應用從例項主要用於備份,主例項提供讀寫)該方式要實現 HA 主要有如下幾種方案:

  • keepalived: 通過 keepalived 的虛擬 IP,提供主從的統一訪問,在主出現問題時, 通過 keepalived 執行指令碼將從提升為主,待主恢復後先同步後自動變為主,該方案的好處是主從切換後,應用程式不需要知道(因為訪問的虛擬 IP 不變),壞處是引入 keepalived 增加部署複雜性,在有些情況下會導致資料丟失
  • zookeeper: 通過 zookeeper 來監控主從例項, 維護最新有效的 IP, 應用通過 zookeeper 取得 IP,對 Redis 進行訪問,該方案需要編寫大量的監控程式碼
  • sentinel: 通過 Sentinel 監控主從例項,自動進行故障恢復,該方案有個缺陷:因為主從例項地址( IP & PORT )是不同的,當故障發生進行主從切換後,應用程式無法知道新地址,故在 Jedis2.2.2 中新增了對 Sentinel 的支援,應用通過 redis.clients.jedis.JedisSentinelPool.getResource() 取得的 Jedis 例項會及時更新到新的主例項地址

Redis Sentinel

Redis Sentinel是官方推薦的高可用性解決方案。Sentinel是一個管理多個Redis例項的工具,它可以實現對Redis的監控、通知、自動故障轉移。

Sentinel的主要功能包括主節點存活檢測、主從執行情況檢測、自動故障轉移(failover)、主從切換。Redis的Sentinel最小配置是一主一從。 Redis的Sentinel系統可以用來管理多個Redis伺服器,該系統可以執行以下四個任務:

  • 監控: Sentinel會不斷的檢查主伺服器和從伺服器是否正常執行。
  • 通知: 當被監控的某個Redis伺服器出現問題,Sentinel通過API指令碼向管理員或者其他的應用程式傳送通知。
  • 自動故障轉移: 當主節點不能正常工作時,Sentinel會開始一次自動的故障轉移操作,它會將與失效主節點是主從關係的其中一個從節點升級為新的主節點, 並且將其他的從節點指向新的主節點。
  • 配置提供者: 在Redis Sentinel模式下,客戶端應用在初始化時連線的是Sentinel節點集合,從中獲取主節點的資訊。

Redis Sentinel的工作流程

如圖所示:由一個或多個Sentinel例項組成的Sentinel系統可以監視任意多個主伺服器,以及所有從伺服器,並在被監視的主伺服器進入下線狀態時,自動將下線主伺服器屬下的某個從伺服器升級為新的主伺服器,然後由新的主伺服器代替已下線的主伺服器繼續處理命令請求

Sentinel負責監控叢集中的所有主、從Redis,當發現主故障時,Sentinel會在所有的從中選一個成為新的主。並且會把其餘的從變為新主的從。同時那臺有問題的舊主也會變為新主的從,也就是說當舊的主即使恢復時,並不會恢復原來的主身份,而是作為新主的一個從。

在Redis高可用架構中,Sentinel往往不是隻有一個,而是有3個或者以上。目的是為了讓其更加可靠,畢竟主和從切換角色這個過程還是蠻複雜的。這個是所有分散式系統都要碰到的問題一個是崩潰恢復 一個是資料同步 下面我們就來聊聊

Redis HA方案的 崩潰恢復 和資料同步

崩潰恢復

這個是所有分散式系統的問題 什麼就是崩潰恢復呢 就我們目前的方案來說 我們是用sentinel 來保證redis的高可用 同時 sentinel 本身自己也做了HA 假設一主倆從的情況下 如果主節點掛了 怎麼辦,這就能造成單點故障 讓整個redis 叢集不可用,所以 崩潰恢復就是類似於一個Zookeeper的ZAB 演演算法 從其他節點中選舉一個主節點,具體怎麼選舉一個新的主節點,這邊就不擴充套件了,再說下去,這篇就扯不完了。

資料同步

redis的主從複製
依賴於redis依賴於RDB模式下的持久化儲存;採用複製RDB檔案的形式進行主從節點之間的資料同步
注意: 主從複製時不要開啟AOF持久化模式,因為AOF優先順序高於RDB模式

RDB檔案兩種傳輸方法
1.普通複製
將主節點已經到磁碟上的的ROB檔案,複製到從節點上
2.無盤複製
master端直接將RDB file傳到slave socket,不需要與disk進行互動
無磁碟diskless方式適合磁碟讀寫速度慢但網路頻寬非常高的環境

Redis Sentinel 搭建(可以自己試試)

1.環境說明

主機名稱 IP地址 redis版本和角色說明
redis-master 192.168.56.11:6379 redis 5.0.3(主)
redis-slave01 192.168.56.12:6379 redis 5.0.3(從)
redis-slave02 192.168.56.13:6379 redis 5.0.3(從)
redis-master 192.168.56.11:26379 Sentinel01(主)
redis-slave01 192.168.56.12:26379 Sentinel02(從)
redis-slave02 192.168.56.13:26379 Sentinel03(從)

2.部署Sentinel

# 埠
port 26379

# 是否後臺啟動
daemonize yes

# pid檔案路徑
pidfile /var/run/redis-sentinel.pid

# 日誌檔案路徑
logfile "/var/log/sentinel.log"

# 定義工作目錄
dir /tmp

# 定義Redis主的別名,IP,埠,這裡的2指的是需要至少2個Sentinel認為主Redis掛了才最終會採取下一步行為
sentinel monitor mymaster 127.0.0.1 6379 2

# 如果mymaster 30秒內沒有響應,則認為其主觀失效
sentinel down-after-milliseconds mymaster 30000

# 如果master重新選出來後,其它slave節點能同時並行從新master同步資料的臺數有多少個,顯然該值越大,所有slave節點完成同步切換的整體速度越快,但如果此時正好有人在訪問這些slave,可能造成讀取失敗,影響面會更廣。最保守的設定為1,同一時間,只能有一臺幹這件事,這樣其它slave還能繼續服務,但是所有slave全部完成快取更新同步的程式將變慢。
sentinel parallel-syncs mymaster 1

# 該引數指定一個時間段,在該時間段內沒有實現故障轉移成功,則會再一次發起故障轉移的操作,單位毫秒
sentinel failover-timeout mymaster 180000

# 不允許使用SENTINEL SET設定notification-script和client-reconfig-script。
sentinel deny-scripts-reconfig yes
複製程式碼

修改三臺Sentinel的配置檔案,如下

[root@redis-master ~]# grep -Ev "^$|#" /usr/local/redis/sentinel.conf 
port 26379
daemonize yes
pidfile "/var/run/redis-sentinel.pid"
logfile "/var/log/sentinel.log"
dir "/tmp"
sentinel monitor mymaster 192.168.56.11 6379 2
sentinel down-after-milliseconds mymaster 30000
sentinel parallel-syncs mymaster 1
sentinel failover-timeout mymaster 180000
sentinel deny-scripts-reconfig yes

[root@redis-slave01 ~]# grep -Ev "^$|#" /usr/local/redis/sentinel.conf 
port 26379
daemonize yes
pidfile "/var/run/redis-sentinel.pid"
logfile "/var/log/sentinel.log"
dir "/tmp"
sentinel monitor mymaster 192.168.56.11 6379 2
sentinel down-after-milliseconds mymaster 30000
sentinel parallel-syncs mymaster 1
sentinel failover-timeout mymaster 180000
sentinel deny-scripts-reconfig yes

[root@redis-slave02 ~]# grep -Ev "^$|#" /usr/local/redis/sentinel.conf 
port 26379
daemonize yes
pidfile "/var/run/redis-sentinel.pid"
logfile "/var/log/sentinel.log"
dir "/tmp"
sentinel monitor mymaster 192.168.56.11 6379 2
sentinel down-after-milliseconds mymaster 30000
sentinel parallel-syncs mymaster 1
sentinel failover-timeout mymaster 180000
sentinel deny-scripts-reconfig yes
複製程式碼

3.啟動Sentinel 啟動的順序:主Redis --> 從Redis --> Sentinel1/2/3

[root@redis-master ~]# redis-sentinel /usr/local/redis/sentinel.conf 
[root@redis-master ~]# ps -ef |grep redis
root      1295     1  0 14:03 ?        00:00:06 /usr/local/redis/src/redis-server 192.168.56.11:6379
root      1407     1  1 14:40 ?        00:00:00 redis-sentinel *:26379 [sentinel]
root      1412  1200  0 14:40 pts/1    00:00:00 grep --color=auto redis

[root@redis-slave01 ~]# redis-sentinel /usr/local/redis/sentinel.conf 
[root@redis-slave01 ~]# ps -ef |grep redis
root      1625     1  0 14:04 ?        00:00:06 /usr/local/redis/src/redis-server 192.168.56.12:6379
root      1715     1  1 14:41 ?        00:00:00 redis-sentinel *:26379 [sentinel]
root      1720  1574  0 14:41 pts/0    00:00:00 grep --color=auto redis

[root@redis-slave02 ~]# redis-sentinel /usr/local/redis/sentinel.conf 
[root@redis-slave02 ~]# ps -ef |grep redis
root      1628     1  0 14:07 ?        00:00:06 /usr/local/redis/src/redis-server 192.168.56.13:6379
root      1709     1  0 14:42 ?        00:00:00 redis-sentinel *:26379 [sentinel]
root      1714  1575  0 14:42 pts/0    00:00:00 grep --color=auto redis
複製程式碼

4.Sentinel操作

[root@redis-master ~]# redis-cli -p 26379   #哨兵模式檢視
127.0.0.1:26379> sentinel master mymaster   #輸出被監控的主節點的狀態資訊
 1) "name"
 2) "mymaster"
 3) "ip"
 4) "192.168.56.11"
 5) "port"
 6) "6379"
 7) "runid"
 8) "bae06cc3bc6dcbff7c2de1510df7faf1a6eb6941"
 9) "flags"
10) "master"
......
127.0.0.1:26379> sentinel slaves mymaster   #檢視mymaster的從資訊,可以看到有2個從節點
1)  1) "name"
    2) "192.168.56.12:6379"
    3) "ip"
    4) "192.168.56.12"
    5) "port"
    6) "6379"
    7) "runid"
    8) "c86027e7bdd217cb584b1bd7a6fea4ba79cf6364"
    9) "flags"
   10) "slave"
......
2)  1) "name"
    2) "192.168.56.13:6379"
    3) "ip"
    4) "192.168.56.13"
    5) "port"
    6) "6379"
    7) "runid"
    8) "61597fdb615ecf8bd7fc18e143112401ed6156ec"
    9) "flags"
   10) "slave"
......

127.0.0.1:26379> sentinel sentinels mymaster    #檢視其它sentinel資訊
1)  1) "name"
    2) "ba12e2a4023d2e9bcad282395ba6b14030920070"
    3) "ip"
    4) "192.168.56.12"
    5) "port"
    6) "26379"
    7) "runid"
    8) "ba12e2a4023d2e9bcad282395ba6b14030920070"
    9) "flags"
   10) "sentinel"
......
2)  1) "name"
    2) "14fca3f851e9e1bd3a4a0dc8a9e34bb237648455"
    3) "ip"
    4) "192.168.56.13"
    5) "port"
    6) "26379"
    7) "runid"
    8) "14fca3f851e9e1bd3a4a0dc8a9e34bb237648455"
    9) "flags"
   10) "sentinel"
複製程式碼

Redis Lua 指令碼

再這裡我想說下,為啥我們要用Lua指令碼呢? Lua指令碼的好處
redis對lua指令碼的呼叫是原子性的,所以一些特殊場景,比如像實現分散式鎖,我們可以放在lua中實現
下面我帶大家搭建一個最簡單lua指令碼demo

  • 新增依賴
<dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-data-redis</artifactId>
    </dependency>
    <dependency>
         <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
複製程式碼
  • 編寫Lua指令碼 命名為 Test.lua 放在 resources下
local key = KEYS[1]
    --- 獲取value
    local val = KEYS[2]
    --- 獲取一個引數
    local expire = ARGV[1]
    --- 如果redis找不到這個key就去插入
    if redis.call("get",key) == false then
        --- 如果插入成功,就去設定過期值
        if redis.call("set",key,val) then
            --- 由於lua指令碼接收到引數都會轉為String,所以要轉成數字型別才能比較
            if tonumber(expire) > 0 then
                --- 設定過期時間
                redis.call("expire",expire)
            end
            return true
        end
        return false
    else
        return false
    end
複製程式碼
  • 編寫配置類
@Configuration
public class LuaConfiguration {
    @Bean
    public DefaultRedisScript<Boolean> redisScript() {
        DefaultRedisScript<Boolean> redisScript = new DefaultRedisScript<>();
        redisScript.setScriptSource(new ResourceScriptSource(new ClassPathResource("Test.lua")));
        redisScript.setResultType(Boolean.class);
        return redisScript;
    }
}

複製程式碼
  • 測試
    @Test
    public void TestLua(){
        System.out.println("測試Lua開始");
        List<String> keys = Arrays.asList("testLua","hello六脈神劍");
        Boolean execute = stringRedisTemplate.execute(redisScript,keys,"10000");
        System.out.println("測試Lua結束,並在下面列印結果");
        String testLua = stringRedisTemplate.opsForValue().get("testLua");
        System.out.println("結果是:"+testLua);
    }

複製程式碼
  • 結果
測試Lua開始
2019-12-03 10:48:01.469  INFO 246868 --- [           main] io.lettuce.core.EpollProvider            : Starting without optional epoll library
2019-12-03 10:48:01.471  INFO 246868 --- [           main] io.lettuce.core.KqueueProvider           : Starting without optional kqueue library
測試Lua結束,並在下面列印結果
結果是:hello六脈神劍

複製程式碼

Redis使用Lua的好處

1.減少網路開銷:本來5次網路請求的操作,可以用一個請求完成,原先5次請求的邏輯放在redis伺服器上完成。使用指令碼,減少了網路往返時延。

2.原子操作:Redis會將整個指令碼作為一個整體執行,中間不會被其他命令插入。

3.複用:客戶端傳送的指令碼會永久儲存在Redis中,意味著其他客戶端可以複用這一指令碼而不需要使用程式碼完成同樣的邏輯。

Redis使用Lua要注意的點

1.Lua指令碼的bug特別可怕,由於Redis的單執行緒特點,一旦Lua指令碼出現不會返回(不是返回值)得問題,那麼這個指令碼就會阻塞整個redis例項。

2.Lua指令碼應該儘量短小實現關鍵步驟即可。(原因同上)

3.Lua指令碼中不應該出現常量Key,這樣會導致每次執行時都會在指令碼字典中新建一個條目,應該使用全域性變數陣列KEYS和ARGV,KEYS和ARGV的索引都從1開始

4.傳遞給lua指令碼的的鍵和引數:傳遞給lua指令碼的鍵列表應該包括可能會讀取或者寫入的所有鍵。傳入全部的鍵使得在使用各種分片或者叢集技術時,其他軟體可以在應用層檢查所有的資料是不是都在同一個分片裡面。另外叢集版redis也會對將要訪問的key進行檢查,如果不在同一個伺服器裡面,那麼redis將會返回一個錯誤。(決定使用叢集版之前應該考慮業務拆分),引數列表無所謂。。

5.lua指令碼跟單個redis命令和事務段一樣都是原子的已經進行了資料寫入的lua指令碼將無法中斷,只能使用SHUTDOWN NOSAVE殺死Redis伺服器,所以lua指令碼一定要測試好。

結尾

我擦就隨便寫了個主從和Lua,就這麼多,哎,一把辛酸一把淚。下一章是最後一章了,看看怎麼寫吧。

因為博主也是一個開發萌新 我也是一邊學一邊寫 我有個目標就是一週 二到三篇 希望能堅持個一年吧 希望各位大佬多提意見,讓我多學習,一起進步。

日常求贊

好了各位,以上就是這篇文章的全部內容了,能看到這裡的人呀,都是人才

創作不易,各位的支援和認可,就是我創作的最大動力,我們下篇文章見

六脈神劍 | 文 【原創】如果本篇部落格有任何錯誤,請批評指教,不勝感激 !