redis哨兵

江流大海_浮萍如塵發表於2020-07-21

博主之前寫了一篇Redis哨兵搭建,並沒有對哨兵進行講解,本篇填坑。

同時,也為博主寫Redis分散式鎖(二)做一些前置知識。

挖坑位置:Redis叢集搭建(哨兵)

Redis主從

在講redis哨兵前,需要先簡單講解一下redis主從。

俗話說,雞蛋放在一個籃子裡容易碎,那就把雞蛋複製一份,放到其他籃子裡。所有的高可用基本都是這個思路。

上一篇文章講主從配置的時候,講到一個配置屬性slaveof

# 這個配置是redis-1中沒有的,需要在redis-2中新增
# 這裡的IP是redis-1的IP地址,埠是redis-1 6379.conf配置檔案中port的值,預設值是6379
slaveof 1.1.1.1 6379

這個屬性就是配置redis主從的。

這裡分析以下上面這張圖,可以發現以下幾個特點

  • 客戶端可以從三個redis中讀取資料
  • 只有主庫可以寫入資料(抱歉,這個沒有在圖中體現)
  • 兩個從庫從主庫中讀取資料

這種redis架構解決了以下問題

  • 主庫當機,仍然可以在從庫中讀取資料,一定程度上提高了可用性
  • 如果三個redis執行正常,資料應該一致
  • 讀取的壓力分擔到了3個節點上

存在以下幾個問題

  • 主庫當機,不能寫入
  • 主庫不能自動切換,需要手動切換
  • ※主庫寫入資料成功,還沒來得及同步到從庫,主庫當機

基於以上的一些問題,我們引出了redis哨兵

Redis哨兵

主從中存在一些問題是我們不能接受的,比如,主庫當機=無法寫入。我們當然期望,當機一個節點的時候,仍然可以對外服務,這才是高可用嘛~

哨兵

假如不能自動切換主庫,我們該怎麼做呢?運維童鞋,先不管主庫了(已經當機了),在從庫中選擇一個作為新的主庫啟動,優先提供服務嘛~至於主庫,稍後再分析當機原因,解決問題。

本著軟體能解決的問題,就不使用人力,我們可不可以下一個軟體,來代替運維童鞋的這些操作?

這個就是哨兵的功能雛形了。

哨兵監視主從redis,一旦redis主庫當機,哨兵切換主庫,客戶端再寫入資料的時候,向新的主庫中寫入。在切換的時候,給運維童鞋一條通知,運維童鞋再處理。是不是很6?

優化

從上面可以看出,我們只有一個哨兵,如果這個哨兵當機了,那我們的保障就沒有了。

我們可以把哨兵也叢集起來,優化結構如下:

分析

你可以把哨兵理解為zookeeper或者是eureka,哨兵相當於一個註冊中心(當然不僅僅是註冊中心的功能),客戶端也從哨兵讀取redis主從庫資訊。三個哨兵組成了一個叢集的註冊中心,當有一個哨兵當機,還有其他哨兵存活,依然可以服務。

這樣哨兵Sentinel也叢集起來了,redis也有主從,我們這個時候可以說,提供了一套高可用的redis。

但這樣的架構並不是絕對完美的,仍然存在一些問題。

依舊不能解決主從中的一個問題“主庫寫入資料成功,還沒來得及同步到從庫,主庫當機”。

例如主庫上寫入了一把鎖,還沒來得及把鎖的資訊同步到從庫,主庫掛了,從庫切換為了主庫,然而這個新的主庫上並沒有鎖。引起了鎖失效。

如何解決呢?

  • 手動調整資料吧,少年。

  • 如果是鎖的場景,可以用zookeeper來代替redis分散式鎖來解決。

哨兵客戶端實現

博主使用的springboot來演示,使用工具包lettuce

依賴

<!-- data-redis中整合了lettuce -->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<!-- redis連結池 -->
<dependency>
    <groupId>org.apache.commons</groupId>
    <artifactId>commons-pool2</artifactId>
</dependency>
<!-- alibaba json -->
<dependency>
    <groupId>com.alibaba</groupId>
    <artifactId>fastjson</artifactId>
    <version>1.2.72</version>
</dependency>

配置檔案

server:
  port: 80
spring:
  redis:
    password: 密碼
    sentinel:
      # 這個配置在 哨兵配置檔案中
      master: 你的叢集名稱
      # 26379 埠是哨兵的預設埠
      nodes: 10.101.36.19:26379,10.101.36.20:26379,10.101.36.21:26379
    lettuce:
      pool:
        # 最大連結數
        max-active: 30
        # 連結池中最大空閒連結數
        max-idle: 15
        # 最大阻塞等待連結時長 預設不限制 -1
        max-wait: 2000
        # 最小空閒連結數
        min-idle: 10
      # 連結超時時長
      shutdown-timeout: 10000

Redis 配置類

import com.alibaba.fastjson.support.spring.GenericFastJsonRedisSerializer;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.StringRedisSerializer;

/**
 * redis 配置類 將RedisTemplate交給spring託管
 */
@Configuration
public class RedisConfig {

    @Bean
    public RedisTemplate<Object, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory){
        RedisTemplate<Object, Object> redisTemplate = new RedisTemplate<>();
        redisTemplate.setConnectionFactory(redisConnectionFactory);

        StringRedisSerializer stringRedisSerializer = new StringRedisSerializer();
        GenericFastJsonRedisSerializer genericFastJsonRedisSerializer = new GenericFastJsonRedisSerializer();

        redisTemplate.setKeySerializer(stringRedisSerializer);
        redisTemplate.setValueSerializer(genericFastJsonRedisSerializer);

        redisTemplate.setHashKeySerializer(stringRedisSerializer);
        redisTemplate.setHashValueSerializer(genericFastJsonRedisSerializer);

        redisTemplate.afterPropertiesSet();

        return redisTemplate;
    }
}

測試類

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import java.util.concurrent.TimeUnit;

@RestController
@RequestMapping("test")
public class TestController {

    @Autowired
    private RedisTemplate redisTemplate;

    @GetMapping
    public String Test(){
        redisTemplate.opsForValue().set("xujp", "sentinel");
        String rst = (String) redisTemplate.opsForValue().get("xujp");
        return rst;
    }

}

測試

postman直接get請求即可

在redis連線工具中檢視

主庫:

從庫:

這個時候把主庫手動關閉

檢視哨兵日誌

可以看到主節點已切換完成

26721:X 20 Jul 2020 15:08:40.941 # +switch-master mymaster 10.101.36.20 6379 10.101.36.19 6379

這時候,在postman中再次傳送請求

請求成功。

有興趣的童鞋可以再測試一下內容:

  • 在切換間隙嘗試獲取redis資料
  • kill掉一個sentinel,再kill掉主庫,檢視主庫切換

這裡博主再挖一坑,redis哨兵模式切換主庫的時候,如何通知運維人員呢?

總結

本文挖坑:

  • redis哨兵模式切換主庫的時候,如何通知運維人員呢?

相關文章