.net封装StackExchange.Redis
Prrogram.cs 增加AddRedisCacheSetup() 用于注册服务services.AddRedisCacheSetup();// 1. 注册 RedisRepositoryTransient 生命周期// 2. 注册 ConnectionMultiplexerSingleton 生命周期using Microsoft.Extensions.DependencyInjection; using TMom.Infrastructure; using StackExchange.Redis; namespace TMom.Application.Ext { /// summary /// Redis缓存 启动服务 /// /summary public static class RedisCacheSetup { public static void AddRedisCacheSetup(this IServiceCollection services) { if (services null) throw new ArgumentNullException(nameof(services)); services.AddTransientIRedisRepository, RedisRepository(); services.AddSingletonConnectionMultiplexer(sp { string redisConfiguration Appsettings.app([Redis, ConnectionString]); var configuration ConfigurationOptions.Parse(redisConfiguration, true); configuration.ResolveDns true; configuration.AbortOnConnectFail false; configuration.SyncTimeout Appsettings.app([Redis, Timeout]).ObjToInt(); return ConnectionMultiplexer.Connect(configuration); }); } } }1. 为什么需要 RedisRepository直接使用 StackExchange.Redis 的 IDatabase 存在以下问题- API 太底层 每次操作都要处理序列化、Key 前缀等细节- 代码重复 每个 Service 都要写相同的序列化逻辑- 缺乏统一管理 Key 命名不规范容易冲突RedisRepository 的作用 封装 StackExchange.Redis提供更简洁、更易用的 API。using Microsoft.Extensions.Logging; using Newtonsoft.Json; using StackExchange.Redis; namespace TMom.Infrastructure { public class RedisRepository : IRedisRepository { private readonly ILoggerRedisRepository _logger; private readonly ConnectionMultiplexer _redis; private readonly IDatabase _database; private readonly string _instanceName Appsettings.app([Redis, InstanceName]); public RedisRepository(ILoggerRedisRepository logger, ConnectionMultiplexer redis) { _logger logger; _redis redis; _database redis.GetDatabase(); } /// summary /// 统一处理key前缀 /// /summary /// param namekey/param /// returns格式: 当前项目配置的实例:key/returns private string RenderKeyPrefix(string key) { if (key.StartsWith(${_instanceName}:)) return key; return ${_instanceName}:{key}; } private IServer GetServer() { var endpoint _redis.GetEndPoints(); return _redis.GetServer(endpoint.First()); } /// summary /// redisDb /// /summary public IDatabase Db _database; /// summary /// pub/sub /// /summary /// returns/returns public ISubscriber GetSubscriber() _redis.GetSubscriber(); public async Task Clear() { foreach (var endPoint in _redis.GetEndPoints()) { var server GetServer(); foreach (var key in server.Keys()) { await _database.KeyDeleteAsync(key); } } } public async Taskbool Exist(string key) { key RenderKeyPrefix(key); return await _database.KeyExistsAsync(key); } public async Taskstring GetValue(string key) { key RenderKeyPrefix(key); return await _database.StringGetAsync(key); } public async Task Remove(string key) { key RenderKeyPrefix(key); await _database.KeyDeleteAsync(key); } /// summary /// 重命名key /// /summary /// param namekey/param /// param namenewKey/param /// returns/returns public async Task KeyRename(string key, string newKey) { key RenderKeyPrefix(key); newKey RenderKeyPrefix(newKey); await _database.KeyRenameAsync(key, newKey); } public async Task Set(string key, object value, TimeSpan cacheTime) { if (value ! null) { key RenderKeyPrefix(key); if (value is string cacheValue) { // 字符串无需序列化 await _database.StringSetAsync(key, cacheValue, cacheTime); } else { //序列化将object值生成RedisValue await _database.StringSetAsync(key, SerializeHelper.Serialize(value), cacheTime); } } } public async TaskTEntity GetTEntity(string key) { key RenderKeyPrefix(key); var value await _database.StringGetAsync(key); if (value.HasValue) { //需要用的反序列化将Redis存储的Byte[]进行反序列化 return SerializeHelper.DeserializeTEntity(value); } else { return default(TEntity); } } public async TaskTEntity HashGetTEntity(string key, string field) { key RenderKeyPrefix(key); var value await _database.HashGetAsync(key, field); if (value.HasValue) { return SerializeHelper.DeserializeTEntity(value); } else { return default(TEntity); } } public async Task HashSetTEntity(string key, string field, TEntity value, TimeSpan? span null, When when When.Always, CommandFlags flags CommandFlags.None) { key RenderKeyPrefix(key); await _database.HashSetAsync(key, field, SerializeHelper.Serialize(value), when, flags); if (span null) return; string script redis.call(EXPIRE, key, expire); var prepared LuaScript.Prepare(script); await _database.ScriptEvaluateAsync(prepared, new { key, expire span.Value.TotalSeconds }, flags); } public async Task HashDelete(string key, string field) { key RenderKeyPrefix(key); await _database.HashDeleteAsync(key, field); } public async Taskbool HashExist(string key, string field) { key RenderKeyPrefix(key); return await _database.HashExistsAsync(key, field); } /// summary /// 根据key模糊匹配 /// /summary /// param namepattern/param /// returns/returns public async TaskRedisResult GetByPatternAsync(string pattern) { pattern RenderKeyPrefix(pattern); return await _database.ScriptEvaluateAsync(LuaScript.Prepare( local res redis.call(KEYS, keypattern) return res ), new { keypattern pattern }); } /// summary /// 根据key获取RedisValue /// /summary /// typeparam nameT/typeparam /// param nameredisKey/param /// returns/returns public async TaskRedisValue[] ListRangeAsync(string redisKey) { redisKey RenderKeyPrefix(redisKey); return await _database.ListRangeAsync(redisKey); } /// summary /// 在列表头部插入值。如果键不存在先创建再插入值 /// /summary /// param nameredisKey/param /// param nameredisValue/param /// returns/returns public async Tasklong ListLeftPushAsync(string redisKey, string redisValue, int db -1) { redisKey RenderKeyPrefix(redisKey); return await _database.ListLeftPushAsync(redisKey, redisValue); } /// summary /// 在列表尾部插入值。如果键不存在先创建再插入值 /// /summary /// param nameredisKey/param /// param nameredisValue/param /// returns/returns public async Tasklong ListRightPushAsync(string redisKey, string redisValue, int db -1) { redisKey RenderKeyPrefix(redisKey); return await _database.ListRightPushAsync(redisKey, redisValue); } /// summary /// 在列表尾部插入数组集合。如果键不存在先创建再插入值 /// /summary /// param nameredisKey/param /// param nameredisValue/param /// returns/returns public async Tasklong ListRightPushAsync(string redisKey, IEnumerablestring redisValue, int db -1) { redisKey RenderKeyPrefix(redisKey); var redislist new ListRedisValue(); foreach (var item in redisValue) { redislist.Add(item); } return await _database.ListRightPushAsync(redisKey, redislist.ToArray()); } /// summary /// 移除并返回存储在该键列表的第一个元素 反序列化 /// /summary /// param nameredisKey/param /// returns/returns public async TaskT ListLeftPopAsyncT(string redisKey, int db -1) where T : class { redisKey RenderKeyPrefix(redisKey); return JsonConvert.DeserializeObjectT(await _database.ListLeftPopAsync(redisKey)); } /// summary /// 移除并返回存储在该键列表的最后一个元素 反序列化 /// 只能是对象集合 /// /summary /// param nameredisKey/param /// returns/returns public async TaskT ListRightPopAsyncT(string redisKey, int db -1) where T : class { redisKey RenderKeyPrefix(redisKey); return JsonConvert.DeserializeObjectT(await _database.ListRightPopAsync(redisKey)); } /// summary /// 移除并返回存储在该键列表的第一个元素 /// /summary /// param nameredisKey/param /// param namedb/param /// returns/returns public async Taskstring ListLeftPopAsync(string redisKey, int db -1) { redisKey RenderKeyPrefix(redisKey); return await _database.ListLeftPopAsync(redisKey); } /// summary /// 移除并返回存储在该键列表的最后一个元素 /// /summary /// typeparam nameT/typeparam /// param nameredisKey/param /// param namedb/param /// returns/returns public async Taskstring ListRightPopAsync(string redisKey, int db -1) { redisKey RenderKeyPrefix(redisKey); return await _database.ListRightPopAsync(redisKey); } /// summary /// 列表长度 /// /summary /// param nameredisKey/param /// param namedb/param /// returns/returns public async Tasklong ListLengthAsync(string redisKey, int db -1) { redisKey RenderKeyPrefix(redisKey); return await _database.ListLengthAsync(redisKey); } /// summary /// 返回在该列表上键所对应的元素 /// /summary /// param nameredisKey/param /// returns/returns public async TaskIEnumerablestring ListRangeAsync(string redisKey, int db -1) { redisKey RenderKeyPrefix(redisKey); var result await _database.ListRangeAsync(redisKey); return result.Select(o o.ToString()); } /// summary /// 根据索引获取指定位置数据 /// /summary /// param nameredisKey/param /// param namestart/param /// param namestop/param /// param namedb/param /// returns/returns public async TaskIEnumerablestring ListRangeAsync(string redisKey, int start, int stop, int db -1) { redisKey RenderKeyPrefix(redisKey); var result await _database.ListRangeAsync(redisKey, start, stop); return result.Select(o o.ToString()); } /// summary /// 删除List中的元素 并返回删除的个数 /// /summary /// param nameredisKeykey/param /// param nameredisValue元素/param /// param nametype大于零 : 从表头开始向表尾搜索小于零 : 从表尾开始向表头搜索等于零移除表中所有与 VALUE 相等的值/param /// param namedb/param /// returns/returns public async Tasklong ListDelRangeAsync(string redisKey, string redisValue, long type 0, int db -1) { redisKey RenderKeyPrefix(redisKey); return await _database.ListRemoveAsync(redisKey, redisValue, type); } /// summary /// 清空List /// /summary /// param nameredisKey/param /// param namedb/param public async Task ListClearAsync(string redisKey, int db -1) { redisKey RenderKeyPrefix(redisKey); await _database.ListTrimAsync(redisKey, 1, 0); } } }Q1: RedisRepository 的设计优势是什么答 1. 封装性 封装了 StackExchange.Redis 的底层 API2. 易用性 提供了更简洁、更直观的方法3. 安全性 自动添加 Key 前缀避免冲突4. 灵活性 支持 String、Hash、List 等多种数据结构5. 性能 使用 Lua 脚本保证原子性 Q2: 如何保证 Redis 和数据库的一致性答 1. Cache-Aside Pattern 旁路缓存模式- 读先查缓存缓存未命中再查数据库- 写先写数据库再删除缓存2. 设置过期时间 保证最终一致性3. 主动更新 修改数据时主动清除相关缓存 Q3: Redis 的 Key 前缀有什么作用答 1. 多项目隔离 不同项目使用不同的前缀避免 Key 冲突2. 批量操作 可以按前缀批量删除某个项目的缓存3. 可维护性 通过前缀可以快速识别 Key 的来源 Q4: 为什么 ConnectionMultiplexer 要使用 Singleton答 1. 连接池共享 所有请求共享同一个连接池减少连接开销2. 性能最优 避免频繁创建和销毁连接3. 线程安全 ConnectionMultiplexer 内部实现了线程安全4. 自动重连 连接断开后自动重连RedisRepository 是一个封装了 StackExchange.Redis 的仓储类提供了 String、Hash、List 等多种数据结构的操作方法自动处理 Key 前缀隔离、对象序列化/反序列化、Lua 脚本原子性操作通过 ConnectionMultiplexer 实现连接池管理为项目提供高效、安全、易用的缓存服务。