概览 - Snet Docs

🗄️ Redis 概述

Snet.Redis 是一个高性能 Redis 客户端库,基于 StackExchange.Redis 封装,面向 .NET 8/10。它提供了统一的 API,提供 String、List、Hash、SortedSet 与通用 Key 操作(没有独立 Set API)以及发布/订阅功能。主要特性包括 TAG 前缀命名空间、对象存储的 JSON 序列化、同步/异步双模 API、连接事件监控以及基于 Lua 脚本的 通配符键删除。

属性 值
包名 Snet.Redis
命名空间 Snet.Redis
目标框架 .NET 8.0 / .NET 10.0
依赖项 StackExchange.Redis 3.4.20, Snet.Core
许可证 MIT

安装版本基线为 26.301.1(2026-10-29 已核对 NuGet)。本文行为及第三方依赖按当前源码核对;源码 <Version>1.0.0.0</Version> 与内部 ProjectReference 是构建配置,不是安装版本;源码行为可能晚于已发布包。


▶️ 快速开始

安装包:

dotnet add package Snet.Redis

以下示例涵盖连接、字符串操作、对象序列化与断开连接:

using Snet.Redis;

var redis = RedisOperate.Instance(new RedisData.Basics
{
    ConnectStr = "127.0.0.1:6379",
    DataBaseID = 0,
    Expiry = 86400000,     // 24 小时(毫秒)
    TAG = "MyApp:"
});

// 连接
var result = await redis.OnAsync();
if (!result.Status) { Console.WriteLine($"连接失败: {result.Message}"); return; }

// 字符串操作
await redis.StringSetAsync("greeting", "Hello Redis!");
var value = await redis.StringGetAsync("greeting");
Console.WriteLine(value); // "Hello Redis!"

// 对象序列化
await redis.StringSetAsync("user:1", new { Name = "John", Age = 30 });
var user = await redis.StringGetAsync<User>("user:1");
Console.WriteLine($"{user.Name}, {user.Age}");

// 断开连接
await redis.OffAsync();

// 类型声明放在顶级语句之后。
public class User
{
    public string Name { get; set; } = string.Empty;
    public int Age { get; set; }
}

⚙️ 安装与配置

通过 RedisData.Basics 类进行配置:

参数 类型 默认值 描述
ConnectStr string — Redis 连接字符串(如 "127.0.0.1:6379")
DataBaseID int 0 Redis 数据库编号(0-15)
Expiry int 86400000 单值 StringSet/StringSetAsync(含泛型重载)的 TTL,单位毫秒,默认 24 小时
TAG string "S:" 键前缀,用于命名空间隔离

连接字符串格式:

// 本地
"127.0.0.1:6379"

// 远程 + 密码
"remote-host:6379,password=xxx"

// 哨兵模式
"sentinel-host:26379,serviceName=mymaster"

// 多端点
"host1:6379,host2:6379,host3:6379"

🧠 核心概念

1. TAG 前缀系统

大多数单键操作通过 GTAG() 添加非空白的 TAG。批量 KeyDelete(IEnumerable<string>) 及其异步版本接收完整 Redis 键,不自动添加 TAG。KeyRename/KeyRenameAsync 仅给旧键添加前缀,newKey 原样使用。发布/订阅频道也原样使用传入名称,不添加 TAG。

// 当 TAG = "MyApp:"
await redis.StringSetAsync("user:1", "Alice");
// 实际 Redis 键: "MyApp:user:1"

2. JSON 序列化

对象使用 value.ToJson()(来自 Snet.Core)存储,并通过 JsonSerializer.Deserialize<T>() 读取。这允许无缝存储复杂的 .NET 对象,无需手动序列化。

await redis.StringSetAsync("user:1", new User { Name = "John", Age = 30 });
var user = await redis.StringGetAsync<User>("user:1");

3. 同步/异步双模 API

操作提供同步和异步版本,但部分参数和行为有所不同:

// 同步
redis.StringSet("key", "value");
var val = redis.StringGet("key");

// 异步
await redis.StringSetAsync("key", "value");
var val = await redis.StringGetAsync("key");

4. 单例模式

RedisOperate 内部使用 CoreUnify<T, B>.Instance(),确保每个唯一配置对应一个连接池。重用相同配置会返回同一实例。

5. 连接事件

