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

相关新闻

MCP 模型上下文协议:系列目录与导读(TaoToken 统一 Key 接入版)

MCP 模型上下文协议:系列目录与导读(TaoToken 统一 Key 接入版)

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/4 21:56:44 阅读更多 →
OpenClaw 电脑 AI 自动化|Windows/Mac 双端完整落地教程:把 settings 改到 TaoToken

OpenClaw 电脑 AI 自动化|Windows/Mac 双端完整落地教程:把 settings 改到 TaoToken

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/3 19:22:21 阅读更多 →
2026年01月10日最热门的开源项目(Github):用TaoToken统一Key跑通AI编码代理

2026年01月10日最热门的开源项目(Github):用TaoToken统一Key跑通AI编码代理

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/3 19:22:21 阅读更多 →

最新新闻

Trade.dll与TradeX.dll调用避坑指南:通达信程序化交易接口实战

Trade.dll与TradeX.dll调用避坑指南:通达信程序化交易接口实战

简介:面向量化交易与程序化交易开发者的通达信交易接口资料包,整合老版Trade.dll与新一代TradeX.dll行情交易二合一接口,通过TdxTradeServer将交易请求封装为HTTP REST API,解决DLL直连的跨语言调用与远程接入难题,适用…

2026/10/4 21:56:06 阅读更多 →
AI论文网站排行:10款避坑,毕业生真心推荐TaoToken

AI论文网站排行:10款避坑,毕业生真心推荐TaoToken

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/4 21:56:06 阅读更多 →
内存压力测试实战指南:从工具选型到容器避坑全解析

内存压力测试实战指南:从工具选型到容器避坑全解析

简介:一份内存压力测试工具memtester 4.1.2的源码压缩包,面向Linux系统管理员、运维人员及底层开发者,用于检测服务器或PC内存的稳定性与潜在错误,解决因内存位翻转、数据丢失或内存泄漏导致的系统崩溃、数据损坏等隐患。压缩包仅…

2026/10/4 21:56:06 阅读更多 →
Cursor插件四层架构:解决加载失败与中文支持实战指南

Cursor插件四层架构:解决加载失败与中文支持实战指南

1. 项目概述:从“plugins”标题看Cursor生态的底层逻辑与实操真相“plugins”这个词在Cursor语境下,绝不是简单的一个文件夹名或配置项。它直指当前AI编程工具最核心、也最容易被新手忽略的命脉——可扩展性架构。我用Cursor三年,从最早手动改…

2026/10/4 21:55:05 阅读更多 →
Unity3D嵌入WPF实战:窗口句柄、D3DImage纹理共享与视频流方案选型

Unity3D嵌入WPF实战:窗口句柄、D3DImage纹理共享与视频流方案选型

简介:面向需要在桌面应用中集成三维交互能力的开发者,这份资源围绕Unity3D嵌入WPF的实现流程,提供了从Unity场景设计、工程导出到WPF宿主集成的完整示例,覆盖了WindowsFormsHost控件承载渲染窗口、场景加载,以及Unity与…

2026/10/4 21:55:05 阅读更多 →
代码 Agent 自动处理 GitHub Issue 并生成 PR 的工程实践

代码 Agent 自动处理 GitHub Issue 并生成 PR 的工程实践

1. 为什么我要把 Issue 到 PR 这条链路交给代码 Agent第一次冒出“让代码 Agent 处理 GitHub Issue”这个念头,是因为我实在受够了那种机械重复的搬运工作。团队里每天都有新 Issue 进来,一半是拼写错误、空指针防护、日志格式不统一这类小修小补&#x…

2026/10/4 21:55:05 阅读更多 →

日新闻

KT148A语音芯片外挂8002D功放的工程实践指南

KT148A语音芯片外挂8002D功放的工程实践指南

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/4 1:00:58 阅读更多 →
LLC谐振变换器增益公式推导:从FHA等效到完整归一化表达式

LLC谐振变换器增益公式推导:从FHA等效到完整归一化表达式

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/4 1:00:58 阅读更多 →
ARM架构深度解析:从RISC设计理念到交叉编译实战

ARM架构深度解析:从RISC设计理念到交叉编译实战

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/4 1:00:58 阅读更多 →

周新闻

KT148A语音芯片外挂8002D功放的工程实践指南

KT148A语音芯片外挂8002D功放的工程实践指南

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/4 1:00:58 阅读更多 →
LLC谐振变换器增益公式推导:从FHA等效到完整归一化表达式

LLC谐振变换器增益公式推导:从FHA等效到完整归一化表达式

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/4 1:00:58 阅读更多 →
ARM架构深度解析:从RISC设计理念到交叉编译实战

ARM架构深度解析:从RISC设计理念到交叉编译实战

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/4 1:00:58 阅读更多 →

月新闻

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/4 11:40:45 阅读更多 →
Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/4 9:43:54 阅读更多 →
黑夜航拍船只数据集训练YOLOV5模型全流程解析

黑夜航拍船只数据集训练YOLOV5模型全流程解析

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/4 20:14:29 阅读更多 →