Redis Master-Slave-Sentinel for Windows Container

Redis Master-Slave-Sentinel for Windows Container

Master-Slave-Sentinel 本機測試

我們先在本機進行 Redis for Windows 的 Master-Slave-Sentinel 架構測試。由於微軟官方已經不再繼續維護 Redis for Windows,目前還有一位 tporadowski fork 後持繼在更新與維護。讓我們謝謝他。

本機 Master

Master 組態參考:https://github.com/tporadowski/redis/blob/develop/redis.conf

redis.docker-master.conf 修改處:

bind 0.0.0.0
protected-mode no
#requirepass "yourpass"
dir "c:\\redis_data"
appendonly yes

預設沒有加上 requirepass 等於是任何人都能連線,如果設定了,其他 Salve 或 Sentinel 也要設置。另外,我們啟用 AOF,將資料備份本實體機上,這樣就算實體機重開機也能讓資料回復。

啟動:

redis-server.exe redis.docker-master.conf

測試:

redis-cli.exe -h localhost -a {yourpass} info
redis-cli.exe -h localhost -a {yourpass} info replication

本機 Slave

Slave 組態參考:https://github.com/tporadowski/redis/blob/develop/redis.conf

redis.docker-slave.conf 修改處:

bind 0.0.0.0
protected-mode no
port 6380
dir "c:\\redis_data"
slaveof 127.0.0.1 6379
#masterauth <master-yourpass>

slaveof 是重點所在,也是晚點要處理的重點,也就是,在容器環境,基本上取得的 IP 位置會是隨機的,所以要一開始就規劃好的 Master 網路的 IP 位置。這樣在 Slave 容器起來之後,才知道要如何找到 Master 連線與同步。

執要與測試:

redis-server.exe redis.docker-slave.conf
redis-cli.exe -h localhost -p 6380 info replication

在 Master 輸入:redis-cli.exe -h localhost -a yourpass set mykey myvalue 在 Slave 輸入:redis-cli.exe -h localhost -p 6380 get mykey

由於 Master-Slave 兩個執行個體都是同一主機,本機測試應該很順利。

本機 Sentinel

Sentinel 組態參考:https://github.com/tporadowski/redis/blob/develop/sentinel.conf

redis.docker-sentinel.conf 修改處:

# Add Setting For Docker
bind 0.0.0.0
protected-mode no
# Sentinel
port 26379
dir "c:\\redis_data"
sentinel monitor master1 127.0.0.1 6379 1
sentinel down-after-milliseconds master1 30000
sentinel failover-timeout master1 180000
sentinel parallel-syncs master1 1
#sentinel auth-pass master1 foobared

sentinel monitor master1 127.0.0.1 6379 1 指要監控那一台 Master,一個 Sentinel 可以監控多個,一個 Master 可以被多個 Sentinel 監控。只要重覆那 4+1 個 sentinel 組態,並指到另一 Master 即可。組態 6379 後面的 1 是指幾個 Sentinel 判定的失敗就是失敗,預設是 2,目前我們只啟動一台,所以改為 1。

執行:

redis-server.exe redis.docker-sentinel.conf --sentinel

Master-Slave-Sentinel 容器建置

有了組態檔之後,我們可以開始來打包所需要 Redis for Windows 容器。

我們採用 mcr.microsoft.com/windows/nanoserver:1909 來當基礎映像檔,但 nanoserver:1909 沒有 PowerShell,而 mcr.microsoft.com/powershell 1909 版本一直建置不出來,所以 Dockerfile 部分我們採用下載 Redis 程式再複製的方式,而不是全自動 Redis 容器的建置過程PowerShell 1909 已經發行了。

網路配置

前面提到,我們在本機或 VM 環境,要取得 IP 位置相對簡單,但容器在啟動時,本身是動態配置 IP 區段與位置的。而且如果你有注意的話,在 Slave 與 Sentinel 的組態裡,都是用 127.0.0.1 去連接 Master,這有二個問題:一、IP 位置寫死。二、這樣的組態在容器是不會有效的。

Slave 本身如果是用 Docker Compose 來啟動,有找個一個解法。查詢 redis-server.exe --help 有提供一個 --slaveof 參數,利用 links: 之後能利用名稱來指定,算是解了一半。

redis-server.exe --slaveof redis-master 6379

