ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

Blazor Admin 如何使用 Redis?EasyAdminBlazor 缓存与分布式锁实战

Blazor Admin 如何使用 Redis?EasyAdminBlazor 缓存与分布式锁实战 后台系统里 Redis 通常承担四件事缓存、分布式锁、发布订阅、跨实例共享状态。这篇文章讲 EasyAdminBlazor 2.3 分别是怎么做的以及多实例部署时该注意什么。一、接入一行注册builder.AddEasyAdminBlazor(newEasyAdminBlazorOptions{...}).AddEasyAdminBlazorRedis(configuration[Redis:ConnectionString]!);{Redis:{ConnectionString:127.0.0.1:6379,poolsize10}}注册实现只有两个服务publicstaticWebApplicationBuilderAddEasyAdminBlazorRedis(thisWebApplicationBuilderbuilder,stringconnectionString){builder.Services.TryAddSingletonEasyAdminBlazor.IRedisService(spnewRedisService(connectionString));builder.Services.TryAddSingletonIDistributedCache(spnewFreeRedisDistributedCache(sp.GetRequiredServiceEasyAdminBlazor.IRedisService()));returnbuilder;}用TryAddSingleton意味着宿主项目自己注册的同类型服务优先。如果你已经有统一的 Redis 封装可以替换掉它。不装这个扩展时DI 里根本没有IRedisService依赖它的功能聊天、消息实时推送会自动降级——这也是为什么没配 Redis 也能跑后台。二、两条缓存通道不要用混ICacheServiceIRedisService默认实现MemoryCacheService进程内IMemoryCacheRedisServiceFreeRedis注册方式TryAddSingletonICacheService, MemoryCacheService()装 Redis 扩展后注册多实例一致性❌ 各实例一份✅ 共享适用场景单机部署、纯读多写少、可容忍短暂不一致多实例、需要共享状态、分布式锁、发布订阅可替换EasyAdminBlazor.FusionCache扩展自行实现IRedisService一条实用规则只要部署了多个实例能被写入的数据就不要依赖ICacheService。权限缓存就是典型。它的缓存键里带了权限版本号版本号本身存在ICacheService里privatestringScopedPermissionVersionKey${TenantCachePrefix}{PermissionVersionKey};privateTaskintGetPermissionVersionAsync()cache.GetOrCreateAsync(ScopedPermissionVersionKey,()Task.FromResult(0),TimeSpan.FromDays(30));单实例下没问题多实例下管理员改了角色权限版本号只在 A 实例上加 1这种情况在多实例部署里需要额外考虑例如接入 FusionCache Redis 做二级缓存。这是部署形态带来的差异不是权限模型本身的缺陷。三、IRedisService 的能力地图接口按用途分了七个区块先看全貌publicinterfaceIRedisService:IDisposable{#region在线状态voidJoinOnline(longuserId);voidRemoveOnline(longuserId);IEnumerablelongGetClientListByOnline();boolHasOnline(longclientId);#endregion#region发布订阅voidPublishT(SubscribeChannelchannel,Tmessage);IDisposableSubscribeT(SubscribeChannelchannel,ActionThandler);voidUnsubscribe(SubscribeChannelchannel);#endregion#region分布式锁object?Lock(stringkey,intexpireSeconds);voidReleaseLock(object?lockHandle);#endregion#regionKey / String / Hash / List / Set / Sorted Set...#endregion}对应到后台开发常见用途是区块典型用途在线状态聊天、消息推送的用户是否在线发布订阅跨实例通知消息已读、系统广播分布式锁定时任务、缓存落库、一次性初始化String / Hash缓存对象、计数器、在线列表List聊天消息队列写入待落库Set / Sorted Set去重集合、排行榜四、发布订阅两种频道publicenumSubscribeChannel{MessageNotification,ReadNotification}发布和订阅直接用泛型方法序列化用Newtonsoft.JsonpublicvoidPublishT(SubscribeChannelchannel,Tmessage){_redisClient.Publish(channel.ToString(),JsonConvert.SerializeObject(message));}publicIDisposableSubscribeT(SubscribeChannelchannel,ActionThandler){varsubscription_redisClient.Subscribe(channel.ToString(),(redisChannel,redisValue){try{vardataJsonConvert.DeserializeObjectT(redisValue.ToString());if(data!null)handler(data);}catch{// 单条消息反序列化/处理失败不影响订阅跳过该消息}});returnnewSubscriptionDisposer(()_redisClient.UnSubscribe(channel.ToString()));}两个细节值得学单条消息处理失败被隔离在 try/catch 里。一条脏消息不应该把整个订阅打断。返回IDisposable。SubscriptionDisposer保证退订只执行一次调用方可以直接using。五、分布式锁怎么拿、怎么放publicobject?Lock(stringkey,intexpireSeconds){return_redisClient.Lock(key,expireSeconds);}publicvoidReleaseLock(object?lockHandle){if(lockHandlenull)return;if(lockHandleisIDisposabledisposable){disposable.Dispose();return;}varunlockMethodlockHandle.GetType().GetMethod(Unlock);unlockMethod?.Invoke(lockHandle,null);}要点锁对象是object?null表示没抢到锁。调用方必须先判空。必须传过期时间expireSeconds。持锁进程崩溃时锁会到期自动释放不会永久堵塞。释放方式做了兼容实现IDisposable的直接Dispose()否则反射调用Unlock()。用起来是这样varlockObjredis.Lock(${admin.TenantCachePrefix}save_messages_lock,30);if(lockObjnull){// 没抢到锁说明别的实例正在处理直接跳过return;}try{// 临界区只允许一个实例执行}finally{redis.ReleaseLock(lockObj);}锁键要带租户前缀原因上一篇讲过全局锁会让一个租户的操作阻塞另一个租户。六、实战聊天消息的缓存 批量落库这是仓库里分布式锁最完整的一个用例位于AdminMessageServiceprivateconststringLockKeysave_messages_lock;privateconstintLockExpireSeconds30;publicasyncTaskSaveMessagesToDatabase(){if(_redisnull)return;varlockObj_redis.Lock(${_admin.TenantCachePrefix}{LockKey},LockExpireSeconds);if(lockObj!null){varkeys_redis.Keys(${_admin.TenantCachePrefix}chat_messages:*);foreach(varkeyinkeys){varmessagesJson_redis.LRange(key,0,-1);if(messagesJson!nullmessagesJson.Length0){varmessagesmessagesJson.Select(jsonJsonConvert.DeserializeObjectSysMessage(json)).Where(mm!null).ToList();if(messages.Count0)await_repo.InsertAsync(messages);_redis.Del(key);}}_redis.ReleaseLock(lockObj);}}这个设计解决了三个问题问题做法聊天消息量大逐条写库压力大先LPush到 Redis 列表定时批量落库多实例同时落库会重复插入分布式锁 30 秒过期只有一个实例执行落库后重复消费落库成功后Del掉 key对应的写入端Chat.razor_redisService?.LPush(${admin.TenantCachePrefix}chat_messages:{receiverId},JsonConvert.SerializeObject(newMessage));读写两端用同一个前缀规则是这套机制能成立的前提。需要提醒一点Keys(pattern)在 Redis 上是KEYS命令数据量大时会阻塞单线程。示例里配合了前缀和较短的消息列表生命周期生产环境如果消息量很大建议改成SCAN或维护一个待落库 key 集合例如用 Set 记录活跃的会话 key。七、IDistributedCache 适配器如果宿主项目里有依赖 ASP.NET Core 标准IDistributedCache的组件比如 Session、Data Protection可以直接复用publicclassFreeRedisDistributedCache:IDistributedCache{privatereadonlyEasyAdminBlazor.IRedisService_redisService;publicbyte[]?Get(stringkey)_redisService.GetBytes(key);publicasyncTaskbyte[]?GetAsync(stringkey,CancellationTokentokendefault)await_redisService.GetBytesAsync(key,token);...privateTimeSpan?GetExpiry(DistributedCacheEntryOptionsoptions){if(options.AbsoluteExpiration.HasValue)returnoptions.AbsoluteExpiration.Value-DateTimeOffset.Now;elseif(options.AbsoluteExpirationRelativeToNow.HasValue)returnoptions.AbsoluteExpirationRelativeToNow.Value;elseif(options.SlidingExpiration.HasValue)returnoptions.SlidingExpiration.Value;returnnull;}}三种过期策略都做了映射绝对时间、相对时间、滑动过期。注意SlidingExpiration在这里是按过期时长直接设置的不是每次读续期如果需要严格的滑动语义要自己扩展。八、多实例部署的检查清单共享状态不要放进程内缓存。需要跨实例一致的权限版本号、在线状态、待落库队列都走 Redis。锁的粒度和过期时间要设计。太粗会互相阻塞太细保护不住过期时间要大于正常执行时间同时能兜住进程崩溃。锁保护的操作要幂等。锁过期后另一个实例可能重入业务上要能重复执行而不出错。慎用KEYS。大数据量下用SCAN或维护索引集合。缓存键统一带租户前缀。见上一篇。序列化要统一。RedisService用Newtonsoft.Json自定义类型要确认能正确序列化/反序列化。连接串加连接池参数。示例配置是127.0.0.1:6379,poolsize10。Redis 不可用要有降级预期。聊天、消息推送会失效但后台的 CRUD、权限、审批不应因此不可用——这也是框架把 Redis 做成可选扩展的原因。九、小结需求用哪个关键点单机缓存ICacheService简单、快多实例下不一致跨实例共享缓存IRedisService 租户前缀键规范统一分布式锁Lock/ReleaseLock必带过期时间键带租户前缀临界区幂等跨实例通知Publish/Subscribe单条消息失败要隔离标准分布式缓存接口IDistributedCache适配器可被 Session 等组件复用Redis 在后台系统里不是加了就快而是多实例部署时的共享基础设施。用对了是分布式锁和缓存用错了就是把单机 bug 放大到集群。如果你正在用 .NET 10 Blazor 做后台需要缓存与分布式锁可以看看 EasyAdminBlazor 的 Redis 扩展接口分层清晰锁和缓存都有现成实现源码可直接对照。文档https://easyadmin.wang-zhan.com.cn/doc源码https://gitee.com/gudufy/EasyAdminBlazor
返回列表