1. 程式人生 > >6.redis sentinel實現高可用讀寫分離

6.redis sentinel實現高可用讀寫分離

1. redis sentinel

image

故障轉移的基本原理:

  • 多個sentinel發現並確認master有問題
  • 選舉出一個sentinel作為領導
  • 選出一個slave稱為新的master的slave
  • 通知其他的slave稱為新的master的slave
  • 通知客戶端主從變化
  • 等待老的master復活稱為新的master的slave

也支援多個master-slave結構:

image

2. 安裝與配置

  1. 配置開啟主從節點
  2. 配置開啟sentinel監控主節點(sentinel是特殊的redis)
  3. 實際應該多臺機器,但是演示方便,只用一臺機器來搭建
  4. 詳細配置節點

本地安裝的結構圖:

image

對於master:redis-7000.conf配置:

image

port 7000
daemonize yes
pidfile /usr/local/redis/data/redis-7000.pid
logfile "7000.log"
dir "/usr/local/redis/data"

對於slave:redis-7001和redis-7002配置:

image

port 7001
daemonize yes
pidfile /usr/local/redis/data/redis-7001.pid
logfile "7001.log"
dir "/usr/local/redis/data"
slaveof 127.0.0.1 7000

啟動redis服務:

redis-server
../config/redis-7000.conf

訪問7000埠的master redis:

redis-cli -p 7000 info replication

顯示他有兩個從節點:

# Replication
role:master
connected_slaves:2
slave0:ip=127.0.0.1,port=7002,state=online,offset=99550,lag=1
slave1:ip=127.0.0.1,port=7001,state=online,offset=99816,lag=0
master_repl_offset:99816
repl_backlog_active:1
repl_backlog_size:
1048576 repl_backlog_first_byte_offset:2 repl_backlog_histlen:99815

對於sentinel主要配置:

image

master sentinel config:

port 26379
daemonize yes
dir "/usr/local/redis/data"
logfile "26379.log"
sentinel monitor mymaster 127.0.0.1 7000 2
...

啟動redis sentinel:

redis-sentinel ../config/redis-sentinel-26379.conf

訪問26379 redis sentinel master:

redis-cli -p 26379 info sentinel

顯示:

# Sentinel
sentinel_masters:1
sentinel_tilt:0
sentinel_running_scripts:0
sentinel_scripts_queue_length:0
master0:name=mymaster,status=ok,address=127.0.0.1:7000,slaves=2,sentinels=3
檢視這六個程序是否都起來了:ps -ef | grep redis

注意,如果上面是配置在虛擬機器的話,需要將127.0.0.1改為虛擬機器的ip,要不然找不著。

3. 故障轉移演練

3.1 java客戶端程式

JedisSentinelPool只是一個配置中心,不需要具體連線某個redis,注意它不是代理。

private Logger logger = LoggerFactory.getLogger(AppTest.class);

@Test
public void test4(){
    //哨兵配置,我們訪問redis,就通過sentinel來訪問
    String masername = "mymaster";
    Set<String> sentinels = new HashSet<>();
    sentinels.add("10.128.24.176:26379");
    sentinels.add("10.128.24.176:26380");
    sentinels.add("10.128.24.176:26381");

    JedisSentinelPool sentinelPool = new JedisSentinelPool(masername,sentinels);

    //一個while死迴圈,每隔一秒往master塞入一個值,並且日誌列印
    while (true){
        Jedis jedis = null;
        try{
            jedis = sentinelPool.getResource();

            int index = new Random().nextInt(100000);
            String key = "k-" + index;
            String value = "v-" + index;
            jedis.set(key,value);
            logger.info("{}  value is {}",key,jedis.get(key));

            TimeUnit.MILLISECONDS.sleep(1000);
        }catch (Exception e){
            logger.error(e.getMessage(),e);
        }finally {
            if(jedis != null){
                jedis.close();
            }
        }
    }
}

maven依賴是:

<!--jedis-->
<dependency>
  <groupId>redis.clients</groupId>
  <artifactId>jedis</artifactId>
  <version>2.9.0</version>
</dependency>
<!--slf4j日誌介面-->
<dependency>
  <groupId>org.slf4j</groupId>
  <artifactId>slf4j-api</artifactId>
  <version>1.7.6</version>
</dependency>
<!--logback日誌實現-->
<dependency>
  <groupId>ch.qos.logback</groupId>
  <artifactId>logback-classic</artifactId>
  <version>1.1.1</version>
</dependency>

啟動程式,發現是正常寫入:

16:16:01.424 [main] INFO  com.njupt.swg.AppTest - k-54795  value is v-54795
16:16:02.426 [main] INFO  com.njupt.swg.AppTest - k-55630  value is v-55630
16:16:03.429 [main] INFO  com.njupt.swg.AppTest - k-70642  value is v-70642
16:16:04.430 [main] INFO  com.njupt.swg.AppTest - k-42978  value is v-42978
16:16:05.431 [main] INFO  com.njupt.swg.AppTest - k-96297  value is v-96297
16:16:06.433 [main] INFO  com.njupt.swg.AppTest - k-4220  value is v-4220
16:16:07.435 [main] INFO  com.njupt.swg.AppTest - k-34103  value is v-34103
16:16:08.436 [main] INFO  com.njupt.swg.AppTest - k-9177  value is v-9177
16:16:09.437 [main] INFO  com.njupt.swg.AppTest - k-24389  value is v-24389
16:16:10.439 [main] INFO  com.njupt.swg.AppTest - k-32325  value is v-32325
16:16:11.440 [main] INFO  com.njupt.swg.AppTest - k-68538  value is v-68538
16:16:12.441 [main] INFO  com.njupt.swg.AppTest - k-36233  value is v-36233
16:16:13.443 [main] INFO  com.njupt.swg.AppTest - k-305  value is v-305
16:16:14.444 [main] INFO  com.njupt.swg.AppTest - k-59279  value is v-59279

