很是抱歉,10:00~10:30 左右博客站点出现故障,给您带来麻烦了,请您谅解。html
故障缘由与博文中谈到的部署变动有关,但背后的问题变得很是复杂,复杂到咱们都在怀疑与阿里云服务器 CPU 特性有关。redis
这篇博文原本准备 9:30 左右发布的,但发布博文时出现了 docker swarm 部署异常状况,切换到 docker-compose 部署后问题依旧,一直到 10:30 左右才恢复正常,继续发布这篇博文,在标题中加上了“翻车记”。docker
原先的博文正文开始:数据库
周一贯你们汇报车况以后,咱们的 .NET Core 新车继续以 docker-compose 手动挡的驾驶方式行驶在信息高速公路上,即便昨天驶上了更快的高速(并发量更大的访问高峰),也没有翻车。通过这周3天访问高峰的考验,咱们终于能够充满信心地宣布——咱们度过了新车上路最艰难的磨合期,开新车的剧情从“翻车记”进入到了“行车记”。缓存
翻车成为历史,行车正在进行时,但离咱们的目标“飙车”还有很长的一段距离,“行车记”更多的是修车记,新车改造记。服务器
目前这辆 .NET Core 新车有2个重大问题,一是油耗高(CPU消耗高),有时还会断油(CPU 100% 形成 502),二是手动挡驾驶实在太累。并发
针对油耗高问题,这两天咱们从节能降耗角度对博客系统的 C# 代码进行了优化。app
从日志中发现,有些特别长的 url 会形成 ASP.NET Core 内置的 url rewrite 中间件在正则处理时执行超时。负载均衡
System.Text.RegularExpressions.RegexMatchTimeoutException: The RegEx engine has timed out while trying to match a pattern to an input string. This can occur for many reasons, including very large inputs or excessive backtracking caused by nested quantifiers, back-references and other factors. at System.Text.RegularExpressions.RegexRunner.DoCheckTimeout() at Go64(RegexRunner ) at System.Text.RegularExpressions.RegexRunner.Scan(Regex regex, String text, Int32 textbeg, Int32 textend, Int32 textstart, Int32 prevlen, Boolean quick, TimeSpan timeout) at System.Text.RegularExpressions.Regex.Run(Boolean quick, Int32 prevlen, String input, Int32 beginning, Int32 length, Int32 startat) at System.Text.RegularExpressions.Regex.Match(String input, Int32 startat) at Microsoft.AspNetCore.Rewrite.UrlMatches.RegexMatch.Evaluate(String pattern, RewriteContext context) at Microsoft.AspNetCore.Rewrite.IISUrlRewrite.IISUrlRewriteRule.ApplyRule(RewriteContext context) at Microsoft.AspNetCore.Rewrite.RewriteMiddleware.Invoke(HttpContext context)
对于这个问题,咱们采起的节能降耗措施是借助 AspNetCore.Rewrite 的机制检查 url 的长度,对超出长度限制的 url 直接返回 400 状态码。异步
public class UrlLengthLimitRule : IRule { private readonly int _maxLength; private readonly int _statusCode; public UrlLengthLimitRule(int maxLength, int statusCode) { _maxLength = maxLength; _statusCode = statusCode; } public void ApplyRule(RewriteContext context) { var url = context.HttpContext.Request.GetDisplayUrl(); if (url.Length > _maxLength) { context.HttpContext.Response.StatusCode = _statusCode; context.Result = RuleResult.EndResponse; context.Logger.LogWarning($"The Url is too long to proceed(length: {url.Length}): {url}"); } } }
为了节约每次请求时建立 DbContext 的开销,从新启用了 DbContextPool ,从省吃俭用的角度进一步下降油耗。
services.AddDbContextPool<CnblogsDbContext>(options => { options.UseSqlServer(Configuration.GetConnectionString("BlogDb"), builder => { builder.UseRowNumberForPaging(); builder.EnableRetryOnFailure( maxRetryCount: 3, maxRetryDelay: TimeSpan.FromSeconds(10), errorNumbersToAdd: new int[] { 2 }); }); options.UseQueryTrackingBehavior(QueryTrackingBehavior.NoTracking); });
限制了一个耗油大户,有些字符数特别多的博文内容(好比将图片以 base64string 保存在博文内容中)在正则处理时特别消耗 CPU ,并且 memcached 没法缓存(之后会改用 redis 缓存解决这个问题),对这些博文采起了限制措施。这是咱们在迁移时本身给本身挖的坑,旧版中已经采起了措施,但在迁移时遗漏了。
另外一个节能降耗措施一样是针对博文内容,将从数据库中获取博文内容的代码由 EF Core + LINQ 改成 Dapper + 存储过程,以避开 好大一个坑: EF Core 异步读取大字符串字段比同步慢100多倍 。在执行 DbCommand.ExecuteReaderAsync 时,EF Core 使用的是 CommandBehavior.Default ,Dapper 使用的是 CommandBehavior.SequentialAccess 。在有些场景下使用 CommandBehavior.Default 查询很大的字符串,有严重的性能问题,不只查询速度极慢,并且很耗 CPU (也有可能与使用的 SQL Server 版本有关),只要使用 EF Core ,就只能使用 CommandBehavior.Default ,EF Core 没有提供任何修改 CommandBehavior 的配置能力,因此换成 Dapper 也是无奈之举。
public async Task<string> GetByPostIdAsync(int postId) { using (var conn = new SqlConnection(GlobalSettings.PostBodyConnectionString)) { return await conn.QueryFirstOrDefaultAsync<string>( "[dbo].[Cnblogs_PostBody_Get]", new { postId }, commandType: CommandType.StoredProcedure); } }
对于手动挡驾驶太累问题,在此次改造过程当中,咱们采起一个被全园人都反对的举措,没有安装众星捧月的 k8s 高档自动驾驶系统,而是安装了小众的 docker swam 中档自动驾驶系统。这种“docker swarm 虐我千百遍,我待 docker swarm 如初恋”的情有独钟的傻劲,也许是受《try everything》这首歌的影响,咱们仍是想试试在优化后是否可使用 docker swarm 自动驾驶系统在高速上正常开车(抗住访问高峰),先看看 docker swarm 到底是弱不由风,仍是只是养尊处优?
为了照顾 docker swarm 的养尊处优,咱们在代码中减小一处额外的 HttpClient 形成的 socket 链接开销。在新版博客系统中为了防止有些地方在迁移时遗漏了,咱们在一个 middleware 中会跟踪全部 404 响应,并用 404 对应的 url 向旧版博客发请求,若是旧版响应是 200 ,就记录的日志中留待排查。在访问高峰,大量的 404 请求也会带来很多的 socket 链接开销。
Docker swarm 部署的 .NET Core 博客站点昨天晚上就已经上线观察了,但昨天是 docker swarm 与 docker-compose 混合部署,今天一大早已经所有换成 docker swarm 部署了,新车以由手动挡驾驶模式切换为 docker swarm 自动驾驶模式行驶,目前一切情况良好(9:10左右),就看今天上高速的状况了。
咱们准备了备案,假如 docker swam 在访问高峰撑不住,随时能够切换到手动挡(docker-compose 部署随地待命)。
-----原先的博文正文结束-----
9:30 左右,刚准备发这篇博文时发现还没上高速才刚上快速路 docker swarm 就有点撑不住了(3台8核16G的阿里云服务器),赶忙向手动挡切换,当即向负载均衡添加了3台4核8G的 docker-compose 部署的阿里云服务器(这3台在向手动挡切换前就一直处于运行状态),6台服务器撑住了。
根据当时的状况,咱们彻底认为就是 docker swarm 的问题,是 docker swarm 弱不由风,docker swarm 是一个低档的自动驾驶系统,没法用它在高速上开车(如今来看不必定是 docker swarm 的问题)。因而,咱们进行进一步的切换,将处于关机状态的另外4台 docker-compose 部署的服务器开起来加入负载均衡,将 docker swarm 的服务器摘下负载均衡并关机,这时负载均衡中有7台4核8G的 docker-compose 部署的服务器,按照前几天的状况看,彻底能够撑住。可是,万万没有想到,从 10:00 左右开始,这7台居然也撑不住,并且问题表现与以前 docker swarm 遇到的问题同样,部分服务器本机请求时快时慢,快的时候在10毫秒左右,慢的时候请求执行时间超过30秒,甚至超时。赶忙继续加服务器,但这时加服务器须要购买、启动、预热,虽然是脚本自动完成的,但也比较慢,加了服务器后,问题依旧,因而将一些出问题的服务器下线,但会有其余服务器又出现这个问题,即便新加的服务器也会出现这个问题,在一边加服务器一边将出问题的服务器下线的同时,将 docker swarm 集群的3台服务器也启动起来加入集群分担压力,但很快 docker swarm 集群中的部分服务器也出现了一样的问题。。。
10:30 左右,当达到某种咱们所不知道的平衡点时,当即风平浪静,一切都回归正常,全部服务器本机器本机请求都飞快,包含 docker swarm 集群中的服务器。
如今问题变得格外复杂,回想以前的翻车与正常行驶的状况,从直觉判断中彷佛感受到了一点点新的蛛丝马迹,一个咱们从没怀疑的点可能要归入考虑范围 —— 阿里云服务器 CPU 的性格特色。接下来,咱们会仔细分析一下,看能不能找到一点规律,按照比较符合阿里云服务器 CPU 性格特色的方式接入负载,看是否能够避开这个问题。
再次抱歉,给你们带来这么大的麻烦,请谅解。此次故障咱们万万没有想到,高速开车比咱们想象的难不少,即便一样的部署,接入负载或者增长服务器的时间点不同,也会有不同的表现。
Powered by .NET Core 系列博文:
园友相关博文:
原文出处:https://www.cnblogs.com/cmt/p/11391410.html