直播网站可以做毕设吗struts2 做的网站
2026/4/18 10:03:47 网站建设 项目流程
直播网站可以做毕设吗,struts2 做的网站,中学建设校园网站方案,做暧暖ox免费网站噗#xff0c;这个标题是不是有点AI味#xff1f;哈哈#xff0c;确实有让AI起名#xff0c;但只是起了个名#xff0c;我原来的标题是#xff1a;“给你的数据接口提提速#xff0c;聊聊二级缓存的架构设计” 前言 前阵子给项目做了点性能优化#xff0c;最核心的手段…噗这个标题是不是有点AI味哈哈确实有让AI起名但只是起了个名我原来的标题是“给你的数据接口提提速聊聊二级缓存的架构设计”前言前阵子给项目做了点性能优化最核心的手段就是加上了二级缓存的设计趁着今天有机会我想好好聊聊这个话题。事实上我们的业务系统一直在采用这一套基于Redis的缓存策略但最近我们上线的这套系统是一个可预见的高并发系统顺利上线的话可能比以往任何系统的并发量都要高所以我觉得仅靠Redis就显得有些力不从心了。在大规模流量冲击下Redis 的网络 I/O、内网带宽以及频繁的反序列化开销会逐渐成为压垮 Web 服务器CPU的最后一根稻草。为了提早消除隐患我稍微改造了一下原有的缓存架构增加了二级缓存的设计这其中也踩了点小坑拿出来一看聊聊。架构设计我这里的系统缓存架构是基于EasyCaching这个第三方库构建的当然这个不是核心如果你不喜欢第三方库完全可以基于原生api自行构造。一级缓存L1一级缓存的载体就是内存这是超高并发场景下最有效的防护罩从计算机硬件上它离CPU最近执行速度极快也没有网络开销我觉得市面上所有多级缓存的架构设计第一层基本都得是内存吧。二级缓存L2二级缓存就是Redis了它负责数据共享和持久化支撑是一级缓存穿透后的坚实后盾。因为我们的系统基本都是分布式部署的所以为了保证架构的简单性之前的项目里都是直接用Redis做缓存的引入L1后它也如释重负了在高并发的场景下它的核心作用主要是确保高可用和平衡多个服务节点的数据一致性了。三级缓存L3其实三级就不叫缓存就直接落到数据库了当二级缓存失效请求最终还是会来到数据库我们之前写过的检索逻辑还是一切如常不需要为多级缓存的设计过度修改但此时它的压力就更小了在前面两层防护的保护下即便是高并发的场景应付起来也能游刃有余。架构图*这个架构图如下需要说明的是当2级缓存没有命中的话并不是直接去数据库查询因为这里还设计了一个信号锁而关于信号锁的作用主要是预防缓存击穿当某个热点Key过期后只有1个线程去查数据库其他线程会在信号量处等待然后直接读取第一个线程查出来的缓存。更多的内容大家可以自行GPT一下。实际理解起来可以跳过这点认为L2失效就是打到数据库就可以了。代码安装依赖因为我这里依赖了EasyCaching的生态所以需要先引入EasyCaching.Memory和EasyCaching.CSRedis。注意EasyCaching对于Redis的封装包有两个CSRedis和RedisCSRedis是国人封装的对中文系统应该来说更接地气一点配置更简单而Redis就是基于StackExchange.Redis底层实现虽有差别但在EasyCaching里提供的抽象接口都是一致的所以用谁都可以。PackageReferenceIncludeEasyCaching.InMemoryVersion1.9.2/PackageReferenceIncludeEasyCaching.CSRedisVersion1.9.2/注入服务services.AddEasyCaching(options{options.UseCSRedis(configuration,redis,Easycaching:redisSentinel)options.UseInMemory(conf{conf.MaxRdSecond2;conf.EnableLoggingfalse;conf.DBConfignewEasyCaching.InMemory.InMemoryCachingOptions{SizeLimit1024};},memory);});我这里redis是以哨兵集群的方式接入配置文件如下redisSentinel:{MaxRdSecond:5,EnableLogging:false,LockMs:5000,SleepMs:300,SerializerName:redis,dbconfig:{ConnectionStrings:[略],Sentinels:[节点1,节点2,节点3],ReadOnly:false}},需要注意MaxRdSecond这个参数我这里设置的默认值在redis里是5内存里是2这个参数的意义是EasyCaching为了预防出现缓存雪崩的一个小设计在写入缓存的时候随机加入一个不大于这个MaxRdSecond的时长所以这个值是多少或者需不需要用还是要看你的项目场景。接口和实现publicinterfaceIMultiLevelCacheService{TaskTGetOrCreateAsyncT(stringkey,FuncTaskTfactory,int?l2Secondsnull,CancellationTokenctdefault);TaskResultTGetOrCreateForResultAsyncT(stringkey,FuncTaskResultTfactory,int?l2Secondsnull,CancellationTokenctdefault);TaskRemoveAsync(stringkey,CancellationTokenctdefault);}publicclassMultiLevelCacheService:IMultiLevelCacheService{privatereadonlyIEasyCachingProvider_l2Provider;// RedisprivatereadonlyIEasyCachingProvider_l1Provider;// MemoryprivatereadonlyILoggerMultiLevelCacheService_logger;privatereadonlyMultiLevelCacheOptions_options;//本地锁防止同一个 Key 的缓存失效时大量请求同时冲向数据库防击穿privatestaticreadonlyConcurrentDictionarystring,SemaphoreSlim_locksnew();publicMultiLevelCacheService(IEasyCachingProviderFactoryfactory,IOptionsMultiLevelCacheOptionsoptions,ILoggerMultiLevelCacheServicelogger){_l2Providerfactory.GetCachingProvider(redis);_l1Providerfactory.GetCachingProvider(memory);_loggerlogger;_optionsoptions.Value;}publicasyncTaskResultTGetOrCreateForResultAsyncT(stringkey,FuncTaskResultTfactory,int?l2Secondsnull,CancellationTokenctdefault){//预处理过期时间NormalizeL2Seconds(refl2Seconds);varl1SecondsCalculateL1Seconds(l2Seconds!.Value);//尝试从一级缓存读取 (最快)varl1Resultawait_l1Provider.GetAsyncT(key,ct);if(l1Result.HasValue){ConsoleHelper.WriteLine(1级缓存命中key,ConsoleColor.DarkGreen);returnResultT.Success(l1Result.Value);}//尝试从二级缓存读取try{varl2Resultawait_l2Provider.GetAsyncT(key,ct);if(l2Result.HasValue){ConsoleHelper.WriteLine(2级缓存命中key,ConsoleColor.DarkBlue);//取 Redis 剩余 TTL 和配置上限的最小值塞回L1varttlawait_l2Provider.GetExpirationAsync(key,ct);varremainingL1NormalizeRemainingSeconds(ttl,l1Seconds);await_l1Provider.SetAsync(key,l2Result.Value,TimeSpan.FromSeconds(remainingL1),ct);returnResultT.Success(l2Result.Value);}}catch(Exceptionex){_logger.LogWarning(ex,L2 缓存读取异常Key: {Key},key);}//防击穿加锁回源获取或创建针对该 Key 的信号量varsemaphore_locks.GetOrAdd(key,_newSemaphoreSlim(1,1));awaitsemaphore.WaitAsync(ct);try{//在获取锁的期间可能上一个线程已经把缓存写好了vardoubleCheckawait_l1Provider.GetAsyncT(key,ct);if(doubleCheck.HasValue)returnResultT.Success(doubleCheck.Value);ConsoleHelper.WriteLine(缓存未命中回源加载数据key,ConsoleColor.DarkYellow);//执行回源业务逻辑varfreshResultawaitfactory();//成功则写入双级缓存if(freshResult.IsSuccess){awaitWriteBothAsync(key,freshResult.Value,l2Seconds.Value,l1Seconds,ct);}returnfreshResult;}finally{semaphore.Release();//如果没有人在等待这个锁了可以从字典中移除节省内存if(semaphore.CurrentCount0)_locks.TryRemove(key,out_);}}publicasyncTaskTGetOrCreateAsyncT(stringkey,FuncTaskTfactory,int?l2Secondsnull,CancellationTokenctdefault){//逻辑与上面类似仅返回值处理不同此处略varresultawaitGetOrCreateForResultAsync(key,async(){varvalawaitfactory();returnResultT.Success(val);},l2Seconds,ct);returnresult.Value;}publicasyncTaskRemoveAsync(stringkey,CancellationTokenctdefault){awaitTask.WhenAll(_l1Provider.RemoveAsync(key,ct),_l2Provider.RemoveAsync(key,ct));}}大概解释下我这里主要用到的方法实现是GetOrCreateForResultAsync因为我的数据接口场景里数据回传到接口层时外层包了一个统一的Result接口案例如下[HttpGet(GetArticleDetail/{id})]publicasyncTaskIActionResultGetArticleDetail(longid){stringcacheKeyApiCachePrefixKeys.BuildKey(ApiCachePrefixKeys.DecArticles,id);;varresultawait_multiLevelCacheService.GetOrCreateForResultAsync(cacheKey,()_decArticleRepo.GetArticleDetail(id));//不用缓存时//var result await _decArticleRepo.GetArticleDetail(id);if(result.IsSuccess){returnOk(ApiResult.Success(result));}returnAccepted(ResultExtensions.ToApiResult(result));}这是一个读取文章的案例增加缓存机制后需要将编译后的委托缓存起来所以写法看起来是现在这个样子。如果不需要外层包Result直接拿到数据对象那就可以直接使用GetOrCreateAsync就好。执行效果最后看一下执行的效果第一次请求数据未命中缓存此时Redis里可以看到我们刚刚缓存的数据接下来马上进行第二次请求如期命中一级缓存L1再过一小会儿在请求按预期命中二级缓存L2至此我们的缓存架构就基本完成了而删除缓存的案例就不演示了一个小坑*如同我前面说到我的场景里特殊的返回值类型目前是缓存的编译后的委托而在这之前为了追求方便我使用的表达式函数像下面这样varfreshResultawaitmethodCall.Compile()();看起来也不错而且注意这是2个括号第一个括号是把表达式函数编译成委托返回一个FuncTask第二个括号才是的到这个函数之后的执行。而问题也就出在这两个“括号”上这样看起来优雅实际上隐藏着很大的性能隐患就在第一个括号执行的时候会调用CPU执行IL编译尽管有前面2层缓存做防护真到了高并发的场景一下子多次执行编译工作CPU也得冒烟这就得不偿失了最后改成了现在的样子。当然肯定不是所有小伙伴都能遇到这个问题但如果恰好遇到又恰好也看到这只能说咱们太有缘了点个关注吧哈哈。结语最后我经常听到一些阴阳怪气的声音什么“就你这业务量还搞多级缓存””不想着早点交差一天在这些地方浪费时间有什么用“…巴拉巴拉。我说想有这种声音的人如果你真的理解技术理解业务也亲身验证过这个技术不适合你的业务那你说什么都OK。而现实情况是大部分所谓的专家基本都不做验证性工作他们只想着交差恨不得代码写完再也不改连自己写的代码都不想多看一眼。。。这种人发出的这种声音我的态度是当放屁就好千万别让他们阻挡了我们的好奇心对自己产生怀疑他们的话不值一提。再聊回多级缓存这从来都不是高深技术哪怕是一个日活几百的小系统只要存在重复读、热点数据或对响应速度有要求或者作为开发者的你不甘于只做简单架构那从设计一个多级缓存模块开始吧它一定可以给你和你的系统带来立竿见影的体验提升。还有即便是小项目你能保证它一辈子当个“小项目”吗架构设计不是一锤子买卖而是在演进中预留弹性提前埋下合理的扩展点这远比在流量突增时连滚带爬的救火要从容得多。有偏见的开发者永远写不出好用的系统。好了至此这个多级缓存的话题差不多就聊完了下次再见。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询