我們將現在的埠為7000的redis master 給kill掉

kill -9 master的pid

我們會發現:客戶端報異常,但是在大概十幾秒之後,就繼續正常塞值了。原因是服務端的哨兵機制的選舉matser需要一定的時間。

4. 三個定時任務

4.1 每10秒每個sentinel對master和slave執行Info

  • 發現slave節點
  • 確認主從關係

image

4.2 每2秒每個sentinel通過master節點的channel交換資訊(pub/sub)

  • 通過sentinel:hello進行頻道互動
  • 互動對節點的“看法”和自身資訊

image

4.3 每1秒每個sentinel對其他sentinel和redis執行ping

  • 心跳監測,失敗判定依據

image

5. 主觀下線和客觀下線

對於之前的Sentinel配置檔案中有兩條配置:

監控master redis節點,這裡是當超過兩個sentinel認為master掛了,則認為master掛了。

sentinel monitor
sentinel monitor mymaster 127.0.0.1 6379 2

這裡是每秒sentinel都回去Ping周圍的master redis,超過30秒沒有任何相應,說明其掛了。

sentinel down-after-milliseconds
sentinel down-after-milliseconds mymaster 300000

5.1 主觀下線

主觀下線:每個sentinel節點對Redis節點失敗的“偏見”

這是一種主觀下線。因為在複雜的網路環境下,這個sentinel與這個master不通,但是master與其他的sentinel都是通的呢?所以是一種“偏見”

這是依靠的第三種定時:每秒去ping一下週圍的sentinel和redis。對於slave redis,可以使用這個主觀下線,因為他不需要進行故障轉移。

5.2 客觀下線

客觀下線:所有sentinel節點對master Redis節點失敗“達成共識”(超過quorum個則統一)

這是依靠的第二種定時:每兩秒,sentinel之間進行“商量”,傳遞的訊息是:sentinel is-master-down-by-addr

對於master redis的下線,必須要達成共識才可以,因為涉及故障轉移,僅僅依靠一個sentinel判斷是不夠的。

6. 領導者選舉

原因:只有一個sentinel節點完成故障轉移

選舉:通過sentinel is-master-down-by-addr命令都希望成為領導者

  • 每個做主觀下線的sentinel節點向其他sentinel節點發送命令,要求將它設定為領導者
  • 收到命令的sentinel節點如果沒有同意通過其他semtinel節點發送的命令,那麼將同意該請求,否則拒絕
  • 如果該sentinel節點發現自己的票數已經超過sentinel集合半數並且超過quorum,那麼它將成為領導者。
  • 如果此過程中多個sentinel節點成為了領導者,那麼將等待一段時間重新進行選舉

7. 故障轉移

  • 從slave節點中選出一個“合適的”節點作為新的master節點
  • 對上述的slave節點執行“slaveof no one”命令使其成為master節點
  • 向剩餘的slave節點發送命令,讓它們成為新master節點的slave節點,複製規則和parallel-syncs引數一樣
  • 更新對原來的master節點配置為slave,並保持著對其“關注”,當恢復後命令他去複製新的master節點

那麼,如何選擇“合適”的slave節點呢?

  • 選擇slave-priority(slave節點優先順序)最高的slave節點,如果存在則返回,不存在則繼續。
  • 選擇複製偏移量對打的slave節點(複製得最完整),如果存在則返回,不存在則繼續
  • 選擇run_id最小的slave節點(最早的節點)

8. 節點下線

主節點下線:sentinel failover

從節點下線要注意讀寫分離問題。

9. 總結與思考

redis sentinel是redis高可用實現方案:故障發現、故障自動轉移、配置中心、客戶端通知。

redis sentinel從redis2.8版本才正式生產可用,之前版本不可生產用。

儘可能在不同物理機上部署redis sentinel所有節點。

redis sentinel中的sentinel節點個數應該大於等於3且最好是奇數。

redis sentinel中的資料節點和普通資料節點沒有區別。每個sentinel節點在本質上還是一個redis例項,只不過和redis資料節點不同的是,其主要作用是監控redis資料節點

客戶端初始化時連線的是sentinel節點集合,不再是具體的redis節點,但sentinel只是配置中心不是代理。

redis sentinel通過三個定時任務實現了sentinel節點對於主節點、從節點、其餘sentinel節點的監控。

redis sentinel在對節點做失敗判定時分為主觀下線和客觀下線。

看懂redis sentinel故障轉移日誌對於redis sentinel以及問題排查非常有用。

redis sentinel實現讀寫分離高可用可以依賴sentinel節點的訊息通知,獲取redis資料節點的狀態變化。

redis sentinel可以實現高可用的讀寫分離,高可用體現在故障轉移,那麼實現高可用的基礎就是要有從節點,主從節點還實現了讀寫分離,減少master的壓力。但是如果是從節點下線了,sentinel是不會對其進行故障轉移的,並且連線從節點的客戶端也無法獲取到新的可用從節點,而這些問題在Cluster中都得到了有效的解決。

對於效能提高、容量擴充套件的時候,這種方式是比較複雜的,比較推薦的是使用叢集,就是下面討論的redis cluster!