九、.NET 6 集成通用 Redis 功能:从基础到高级实践

Redis 作为高性能的内存键值存储数据库,在.NET 应用中广泛用于缓存、分布式锁、消息队列等场景。直接使用原生 SDK 会导致代码重复、维护成本高,因此封装通用 Redis 服务是企业级项目的常规操作。本文将基于.NET 6 特性,从零开始完成 Redis 环境搭建、基础集成、通用功能封装、高级功能实现及最佳实践,帮助你快速构建可复用的 Redis 能力。

目录#

  1. 准备工作:Redis 环境与.NET 6 项目创建
  2. 基础集成:StackExchange.Redis 与.NET 6 服务注册
  3. 通用 Redis 服务封装:接口定义与核心实现
  4. 示例实战:通用服务的场景化使用
  5. 高级功能:分布式锁、缓存防雪崩等解决方案
  6. 最佳实践:连接池、键规范、错误处理等
  7. 常见问题与排查指南
  8. 总结与参考资料

1. 准备工作#

1.1 Redis 环境搭建#

推荐使用 Docker 快速启动本地 Redis 服务,无需手动安装:

# 启动 Redis 容器(带持久化、密码认证)
docker run -d --name redis-local -p 6379:6379 \
  redis:7-alpine redis-server \
  --appendonly yes \
  --requirepass "yourRedisPassword"

验证连接:使用 redis-cli 执行 AUTH yourRedisPassword,返回 OK 则表示环境正常。

1.2 .NET 6 项目创建#

使用 CLI 或 Visual Studio 创建 ASP.NET Core Web API 项目:

dotnet new webapi -n RedisDemo
cd RedisDemo

2. 基础集成:StackExchange.Redis 服务注册#

.NET 生态中主流的 Redis SDK 是 StackExchange.Redis,官方维护且性能优异。

2.1 安装依赖包#

# 核心 Redis 包
dotnet add package StackExchange.Redis
# 官方缓存扩展(可选,用于 IDistributedCache 兼容)
dotnet add package Microsoft.Extensions.Caching.StackExchangeRedis

2.2 配置 Redis 连接信息#

appsettings.json 中添加 Redis 配置:

{
  "Redis": {
    "ConnectionString": "localhost:6379,password=yourRedisPassword,connectTimeout=5000,syncTimeout=5000",
    "InstanceName": "RedisDemo:", // 键前缀,用于多实例隔离
    "DefaultExpiry": "00:30:00" // 默认过期时间
  }
}

配置项说明:

  • ConnectionString:包含地址、密码、超时时间等
  • InstanceName:自动为所有键添加前缀,避免多项目键冲突
  • DefaultExpiry:缓存默认过期时间

2.3 注册 Redis 服务(.NET 6 特性)#

.NET 6 采用顶级语句(Top-level statements),直接在 Program.cs 中注册服务:

using StackExchange.Redis;
 
var builder = WebApplication.CreateBuilder(args);
 
// 注册 Redis 连接多路复用器(核心对象,全局单例)
builder.Services.AddSingleton<IConnectionMultiplexer>(sp =>
{
    var configuration = ConfigurationOptions.Parse(
        builder.Configuration["Redis:ConnectionString"],
        true // 忽略未知配置项
    );
    // 添加键前缀(可选)
    configuration.DefaultDatabase = 0; // 指定默认数据库
    return ConnectionMultiplexer.Connect(configuration);
});
 
// 注册 IDatabase(可选,直接注入使用)
builder.Services.AddSingleton(sp =>
{
    var multiplexer = sp.GetRequiredService<IConnectionMultiplexer>();
    return multiplexer.GetDatabase();
});
 
// 注册官方分布式缓存(可选,用于兼容 IDistributedCache 接口)
builder.Services.AddStackExchangeRedisCache(options =>
{
    options.Configuration = builder.Configuration["Redis:ConnectionString"];
    options.InstanceName = builder.Configuration["Redis:InstanceName"];
});
 
// 其他服务注册...
builder.Services.AddControllers();
builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen();
 
var app = builder.Build();
 
// 中间件配置...
if (app.Environment.IsDevelopment())
{
    app.UseSwagger();
    app.UseSwaggerUI();
}
 
app.UseHttpsRedirection();
app.UseAuthorization();
app.MapControllers();
 
app.Run();

3. 通用 Redis 服务封装#

