🗄️ Redis 概述
Snet.Redis 是一个高性能 Redis 客户端库,基于 StackExchange.Redis 封装,面向 .NET 8/10。它提供了统一的 API,覆盖全部 5 种 Redis 数据类型(String、List、Hash、SortedSet、Key)以及发布/订阅功能。主要特性包括 TAG 前缀命名空间、对象存储的 JSON 序列化、同步/异步双模 API、连接事件监控以及基于 Lua 脚本的 通配符键删除。
| 属性 | 值 |
|---|---|
| 包名 | Snet.Redis |
| 命名空间 | Snet.Redis |
| 目标框架 | .NET 8.0 / .NET 10.0 |
| 依赖项 | StackExchange.Redis 3.0.17, Snet.Core |
| 许可证 | MIT |
▶️ 快速开始
安装包:
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();
⚙️ 安装与配置
通过 RedisData.Basics 类进行配置:
| 参数 | 类型 | 默认值 | 描述 |
|---|---|---|---|
ConnectStr |
string |
— | Redis 连接字符串(如 "127.0.0.1:6379") |
DataBaseID |
int |
0 |
Redis 数据库编号(0-15) |
Expiry |
int |
86400000 |
默认 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 前缀,防止共享同一 Redis 实例的应用之间发生键冲突。
// 当 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. 连接事件
库暴露七种连接事件,均通过 OnInfoEventHandler 上报:
| 事件 | 描述 |
|---|---|
ConnectionRestored |
丢失的连接重新建立时触发 |
ConnectionFailed |
连接尝试失败时触发 |
ErrorMessage |
Redis 错误消息时触发 |
ConfigurationChanged |
主/副本拓扑变更时触发 |
HashSlotMoved |
哈希槽迁移时触发 |
InternalError |
库内部错误时触发 |
ConfigurationChangedBroadcast |
广播配置变更时触发 |
redis.OnInfoEventHandler += (sender, args) =>
{
Console.WriteLine($"[{args.EventType}] {args.Message}");
};
6. 断开连接时的返回值约定
当 Redis 不可达时,方法返回安全的默认值:
| 返回类型 | 默认值 |
|---|---|
bool |
false |
long |
-1 |
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() |
获取连接状态 |
Dispose |
void Dispose() |
清理资源 |
DisposeAsync |
ValueTask DisposeAsync() |
异步清理资源 |
字符串操作(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?>> |
异步获取范围内的值 |
哈希操作(18 个方法)
| 方法 | 返回 | 描述 |
|---|---|---|
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?>> |
异步获取值 |
有序集合操作(10 个方法)
| 方法 | 返回 | 描述 |
|---|---|---|
SortedSetAdd(key, member, score) |
bool |
添加带分数的成员 |
SortedSetAddAsync(key, member, score) |
Task<bool> |
异步添加成员 |
SortedSetRemove(key, member) |
bool |
移除成员 |
SortedSetRemoveAsync(key, member) |
Task<bool> |
异步移除成员 |
SortedSetRangeByRank(key, start, stop) |
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> |
异步增加分数 |
键操作(8 个方法)
| 方法 | 返回 | 描述 |
|---|---|---|
KeyDelete(key) |
bool |
删除键(通过 Lua 支持 * 通配符) |
KeyDeleteAsync(key) |
Task<bool> |
异步删除键 |
KeyExists(key) |
bool |
检查键是否存在 |
KeyExistsAsync(key) |
Task<bool> |
异步检查键存在性 |
KeyRename(key, newKey) |
bool |
重命名键 |
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> |
异步发布序列化对象 |
💻 代码示例
完整示例,涵盖全部 5 种 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
await redis.KeyDeleteAsync("*"); // 清除所有 Demo: 键
// ── 发布/订阅 ─────────────────────────────────
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.OnInfoEventHandler += (sender, args) =>
{
switch (args.EventType)
{
case "ConnectionRestored":
Console.WriteLine($"[重新连接] {args.Message}");
break;
case "ConnectionFailed":
Console.WriteLine($"[连接失败] {args.Message}");
break;
case "ErrorMessage":
Console.WriteLine($"[错误] {args.Message}");
break;
}
};
❓ FAQ
1. 如何连接远程 Redis 服务器?
将 ConnectStr 设置为远程端点,可选带密码:
"remote-host:6379,password=xxx"
如需 TLS/SSL 连接,追加 ,ssl=true。
2. Redis 宕机时会发生什么?
所有方法返回其断开连接时的默认值(布尔值返回 false,字符串返回 null,长整型返回 -1,泛型对象返回 default)。连接事件会在恢复时触发,方便您响应重连。
3. TAG 前缀是否支持通配符删除?
支持。同步方法 KeyDelete("*") 使用 Lua KEYS 匹配所有带 TAG 前缀的键,确保仅删除属于您应用的键。
4. 操作是否线程安全?
是的。StackExchange.Redis 的 ConnectionMultiplexer 是完全线程安全的。RedisOperate 中的单例模式确保在您的应用中安全共享。
5. 如何处理对象序列化?
使用泛型 <T> 方法,写入时自动通过 JSON(Snet.Core 的 ToJson())进行序列化,读取时自动反序列化。无需手动序列化。
6. 同步和异步 API 有什么区别?
两种 API 提供相同的行为。异步方法使用基于 Task 的模式,在异步上下文(ASP.NET Core、后台服务)中应优先使用,以避免阻塞线程。同步方法适用于控制台应用和同步上下文。
📅 版本历史
| 日期 | 版本 | 变更 |
|---|---|---|
| 2026-07-23 | — | 当前版本,支持 .NET 8/10 |
