Redis集群部署+Redis 链接地址放入Aollo +c#项目接入Redis

第 1 步:下载安装包

PowerShell 命令行下载

curl.exe -L -o "$env:USERPROFILE\Downloads\Redis-x64-5.0.14.1.zip" "https://ghfast.top/https://github.com/tporadowski/redis/releases/download/v5.0.14.1/Redis-x64-5.0.14.1.zip"

第 2 步:解压到安装目录

D:\Program Files\Redis

第 3 步:为每个节点建独立目录和配置

为什么:每个节点必须有独立的 nodes.conf(集群元数据)、数据文件和日志,混在一起会互相覆盖。目录结构:

D:\Program Files\Redis

├── redis-server.exe、redis-cli.exe # 程序共用

└── cluster

├── 7000\ redis.conf data\ # 每节点一份

├── 7001\ ...

└── 7005\ ...

每节点的 redis.conf 关键配置(以 7000 为例):

port 7000 # 节点端口,6 个节点各不相同

bind 127.0.0.1 # 只监听本机

cluster-enabled yes # ★ 开启集群模式,没有它就是普通单机实例

cluster-config-file nodes.conf # 集群元数据(节点ID、槽位分配、主从关系),自动生成自动维护

cluster-node-timeout 15000 # 节点失联 15 秒触发故障转移

cluster-require-full-coverage no # 部分槽位不可用时集群仍对外服务

appendonly yes # AOF 持久化,重启不丢数据

dir "D:/Program Files/Redis/cluster/7000/data"

logfile "D:/Program Files/Redis/cluster/7000/redis.log"

3.1 在 D:\Program Files\Redis 下创建 cluster

管理员 PowerShell 执行(一行建 6 个节点目录 + data 子目录):

bash 复制代码
7000..7005 | ForEach-Object { New-Item -ItemType Directory -Force -Path "D:\Program Files\Redis\cluster\$_\data" }

3.2 写配置文件

每节点的配置只有 3 处不同:port、dir、logfile 里的端口号,其余完全一样。

以 7000 为例,新建 D:\Program Files\Redis\cluster\7000\redis.conf,内容:

bash 复制代码
# 端口(每节点唯一:7000/7001/.../7005)
port 7000

# 只监听本机回环地址,安全
bind 127.0.0.1
protected-mode yes

# ========== 集群核心配置(缺一不可) ==========
# 开启集群模式;没有这行就是普通单机实例
cluster-enabled yes
# 集群元数据文件:节点ID、槽位分配、主从关系。
# 只写文件名(相对路径),Redis 会在 dir 指定目录下自动生成和维护它
cluster-config-file nodes.conf
# 节点失联 15 秒判定为主观下线,触发故障转移
cluster-node-timeout 15000
# 部分槽位不可用时集群仍可服务(默认 yes 会整体拒服务)
cluster-require-full-coverage no

# ========== 持久化 ==========
# AOF:每秒刷盘,重启数据不丢(集群实验环境推荐开)
appendonly yes
appendfilename "appendonly.aof"
dbfilename dump.rdb
databases 16

# ========== 路径(每节点指向自己的目录) ==========
dir "D:/Program Files/Redis/cluster/7000/data"
logfile "D:/Program Files/Redis/cluster/7000/redis.log"

3.3 复制出另外 5 份并改端口号

bash 复制代码
# 读取 7000 的配置作为模板,替换端口号后生成 7001-7005
$template = Get-Content "D:\Program Files\Redis\cluster\7000\redis.conf" -Raw
7001..7005 | ForEach-Object {
    $content = $template -replace '7000', "$_"
    Set-Content "D:\Program Files\Redis\cluster\$_\redis.conf" $content
}

3.4 自检(防踩坑的关键一步)

bash 复制代码
7000..7005 | ForEach-Object {
    "=== $_ ==="
    Select-String -Path "D:\Program Files\Redis\cluster\$_\redis.conf" -Pattern '^(port|dir|logfile|cluster-enabled)'
}

第 4 步:注册为 Windows 服务

为什么:redis-server.exe 直接跑会随控制台关闭而退出;注册成服务才能开机自启、后台常驻。tporadowski 版内置了服务管理命令(不需要 NSSM):

bash 复制代码
cd "D:\Program Files\Redis"
.\redis-server.exe --service-install "D:\Program Files\Redis\cluster\7002\redis.conf" --service-name Redis7002
.\redis-server.exe --service-install "D:\Program Files\Redis\cluster\7003\redis.conf" --service-name Redis7003
.\redis-server.exe --service-install "D:\Program Files\Redis\cluster\7004\redis.conf" --service-name Redis7004
.\redis-server.exe --service-install "D:\Program Files\Redis\cluster\7005\redis.conf" --service-name Redis7005