直接使用 IDatabase 会导致业务代码与 Redis 耦合,因此需要封装通用服务,统一管理所有 Redis 操作。

3.1 定义通用服务接口 IRedisService#

创建 Services/IRedisService.cs,涵盖 Redis 核心数据结构操作及高级功能:

using System;
using System.Collections.Generic;
using System.Threading.Tasks;
 
namespace RedisDemo.Services
{
    public interface IRedisService
    {
        #region String 类型操作
        /// <summary>
        /// 获取字符串值
        /// </summary>
        Task<T> StringGetAsync<T>(string key);
        
        /// <summary>
        /// 设置字符串值
        /// </summary>
        Task<bool> StringSetAsync<T>(string key, T value, TimeSpan? expiry = null);
        
        /// <summary>
        /// 自增字符串值
        /// </summary>
        Task<long> StringIncrementAsync(string key, long delta = 1);
        #endregion
 
        #region Hash 类型操作
        /// <summary>
        /// 获取 Hash 所有键值对
        /// </summary>
        Task<Dictionary<string, T>> HashGetAllAsync<T>(string key);
        
        /// <summary>
        /// 设置 Hash 键值对
        /// </summary>
        Task<bool> HashSetAsync<T>(string key, Dictionary<string, T> hashFields);
        #endregion
 
        #region 分布式锁
        /// <summary>
        /// 获取分布式锁
        /// </summary>
        Task<bool> AcquireLockAsync(string lockKey, string lockValue, TimeSpan expiry);
        
        /// <summary>
        /// 释放分布式锁
        /// </summary>
        Task<bool> ReleaseLockAsync(string lockKey, string lockValue);
        #endregion
 
        #region 通用操作
        /// <summary>
        /// 设置键过期时间
        /// </summary>
        Task<bool> SetKeyExpiryAsync(string key, TimeSpan expiry);
        
        /// <summary>
        /// 删除键
        /// </summary>
        Task<long> DeleteKeyAsync(params string[] keys);
        #endregion
    }
}

3.2 实现通用服务 RedisService#

创建 Services/RedisService.cs,基于 IConnectionMultiplexer 实现接口:

using StackExchange.Redis;
using System;
using System.Collections.Generic;
using System.Text.Json;
using System.Threading.Tasks;
 
namespace RedisDemo.Services
{
    public class RedisService : IRedisService
    {
        private readonly IDatabase _database;
        private readonly JsonSerializerOptions _serializerOptions;
        private readonly string _keyPrefix;
 
        public RedisService(IConnectionMultiplexer multiplexer, IConfiguration configuration)
        {
            _database = multiplexer.GetDatabase();
            _keyPrefix = configuration["Redis:InstanceName"] ?? "";
            // 配置 JSON 序列化选项
            _serializerOptions = new JsonSerializerOptions
            {
                PropertyNamingPolicy = JsonNamingPolicy.CamelCase,
                WriteIndented = false
            };
        }
 
        #region 私有工具方法
        private string GetFullKey(string key) => $"{_keyPrefix}{key}";
 
        private string Serialize<T>(T value)
        {
            if (value is string str) return str;
            return JsonSerializer.Serialize(value, _serializerOptions);
        }
 
        private T Deserialize<T>(RedisValue value)
        {
            if (value.IsNull) return default;
            if (typeof(T) == typeof(string)) return (T)(object)value.ToString();
            return JsonSerializer.Deserialize<T>(value.ToString(), _serializerOptions);
        }
        #endregion
 
        #region String 类型实现
        public async Task<T> StringGetAsync<T>(string key)
        {
            var fullKey = GetFullKey(key);
            var value = await _database.StringGetAsync(fullKey);
            return Deserialize<T>(value);
        }
 
        public async Task<bool> StringSetAsync<T>(string key, T value, TimeSpan? expiry = null)
        {
            var fullKey = GetFullKey(key);
            var serializedValue = Serialize(value);
            return await _database.StringSetAsync(fullKey, serializedValue, expiry);
        }
 
        public async Task<long> StringIncrementAsync(string key, long delta = 1)
        {
            var fullKey = GetFullKey(key);
            return await _database.StringIncrementAsync(fullKey, delta);
        }
        #endregion
 
