一、Redis 简介
Redis 是一种开源的、基于内存的数据结构存储系统,可用作数据库、缓存和消息代理。它支持多种数据结构,包括字符串、哈希、列表、集合、有序集合等,具有极高的读写性能,在互联网开发中应用广泛。
二、环境准备
2.1 安装 Redis 服务
Windows 环境:
Redis 官方未提供 Windows 版本安装包,可使用第三方维护的版本。下载地址:https://github.com/MicrosoftArchive/redis/releases
下载后直接安装,安装完成后确保 Redis 服务设置为"自动"启动类型。
如需设置密码,找到安装目录下的redis.windows-service.conf文件,搜索# requirepass foobared,在下方添加:
requirepass 你的密码修改后重启 Redis 服务即可。
验证安装:
打开命令行工具,进入 Redis 安装目录,输入:
redis-cli如果设置了密码,需要先认证:
auth 你的密码然后测试基本操作:
set testkey "Hello Redis" get testkey能正常返回结果说明安装成功。
2.2 安装 C# 客户端库
在 C# 项目中操作 Redis,推荐使用 StackExchange.Redis,这是 .NET 生态中最成熟、性能最好的 Redis 客户端。
通过 NuGet 安装:
Install-Package StackExchange.Redis或使用 .NET CLI:
dotnet add package StackExchange.Redis三、连接 Redis
3.1 单例模式连接(推荐)
ConnectionMultiplexer是线程安全的,设计为在整个应用生命周期中复用,不应频繁创建和销毁。推荐使用单例模式 + 异步初始化:
using StackExchange.Redis; public class RedisConnectionManager { private static Lazy<ConnectionMultiplexer> _lazyConnection; private static readonly object _lock = new object(); /// <summary> /// 获取 Redis 连接实例(单例) /// </summary> public static ConnectionMultiplexer Instance { get { if (_lazyConnection == null) { lock (_lock) { if (_lazyConnection == null) { _lazyConnection = new Lazy<ConnectionMultiplexer>(() => { var config = new ConfigurationOptions { EndPoints = { "127.0.0.1:6379" }, Password = "你的密码", // 无密码则省略 ConnectTimeout = 5000, // 连接超时(毫秒) SyncTimeout = 5000, // 同步操作超时(毫秒) AbortOnConnectFail = false, // 连接失败不抛异常,后台自动重试 KeepAlive = 30, // 保活间隔(秒) DefaultDatabase = 0 // 默认数据库编号 }; return ConnectionMultiplexer.Connect(config); }); } } } return _lazyConnection.Value; } } /// <summary> /// 获取数据库实例 /// </summary> public static IDatabase GetDatabase(int db = -1) { return Instance.GetDatabase(db); } /// <summary> /// 释放连接 /// </summary> public static void Dispose() { if (_lazyConnection != null && _lazyConnection.IsValueCreated) { _lazyConnection.Value.Dispose(); _lazyConnection = null; } } }关键参数说明:
表格
| 参数 | 说明 |
|---|---|
AbortOnConnectFail = false | 保命开关,首次连接失败不抛异常,后台自动重试 |
ConnectTimeout | 连接超时时间,默认 5000ms,跨网络环境可适当调大 |
SyncTimeout | 同步操作超时上限,防止线程长时间阻塞 |
KeepAlive | 心跳保活间隔,防止 NAT 设备或负载均衡器断开空闲连接 |
3.2 在 ASP.NET Core 中集成
// Program.cs 或 Startup.cs builder.Services.AddSingleton<ConnectionMultiplexer>(sp => { var config = new ConfigurationOptions { EndPoints = { "127.0.0.1:6379" }, Password = "你的密码", AbortOnConnectFail = false, ConnectTimeout = 5000, SyncTimeout = 5000, KeepAlive = 30 }; return ConnectionMultiplexer.Connect(config); });使用时通过依赖注入获取:
public class MyService { private readonly ConnectionMultiplexer _redis; public MyService(ConnectionMultiplexer redis) { _redis = redis; } public async Task DoSomethingAsync() { var db = _redis.GetDatabase(); await db.StringSetAsync("key", "value"); } }四、基本数据操作
4.1 字符串操作
字符串是 Redis 最基础的数据类型,可以存储文本、数字或序列化后的对象。
var db = RedisConnectionManager.GetDatabase(); // 设置值 db.StringSet("username", "张三"); // 设置值并指定过期时间 db.StringSet("token", "abc123", TimeSpan.FromMinutes(30)); // 获取值 RedisValue value = db.StringGet("username"); Console.WriteLine(value); // 输出: 张三 // 判断键是否存在 bool exists = db.KeyExists("username"); // 删除键 db.KeyDelete("username"); // 数值递增 db.StringSet("counter", "0"); db.StringIncrement("counter"); // 变为 1 db.StringIncrement("counter", 5); // 变为 6 // 数值递减 db.StringDecrement("counter", 2); // 变为 4 // 设置键的过期时间 db.KeyExpire("token", TimeSpan.FromHours(1)); // 查看剩余过期时间 TimeSpan? ttl = db.KeyTimeToLive("token");重要提醒:
StringGet返回的是RedisValue类型,不是普通的string。判断键是否存在应使用value.IsNull,而不是value == null或string.IsNullOrEmpty(value):
RedisValue result = db.StringGet("不存在的键"); if (result.IsNull) { Console.WriteLine("键不存在"); }4.2 对象序列化存储
// 定义实体类 public class User { public int Id { get; set; } public string Name { get; set; } public int Age { get; set; } } // 存储对象(JSON 序列化) var user = new User { Id = 1001, Name = "李四", Age = 25 }; string json = JsonSerializer.Serialize(user); db.StringSet("user:1001", json, TimeSpan.FromHours(2)); // 读取对象 RedisValue cachedJson = db.StringGet("user:1001"); if (!cachedJson.IsNull) { var cachedUser = JsonSerializer.Deserialize<User>(cachedJson); Console.WriteLine($"姓名: {cachedUser.Name}, 年龄: {cachedUser.Age}"); }4.3 批量操作
避免在循环中逐个调用 Redis 命令,应使用批量操作减少网络往返:
// 批量设置 var pairs = new KeyValuePair<RedisKey, RedisValue>[] { new("key1", "value1"), new("key2", "value2"), new("key3", "value3") }; db.StringSet(pairs); // 批量获取 RedisKey[] keys = { "key1", "key2", "key3" }; RedisValue[] values = db.StringGet(keys); for (int i = 0; i < keys.Length; i++) { Console.WriteLine($"{keys[i]}: {values[i]}"); }五、高级数据结构操作
5.1 哈希表(Hash)
适合存储对象的多个字段,如用户资料、商品信息等。
var db = RedisConnectionManager.GetDatabase(); // 设置单个字段 db.HashSet("user:1002", "name", "王五"); db.HashSet("user:1002", "age", "24"); db.HashSet("user:1002", "email", "wangwu@example.com"); // 批量设置字段 var hashEntries = new HashEntry[] { new("name", "赵六"), new("age", "30"), new("city", "北京") }; db.HashSet("user:1003", hashEntries); // 获取单个字段 RedisValue name = db.HashGet("user:1002", "name"); // 获取所有字段 HashEntry[] allFields = db.HashGetAll("user:1002"); foreach (var field in allFields) { Console.WriteLine($"{field.Name}: {field.Value}"); } // 获取指定多个字段 RedisValue[] selectedFields = db.HashGet("user:1002", new RedisValue[] { "name", "email" }); // 检查字段是否存在 bool hasField = db.HashExists("user:1002", "age"); // 删除字段 db.HashDelete("user:1002", "email"); // 数值递增(如库存管理) db.HashIncrement("product:1000", "stock", -1); // 库存减 15.2 列表(List)
基于链表实现,支持从两端快速插入和删除,适合消息队列、最新动态等场景。
var db = RedisConnectionManager.GetDatabase(); // 从左侧插入 db.ListLeftPush("tasks", "任务1"); db.ListLeftPush("tasks", "任务2"); db.ListLeftPush("tasks", "任务3"); // 从右侧插入 db.ListRightPush("tasks", "任务4"); // 从左侧弹出 RedisValue task = db.ListLeftPop("tasks"); // 从右侧弹出 RedisValue lastTask = db.ListRightPop("tasks"); // 获取指定范围的元素(不删除) RedisValue[] items = db.ListRange("tasks", 0, -1); // 获取全部 // 获取列表长度 long count = db.ListLength("tasks"); // 修剪列表(保留指定范围) db.ListTrim("tasks", 0, 9); // 只保留前 10 条消息队列示例:
// 生产者 public void EnqueueOrder(string orderId) { db.ListLeftPush("order_queue", orderId); } // 消费者 public string DequeueOrder() { return db.ListRightPop("order_queue"); }5.3 集合(Set)
无序且元素唯一的字符串集合,适合标签、去重等场景。
var db = RedisConnectionManager.GetDatabase(); // 添加元素 db.SetAdd("tags", "C#"); db.SetAdd("tags", "Redis"); db.SetAdd("tags", "数据库"); // 检查元素是否存在 bool exists = db.SetContains("tags", "C#"); // 获取所有元素 RedisValue[] allTags = db.SetMembers("tags"); // 获取集合大小 long size = db.SetLength("tags"); // 移除元素 db.SetRemove("tags", "数据库"); // 集合运算 db.SetAdd("set1", "a", "b", "c"); db.SetAdd("set2", "b", "c", "d"); RedisValue[] union = db.SetCombine(SetOperation.Union, "set1", "set2"); // 并集 RedisValue[] intersect = db.SetCombine(SetOperation.Intersect, "set1", "set2"); // 交集 RedisValue[] diff = db.SetCombine(SetOperation.Difference, "set1", "set2"); // 差集5.4 有序集合(Sorted Set)
每个元素关联一个分数,按分数排序,适合排行榜、优先级队列等场景。
var db = RedisConnectionManager.GetDatabase(); // 添加元素(带分数) db.SortedSetAdd("leaderboard", new SortedSetEntry[] { new("玩家A", 1000), new("玩家B", 850), new("玩家C", 920), new("玩家D", 780) }); // 增加分数 db.SortedSetIncrement("leaderboard", "玩家B", 50); // 获取排名(从低到高) long? rank = db.SortedSetRank("leaderboard", "玩家A"); // 获取排名(从高到低) long? reverseRank = db.SortedSetReverseRank("leaderboard", "玩家A"); // 获取分数 double? score = db.SortedSetScore("leaderboard", "玩家A"); // 获取 Top 3 SortedSetEntry[] top3 = db.SortedSetRangeByRankWithScores( "leaderboard", 0, 2, Order.Descending); foreach (var entry in top3) { Console.WriteLine($"{entry.Element}: {entry.Score}"); }六、发布与订阅
Redis 支持消息发布/订阅模式,适合系统间解耦通信。
// 获取订阅者 var subscriber = RedisConnectionManager.Instance.GetSubscriber(); // 订阅频道 subscriber.Subscribe("chat_channel", (channel, message) => { Console.WriteLine($"收到消息: {message}"); }); // 发布消息 subscriber.Publish("chat_channel", "Hello, Redis!"); // 取消订阅 subscriber.Unsubscribe("chat_channel");七、事务与管道
7.1 事务
Redis 事务保证一组命令按顺序执行且不被其他命令打断。
var db = RedisConnectionManager.GetDatabase(); // 创建事务 ITransaction transaction = db.CreateTransaction(); // 添加操作 transaction.StringSetAsync("key1", "value1"); transaction.StringSetAsync("key2", "value2"); transaction.StringIncrementAsync("counter"); // 执行事务 bool committed = await transaction.ExecuteAsync();7.2 管道(Batch)
管道将多个命令打包发送,减少网络往返,但不保证原子性。
var db = RedisConnectionManager.GetDatabase(); IBatch batch = db.CreateBatch(); // 批量添加操作 await batch.StringSetAsync("batch_key1", "value1"); await batch.StringSetAsync("batch_key2", "value2"); await batch.KeyExpireAsync("batch_key1", TimeSpan.FromMinutes(10)); // 执行 batch.Execute();八、最佳实践
8.1 连接管理
- 全局单例:
ConnectionMultiplexer必须全局复用,不要在每次请求时创建新实例 - 异步初始化:使用
Lazy<ConnectionMultiplexer>延迟初始化,避免启动时阻塞 - 不要缓存 IDatabase:
IDatabase是轻量级对象,每次通过GetDatabase()获取即可
8.2 性能优化
- 批量操作优先:使用
StringSet(KeyValuePair[])或StringGet(RedisKey[])代替循环单次调用 - 管道减少 RTT:复杂混合命令使用
IBatch打包 - 合理设置过期时间:避免内存无限增长
- 使用异步 API:所有方法都有对应的
Async版本,在异步上下文中务必使用异步方法
8.3 异常处理
try { var db = RedisConnectionManager.GetDatabase(); await db.StringSetAsync("key", "value"); } catch (RedisConnectionException ex) { // 连接异常处理 Console.WriteLine($"Redis 连接失败: {ex.Message}"); } catch (RedisTimeoutException ex) { // 超时异常处理 Console.WriteLine($"Redis 操作超时: {ex.Message}"); } catch (RedisException ex) { // 其他 Redis 异常 Console.WriteLine($"Redis 异常: {ex.Message}"); }8.4 连接字符串注意事项
- 参数名必须严格小写,如
password、connectTimeout、abortConnect - 不要使用 URL 格式(如
redis://:pwd@host:6379),应使用host:port,password=xxx格式 - Docker 或云环境必须将
localhost替换为真实 IP 或服务名
九、完整示例
using StackExchange.Redis; using System.Text.Json; class Program { static async Task Main(string[] args) { // 获取数据库实例 var db = RedisConnectionManager.GetDatabase(); // 1. 缓存用户信息 var user = new { Id = 1, Name = "测试用户", Email = "test@example.com" }; string userJson = JsonSerializer.Serialize(user); await db.StringSetAsync("user:1", userJson, TimeSpan.FromMinutes(30)); // 2. 读取缓存 RedisValue cached = await db.StringGetAsync("user:1"); if (!cached.IsNull) { var cachedUser = JsonSerializer.Deserialize<object>(cached); Console.WriteLine($"缓存命中: {cachedUser}"); } // 3. 计数器 await db.StringSetAsync("page_views", "0"); await db.StringIncrementAsync("page_views"); Console.WriteLine($"页面访问量: {await db.StringGetAsync("page_views")}"); // 4. 哈希存储 await db.HashSetAsync("product:100", new HashEntry[] { new("name", "笔记本电脑"), new("price", "5999"), new("stock", "100") }); // 5. 列表操作 await db.ListLeftPushAsync("recent_orders", "ORD001"); await db.ListLeftPushAsync("recent_orders", "ORD002"); RedisValue[] orders = await db.ListRangeAsync("recent_orders", 0, -1); Console.WriteLine($"最近订单: {string.Join(", ", orders)}"); } }通过本教程,你应该已经掌握了在 C# 中使用 StackExchange.Redis 进行连接、数据操作和性能优化的核心技能。在实际项目中,建议根据业务场景合理选择数据结构,并始终遵循单例连接、批量操作、异步调用等最佳实践。