天天看點

redis高可用叢集方案

絮叨

半步神遊,神遊之下,天下無敵。一夢一遊 便是天下。

Redis前面幾篇的文章連結:

redis高可用叢集方案
從零開始學Redis之金剛凡境
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配置檔案
[[email protected] ~]# 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/

[[email protected] ~]# 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   #配置主的密碼

[[email protected] ~]# 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主從不用事先同步資料,它會自動同步過去
[[email protected] ~]# systemctl start redis
[[email protected] ~]# systemctl start redis
[[email protected] ~]# systemctl start redis
[[email protected] ~]# netstat -tulnp |grep redis
tcp        0      0 192.168.56.11:6379      0.0.0.0:*               LISTEN      1295/redis-server 1 
[[email protected] ~]# netstat -tulnp |grep redis
tcp        0      0 192.168.56.12:6379      0.0.0.0:*               LISTEN      1625/redis-server 1 
[[email protected] ~]# netstat -tulnp |grep redis
tcp        0      0 192.168.56.13:6379      0.0.0.0:*               LISTEN      1628/redis-server 1 
           
4、資料同步驗證
[[email protected] ~]# 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

[[email protected] ~]# 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"

[[email protected] ~]# 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的工作流程

redis高可用叢集方案
redis高可用叢集方案

如圖所示:由一個或多個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的配置檔案,如下

[[email protected] ~]# 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

[[email protected] ~]# 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

[[email protected] ~]# 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

[[email protected] ~]# redis-sentinel /usr/local/redis/sentinel.conf 
[[email protected] ~]# 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

[[email protected] ~]# redis-sentinel /usr/local/redis/sentinel.conf 
[[email protected] ~]# 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

[[email protected] ~]# redis-sentinel /usr/local/redis/sentinel.conf 
[[email protected]-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操作

[[email protected] ~]# 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腳本一定要測試好

繼續閱讀