前言 作为 .NET 开发人员,Redis 是一个必须掌握的技术。Redis 是一个开源的内存数据结构存储系统,可以用作数据库、缓存和消息代理。
什么是 Redis? Redis(Remote Dictionary Server)是一个基于内存的键值存储系统,具有以下特点:
高性能 :数据存储在内存中,读写速度极快
丰富的数据类型 :支持字符串、哈希、列表、集合、有序集合等
持久化 :支持数据持久化到磁盘
原子性操作 :所有操作都是原子性的
支持事务 :支持事务操作
发布订阅 :支持消息发布/订阅模式
安装 Redis Windows 环境
下载 Redis for Windows:访问 https://github.com/microsoftarchive/redis/releases
解压到指定目录
运行 redis-server.exe 启动服务
运行 redis-cli.exe 打开客户端
Docker 环境(推荐) 1 2 3 4 5 6 7 8 docker pull redis:latest docker run --name my-redis -p 6379:6379 -d redis docker exec -it my-redis redis-cli
Redis 基本数据类型 1. String(字符串) 字符串是 Redis 最基本的数据类型,一个键最大能存储 512MB。
1 2 3 4 5 6 7 8 9 10 11 12 SET mykey "Hello Redis" GET mykey SETEX session:user1 3600 "userdata" INCR counter DECR counter
2. Hash(哈希) 哈希是一个键值对集合,适合存储对象。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 HSET user:1000 name "张三" HSET user:1000 age 30 HSET user:1000 email "zhangsan@example.com" HMSET user:1001 name "李四" age 25 email "lisi@example.com" HGET user:1000 name HGETALL user:1000 HMGET user:1000 name age
3. List(列表) 列表是简单的字符串列表,按插入顺序排序。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 LPUSH mylist "world" LPUSH mylist "hello" RPUSH mylist "!" LRANGE mylist 0 -1 LLEN mylist LPOP mylist RPOP mylist
4. Set(集合) 集合是无序的字符串集合,不允许重复元素。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 SADD myset "apple" SADD myset "banana" SADD myset "orange" SMEMBERS myset SISMEMBER myset "apple" SINTER set1 set2 SUNION set1 set2 SDIFF set1 set2
5. Sorted Set(有序集合) 有序集合在集合的基础上增加了一个分数(score)参数,元素按分数排序。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 ZADD leaderboard 100 "player1" ZADD leaderboard 200 "player2" ZADD leaderboard 150 "player3" ZRANGE leaderboard 0 -1 WITHSCORES ZREVRANGE leaderboard 0 -1 WITHSCORES ZSCORE leaderboard "player1" ZINCRBY leaderboard 50 "player1"
在 .NET 中使用 Redis 安装 NuGet 包 推荐使用 StackExchange.Redis,这是 .NET 最流行的 Redis 客户端库。
1 dotnet add package StackExchange.Redis
基本连接 1 2 3 4 5 6 7 8 9 10 using StackExchange.Redis;var redis = ConnectionMultiplexer.Connect("localhost:6379" );var db = redis.GetDatabase();db.StringSet("mykey" , "Hello Redis from .NET" ); var value = db.StringGet("mykey" );Console.WriteLine(value );
配置连接选项 1 2 3 4 5 6 7 8 9 10 11 var configOptions = new ConfigurationOptions{ EndPoints = { "localhost:6379" }, Password = "your_password" , ConnectTimeout = 5000 , SyncTimeout = 5000 , AbortOnConnectFail = false , DefaultDatabase = 0 }; var redis = ConnectionMultiplexer.Connect(configOptions);
实际应用示例 1. 缓存用户信息 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 public class UserService { private readonly IDatabase _redis; public UserService (IConnectionMultiplexer redis ) { _redis = redis.GetDatabase(); } public async Task<User> GetUserAsync (int userId ) { var cacheKey = $"user:{userId} " ; var cachedUser = await _redis.StringGetAsync(cacheKey); if (cachedUser.HasValue) { return JsonSerializer.Deserialize<User>(cachedUser); } var user = await _dbContext.Users.FindAsync(userId); if (user != null ) { var serialized = JsonSerializer.Serialize(user); await _redis.StringSetAsync(cacheKey, serialized, TimeSpan.FromHours(1 )); } return user; } }
2. 分布式锁 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 public class DistributedLockService { private readonly IDatabase _redis; public async Task<bool > AcquireLockAsync (string resource, string token, TimeSpan expiry ) { var key = $"lock:{resource} " ; return await _redis.StringSetAsync(key, token, expiry, When.NotExists); } public async Task<bool > ReleaseLockAsync (string resource, string token ) { var key = $"lock:{resource} " ; var currentValue = await _redis.StringGetAsync(key); if (currentValue == token) { return await _redis.KeyDeleteAsync(key); } return false ; } public async Task ProcessWithLockAsync () { var token = Guid.NewGuid().ToString(); var lockAcquired = await AcquireLockAsync("myresource" , token, TimeSpan.FromSeconds(30 )); if (lockAcquired) { try { await DoSomethingAsync(); } finally { await ReleaseLockAsync("myresource" , token); } } } }
3. 计数器和限流 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 public class RateLimiter { private readonly IDatabase _redis; public async Task<bool > IsAllowedAsync (string userId, int maxRequests, TimeSpan window ) { var key = $"ratelimit:{userId} " ; var count = await _redis.StringIncrementAsync(key); if (count == 1 ) { await _redis.KeyExpireAsync(key, window); } return count <= maxRequests; } } var rateLimiter = new RateLimiter(redis);if (await rateLimiter.IsAllowedAsync("user123" , 100 , TimeSpan.FromMinutes(1 ))){ } else { }
4. 发布/订阅 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 public class MessageSubscriber { public void Subscribe (IConnectionMultiplexer redis ) { var subscriber = redis.GetSubscriber(); subscriber.Subscribe("notifications" , (channel, message) => { Console.WriteLine($"收到消息: {message} " ); }); } } public class MessagePublisher { private readonly IConnectionMultiplexer _redis; public async Task PublishAsync (string message ) { var subscriber = _redis.GetSubscriber(); await subscriber.PublishAsync("notifications" , message); } }
5. 排行榜系统 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 public class LeaderboardService { private readonly IDatabase _redis; private const string LeaderboardKey = "game:leaderboard" ; public async Task UpdateScoreAsync (string playerId, double score ) { await _redis.SortedSetAddAsync(LeaderboardKey, playerId, score); } public async Task<List<(string PlayerId, double Score)>> GetTopPlayersAsync(int count) { var entries = await _redis.SortedSetRangeByRankWithScoresAsync( LeaderboardKey, 0 , count - 1 , Order.Descending ); return entries.Select(e => (e.Element.ToString(), e.Score)).ToList(); } public async Task<long ?> GetPlayerRankAsync(string playerId) { var rank = await _redis.SortedSetRankAsync(LeaderboardKey, playerId, Order.Descending); return rank.HasValue ? rank.Value + 1 : null ; } }
ASP.NET Core 集成 依赖注入配置 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 public void ConfigureServices (IServiceCollection services ){ services.AddSingleton<IConnectionMultiplexer>(sp => { var configuration = ConfigurationOptions.Parse( Configuration.GetConnectionString("Redis" ) ); return ConnectionMultiplexer.Connect(configuration); }); services.AddScoped<IUserService, UserService>(); services.AddSingleton<IDistributedLockService, DistributedLockService>(); }
appsettings.json 1 2 3 4 5 { "ConnectionStrings" : { "Redis" : "localhost:6379,password=your_password,ssl=false,abortConnect=false" } }
使用 IDistributedCache 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 services.AddStackExchangeRedisCache(options => { options.Configuration = Configuration.GetConnectionString("Redis" ); options.InstanceName = "MyApp_" ; }); public class CachedDataService { private readonly IDistributedCache _cache; public async Task<string > GetDataAsync (string key ) { var cachedData = await _cache.GetStringAsync(key); if (cachedData != null ) { return cachedData; } var data = await FetchDataFromSourceAsync(); var options = new DistributedCacheEntryOptions { AbsoluteExpirationRelativeToNow = TimeSpan.FromHours(1 ) }; await _cache.SetStringAsync(key, data, options); return data; } }
Redis 持久化 RDB(快照)
在指定时间间隔内生成数据集的时间点快照
适合备份和灾难恢复
配置示例:
1 2 3 4 save 900 1 save 300 10 save 60 10000
AOF(追加文件)
1 2 3 appendonly yes appendfsync everysec
最佳实践 1. 键命名规范 1 2 3 4 5 6 "user:1000:profile" "product:5678:stock" "session:abc123def"
2. 设置过期时间 1 2 await db.StringSetAsync("key" , "value" , TimeSpan.FromHours(1 ));
3. 使用管道(Pipeline) 1 2 3 4 5 6 7 8 9 10 11 var batch = db.CreateBatch();var tasks = new List<Task>();for (int i = 0 ; i < 1000 ; i++){ tasks.Add(batch.StringSetAsync($"key:{i} " , $"value:{i} " )); } batch.Execute(); await Task.WhenAll(tasks);
4. 避免大键 不要在单个键中存储过大的数据(建议不超过10KB)
考虑拆分大对象或使用哈希结构
5. 使用连接池 1 2 services.AddSingleton<IConnectionMultiplexer>(...);
性能优化 1. 使用异步方法 1 2 3 await db.StringGetAsync("key" );await db.HashSetAsync("hash" , "field" , "value" );
2. 批量操作 1 2 3 4 5 6 7 8 var hashEntries = new HashEntry[]{ new HashEntry("field1" , "value1" ), new HashEntry("field2" , "value2" ), new HashEntry("field3" , "value3" ) }; await db.HashSetAsync("myhash" , hashEntries);
3. 使用 Lua 脚本 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 var script = @" local current = redis.call('GET', KEYS[1]) if current == false or tonumber(current) < tonumber(ARGV[1]) then redis.call('SET', KEYS[1], ARGV[1]) return 1 end return 0 " ;var result = await db.ScriptEvaluateAsync( script, new RedisKey[] { "mykey" }, new RedisValue[] { 100 } );
常见问题 1. 缓存雪崩 大量缓存同时过期,导致请求全部打到数据库。
解决方案 :
设置随机过期时间
使用热点数据永不过期
使用互斥锁
1 2 var expiry = TimeSpan.FromMinutes(30 + Random.Shared.Next(0 , 10 ));await db.StringSetAsync(key, value , expiry);
2. 缓存穿透 查询不存在的数据,缓存和数据库都没有。
解决方案 :
1 2 3 4 5 6 var user = await GetUserFromDbAsync(userId);if (user == null ){ await db.StringSetAsync(cacheKey, "null" , TimeSpan.FromMinutes(5 )); }
3. 缓存击穿 热点数据过期瞬间,大量请求同时访问。
解决方案 :
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 public async Task<User> GetUserWithLockAsync (int userId ){ var cacheKey = $"user:{userId} " ; var cached = await db.StringGetAsync(cacheKey); if (cached.HasValue) return JsonSerializer.Deserialize<User>(cached); var lockKey = $"lock:{cacheKey} " ; var lockToken = Guid.NewGuid().ToString(); if (await db.StringSetAsync(lockKey, lockToken, TimeSpan.FromSeconds(10 ), When.NotExists)) { try { var user = await GetUserFromDbAsync(userId); if (user != null ) { await db.StringSetAsync(cacheKey, JsonSerializer.Serialize(user), TimeSpan.FromHours(1 )); } return user; } finally { await db.KeyDeleteAsync(lockKey); } } await Task.Delay(50 ); return await GetUserWithLockAsync(userId); }
监控与运维 常用命令 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 INFO INFO memory CLIENT LIST SLOWLOG GET 10 MONITOR DBSIZE FLUSHALL
.NET 中的监控 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 public class RedisMonitor { private readonly IConnectionMultiplexer _redis; public async Task<RedisInfo> GetInfoAsync () { var server = _redis.GetServer(_redis.GetEndPoints().First()); var info = await server.InfoAsync(); return new RedisInfo { UsedMemory = info.First(x => x.Key == "used_memory" ).Value, ConnectedClients = info.First(x => x.Key == "connected_clients" ).Value, TotalCommandsProcessed = info.First(x => x.Key == "total_commands_processed" ).Value }; } }
参考资源