從零開始學Redis之半步神遊

前言

文本已收錄至我的GitHub倉庫,歡迎Star:https://github.com/bin392328206/six-finger
種一棵樹最好的時間是十年前,其次是現在
我知道很多人不玩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", key, 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,就這麼多,哎,一把辛酸一把淚。下一章是最後一章了,看看怎麼寫吧。

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

日常求贊

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

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

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

發佈了47 篇原創文章 · 獲贊 9 · 訪問量 6235
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章