库暴露七种连接事件,均通过 OnInfoEvent 上报:

事件 描述
ConnectionRestored 丢失的连接重新建立时触发
ConnectionFailed 连接尝试失败时触发
ErrorMessage Redis 错误消息时触发
ConfigurationChanged 主/副本拓扑变更时触发
HashSlotMoved 哈希槽迁移时触发
InternalError 库内部错误时触发
ConfigurationChangedBroadcast 广播配置变更时触发
redis.OnInfoEvent += (sender, e) =>
{
    Console.WriteLine($"[{e.Status}] {e.Message}");
};

6. 断开连接时的返回值约定

大多数数据操作先调用 GetStatus(),若检查结果为未连接则返回下表中的值。这不是异常兜底保证:命令失败和 JSON 反序列化异常可以向外抛出。发布/订阅方法直接使用连接,没有断连默认值检查。

返回类型 默认值
bool false
long -1
double -1(两个 SortedSetIncrement 版本)
string? null
T? default
IEnumerable? null

📚 API 参考

生命周期方法

方法 签名 描述
On OperateResult On() 同步连接
OnAsync Task<OperateResult> OnAsync(CancellationToken token = default) 异步连接
Off OperateResult Off(bool hardClose = false) 同步断开连接
OffAsync Task<OperateResult> OffAsync(bool hardClose = false, CancellationToken token = default) 异步断开连接
GetStatus OperateResult GetStatus() 获取连接状态
GetStatusAsync Task<OperateResult> GetStatusAsync(CancellationToken token = default) 异步获取连接状态
Dispose void Dispose() 清理资源
DisposeAsync ValueTask DisposeAsync() 异步清理资源
GetBaseObject OperateResult GetBaseObject() 获取底层 ConnectionMultiplexer(通过 ResultData)
GetBaseObjectAsync Task<OperateResult> GetBaseObjectAsync(CancellationToken token = default) 异步获取底层连接对象

字符串操作(10 个方法)

方法 返回 描述
StringSet(key, value) bool 存储字符串值
StringSetAsync(key, value) Task<bool> 异步存储字符串
StringGet(key) string? 获取字符串值
StringGetAsync(key) Task<string?> 异步获取字符串
StringSet<T>(key, value) bool 以 JSON 格式存储对象
StringSetAsync<T>(key, value) Task<bool> 异步以 JSON 格式存储对象
StringGet<T>(key) T? 获取并反序列化对象
StringGetAsync<T>(key) Task<T?> 异步获取并反序列化对象
StringSet(kvPairs) bool 批量存储键值对
StringSetAsync(kvPairs) Task<bool> 异步批量存储

列表操作(22 个方法)

方法 返回 描述
ListLeftPush(key, value) long 将值推入列表头部
ListLeftPushAsync(key, value) Task<long> 异步推入头部
ListRightPush(key, value) long 将值推入列表尾部
ListRightPushAsync(key, value) Task<long> 异步推入尾部
ListLeftPop(key) string? 从头部弹出值
ListLeftPopAsync(key) Task<string?> 异步从头部弹出
ListRightPop(key) string? 从尾部弹出值
ListRightPopAsync(key) Task<string?> 异步从尾部弹出
ListLeftPush<T>(key, value) long 将序列化对象推入头部
ListLeftPushAsync<T>(key, value) Task<long> 异步推入序列化对象到头部
ListRightPush<T>(key, value) long 将序列化对象推入尾部
ListRightPushAsync<T>(key, value) Task<long> 异步推入序列化对象到尾部
ListLeftPop<T>(key) T? 从头部弹出并反序列化
ListLeftPopAsync<T>(key) Task<T?> 异步从头部弹出并反序列化
ListRightPop<T>(key) T? 从尾部弹出并反序列化
ListRightPopAsync<T>(key) Task<T?> 异步从尾部弹出并反序列化
ListRemove(key, value) long 从列表中移除值
ListRemoveAsync(key, value) Task<long> 异步移除值
ListLength(key) long 获取列表长度
ListLengthAsync(key) Task<long> 异步获取列表长度
ListRange(key, start, stop) IEnumerable<string?> 获取范围内的值
ListRangeAsync(key, start, stop) Task<IEnumerable<string?>?> 异步获取范围内的值

哈希操作(22 个方法)