        #region Hash 类型实现
        public async Task<Dictionary<string, T>> HashGetAllAsync<T>(string key)
        {
            var fullKey = GetFullKey(key);
            var hashEntries = await _database.HashGetAllAsync(fullKey);
            var result = new Dictionary<string, T>();
            foreach (var entry in hashEntries)
            {
                result[entry.Name] = Deserialize<T>(entry.Value);
            }
            return result;
        }
 
        public async Task<bool> HashSetAsync<T>(string key, Dictionary<string, T> hashFields)
        {
            var fullKey = GetFullKey(key);
            var entries = new List<HashEntry>();
            foreach (var field in hashFields)
            {
                entries.Add(new HashEntry(field.Key, Serialize(field.Value)));
            }
            return await _database.HashSetAsync(fullKey, entries.ToArray());
        }
        #endregion
 
        #region 分布式锁实现
        public async Task<bool> AcquireLockAsync(string lockKey, string lockValue, TimeSpan expiry)
        {
            var fullKey = GetFullKey($"lock:{lockKey}");
            // SETNX + EX 原子操作:不存在则设置并过期
            return await _database.StringSetAsync(fullKey, lockValue, expiry, When.NotExists);
        }
 
        public async Task<bool> ReleaseLockAsync(string lockKey, string lockValue)
        {
            var fullKey = GetFullKey($"lock:{lockKey}");
            // Lua 脚本保证原子释放:只有持有锁的客户端才能释放
            var script = @"
                if redis.call('GET', KEYS[1]) == ARGV[1] then
                    return redis.call('DEL', KEYS[1])
                else
                    return 0
                end";
            var result = await _database.ScriptEvaluateAsync(script, new RedisKey[] { fullKey }, new RedisValue[] { lockValue });
            return (long)result == 1;
        }
        #endregion
 
        #region 通用操作实现
        public async Task<bool> SetKeyExpiryAsync(string key, TimeSpan expiry)
        {
            var fullKey = GetFullKey(key);
            return await _database.KeyExpireAsync(fullKey, expiry);
        }
 
        public async Task<long> DeleteKeyAsync(params string[] keys)
        {
            var fullKeys = keys.Select(k => GetFullKey(k)).ToArray();
            return await _database.KeyDeleteAsync(fullKeys);
        }
        #endregion
    }
}

3.3 注册通用服务#

Program.cs 中添加服务注册:

// 注册通用 Redis 服务
builder.Services.AddSingleton<IRedisService, RedisService>();

4. 示例实战:通用服务的场景化使用#

4.1 在 API 控制器中使用#

创建 Controllers/RedisDemoController.cs,注入 IRedisService 并演示操作:

using Microsoft.AspNetCore.Mvc;
using RedisDemo.Services;
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
 
namespace RedisDemo.Controllers
{
    [ApiController]
    [Route("[controller]")]
    public class RedisDemoController : ControllerBase
    {
        private readonly IRedisService _redisService;
 
        public RedisDemoController(IRedisService redisService)
        {
            _redisService = redisService;
        }
 
        #region String 操作示例
        [HttpGet("string/get")]
        public async Task<IActionResult> GetString([FromQuery] string key)
        {
            var value = await _redisService.StringGetAsync<string>(key);
            return Ok(new { Key = key, Value = value });
        }
 
        [HttpPost("string/set")]
        public async Task<IActionResult> SetString([FromQuery] string key, [FromQuery] string value)
        {
            var success = await _redisService.StringSetAsync(key, value, TimeSpan.FromMinutes(10));
            return Ok(new { Success = success });
        }
        #endregion
 
        #region 分布式锁示例
        [HttpPost("lock/acquire")]
        public async Task<IActionResult> AcquireLock([FromQuery] string lockKey)
        {
            var lockValue = Guid.NewGuid().ToString();
            var success = await _redisService.AcquireLockAsync(lockKey, lockValue, TimeSpan.FromSeconds(30));
            return Ok(new { Success = success, LockValue = lockValue });
        }
 
        [HttpPost("lock/release")]
        public async Task<IActionResult> ReleaseLock([FromQuery] string lockKey, [FromQuery] string lockValue)
        {
            var success = await _redisService.ReleaseLockAsync(lockKey, lockValue);
            return Ok(new { Success = success });
        }
        #endregion
    }
}

4.2 测试接口#

启动项目后,通过 Swagger 或 Postman 测试接口:

  • GET /RedisDemo/string/get?key=testKey
  • POST /RedisDemo/string/set?key=testKey&value=helloRedis
  • POST /RedisDemo/lock/acquire?lockKey=order:123

