GaoXinLibrary.Redis 1.0.2

dotnet add package GaoXinLibrary.Redis --version 1.0.2
                    
NuGet\Install-Package GaoXinLibrary.Redis -Version 1.0.2
                    
This command is intended to be used within the Package Manager Console in Visual Studio, as it uses the NuGet module's version of Install-Package.
<PackageReference Include="GaoXinLibrary.Redis" Version="1.0.2" />
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="GaoXinLibrary.Redis" Version="1.0.2" />
                    
Directory.Packages.props
<PackageReference Include="GaoXinLibrary.Redis" />
                    
Project file
For projects that support Central Package Management (CPM), copy this XML node into the solution Directory.Packages.props file to version the package.
paket add GaoXinLibrary.Redis --version 1.0.2
                    
#r "nuget: GaoXinLibrary.Redis, 1.0.2"
                    
#r directive can be used in F# Interactive and Polyglot Notebooks. Copy this into the interactive tool or source code of the script to reference the package.
#:package GaoXinLibrary.Redis@1.0.2
                    
#:package directive can be used in C# file-based apps starting in .NET 10 preview 4. Copy this into a .cs file before any lines of code to reference the package.
#addin nuget:?package=GaoXinLibrary.Redis&version=1.0.2
                    
Install as a Cake Addin
#tool nuget:?package=GaoXinLibrary.Redis&version=1.0.2
                    
Install as a Cake Tool

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.NegativeInfinitydouble.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 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. 
Compatible target framework(s)
Included target framework(s) (in package)
Learn more about Target Frameworks and .NET Standard.

NuGet packages

This package is not used by any NuGet packages.

GitHub repositories

This package is not used by any popular GitHub repositories.

Version Downloads Last Updated
1.0.2 107 3/18/2026
1.0.1 102 3/18/2026