建立 .NET 6 + Redis 本機開發環境

Redis 已經幾乎是系統必備的基礎建設,在本機搭建一套 Local Server,除了用於開發除錯之外,它也可以用來當成測試替身(模擬器),接下來,就來分享我的配置

開發環境

  • Windows 11
  • Rider 2022.2.3
  • .NET 6
  • StackExchange.Redis 2.6.70

 

利用 Docker 建立 Redis 開發環境

通過 Docker 搭建 Redis 的開發環境,很容易在本機搭建起 Local Redis Server,作為測試替身也是非常的合適 redis - Official Image | Docker Hub

有了 Redis 再搭配一套 Client,rebrow 是一套 Web UI 的 Redis 管理工具,搭配他用來除錯是一個不錯的選擇,他也提供了 Docker marian's Profile | Docker Hub

 

將以下內容另存 docker-compose.yml

version: "3.8"

services:
  redis:
    image: redis
    ports:
      - 6379:6379

  # 在登入頁面 
  # host:redis
  # port:6379
  redis-admin:
    image: marian/rebrow
    ports:
      - 5001:5001
    depends_on:
      - redis

 

用 Shell 啟動它

docker-compose.yml up -d

 

不過,預設的登入頁面會有無法登入的問題,連線位置由原本的 localhost 換成 redis 即可

 

AnotherRedisDesktopManager 也是 Redis 的選擇之一,安裝指令如下

winget install qishibo.AnotherRedisDesktopManager

 

.NET SDK

官方列出了各家語言實作的 Redis Client,.NET 最受歡迎的則是 StackExchange.Redis,原始碼文件

StackExchange.Redis 實作了 Multiplex,它對 Redis 只建立一條connection,透過 Multiplex 多路複用去服務大量的請求,所以我們在使用
ConnectionMultiplexer 建立連線時,不需要重複建立 ConnectionMultiplexer 物件。

 

使用 StackExchange.Redis 訪問 Redis Server

開啟一個新的 .NET 6 的 Library 專案

dotnet new classlib --framework "net6.0" --output Lib.Redis.Client --language "C#"

安裝套件

dotnet add package StackExchange.Redis --version 2.6.70

建立連線可以使用 ConnectionMultiplexer.Connect(string) 或是 ConfigurationOptions 類別,代碼如下

var conn = ConnectionMultiplexer.Connect("127.0.0.1:6379");
var config = ConfigurationOptions.Parse("127.0.0.1:6379");
var conn = ConnectionMultiplexer.Connect(config.ToString()); 

 

錯誤的寫法

using (var redis = ConnectionMultiplexer.Connect("localhost:6379"))
{
    var db = redis.GetDatabase();
    db.StringSet("test", 9527);
}

HttpClient 的用法也是一樣

 

ConnectionMultiplexer 需要用單例(Singleton)模式,這裡我用 Lazy 來達到此效果 

public class RedisClient
{
    private static readonly Lazy<ConnectionMultiplexer> s_connectionLazy;
    private static string _setting;

    private ConnectionMultiplexer Instance => s_connectionLazy.Value;

    public IDatabase Database => this.Instance.GetDatabase();

    static RedisClient()
    {
        s_connectionLazy = new Lazy<ConnectionMultiplexer>(() =>
        {
            if (string.IsNullOrWhiteSpace(_setting))
            {
                return ConnectionMultiplexer.Connect("localhost");
            }

            return ConnectionMultiplexer.Connect(_setting);
        });
    }

    public static void Init(string setting)
    {
        _setting = setting;
    }
}

更多單例的實作方式可以,參考 

https://csharpindepth.com/Articles/Singleton

 

稍微調整一下,把單例模式再加上 Pool,不同的 Setting 使用不同的 ConnectionMultiplexer,每一個 ConnectionMultiplexer 只會 Connect() 一次

public class RedisConnection
{
    private static ConcurrentDictionary<string, Lazy<ConnectionMultiplexer>> s_connectionPool = new();

    public IDatabase Connect(string setting = "localhost")
    {
        var connMultiplexer = s_connectionPool.GetOrAdd(setting,
            new Lazy<ConnectionMultiplexer>(() => ConnectionMultiplexer.Connect(setting)));

        return connMultiplexer.Value.GetDatabase();
    }

    public IDatabase GetDatabase(string setting = "localhost")
    {
        if (s_connectionPool.TryGetValue(setting, out var connMultiplexer))
        {
            return connMultiplexer.Value.GetDatabase();
        }

        return default;
    }
}

 

再把常用的方法包裝一下

public static class RedisDatabaseExtensions
{
    public static bool IsExist(this IDatabase db, string key)
    {
        return db.KeyExists(key);
    }

    public static void Set<T>(this IDatabase db, string key, T value,
        TimeSpan? expiry = default,
        When when = When.Always,
        CommandFlags flags = CommandFlags.None,
        JsonSerializerOptions options = default)
    {
        db.StringSet(key, Serialize(value, options), expiry, when, flags);
    }

    private static string Serialize<T>(T value, JsonSerializerOptions options)
    {
        return JsonSerializer.Serialize(value, options);
    }

    public static T Get<T>(this IDatabase db, string key, JsonSerializerOptions options = default)
    {
        if (db.IsExist(key))
        {
            return Deserialize<T>(db.StringGet(key), options);
        }

        return default;
    }

    private static T? Deserialize<T>(RedisValue value, JsonSerializerOptions options)
    {
        return JsonSerializer.Deserialize<T>(value, options);
    }
}

 

調用方法如下

[TestMethod]
public void SetDTO()
{
    var connection = new RedisConnection();
    // var database = connection.Connect("localhost:6379");
    var config = ConfigurationOptions.Parse("127.0.0.1:6379");
    var database = connection.Connect(config.ToString());
    var model = new Model
    {
        Name = "小章",
        Age = 29
    };

    database.Set("dto", model);
    var actual = database.Get<Model>("dto");
    Assert.AreEqual(model, actual);
}

 

用 AnotherRedisDesktopManager 觀察結果

 

範例位置

sample.dotblog/Redis/Lab.Redis.Client at master · yaochangyu/sample.dotblog (github.com)

若有謬誤,煩請告知,新手發帖請多包涵


Microsoft MVP Award 2010~2017 C# 第四季
Microsoft MVP Award 2018~2022 .NET

Image result for microsoft+mvp+logo