另一種方式,就是先設計好網路架構,並且在啟動時指定好 Master 的 IP 位置,如此一來,就不怕不知道 Master 的 IP 位置,如何對 Slave 與 Sentinel 進行組態而傷腦筋。

docker network create -d nat --subnet=10.10.10.0/24 --gateway 10.10.10.1 --ipam-driver windows --opt com.docker.network.windowsshim.networkname=redis-net redis-net

Build Redis Images

Dockerfile-Master

# escape=`

FROM  mcr.microsoft.com/windows/nanoserver:1909
LABEL maintainer="kkbruce"

RUN mkdir c:\redis_data
COPY Redis /Redis
ADD redis.docker-master-6379.conf /Redis
WORKDIR /Redis
EXPOSE 6379

CMD ["redis-server.exe", "redis.docker-master-6379.conf"]

Dockerfile-Slave

# escape=`

FROM  mcr.microsoft.com/windows/nanoserver:1909
LABEL maintainer="kkbruce"

RUN mkdir c:\redis_data
COPY Redis /Redis
ADD redis.docker-slave-6380.conf /Redis
WORKDIR /Redis
EXPOSE 6380

CMD ["redis-server.exe", "redis.docker-slave-6380.conf"]

Dockerfile-Sentinel

# escape=`

FROM  mcr.microsoft.com/windows/nanoserver:1909
LABEL maintainer="kkbruce"

RUN mkdir c:\redis_data
COPY Redis /Redis
ADD redis.docker-sentinel-6379.conf /Redis
WORKDIR /Redis
EXPOSE 26379

CMD ["redis-server.exe", "redis.docker-sentinel-6379.conf", "--sentinel"]

Build Image

docker build -t kkbruce/redis-master:4.0.14.2-aof-nanoserver-1909 -f .\Dockerfile-Master .
docker build -t kkbruce/redis-slave:4.0.14.2-aof-nanoserver-1909 -f .\Dockerfile-Slave .
docker build -t kkbruce/redis-sentinel:4.0.14.2-aof-nanoserver-1909 -f .\Dockerfile-Sentinel .

Run Test:

docker run -d -p 6379:6379 --name redis-master --rm -v C:\redis_data\:c:\redis_data --network redisnet --ip=10.10.10.10  kkbruce/redis:master-4.0.14.2-aof-nanoserver-1909
docker run -d -p 6380:6380 --name redis-slave --rm -v C:\redis_data\:c:\redis_data kkbruce/redis:slave-4.0.14.2-aof-nanoserver-1909
docker run -d -p 26379:26379 --name redis-sentinel --rm -v C:\redis_data\:c:\redis_data kkbruce/redis:sentinel-4.0.14.2-aof-nanoserver-1909

Docker Compose

將上面最後的測試指定翻成 Docker Compose 的 YAML 組態:

version: '3.7'
services:
  master:
    image: "kkbruce/redis:master-4.0.14.2-aof-nanoserver-1909"
    ports:
      - 6379:6379
    hostname: redis-master
    volumes:
      - redis-data:c:\redis_data
    networks:
      redis-net:
        ipv4_address: 10.10.10.10
    restart: unless-stopped

  slave:
    image: "kkbruce/redis:slave-4.0.14.2-aof-nanoserver-1909"
    ports:
      - 6380:6380
    hostname: redis-slave
    volumes:
      - redis-data:c:\redis_data
    networks:
      - redis-net
    restart: unless-stopped
    depends_on:
      - master

  sentinel:
    image: "kkbruce/redis:sentinel-4.0.14.2-aof-nanoserver-1909"
    ports:
      - 26379:26379
    hostname: redis-sentinel
    volumes:
      - redis-data:c:\redis_data
    networks:
      - redis-net
    restart: unless-stopped
    depends_on:
      - master
      - slave

networks:
  redis-net:
    external: true
    name: redis-net

volumes:
  redis-data:

這樣就完了我們第一個 Redis for Windows 的 Master-Slave-Sentinel 架構的容器。

以上架構有個缺點,就是 Master-Slave-Sentinel 都只能有一個實體。因為我們繫結了主機的 Port Mapping。您可以試著把 slave 的 Port 組態註解後,進行 scale 來測試。

docker-compose.exe up -d
docker-compose.exe stop master
docker-compose.exe start master
docker-compose.exe stop slave
docker-compose.exe start slave

C# with Redis Sentinel