5. 高级功能与优化#

5.1 缓存雪崩/击穿/穿透解决方案#

  • 缓存雪崩:为缓存键添加随机过期时间(如 TimeSpan.FromMinutes(30 + new Random().Next(10))
  • 缓存击穿:针对热点键,使用互斥锁 + 缓存重建逻辑
  • 缓存穿透:布隆过滤器过滤不存在的键,或缓存空值(设置较短过期时间)

5.2 序列化优化#

如果需要使用 Newtonsoft.Json 替代 System.Text.Json,可修改 Serialize/Deserialize 方法:

private string Serialize<T>(T value)
{
    if (value is string str) return str;
    return Newtonsoft.Json.JsonConvert.SerializeObject(value);
}
 
private T Deserialize<T>(RedisValue value)
{
    if (value.IsNull) return default;
    if (typeof(T) == typeof(string)) return (T)(object)value.ToString();
    return Newtonsoft.Json.JsonConvert.DeserializeObject<T>(value.ToString());
}

5.3 分布式锁续约#

针对长任务场景,可实现锁续约机制:

public async Task StartLockRenewal(string lockKey, string lockValue, TimeSpan expiry)
{
    var renewalInterval = expiry / 3;
    while (true)
    {
        await Task.Delay(renewalInterval);
        // 只有持有锁时才续约
        var script = @"
            if redis.call('GET', KEYS[1]) == ARGV[1] then
                return redis.call('EXPIRE', KEYS[1], ARGV[2])
            else
                return 0
            end";
        var result = await _database.ScriptEvaluateAsync(script, new RedisKey[] { GetFullKey($"lock:{lockKey}") }, new RedisValue[] { lockValue, expiry.TotalSeconds });
        if ((long)result == 0) break; // 锁已释放,停止续约
    }
}

6. 最佳实践#

6.1 连接池与复用#

  • IConnectionMultiplexer 是全局单例,不要每次操作都创建新实例
  • 配置合理的连接超时(connectTimeout=5000)和同步超时(syncTimeout=5000

6.2 键名规范#

采用 业务模块:功能:唯一标识 的格式,例如:

  • user:info:1001(用户信息)
  • order:lock:12345(订单锁)
  • cache:hot:productlist(热点商品列表)

6.3 过期策略#

  • 所有缓存键必须设置过期时间,避免内存泄漏
  • 热点数据可设置较长过期时间 + 主动更新逻辑
  • 临时数据设置较短过期时间(如 5-10 分钟)

6.4 错误处理与日志#

  • 捕获 Redis 连接异常、超时异常,降级处理(如直接查数据库)
  • 使用 Serilog 或 NLog 记录 Redis 操作日志,便于排查问题
try
{
    await _redisService.StringSetAsync(key, value);
}
catch (RedisConnectionException ex)
{
    _logger.LogError(ex, "Redis 连接失败,Key: {Key}", key);
    // 降级逻辑:查数据库
}

6.5 监控与调优#

  • 使用 Redis CLI 的 INFO stats 命令监控连接数、命中率
  • 配置 Redis 慢日志(slowlog get 10)排查慢查询
  • 生产环境开启 Redis 持久化(RDB + AOF)

7. 常见问题与排查#

7.1 连接失败#

  • 检查 Redis 地址、端口、密码是否正确
  • 确认防火墙/安全组是否开放 6379 端口
  • 检查 Redis 配置是否允许远程连接(bind 0.0.0.0

7.2 序列化失败#

  • 确保对象可序列化(无循环引用、私有字段需标记 [JsonProperty]
  • 复杂对象建议使用 Newtonsoft.Json 替代 System.Text.Json

7.3 分布式锁释放失败#

  • 确保释放锁的 lockValue 与获取锁时一致
  • 避免锁超时未续约导致的误释放

8. 总结与参考资料#

本文基于.NET 6 特性,完成了 Redis 从基础集成到通用服务封装、高级功能实现的全流程。通过封装通用服务,可大幅提升代码复用性与维护性,同时遵循最佳实践可避免常见的 Redis 坑。

参考资料#

  1. StackExchange.Redis 官方文档
  2. ASP.NET Core 分布式缓存文档
  3. Redis 官方文档
  4. Redis 设计与实现