方法 返回 描述
HashExists(key, hashField) bool 检查哈希字段是否存在
HashExistsAsync(key, hashField) Task<bool> 异步检查字段存在性
HashDelete(key, hashField) bool 删除哈希字段
HashDeleteAsync(key, hashField) Task<bool> 异步删除字段
HashSet(key, hashField, value) bool 设置哈希字段值
HashSetAsync(key, hashField, value) Task<bool> 异步设置字段值
HashGet(key, hashField) string? 获取哈希字段值
HashGetAsync(key, hashField) Task<string?> 异步获取字段值
HashSet<T>(key, hashField, value) bool 设置序列化对象作为字段值
HashSetAsync<T>(key, hashField, value) Task<bool> 异步设置序列化字段
HashGet<T>(key, hashField) T? 获取并反序列化字段值
HashGetAsync<T>(key, hashField) Task<T?> 异步获取反序列化字段
HashKeys(key) IEnumerable<string?> 获取所有哈希字段名
HashKeysAsync(key) Task<IEnumerable<string?>?> 异步获取字段名
HashValues(key) IEnumerable<string?> 获取所有哈希值
HashValuesAsync(key) Task<IEnumerable<string?>?> 异步获取值

多字段重载:

方法 返回 描述
HashGet(key, IEnumerable<string> hashFields) IEnumerable<string?> 一次获取多个字段
HashGetAsync(key, IEnumerable<string> hashFields, string value) Task<IEnumerable<string?>?> 异步多字段获取
HashSet(key, IEnumerable<KeyValuePair<string, string>> hashFields) void 一次设置多个字段
HashSetAsync(key, IEnumerable<KeyValuePair<string, string>> hashFields) Task 异步多字段设置
HashDelete(key, IEnumerable<string> hashFields) long 删除多个字段
HashDeleteAsync(key, IEnumerable<string> hashFields) Task<long> 异步多字段删除

有序集合操作(12 个方法)

方法 返回 描述
SortedSetAdd(key, member, score) bool 添加带分数的成员
SortedSetAddAsync(key, member, score) Task<bool> 异步添加成员
SortedSetAdd<T>(key, member, score) bool 添加序列化成员及分数
SortedSetAddAsync<T>(key, member, score) Task<bool> 异步添加序列化成员
SortedSetRemove(key, member) bool 移除成员
SortedSetRemoveAsync(key, member) Task<bool> 异步移除成员
SortedSetRangeByRank(key, start = 0L, stop = -1L, order = RedisOperate.OrderType.Asc) IEnumerable<string> 按排名范围和排序方向获取成员
SortedSetRangeByRankAsync(key) Task<IEnumerable<string?>?> 异步按排名获取所有成员
SortedSetLength(key) long 获取集合基数
SortedSetLengthAsync(key) Task<long> 异步获取基数
SortedSetIncrement(key, member, value) double 增加成员分数
SortedSetIncrementAsync(key, member, value) Task<double> 异步增加分数

键操作(10 个方法)

RedisOperate.OrderType 是公开的嵌套排序枚举:Asc = 0、Desc = 1。同步排名范围方法接收该参数;SortedSetRangeByRankAsync(string key) 没有范围或排序参数,按升序读取全部成员。

批量字符串写入以及 List/Hash/SortedSet 写入不会自动应用 Expiry。需要 TTL 时调用 KeyExpire/KeyExpireAsync。

方法 返回 描述
KeyDelete(key) bool 删除键(通过 Lua 支持 * 通配符)
KeyDeleteAsync(key) Task<bool> 异步删除键(仅支持单个键,不支持通配符)
KeyDelete(IEnumerable<string> keys) long 删除多个完整 Redis 键,不自动添加 TAG
KeyDeleteAsync(IEnumerable<string> keys) Task<long> 异步删除完整 Redis 键,不自动添加 TAG
KeyExists(key) bool 检查键是否存在
KeyExistsAsync(key) Task<bool> 异步检查键存在性
KeyRename(key, newKey) bool 仅旧键添加前缀,newKey 原样使用
KeyRenameAsync(key, newKey) Task<bool> 异步重命名,前缀规则相同
KeyExpire(key, expiry) bool 设置键的 TTL
KeyExpireAsync(key, expiry) Task<bool> 异步设置 TTL

发布/订阅操作(6 个方法)

