九、.NET 6 集成通用 Redis 功能:从基础到高级实践
Redis 作为高性能的内存键值存储数据库,在.NET 应用中广泛用于缓存、分布式锁、消息队列等场景。直接使用原生 SDK 会导致代码重复、维护成本高,因此封装通用 Redis 服务是企业级项目的常规操作。本文将基于.NET 6 特性,从零开始完成 Redis 环境搭建、基础集成、通用功能封装、高级功能实现及最佳实践,帮助你快速构建可复用的 Redis 能力。
目录#
- 准备工作:Redis 环境与.NET 6 项目创建
- 基础集成:StackExchange.Redis 与.NET 6 服务注册
- 通用 Redis 服务封装:接口定义与核心实现
- 示例实战:通用服务的场景化使用
- 高级功能:分布式锁、缓存防雪崩等解决方案
- 最佳实践:连接池、键规范、错误处理等
- 常见问题与排查指南
- 总结与参考资料
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 RedisDemo2. 基础集成:StackExchange.Redis 服务注册#
.NET 生态中主流的 Redis SDK 是 StackExchange.Redis,官方维护且性能优异。
2.1 安装依赖包#
# 核心 Redis 包
dotnet add package StackExchange.Redis
# 官方缓存扩展(可选,用于 IDistributedCache 兼容)
dotnet add package Microsoft.Extensions.Caching.StackExchangeRedis2.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 坑。