查看服务

bash 复制代码
Get-Service Redis*

第 5 步:启动全部节点

bash 复制代码
.\redis-server.exe --service-start --service-name Redis7000
.\redis-server.exe --service-start --service-name Redis7001
.\redis-server.exe --service-start --service-name Redis7002
.\redis-server.exe --service-start --service-name Redis7003
.\redis-server.exe --service-start --service-name Redis7004
.\redis-server.exe --service-start --service-name Redis7005

此时 6 个节点各自独立运行,还不是集群

第 6 步:组建集群(核心一步)

用 redis-cli 的集群管理命令把 6 个节点"编织"成集群:

bash 复制代码
.\redis-cli.exe --cluster create `
  127.0.0.1:7000 127.0.0.1:7001 127.0.0.1:7002 `
  127.0.0.1:7003 127.0.0.1:7004 127.0.0.1:7005 `
  --cluster-replicas 1

--cluster create 进入「集群创建」模式

--cluster-replicas 1 每个主节点配 1 个从节点

所以 6 个节点会被自动分成:

  • 3 个 master(主节点):7000、7001、7002 → 负责读写,数据分片(16384 个哈希槽平均分给这 3 个主节点)
  • 3 个 replica(从节点):7003、7004、7005 → 分别是各主节点的备份,主挂了自动顶上

第 7 步:验证

bash 复制代码
.\redis-cli.exe -p 7000 cluster info
bash 复制代码
.\redis-cli.exe -p 7000 cluster nodes
# 能看到 3 个 master 和 3 个 slave 及各自槽位段
bash 复制代码
.\redis-cli.exe -c -p 7000 set test:key hello
.\redis-cli.exe -c -p 7001 get test:key
# -c 是 cluster 模式:key 不在当前节点时自动跟随 MOVED 重定向
# 能写入后从另一个节点读出 = 分片路由正常

第 8 步(应用接入):配置放 Apollo + c# 接入

安装 StackExchange.Redis

Program.cs 代码

bash 复制代码
using Com.Ctrip.Framework.Apollo;
using WebApplication1.Redis;
var builder = WebApplication.CreateBuilder(args);

// Add services to the container.
builder.Configuration.AddApollo(
    builder.Configuration.GetSection("apollo"));
builder.Services.AddControllers();
// Learn more about configuring OpenAPI at https://aka.ms/aspnet/openapi

// Redis:连接串从 Apollo 读取(redis.connectionString),此处一次性初始化,全局单例
builder.Services.AddRedis(builder.Configuration);



var app = builder.Build();

// Configure the HTTP request pipeline.
if (app.Environment.IsDevelopment())
{
    app.MapOpenApi();
}

app.UseHttpsRedirection();

app.UseAuthorization();

app.MapControllers();



app.Run();

IRedisService 代码

bash 复制代码
using System.Text.Json;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using StackExchange.Redis;

namespace WebApplication1.Redis;

public interface IRedisService
{
    Task<string?> GetAsync(string key);
    Task<bool> SetAsync(string key, string value, TimeSpan? expiry = null);
    Task<bool> RemoveAsync(string key);
    Task<long> IncrementAsync(string key, long delta = 1);
    Task<T?> GetObjectAsync<T>(string key);
    Task SetObjectAsync<T>(string key, T value, TimeSpan? expiry = null);
    Task<bool> ExistsAsync(string key);
    Task<bool> KeyExpireAsync(string key, TimeSpan expiry);
    Task<bool> LockTakeAsync(string key, string value, TimeSpan expiry);
    Task<bool> LockReleaseAsync(string key, string value);
}

public sealed class RedisService : IRedisService
{
    private readonly IDatabase _db;
    private static readonly JsonSerializerOptions JsonOpts = new(JsonSerializerDefaults.Web);

    public RedisService(IConnectionMultiplexer redis)
    {
        _db = redis.GetDatabase(); // 轻量包装,不建立新连接
    }

    public Task<string?> GetAsync(string key) =>
        Task.FromResult((string?)_db.StringGet(key));

    public Task<bool> SetAsync(string key, string value, TimeSpan? expiry = null) =>
        _db.StringSetAsync(key, value, expiry);

    public Task<bool> RemoveAsync(string key) =>
        _db.KeyDeleteAsync(key);

    public Task<long> IncrementAsync(string key, long delta = 1) =>
        _db.StringIncrementAsync(key, delta);

    public async Task<T?> GetObjectAsync<T>(string key)
    {
        var json = await _db.StringGetAsync(key);
        return json.IsNull ? default : JsonSerializer.Deserialize<T>(json.ToString(), JsonOpts);
    }

    public Task SetObjectAsync<T>(string key, T value, TimeSpan? expiry = null) =>
        _db.StringSetAsync(key, JsonSerializer.Serialize(value, JsonOpts), expiry);

    public Task<bool> ExistsAsync(string key) =>
        _db.KeyExistsAsync(key);

    public Task<bool> KeyExpireAsync(string key, TimeSpan expiry) =>
        _db.KeyExpireAsync(key, expiry);

    // 分布式锁:用完务必在 finally 中 Release
    public Task<bool> LockTakeAsync(string key, string value, TimeSpan expiry) =>
        _db.LockTakeAsync(key, value, expiry);

    public Task<bool> LockReleaseAsync(string key, string value) =>
        _db.LockReleaseAsync(key, value);
}

/// <summary>
/// Redis 一次性初始化:连接串从 Apollo 读取(key: redis.connectionString),
/// ConnectionMultiplexer 与 IRedisService 均为单例,全应用复用,业务侧只注入调用。
/// </summary>
public static class RedisSetup
{
    public static IServiceCollection AddRedis(this IServiceCollection services, IConfiguration config)
    {
        var connStr = config["redis.connectionString"];
        if (string.IsNullOrWhiteSpace(connStr))
            throw new InvalidOperationException(
                "未读取到 redis.connectionString,请检查 Apollo(test001 / application 命名空间)是否已发布该配置项");

        services.AddSingleton<IConnectionMultiplexer>(_ =>
        {
            var mux = ConnectionMultiplexer.Connect(connStr);
            mux.ConnectionFailed += (_, e) =>
                Console.WriteLine($"[Redis] 连接失败 {e.EndPoint} {e.FailureType}");
            mux.ConnectionRestored += (_, e) =>
                Console.WriteLine($"[Redis] 连接恢复 {e.EndPoint}");
            return mux;
        });

        services.AddSingleton<IRedisService, RedisService>();
        return services;
    }
}

UserController.cs

bash 复制代码
// ============================================================
// UserController.cs ------ 业务侧使用示例:构造函数注入 IRedisService 即可,零初始化代码
// ============================================================

using Microsoft.AspNetCore.Mvc;
using WebApplication1.Redis;

namespace WebApplication1.Controllers;

public record UserProfile(string Name, int Age);

[ApiController]
[Route("api/[controller]")]
public class UserController(IRedisService redis) : ControllerBase
{
    // GET /api/user/1001 ------ 先查缓存,未命中回源并写回
    [HttpGet("{id}")]
    public async Task<IActionResult> Get(string id)
    {
        var cacheKey = $"user:{{{id}}}:profile"; // {id} 为 hash tag,集群下同用户相关 key 同槽位

        var cached = await redis.GetObjectAsync<UserProfile>(cacheKey);
        if (cached is not null)
        {
            return Ok(new { from = "cache", data = cached });
        }

        // 模拟回源(实际是查数据库)
        var profile = new UserProfile("张三", 30);
        await redis.SetObjectAsync(cacheKey, profile, TimeSpan.FromMinutes(10));
        return Ok(new { from = "db", data = profile });
    }

    // POST /api/user/1001/login ------ 计数器 + 过期
    [HttpPost("{id}/login")]
    public async Task<IActionResult> Login(string id)
    {
        var count = await redis.IncrementAsync($"user:{{{id}}}:logins");
        await redis.KeyExpireAsync($"user:{{{id}}}:logins", TimeSpan.FromDays(1));
        return Ok(new { loginCount = count });
    }

    // DELETE /api/user/1001 ------ 删除缓存
    [HttpDelete("{id}")]
    public async Task<IActionResult> Delete(string id)
    {
        await redis.RemoveAsync($"user:{{{id}}}:profile");
        return NoContent();
    }
}
相关推荐
用户6919026813393 小时前
Redis 1:简述Redis概念与Agent的联系
redis·后端·设计
小南家的青蛙5 小时前
华为云DCS全形态原生Redis命令兼容性测试方案
redis·华为云·兼容性·兼容性测试·dcs
xiaoqiMikko5 小时前
Redis 实战(三):持久化出错 —— fork 失败、磁盘写满、AOF 膨胀与损坏,逐个复现(6.2.4)
redis·后端
IT规划师5 小时前
面试中被嘲笑Token放在redis里,该如何应对?
redis·面试
我不会起名字3227 小时前
Redis 从 0 到 1(十九):JWT 还是 Redis Session?登录态存储的 Go 实现
redis·go·jwt·session·登录态
我不会起名字3227 小时前
Redis 从 0 到 1(十六):1 亿 UV 只占 12KB?计数去重三件套的 Go 实测
redis·go·uv·布隆过滤器·hyperloglog
BuHuaX8 小时前
Lua 与 ToLua:运行机制及热更新原理
开发语言·unity·junit·c#·lua