方法 返回 描述
Subscribe(channel, handler) void 订阅频道
SubscribeAsync(channel, handler) Task 异步订阅
Publish(channel, message) long 发布消息
PublishAsync(channel, message) Task<long> 异步发布
Publish<T>(channel, message) long 发布序列化对象
PublishAsync<T>(channel, message) Task<long> 异步发布序列化对象

💻 代码示例

完整示例,涵盖四种Redis数据结构和键操作以及发布/订阅:

using Snet.Redis;

var redis = RedisOperate.Instance(new RedisData.Basics
{
    ConnectStr = "127.0.0.1:6379",
    DataBaseID = 0,
    TAG = "Demo:"
});

await redis.OnAsync();

// ── 字符串 ──────────────────────────────────
await redis.StringSetAsync("key1", "value1");
Console.WriteLine(await redis.StringGetAsync("key1"));

// ── 列表 ────────────────────────────────────
await redis.ListRightPushAsync("mylist", "item1");
await redis.ListRightPushAsync("mylist", "item2");
Console.WriteLine(await redis.ListLeftPopAsync("mylist")); // "item1"

// ── 哈希 ────────────────────────────────────
await redis.HashSetAsync("user", "name", "Alice");
await redis.HashSetAsync("user", "age", "30");
Console.WriteLine(await redis.HashGetAsync("user", "name"));

// ── 有序集合 ───────────────────────────────
await redis.SortedSetAddAsync("scores", "player1", 100);
var top = await redis.SortedSetRangeByRankAsync("scores");
foreach (var item in top) Console.WriteLine(item);

// ── 键操作 ──────────────────────────
Console.WriteLine(await redis.KeyExistsAsync("key1")); // True
redis.KeyDelete("*"); // 清除所有 Demo: 键(同步版通过 Lua 支持 `*` 通配符;异步版删除的是字面键)

// ── 发布/订阅 ─────────────────────────────────
await redis.SubscribeAsync("news", (channel, value) =>
    Console.WriteLine($"收到: {value}"));
await redis.PublishAsync("news", "Breaking!");

await redis.OffAsync();

对象序列化示例:

public class Order
{
    public string Id { get; set; }
    public decimal Total { get; set; }
    public DateTime CreatedAt { get; set; }
}

// 存储
var order = new Order { Id = "ORD-001", Total = 99.99m, CreatedAt = DateTime.UtcNow };
await redis.StringSetAsync("order:001", order);

// 读取
var saved = await redis.StringGetAsync<Order>("order:001");
Console.WriteLine($"订单 {saved.Id}: ${saved.Total}");

连接事件监控:

redis.OnInfoEvent += (sender, e) =>
{
    if (e.Status)
        Console.WriteLine($"[信息] {e.Message}");
    else
        Console.WriteLine($"[错误] {e.Message}");
};

❓ FAQ

1. 如何连接远程 Redis 服务器?

将 ConnectStr 设置为远程端点,可选带密码:

"remote-host:6379,password=xxx"

如需 TLS/SSL 连接,追加 ,ssl=true。

2. Redis 宕机时会发生什么?

大多数数据方法在 GetStatus() 返回未连接时返回各自的默认值。发布/订阅没有此检查,命令和序列化异常也可能向外抛出。状态检查后连接仍可能失败,应检查连接结果并处理操作异常,通过连接事件观察恢复。

3. TAG 前缀是否支持通配符删除?

支持。同步方法 KeyDelete("*") 使用 Lua KEYS 匹配所有带 TAG 前缀的键,确保仅删除属于您应用的键。

4. 操作是否线程安全?

StackExchange.Redis 的 ConnectionMultiplexer 支持共享的并发命令,但这不保证 RedisOperate 的并发生命周期变化安全。应协调 On/Off/释放与运行中的命令;断开连接会把连接和数据库字段设为 null。

5. 如何处理对象序列化?

使用泛型 <T> 方法,写入时自动通过 JSON(Snet.Core 的 ToJson())进行序列化,读取时自动反序列化。无需手动序列化。

6. 同步和异步 API 有什么区别?

异步上下文宜使用异步版本,但需核对各个签名:只有同步单键 KeyDelete 通过 Lua 展开 *,只有同步 SortedSetRangeByRank 接收范围和排序参数。HashGetAsync 的多字段重载还要求传入当前未使用的 string value 参数。同步生命周期包装会阻塞等待对应异步实现。