架構好 Redis Master-Slave-Sentinel 之後,我們希望程式可以在主機出錯時自動切換連線主機。在進入程式前另外注意,我在測試時從 Logs 裡發現:

sentinel_1  | [1160] 10 Feb 17:41:06.752 # Next failover delay: I will not start a failover before Mon Feb 10 17:47:07 2020

在我不斷 stopstart Master、Slave 測試時,Sentinel 本身會有延遲啟動的作業,也就是,短時間大量中斷測試時會突然發現切換作業沒有正常動作了,就是這原因。

sentinel failover-timeout master1 180000 可以調整這個值來延長或縮短。

在程式方面,我們採用最通用的 C# 套件 StackExchange.Redis 來實作業,而且很幸運作的在 Basic UsageConfiguration 就很快找到相關設定與組態。

ConnectionMultiplexer redis = ConnectionMultiplexer.Connect("server1:6379,server2:6379");

我們實作一下連接程式:

public static class RedisHaFactory
{
    private static readonly Lazy<ConnectionMultiplexer> Connection;
    /// <summary>
    /// Use EndPoint to connection.
    /// </summary>
    static RedisHaFactory()
    {
        ConfigurationOptions options = new ConfigurationOptions
        {
            EndPoints ={{ "127.0.0.1", 6379 },{ "127.0.0.1", 6380 }},
            DefaultDatabase = 10
        };
        Connection = new Lazy<ConnectionMultiplexer>(() => ConnectionMultiplexer.Connect(options));
    }

    public static ConnectionMultiplexer GetConnection => Connection.Value;
    public static IDatabase RedisDB => GetConnection.GetDatabase();
}

public static class RedisFactory6380
{
    private static readonly Lazy<ConnectionMultiplexer> Connection;
    /// <summary>
    /// Use connectionString to connection.
    /// </summary>
    static RedisFactory6380()
    {
        //var connectionString = "127.0.0.1:6379,127.0.0.1:6380,DefaultDatabase=10";
        var connectionString = "127.0.0.1:6380,defaultDatabase=10";
        var options = ConfigurationOptions.Parse(connectionString);
        Connection = new Lazy<ConnectionMultiplexer>(() => ConnectionMultiplexer.Connect(options));
    }

    public static ConnectionMultiplexer GetConnection => Connection.Value;
    public static IDatabase RedisDB => GetConnection.GetDatabase();
}
  • RedisHaFactory 是用 ConfigurationOptions 來設定 EndPoints
  • RedisFactory6380 是用字串來設定。等一下測試要用的程式碼。

我們在一個 ASP.NET Core Web API 裡去進行測試:

[Route("poc/[action]")]
[ApiController]
public class PocController : ControllerBase
{
    /// <summary>
    /// Write data to Master.
    /// </summary>
    /// <returns>string</returns>
    public IActionResult Demo1()
    {
        RedisHaFactory.RedisDB.StringSet("6379", "Write string value to 6379.");
        var stringGet = RedisHaFactory.RedisDB.StringGet("6379");
        return Ok(stringGet.ToString());
    }

    /// <summary>
    /// Write data to Slave(readonly), It will throw exception.
    /// </summary>
    /// <returns></returns>
    public IActionResult Demo2()
    {
        RedisFactory6380.RedisDB.StringSet("6380", "Write string value to 6380.");
        var stringGet = RedisFactory6380.RedisDB.StringGet("6380");
        return Ok(stringGet.ToString());
    }

    /// <summary>
    /// Shitdown Master and write to Slave(sentinel enable).
    /// </summary>
    /// <returns>string</returns>
    public IActionResult Demo3()
    {
        RedisHaFactory.RedisDB.StringSet("6379down", "Write string value to 6380.");
        var stringGet = RedisHaFactory.RedisDB.StringGet("6379down");
        return Ok(stringGet.ToString());
    }
}

Demo1 正常寫入。Demo2 寫入 Slave 會失敗,因為它是唯讀。進行 Demo3 之前,把 Master 服務停用,讓 Sentinel 去切換,執行後會發現,正常寫入 6380 Port 的主機了,因為現在它被 Sentinel 切換為 Master 角色。

原始碼:https://github.com/kkbruce/RedisSentinelHA

沒有留言:

張貼留言

感謝您的留言,如果我的文章你喜歡或對你有幫助,按個「讚」或「分享」它,我會很高興的。