GaoXinLibrary.Redis
1.0.2
dotnet add package GaoXinLibrary.Redis --version 1.0.2
NuGet\Install-Package GaoXinLibrary.Redis -Version 1.0.2
<PackageReference Include="GaoXinLibrary.Redis" Version="1.0.2" />
<PackageVersion Include="GaoXinLibrary.Redis" Version="1.0.2" />
<PackageReference Include="GaoXinLibrary.Redis" />
paket add GaoXinLibrary.Redis --version 1.0.2
#r "nuget: GaoXinLibrary.Redis, 1.0.2"
#:package GaoXinLibrary.Redis@1.0.2
#addin nuget:?package=GaoXinLibrary.Redis&version=1.0.2
#tool nuget:?package=GaoXinLibrary.Redis&version=1.0.2
GaoXinLibrary.Redis
StackExchange.Redis 简化封装库,支持 .NET 8/9/10。
安装
dotnet add package GaoXinLibrary.Redis
特性
- String 缓存 —
SetAsync<T>/GetAsync<T>自动 JSON 序列化,支持过期时间、SetNX、GetSet、GetDel、GetWithExpiry、SetRange、批量 MGET/MSET - Bitmap 位图 — SETBIT/GETBIT/BITCOUNT/BITOP/BITPOS,适用于用户签到、在线状态、布隆过滤器等场景
- Hash 操作 — 实体映射 Hash、表达式字段读写、HSCAN、批量 Pipeline 操作
- List 操作 — LPUSH/RPUSH/LPOP/RPOP/LRANGE/LINSERT/LTRIM/LMOVE/LPOS 完整支持
- Set 操作 — SADD/SREM/SMEMBERS/SISMEMBER/SPOP/SRANDMEMBER/SMOVE/SUNION/SINTER/SDIFF/SSCAN
- Sorted Set 操作 — ZADD/ZREM/ZSCORE/ZMSCORE/ZRANK/ZINCRBY/ZRANGE/ZRANGEBYSCORE/ZRANGEBYLEX/ZLEXCOUNT/ZPOPMIN/ZPOPMAX/ZSCAN
- HyperLogLog — PFADD/PFCOUNT/PFMERGE 基数估算
- Geo 地理位置 — GEOADD/GEODIST/GEOPOS/GEOHASH/GEOSEARCH 附近搜索,支持批量获取
- Stream 消息流 — XADD/XREAD/XRANGE/XDEL/XTRIM/XINFO/消费者组(XGROUP/XREADGROUP/XACK/XPENDING/XCLAIM/DELCONSUMER/DESTROY)
- 分布式锁(Redlock 增强) — 基础锁(SET NX + Lua 原子释放/续期)、自旋重试+随机退避、Watchdog 自动续期、锁丢失感知、
ExecuteWithLockAsync一行搞定 - 发布/订阅 —
PublishAsync/SubscribeAsync/ 模式订阅(PSUBSCRIBE) - 事务 —
CreateTransaction/ExecuteTransactionAsync支持条件(Condition) - Lua 脚本 —
ScriptEvaluateAsync/ScriptLoadAsync预加载脚本 - Pipeline 批量 —
ExecuteBatchAsync/HashGetFieldBatchAsync/HashGetBatchAsync单次网络往返 - Key 操作 — SCAN/COPY/TOUCH/UNLINK/DUMP/RESTORE/IDLETIME、批量删除、通配符删除、Rename、Type、Persist
- Key 前缀 — 全局
KeyPrefix配置,自动为所有 Key 添加前缀 - 原始命令 —
ExecuteAsync执行任意 Redis 命令,支持 SDK 未直接封装的操作 - 服务器操作 — INFO/DBSIZE/TIME/FLUSHDB/SLOWLOG/CLIENT LIST/CONFIG GET/CONFIG SET/SCRIPT FLUSH
- 健康检查 — 集成 ASP.NET Core
IHealthCheck,支持/health端点探活(含 Ping 延迟) - 配置验证 — 注册时即刻校验必填项,Fail-Fast 避免运行时才报错
- DI 注入 —
AddRedis()一行注册,Singleton 生命周期
快速开始
1. 注册服务
// 方式一:详细配置
builder.Services.AddRedisService(options =>
{
options.Host = "localhost";
options.Port = 6379;
options.Password = "your_password";
options.DatabaseIndex = 0;
options.KeyPrefix = "myapp:"; // 可选:全局 Key 前缀
});
// 方式二:连接字符串
builder.Services.AddRedisService("localhost:6379,password=your_password,defaultDatabase=0");
// 注册健康检查(可选)
builder.Services.AddRedisHealthCheckService();
2. String 缓存
public class UserService
{
private readonly IRedisService _redis;
public UserService(IRedisService redis) => _redis = redis;
public async Task CacheUserAsync(User user)
{
// JSON 序列化存储,30 分钟过期
await _redis.SetAsync($"user:{user.Id}", user, TimeSpan.FromMinutes(30));
// 读取
var cached = await _redis.GetAsync<User>($"user:{user.Id}");
// 仅当 Key 不存在时设置(SET NX)
var success = await _redis.SetIfNotExistsAsync("unique:key", user, TimeSpan.FromMinutes(5));
// 设置新值并返回旧值(GETSET)
var oldUser = await _redis.GetSetAsync("user:latest", user);
// 原始字符串操作
await _redis.SetStringAsync("greeting", "Hello, Redis!");
var greeting = await _redis.GetStringAsync("greeting");
// 追加字符串
await _redis.StringAppendAsync("greeting", " World!");
// 获取字符串子串
var sub = await _redis.StringGetRangeAsync("greeting", 0, 4); // "Hello"
// 获取字符串长度
var len = await _redis.StringLengthAsync("greeting");
// 原子计数器
await _redis.IncrementAsync("page:views", 1);
await _redis.DecrementAsync("stock:item_001", 1);
await _redis.IncrementAsync("price:rate", 0.5); // 浮点数递增
// 批量操作(MSET / MGET)
await _redis.SetMultipleAsync([
new("key1", "value1"),
new("key2", "value2"),
new("key3", "value3")
]);
var values = await _redis.GetMultipleAsync("key1", "key2", "key3");
// 获取值及其过期时间(GET + TTL)
var result = await _redis.StringGetWithExpiryAsync("user:1001");
Console.WriteLine($"Value: {result.Value}, TTL: {result.Expiry}");
// 获取并删除(GETDEL,Redis 6.2+)
var deleted = await _redis.StringGetDeleteAsync("temp:token");
// 覆盖指定偏移位置(SETRANGE)
await _redis.StringSetRangeAsync("greeting", 6, "Redis!");
}
}
3. Bitmap(位图)操作
Bitmap 并不是独立的数据结构,而是 String 类型上的位操作。适用于用户签到、在线状态、布隆过滤器等需要高效存储布尔值的场景。
public class BitmapService
{
private readonly IRedisService _redis;
public BitmapService(IRedisService redis) => _redis = redis;
public async Task TrackSignInAsync()
{
var userId = 1001;
var today = DateTime.Today;
var dayOfYear = today.DayOfYear;
// 设置签到位(SETBIT)
await _redis.StringSetBitAsync($"signin:{userId}:2024", dayOfYear, true);
// 检查是否签到(GETBIT)
var signed = await _redis.StringGetBitAsync($"signin:{userId}:2024", dayOfYear);
Console.WriteLine($"今天是否签到: {signed}");
// 统计全年签到天数(BITCOUNT)
var totalDays = await _redis.StringBitCountAsync($"signin:{userId}:2024");
Console.WriteLine($"全年签到 {totalDays} 天");
// 查找第一个签到的位置(BITPOS)
var firstSignIn = await _redis.StringBitPositionAsync($"signin:{userId}:2024", true);
Console.WriteLine($"第一次签到在第 {firstSignIn} 天");
// 位运算:统计两个用户都签到的天数(BITOP AND)
await _redis.StringBitOperationAsync(
Bitwise.And, "signin:common",
$"signin:1001:2024", $"signin:1002:2024");
var commonDays = await _redis.StringBitCountAsync("signin:common");
// 位运算:统计任意一个用户签到的天数(BITOP OR)
await _redis.StringBitOperationAsync(
Bitwise.Or, "signin:any",
$"signin:1001:2024", $"signin:1002:2024");
var anyDays = await _redis.StringBitCountAsync("signin:any");
}
public async Task TrackOnlineStatusAsync()
{
// 用 Bitmap 记录用户在线状态(每个 bit 代表一个用户)
await _redis.StringSetBitAsync("online:users", 1001, true); // 用户 1001 上线
await _redis.StringSetBitAsync("online:users", 1002, true); // 用户 1002 上线
await _redis.StringSetBitAsync("online:users", 1001, false); // 用户 1001 下线
// 统计在线用户数
var onlineCount = await _redis.StringBitCountAsync("online:users");
Console.WriteLine($"当前在线用户数: {onlineCount}");
}
}
4. Hash 操作
public class PlayerService
{
private readonly IRedisService _redis;
public PlayerService(IRedisService redis) => _redis = redis;
public async Task UpdatePlayerAsync()
{
var player = new Player { Name = "Alice", Level = 10, Score = 9999 };
// 实体写入 Hash(每个属性 = 一个 Field)
await _redis.HashSetAsync("player:1001", player);
// 读取完整实体
var cached = await _redis.HashGetAsync<Player>("player:1001");
// 获取所有字段名 / 值 / 数量
var keys = await _redis.HashKeysAsync("player:1001");
var values = await _redis.HashValuesAsync("player:1001");
var length = await _redis.HashLengthAsync("player:1001");
// 表达式读取单个字段
var level = await _redis.HashGetFieldAsync<Player, int>("player:1001", p => p.Level);
// 表达式批量读取多个字段
var fields = await _redis.HashGetFieldsAsync<Player, int>("player:1001",
p => p.Level, p => p.Score);
// 表达式设置单个字段
await _redis.HashSetFieldAsync<Player, int>("player:1001", p => p.Level, 20);
// 表达式原子递增 / 递减
var newScore = await _redis.HashIncrementAsync<Player, double>("player:1001", p => p.Score, 100);
var decreased = await _redis.HashDecrementAsync<Player, double>("player:1001", p => p.Score, 50);
// 删除多个字段
await _redis.HashDeleteFieldsAsync("player:1001", "TempField1", "TempField2");
// HSCAN 遍历大 Hash
await foreach (var entry in _redis.HashScanAsync("player:1001", "Score*"))
{
Console.WriteLine($"{entry.Name}: {entry.Value}");
}
}
}
5. Hash 批量操作(Pipeline)
public class LeaderboardService
{
private readonly IRedisService _redis;
public LeaderboardService(IRedisService redis) => _redis = redis;
public async Task GetTopPlayersAsync()
{
var keys = new List<string> { "player:1001", "player:1002", "player:1003" };
// 批量获取单字段(Pipeline,单次往返)
var scores = await _redis.HashGetFieldBatchAsync<Player, int>(keys, p => p.Score);
// 批量获取完整对象(Pipeline,单次往返)
var players = await _redis.HashGetBatchAsync<Player>(keys);
// 批量递增多个 Key 的同一字段
var increments = new Dictionary<int, int>
{
{ 1001, 10 }, // player:1001 的 Score +10
{ 1002, 20 }, // player:1002 的 Score +20
};
await _redis.HashIncrementBatchAsync<Player, int>(
p => p.Score, "player:", increments);
}
}
6. List 操作
public class TaskQueueService
{
private readonly IRedisService _redis;
public TaskQueueService(IRedisService redis) => _redis = redis;
public async Task ProcessTasksAsync()
{
// 推入任务(左侧 / 右侧)
await _redis.ListLeftPushAsync("tasks", new TaskItem { Id = 1, Name = "task1" });
await _redis.ListRightPushAsync("tasks", new TaskItem { Id = 2, Name = "task2" });
// 批量推入
var items = Enumerable.Range(3, 5).Select(i => new TaskItem { Id = i, Name = $"task{i}" });
await _redis.ListRightPushAsync("tasks", items);
// 弹出任务(左侧 / 右侧)
var first = await _redis.ListLeftPopAsync<TaskItem>("tasks");
var last = await _redis.ListRightPopAsync<TaskItem>("tasks");
// 获取列表范围
var all = await _redis.ListRangeAsync<TaskItem>("tasks");
var top3 = await _redis.ListRangeAsync<TaskItem>("tasks", 0, 2);
// 列表长度
var count = await _redis.ListLengthAsync("tasks");
// 按索引访问
var item = await _redis.ListGetByIndexAsync<TaskItem>("tasks", 0);
await _redis.ListSetByIndexAsync("tasks", 0, new TaskItem { Id = 99, Name = "updated" });
// 插入 / 移除 / 修剪
await _redis.ListInsertAsync("tasks",
new TaskItem { Id = 2 },
new TaskItem { Id = 100, Name = "inserted" },
insertBefore: true);
await _redis.ListRemoveAsync("tasks", new TaskItem { Id = 99 });
await _redis.ListTrimAsync("tasks", 0, 99); // 只保留前 100 个
// 从源列表弹出并推入目标列表(原子操作)
var moved = await _redis.ListMoveAsync<TaskItem>("tasks", "tasks:processing");
}
}
7. Set 操作
public class TagService
{
private readonly IRedisService _redis;
public TagService(IRedisService redis) => _redis = redis;
public async Task ManageTagsAsync()
{
// 添加成员
await _redis.SetAddAsync("tags:post:1", "C#");
await _redis.SetAddAsync("tags:post:1", new[] { ".NET", "Redis", "Docker" });
// 移除成员
await _redis.SetRemoveAsync("tags:post:1", "Docker");
// 获取所有成员 / 成员数量
var allTags = await _redis.SetMembersAsync<string>("tags:post:1");
var count = await _redis.SetLengthAsync("tags:post:1");
// 检查是否包含
var hasRedis = await _redis.SetContainsAsync("tags:post:1", "Redis");
// 随机弹出 / 随机获取
var popped = await _redis.SetPopAsync<string>("tags:post:1");
var random = await _redis.SetRandomMemberAsync<string>("tags:post:1");
var random3 = await _redis.SetRandomMembersAsync<string>("tags:post:1", 3);
// 集合运算
await _redis.SetAddAsync("tags:post:2", new[] { "C#", "Azure", "Kubernetes" });
var union = await _redis.SetUnionAsync<string>("tags:post:1", "tags:post:2"); // 并集
var inter = await _redis.SetIntersectAsync<string>("tags:post:1", "tags:post:2"); // 交集
var diff = await _redis.SetDifferenceAsync<string>("tags:post:1", "tags:post:2"); // 差集
// 集合运算并存储结果
await _redis.SetUnionStoreAsync("tags:all", "tags:post:1", "tags:post:2");
await _redis.SetIntersectStoreAsync("tags:common", "tags:post:1", "tags:post:2");
// SSCAN 遍历大集合
await foreach (var tag in _redis.SetScanAsync<string>("tags:all", "C*"))
{
Console.WriteLine(tag);
}
}
}
8. Sorted Set(有序集合)
public class RankingService
{
private readonly IRedisService _redis;
public RankingService(IRedisService redis) => _redis = redis;
public async Task ManageRankingAsync()
{
// 添加成员(score = 分数)
await _redis.SortedSetAddAsync("leaderboard", "Alice", 1000);
await _redis.SortedSetAddAsync("leaderboard", "Bob", 850);
await _redis.SortedSetAddAsync("leaderboard", "Charlie", 1200);
// 批量添加
await _redis.SortedSetAddAsync("leaderboard", [
new SortedSetEntry("David", 900),
new SortedSetEntry("Eve", 1100)
]);
// 分数递增 / 递减
var newScore = await _redis.SortedSetIncrementAsync("leaderboard", "Alice", 50); // 1050
var decScore = await _redis.SortedSetDecrementAsync("leaderboard", "Bob", 100); // 750
// 查询成员分数 / 排名
var score = await _redis.SortedSetScoreAsync("leaderboard", "Charlie"); // 1200
var rank = await _redis.SortedSetRankAsync("leaderboard", "Charlie", Order.Descending); // 0(第1名)
// 获取成员数量 / 分数范围内的数量
var total = await _redis.SortedSetLengthAsync("leaderboard");
var above1000 = await _redis.SortedSetCountAsync("leaderboard", 1000, double.PositiveInfinity);
// 按排名获取(Top 3)
var top3 = await _redis.SortedSetRangeByRankAsync<string>("leaderboard", 0, 2, Order.Descending);
var top3WithScores = await _redis.SortedSetRangeByRankWithScoresAsync("leaderboard", 0, 2, Order.Descending);
// 按分数范围获取
var elites = await _redis.SortedSetRangeByScoreAsync<string>("leaderboard", 1000, double.PositiveInfinity);
var elitesWithScores = await _redis.SortedSetRangeByScoreWithScoresAsync("leaderboard", 1000, double.PositiveInfinity);
// 弹出最高分 / 最低分
var highest = await _redis.SortedSetPopMaxAsync("leaderboard");
var lowest = await _redis.SortedSetPopMinAsync("leaderboard");
// 按分数 / 排名范围移除
await _redis.SortedSetRemoveRangeByScoreAsync("leaderboard", 0, 500); // 移除低分
await _redis.SortedSetRemoveRangeByRankAsync("leaderboard", 0, 0); // 移除最低排名
// 合并多个排行榜
await _redis.SortedSetUnionStoreAsync("leaderboard:total", "leaderboard:week1", "leaderboard:week2");
// ZSCAN 遍历
await foreach (var entry in _redis.SortedSetScanAsync("leaderboard"))
{
Console.WriteLine($"{entry.Element}: {entry.Score}");
}
}
}
9. HyperLogLog(基数估算)
适用于统计独立访客数(UV)、独立 IP 数等不需要精确值的大规模去重计数场景,内存开销固定约 12KB。
public class AnalyticsService
{
private readonly IRedisService _redis;
public AnalyticsService(IRedisService redis) => _redis = redis;
public async Task TrackUniqueVisitorsAsync()
{
// 记录访客
await _redis.HyperLogLogAddAsync("uv:2024-01-01", "user_001");
await _redis.HyperLogLogAddAsync("uv:2024-01-01", ["user_002", "user_003", "user_001"]);
// 获取基数估算值(不精确,标准误差 0.81%)
var uv = await _redis.HyperLogLogCountAsync("uv:2024-01-01"); // ≈ 3
// 合并多天数据
await _redis.HyperLogLogMergeAsync("uv:2024-01", "uv:2024-01-01", "uv:2024-01-02");
var monthlyUv = await _redis.HyperLogLogCountAsync("uv:2024-01");
}
}
10. Geo 地理位置
public class NearbyService
{
private readonly IRedisService _redis;
public NearbyService(IRedisService redis) => _redis = redis;
public async Task FindNearbyAsync()
{
// 添加地点
await _redis.GeoAddAsync("stores", 116.397128, 39.916527, "天安门");
await _redis.GeoAddAsync("stores", [
new GeoEntry(116.407526, 39.904030, "王府井"),
new GeoEntry(116.414205, 39.920510, "南锣鼓巷")
]);
// 获取位置
var pos = await _redis.GeoPositionAsync("stores", "天安门");
// 两点距离
var dist = await _redis.GeoDistanceAsync("stores", "天安门", "王府井", GeoUnit.Kilometers);
// GeoHash
var hash = await _redis.GeoHashAsync("stores", "天安门");
// 搜索成员附近(按距离排序)
var nearTiananmen = await _redis.GeoSearchAsync("stores", "天安门",
radius: 5, unit: GeoUnit.Kilometers, count: 10, order: Order.Ascending);
// 搜索指定坐标附近
var nearPoint = await _redis.GeoSearchAsync("stores",
116.400000, 39.910000, radius: 3, unit: GeoUnit.Kilometers);
// 移除成员
await _redis.GeoRemoveAsync("stores", "南锣鼓巷");
}
}
11. Stream 消息流
Redis Stream 是 Redis 5.0 引入的日志型数据结构,支持消费者组、消息确认、待处理列表等功能,适用于事件溯源、消息队列等场景。
public class EventStreamService
{
private readonly IRedisService _redis;
public EventStreamService(IRedisService redis) => _redis = redis;
public async Task BasicStreamAsync()
{
// 添加消息
var id1 = await _redis.StreamAddAsync("events", "type", "order_created");
var id2 = await _redis.StreamAddAsync("events", [
new NameValueEntry("type", "payment_received"),
new NameValueEntry("amount", "99.9"),
new NameValueEntry("orderId", "ORD001")
]);
// 添加消息(限制 Stream 最大长度,自动淘汰旧消息)
await _redis.StreamAddAsync("events", "type", "log", maxLength: 10000);
// 获取 Stream 长度 / 信息
var length = await _redis.StreamLengthAsync("events");
var info = await _redis.StreamInfoAsync("events");
// 按范围读取(XRANGE / XREVRANGE)
var all = await _redis.StreamRangeAsync("events");
var latest5 = await _redis.StreamReverseRangeAsync("events", count: 5);
// 从指定位置开始读取(XREAD)
var newMessages = await _redis.StreamReadAsync("events", "0-0", count: 10);
// 删除消息 / 修剪
await _redis.StreamDeleteAsync("events", id1!);
await _redis.StreamTrimAsync("events", 1000);
}
public async Task ConsumerGroupAsync()
{
// 创建消费者组
await _redis.StreamCreateConsumerGroupAsync("orders", "order-processors", "0");
// 消费者读取消息
var messages = await _redis.StreamReadGroupAsync("orders", "order-processors", "worker-1", count: 5);
foreach (var msg in messages)
{
Console.WriteLine($"消息 {msg.Id}: {string.Join(", ", msg.Values.Select(v => $"{v.Name}={v.Value}"))}");
// 处理完成后确认
await _redis.StreamAcknowledgeAsync("orders", "order-processors", msg.Id!);
}
// 查看待处理消息摘要
var pending = await _redis.StreamPendingAsync("orders", "order-processors");
Console.WriteLine($"待处理: {pending.PendingMessageCount}");
// 认领超时消息(超过 60 秒未确认的消息转给 worker-2)
var claimed = await _redis.StreamClaimAsync("orders", "order-processors", "worker-2",
minIdleTimeMs: 60000, "1234567890-0");
}
}
12. 分布式锁(Redlock 增强)
基于 Redlock 算法核心思想实现:唯一标识值 + Lua 原子操作 + 自旋重试(随机退避) + Watchdog 自动续期 + 锁丢失感知。
public class OrderService
{
private readonly IRedisService _redis;
public OrderService(IRedisService redis) => _redis = redis;
// ==================== 方式一:手动管理锁 ====================
public async Task ManualLockAsync(string orderId)
{
var lockKey = $"lock:order:{orderId}";
var lockValue = Guid.NewGuid().ToString("N");
// 单次尝试获取锁,10 秒自动过期
if (await _redis.LockAsync(lockKey, lockValue, TimeSpan.FromSeconds(10)))
{
try
{
// 长任务可续期(仅当 value 匹配时才延长)
await _redis.LockExtendAsync(lockKey, lockValue, TimeSpan.FromSeconds(10));
// 执行业务逻辑...
}
finally
{
// 原子释放锁(仅当 value 匹配时才释放,防止误删)
await _redis.LockReleaseAsync(lockKey, lockValue);
}
}
}
// ==================== 方式二:自旋重试 + 随机退避 ====================
public async Task RetryLockAsync(string orderId)
{
var lockKey = $"lock:order:{orderId}";
var lockValue = Guid.NewGuid().ToString("N");
// 最多重试 5 次,每次间隔 200ms + 随机抖动(避免惊群效应)
var acquired = await _redis.LockWithRetryAsync(
lockKey, lockValue, TimeSpan.FromSeconds(30),
retryCount: 5, retryDelay: TimeSpan.FromMilliseconds(200));
if (acquired)
{
try { /* 业务逻辑 */ }
finally { await _redis.LockReleaseAsync(lockKey, lockValue); }
}
}
// ==================== 方式三:Watchdog 自动续期锁句柄 ====================
public async Task WatchdogLockAsync(string orderId)
{
var lockKey = $"lock:order:{orderId}";
// 获取自动续期的锁句柄(内部自动生成唯一 value)
// Watchdog 默认每 expiry/3 续期一次,防止业务未完成锁就过期
await using var lockHandle = await _redis.AcquireLockAsync(
lockKey, TimeSpan.FromSeconds(30));
if (lockHandle is null)
{
Console.WriteLine("获取锁失败");
return;
}
// 监听锁丢失事件(可选)
lockHandle.LockLost += lostLock =>
Console.WriteLine($"锁已丢失: {lostLock.Key}");
// lockHandle.LockLostToken 会在锁丢失时被取消
// Dispose 时自动释放锁
}
// ==================== 方式四:Watchdog + 自旋重试 ====================
public async Task WatchdogRetryLockAsync(string orderId)
{
var lockKey = $"lock:order:{orderId}";
// 结合重试策略与 Watchdog 自动续期
await using var lockHandle = await _redis.AcquireLockAsync(
lockKey, TimeSpan.FromSeconds(30),
retryCount: 3, retryDelay: TimeSpan.FromMilliseconds(200));
if (lockHandle is null) return;
// 使用 LockLostToken 传递给下游操作
await DoWorkAsync(lockHandle.LockLostToken);
}
// ==================== 方式五:一行搞定(推荐) ====================
public async Task ExecuteWithLockAsync(string orderId)
{
var lockKey = $"lock:order:{orderId}";
// 自动获取 → 自动续期 → 执行 → 自动释放
// ct 是联合取消令牌:外部取消 或 锁丢失 均会触发
await _redis.ExecuteWithLockAsync(
lockKey, TimeSpan.FromSeconds(30),
async ct =>
{
await ProcessOrderAsync(orderId, ct);
},
retryCount: 3);
}
// 带返回值的版本
public async Task<decimal> CalculateWithLockAsync(string orderId)
{
return await _redis.ExecuteWithLockAsync(
$"lock:order:{orderId}", TimeSpan.FromSeconds(30),
async ct =>
{
// 锁丢失时 ct 会被取消,业务可及时中止
ct.ThrowIfCancellationRequested();
return await CalculateTotalAsync(orderId);
});
}
// ==================== 辅助:查询锁状态 ====================
public async Task QueryLockAsync(string orderId)
{
var lockKey = $"lock:order:{orderId}";
// 查看锁的持有者标识值
var holder = await _redis.LockQueryAsync(lockKey);
Console.WriteLine(holder is null ? "锁未被持有" : $"锁持有者: {holder}");
// 查看锁剩余 TTL
var ttl = await _redis.LockTimeToLiveAsync(lockKey);
Console.WriteLine(ttl.HasValue ? $"剩余 TTL: {ttl.Value.TotalSeconds}s" : "锁不存在");
}
private Task ProcessOrderAsync(string orderId, CancellationToken ct) => Task.CompletedTask;
private Task<decimal> CalculateTotalAsync(string orderId) => Task.FromResult(99.9m);
private Task DoWorkAsync(CancellationToken ct) => Task.CompletedTask;
}
13. 发布/订阅
public class NotificationService
{
private readonly IRedisService _redis;
public NotificationService(IRedisService redis) => _redis = redis;
public async Task SetupAsync()
{
// 精确频道订阅
await _redis.SubscribeAsync("notifications", (channel, message) =>
{
Console.WriteLine($"收到消息 [{channel}]: {message}");
});
// 模式订阅(PSUBSCRIBE,支持通配符)
await _redis.SubscribePatternAsync("order.*", (channel, message) =>
{
Console.WriteLine($"订单事件 [{channel}]: {message}");
});
// 发布字符串消息
await _redis.PublishAsync("notifications", "系统维护通知");
// 发布 JSON 消息
await _redis.PublishAsync("order.created", new { OrderId = "001", Amount = 99.9 });
// 取消订阅
await _redis.UnsubscribeAsync("notifications");
await _redis.UnsubscribePatternAsync("order.*");
}
}
14. 事务(Transaction)
Redis 事务保证一组命令原子执行(MULTI/EXEC),并支持通过 Condition 设置前置条件。
public class TransferService
{
private readonly IRedisService _redis;
public TransferService(IRedisService redis) => _redis = redis;
public async Task TransferAsync(string fromKey, string toKey, decimal amount)
{
// 带条件的事务:仅当 fromKey 存在时才执行
var committed = await _redis.ExecuteTransactionAsync(
conditions: tran => [Condition.KeyExists(fromKey)],
operations: tran =>
[
tran.StringDecrementAsync(fromKey, (long)amount),
tran.StringIncrementAsync(toKey, (long)amount)
]);
if (committed)
Console.WriteLine("转账成功");
else
Console.WriteLine("转账失败(条件不满足)");
}
public async Task AdvancedTransactionAsync()
{
// 直接使用 ITransaction 获得完全控制
var tran = _redis.CreateTransaction();
tran.AddCondition(Condition.StringEqual("account:lock", "unlocked"));
var decrTask = tran.StringDecrementAsync("account:from", 100);
var incrTask = tran.StringIncrementAsync("account:to", 100);
var setTask = tran.StringSetAsync("account:lock", "locked");
if (await tran.ExecuteAsync())
{
await Task.WhenAll(decrTask, incrTask, setTask);
Console.WriteLine("事务提交成功");
}
}
}
15. Lua 脚本
public class ScriptService
{
private readonly IRedisService _redis;
public ScriptService(IRedisService redis) => _redis = redis;
public async Task ExecuteScriptAsync()
{
// 直接执行脚本
var result = await _redis.ScriptEvaluateAsync(
"return redis.call('get', KEYS[1])",
keys: [(RedisKey)"mykey"]);
// 预加载脚本(只传输一次,后续用 SHA 调用,减少网络开销)
var sha = await _redis.ScriptLoadAsync("""
local current = redis.call('get', KEYS[1])
if current == ARGV[1] then
redis.call('set', KEYS[1], ARGV[2])
return 1
else
return 0
end
""");
// 通过 SHA 执行(EVALSHA)
var swapResult = await _redis.ScriptEvaluateAsync(sha,
keys: [(RedisKey)"mykey"],
values: ["old_value", "new_value"]);
}
}
16. Key 管理
public class KeyManagementService
{
private readonly IRedisService _redis;
public KeyManagementService(IRedisService redis) => _redis = redis;
public async Task ManageKeysAsync()
{
// 基础操作
var exists = await _redis.KeyExistsAsync("mykey");
var type = await _redis.KeyTypeAsync("mykey"); // String / Hash / List / Set / SortedSet / Stream
var ttl = await _redis.KeyTimeToLiveAsync("mykey");
// 过期时间
await _redis.KeyExpireAsync("mykey", TimeSpan.FromMinutes(30));
await _redis.KeyExpireAtAsync("mykey", DateTime.UtcNow.AddHours(1));
await _redis.KeyPersistAsync("mykey"); // 移除过期时间
// 重命名
await _redis.KeyRenameAsync("oldkey", "newkey");
// 删除
await _redis.DeleteAsync("key1");
await _redis.DeleteAsync("key1", "key2", "key3"); // 批量删除
// 通配符删除(基于 Lua 脚本,注意生产环境数据量)
await _redis.DeleteByPatternAsync("temp:*");
// SCAN 安全遍历(生产环境推荐,不阻塞 Redis)
await foreach (var key in _redis.ScanKeysAsync("user:*"))
{
Console.WriteLine(key);
}
// 序列化 Key 的值(DUMP)
var dump = await _redis.KeyDumpAsync("mykey");
// 随机获取一个 Key
var randomKey = await _redis.KeyRandomAsync();
}
}
17. Pipeline 批量操作
public class BatchService
{
private readonly IRedisService _redis;
public BatchService(IRedisService redis) => _redis = redis;
public async Task BatchOperationsAsync()
{
await _redis.ExecuteBatchAsync(
batch => batch.StringSetAsync("key1", "value1"),
batch => batch.StringSetAsync("key2", "value2"),
batch => batch.HashSetAsync("hash:1", [new HashEntry("field1", "value1")])
);
}
}
18. 服务器操作
public class AdminService
{
private readonly IRedisService _redis;
public AdminService(IRedisService redis) => _redis = redis;
public async Task ServerInfoAsync()
{
// 服务器信息
var info = await _redis.ServerInfoAsync();
// 当前数据库 Key 数量
var dbSize = await _redis.DatabaseSizeAsync();
// Redis 服务器时间
var serverTime = await _redis.ServerTimeAsync();
// 最近一次持久化时间
var lastSave = await _redis.LastSaveAsync();
// 慢查询日志
var slowLog = await _redis.SlowLogGetAsync(10);
// 客户端连接列表
var clients = await _redis.ClientListAsync();
// 获取/设置服务器配置
var config = await _redis.ConfigGetAsync("maxmemory*");
await _redis.ConfigSetAsync("hz", "100");
// 清除脚本缓存
await _redis.ScriptFlushAsync();
// 执行任意 Redis 命令(用于 SDK 未直接封装的操作)
var pong = await _redis.ExecuteAsync("PING");
var objectEncoding = await _redis.ExecuteAsync("OBJECT", "ENCODING", "mykey");
// 清空数据库(需要 AllowAdmin=true)
// await _redis.FlushDatabaseAsync();
}
}
Key 前缀
配置 KeyPrefix 后,所有操作的 Key 会自动添加前缀,适合多应用共享同一 Redis 实例的场景:
builder.Services.AddRedis(options =>
{
options.Host = "localhost";
options.KeyPrefix = "myapp:";
});
// 实际存储的 Key 为 "myapp:user:1001"
await redis.SetAsync("user:1001", user);
// 实际查询的 Key 为 "myapp:user:1001"
var cached = await redis.GetAsync<User>("user:1001");
注意:
GetDatabase()返回的原始IDatabase不会自动添加前缀,直接使用时需手动拼接。Pub/Sub 的频道名也不受KeyPrefix影响。
数据结构对比
| 数据结构 | 适用场景 | 时间复杂度(常用操作) |
|---|---|---|
| String | 缓存、计数器、分布式锁 | GET/SET: O(1) |
| Bitmap | 用户签到、在线状态、布隆过滤器 | SETBIT/GETBIT: O(1),BITCOUNT: O(N) |
| Hash | 对象存储、局部更新 | HGET/HSET: O(1) |
| List | 消息队列、最新列表 | LPUSH/RPOP: O(1),LRANGE: O(S+N) |
| Set | 标签、去重、集合运算 | SADD/SISMEMBER: O(1),SUNION: O(N) |
| Sorted Set | 排行榜、延迟队列、范围查询 | ZADD/ZSCORE: O(log N),ZRANGE: O(log N+M) |
| HyperLogLog | UV 统计、大规模去重计数 | PFADD: O(1),PFCOUNT: O(1),固定 12KB |
| Geo | 附近的人/店、距离计算 | GEOADD: O(log N),GEOSEARCH: O(N+log M) |
| Stream | 事件溯源、消息队列、日志 | XADD: O(1),XREAD: O(N) |
分布式锁说明(Redlock 增强)
API 一览
| 方法 | 说明 |
|---|---|
LockAsync |
单次尝试获取锁(SET NX PX 原子操作) |
LockReleaseAsync |
Lua 原子释放(compare-and-delete,防止误删他人锁) |
LockExtendAsync |
Lua 原子续期(compare-and-pexpire,防止续期他人锁) |
LockWithRetryAsync |
自旋重试 + 随机退避(Redlock 推荐策略,避免惊群效应) |
LockQueryAsync |
查询锁的当前持有者标识值 |
LockTimeToLiveAsync |
查询锁 Key 的剩余 TTL |
AcquireLockAsync (单次) |
获取 Watchdog 自动续期锁句柄(IAsyncDisposable,Dispose 自动释放) |
AcquireLockAsync (重试) |
Watchdog + 自旋重试,适用于高竞争场景 |
ExecuteWithLockAsync |
一行搞定:自动获取 → 自动续期 → 执行 → 自动释放 |
ExecuteWithLockAsync<T> |
同上,支持返回值 |
IDistributedLock 锁句柄
| 成员 | 说明 |
|---|---|
Key |
锁的 Key(不含全局前缀) |
Value |
锁的唯一标识值 |
IsAcquired |
锁是否仍然持有 |
AcquiredAtUtc |
锁的获取时间(UTC) |
Expiry |
锁的过期时长 |
LockLostToken |
锁丢失时被取消的 CancellationToken,可传递给业务操作 |
LockLost 事件 |
锁丢失时触发的回调 |
ExtendAsync |
手动续期 |
ReleaseAsync |
手动释放 |
DisposeAsync |
停止续期并释放锁 |
Redlock 安全特性
- 唯一标识值 —
AcquireLockAsync内部使用Guid.NewGuid().ToString("N"),防止误删他人锁 - Lua 原子操作 — 释放/续期均通过 Lua 脚本保证 compare-and-operate 原子性
- 随机退避 —
retryDelay + Random.Shared jitter避免多客户端同时竞争导致的惊群效应 - Watchdog 自动续期 — 后台定时器(默认 expiry / 3 间隔)自动延长锁 TTL,防止业务未完成锁就过期
- 锁丢失感知 —
LockLostToken+LockLost事件,续期失败时自动通知业务层 - 联合取消令牌 —
ExecuteWithLockAsync将外部 CancellationToken 和 LockLostToken 合并,任一取消均中止操作
使用建议
| 场景 | 推荐方式 |
|---|---|
| 简单短任务(<1s) | LockAsync + try/finally + LockReleaseAsync |
| 可能竞争的场景 | LockWithRetryAsync |
| 长任务(秒级~分钟级) | AcquireLockAsync(Watchdog 自动续期) |
| 最简使用 | ExecuteWithLockAsync(推荐,一行搞定) |
| 需要感知锁状态 | AcquireLockAsync + LockLost 事件 / LockLostToken |
健康检查
builder.Services.AddRedis(options => { /* ... */ });
builder.Services.AddRedisHealthCheck(); // 默认名称 "redis"
// builder.Services.AddRedisHealthCheck("cache", "ready", "infrastructure");
app.MapHealthChecks("/health");
响应示例:
{
"status": "Healthy",
"entries": {
"redis": {
"status": "Healthy",
"description": "Redis 连接正常(Ping: 0.3ms)"
}
}
}
配置项
| 属性 | 默认值 | 说明 |
|---|---|---|
| ConnectionString | null | Redis 连接字符串(与 Host/Port 二选一) |
| Host | localhost | 主机名 |
| Port | 6379 | 端口 |
| Password | null | 密码 |
| DatabaseIndex | 0 | 默认数据库索引(0-15) |
| ConnectTimeout | 5000 | 连接超时(毫秒) |
| SyncTimeout | 5000 | 同步操作超时(毫秒) |
| AsyncTimeout | 5000 | 异步操作超时(毫秒) |
| AllowAdmin | false | 是否允许管理员命令(FLUSHDB 等) |
| Ssl | false | 是否启用 SSL |
| AbortOnConnectFail | false | 连接失败时是否中止 |
| ClientName | null | 客户端名称(CLIENT LIST 可见) |
| KeyPrefix | null | 全局 Key 前缀 |
常见问题
Q:如何访问底层 StackExchange.Redis API?
var db = redis.GetDatabase(); // IDatabase(不带 Key 前缀)
var connection = redis.GetConnectionMultiplexer(); // IConnectionMultiplexer
Q:Hash 操作支持哪些类型?
支持所有基础类型(int/long/double/string/bool/DateTime/DateTimeOffset/Guid)以及枚举类型。枚举类型以 long 值存储,读取时自动转换回枚举。
Q:DeleteByPatternAsync 在生产环境是否安全?
此方法使用 KEYS 命令扫描匹配的 Key,在数据量极大时可能阻塞 Redis。生产环境建议使用 ScanKeysAsync(基于 SCAN,不阻塞)逐批处理。
Q:List 和 Stream 如何选择做消息队列?
- List:简单 FIFO 队列,
LPUSH+RPOP,无消费确认、无消费者组 - Stream:功能完整的消息队列,支持消费者组、消息确认(ACK)、待处理列表(PEL)、消息持久化、范围查询,推荐复杂场景使用
Q:HyperLogLog 精确吗?
不精确,标准误差 0.81%,但内存开销固定约 12KB(无论元素数量)。适合 UV 统计等不需要精确值的场景。
Q:Sorted Set 的 score 支持哪些类型?
Score 类型为 double,支持 double.NegativeInfinity 和 double.PositiveInfinity 表示最小/最大值。
Q:事务和 Lua 脚本如何选择?
- 事务(Transaction):支持条件(Condition),但不支持在事务中读取值并基于值做分支逻辑
- Lua 脚本:完全原子,支持读-写-分支的复杂逻辑,推荐需要 CAS 操作的场景
Q:KeyPrefix 影响哪些操作?
影响所有 Key 操作(String/Bitmap/Hash/List/Set/SortedSet/HyperLogLog/Geo/Stream/Lock)。不影响 Pub/Sub 频道名和服务器命令。
Q:Bitmap 和 Set 如何选择做去重统计?
- Bitmap:内存效率极高(每个用户只占 1 bit),适合 ID 较小且连续的场景(如用户 ID 从 1 开始递增)。如 1 亿用户只需约 12MB。
- Set:支持查看具体成员,适合需要枚举成员的场景。
- HyperLogLog:固定 12KB,但不精确(误差 0.81%),且无法查看具体成员。
Q:如何执行 SDK 未直接封装的 Redis 命令?
// 使用 ExecuteAsync 执行任意命令
var result = await redis.ExecuteAsync("OBJECT", "ENCODING", "mykey");
var refCount = await redis.ExecuteAsync("OBJECT", "REFCOUNT", "mykey");
// 或者获取底层 IDatabase 直接操作
var db = redis.GetDatabase();
| Product | Versions Compatible and additional computed target framework versions. |
|---|---|
| .NET | net8.0 is compatible. net8.0-android was computed. net8.0-browser was computed. net8.0-ios was computed. net8.0-maccatalyst was computed. net8.0-macos was computed. net8.0-tvos was computed. net8.0-windows was computed. net9.0 is compatible. net9.0-android was computed. net9.0-browser was computed. net9.0-ios was computed. net9.0-maccatalyst was computed. net9.0-macos was computed. net9.0-tvos was computed. net9.0-windows was computed. net10.0 is compatible. net10.0-android was computed. net10.0-browser was computed. net10.0-ios was computed. net10.0-maccatalyst was computed. net10.0-macos was computed. net10.0-tvos was computed. net10.0-windows was computed. |
-
net10.0
- Microsoft.Extensions.DependencyInjection.Abstractions (>= 10.0.5)
- Microsoft.Extensions.Diagnostics.HealthChecks (>= 10.0.5)
- Microsoft.Extensions.Logging.Abstractions (>= 10.0.5)
- Microsoft.Extensions.Options (>= 10.0.5)
- StackExchange.Redis (>= 2.12.1)
-
net8.0
- Microsoft.Extensions.DependencyInjection.Abstractions (>= 8.0.2)
- Microsoft.Extensions.Diagnostics.HealthChecks (>= 8.0.25)
- Microsoft.Extensions.Logging.Abstractions (>= 8.0.3)
- Microsoft.Extensions.Options (>= 8.0.2)
- StackExchange.Redis (>= 2.12.1)
-
net9.0
- Microsoft.Extensions.DependencyInjection.Abstractions (>= 9.0.14)
- Microsoft.Extensions.Diagnostics.HealthChecks (>= 9.0.14)
- Microsoft.Extensions.Logging.Abstractions (>= 9.0.14)
- Microsoft.Extensions.Options (>= 9.0.14)
- StackExchange.Redis (>= 2.12.1)
NuGet packages
This package is not used by any NuGet packages.
GitHub repositories
This package is not used by any popular GitHub repositories.