非常抱歉,10:00~10:30 左右博客站点出现故障,给您带来麻烦了,请您谅解。

故障原因与博文中谈到的部署变更有关,但背后的问题变得非常复杂,复杂到我们都在怀疑与阿里云服务器 CPU 特性有关。

这篇博文本来准备 9:30 左右发布的,但发布博文时出现了 docker swarm 部署异常情况,切换到 docker-compose 部署后问题依旧,一直到 10:30 左右才恢复正常,继续发布这篇博文,在标题中加上了“翻车记”。

原先的博文正文开始:

周一向大家汇报车况之后,我们的 .NET Core 新车继续以 docker-compose 手动挡的驾驶方式行驶在信息高速公路上,即使昨天驶上了更快的高速(并发量更大的访问高峰),也没有翻车。经过这周3天访问高峰的考验,我们终于可以充满信心地宣布——我们度过了新车上路最艰难的磨合期,开新车的剧情从“翻车记”进入到了“行车记”。

翻车成为历史,行车正在进行时,但离我们的目标“飙车”还有很长的一段距离,“行车记”更多的是修车记,新车改造记。

目前这辆 .NET Core 新车有2个重大问题,一是油耗高(CPU消耗高),有时还会断油(CPU 100% 造成 502),二是手动挡驾驶实在太累。

针对油耗高问题,这两天我们从节能降耗角度对博客系统的 C# 代码进行了优化。

从日志中发现,有些特别长的 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: ,
maxRetryDelay: TimeSpan.FromSeconds(),
errorNumbersToAdd: new int[] { });
});
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 系列博文:

园友相关博文:

行车记+翻车记:.NET Core 新车改造,C# 节能降耗,docker swarm 重回赛道的更多相关文章

  1. Powered by .NET Core 进展:用 docker-compose 验证高并发问题嫌疑犯 docker swarm

    相关博文: [故障公告]发布 .NET Core 版博客站点引起大量 500 错误 [网站公告].NET Core 版博客站点第二次发布尝试 暴风雨中的 online : .NET Core 版博客站 ...

  2. A · F · O —— JLOI2018翻车记(附Day1简要题解)

    JLOI2018翻车记 并不知道该怎么写... 算了还是按照标准剧情来吧 这应该是一篇写得非常差的流水账... 2018.04.04 Day -1 省选前在机房的最后一天. 压力并不是很大,毕竟联赛 ...

  3. 详解C#泛型(二) 获取C#中方法的执行时间及其代码注入 详解C#泛型(一) 详解C#委托和事件(二) 详解C#特性和反射(四) 记一次.net core调用SOAP接口遇到的问题 C# WebRequest.Create 锚点“#”字符问题 根据内容来产生一个二维码

    详解C#泛型(二)   一.自定义泛型方法(Generic Method),将类型参数用作参数列表或返回值的类型: void MyFunc<T>() //声明具有一个类型参数的泛型方法 { ...

  4. k8s 开船记-首航:博客站点从 docker swarm 切换到 k8s

    昨天晚上,我们将博客站点的生产环境从 docker swarm 集群切换到了 k8s 集群,开船到目前,航行非常平稳,可以说首航成功! k8s 集群是我们用10台阿里云服务器自己搭建的,1台 mast ...

  5. 七缸发动机预热,docker swarm + .net core 高速飙车成功

    (图片来源:Microsoft Blog) 上周五上午在我们将 .net core 博客站点由 docker swarm 自动驾驶改为 docker-compose 手动驾驶后,依然发生了翻车,意料之 ...

  6. .NET Core多平台开发体验[4]: Docker

    对于一个 .NET开发人员,你可能没有使用过Docker,但是你不可能没有听说过Docker.Docker是Github上最受欢迎的开源项目之一,它号称要成为所有云应用的基石,并把互联网升级到下一代. ...

  7. asp.net core webapi/website+Azure DevOps+GitHub+Docker

    asp.net core webapi/website+Azure DevOps+GitHub+Docker 新春开篇作,主要写一下关于asp.net core web/api 2.2 项目借助dev ...

  8. ASP.NET CORE做的网站运行在docker实践

    用VS2017 建立了 DotNet Core 2.2 的网站后,如何转移到 Docker 下运行? 下面分两种方式来实践: 1.直接手动命今行,将本机目录映射进Docker,运行网站.2.制作 Im ...

  9. .net core Jenkins持续集成Linux、Docker、K8S

    jenkins插件 系统管理 -> 管理插件,安装如下插件. #如果搜索不到去掉Plugin在搜索 GitLab Plugin Gitlab Hook Plugin #使用Gitlab账号做用户 ...

随机推荐

  1. 项目中操作redis改brpop阻塞模式为订阅模式的实现-java实习笔记二

    更改项目需求以及项目之前阻塞模式问题的叙述已经在上一篇说过了,详情可参考:https://www.cnblogs.com/darope/p/10276213.html  ,https://yq.ali ...

  2. 【Android UI】自定义带按钮的标题栏

    自定义标题栏在很多的android app中很常见,可以说是一种很有用的UI设计方法.自 己也本着学习的态度,经过一番各种坑,终于实现了,现总结如下: 一:大致流程 1.      对指定的andro ...

  3. 个人永久性免费-Excel催化剂功能第26波-正确的Excel密码管理之道

    Excel等文档肩负着我们日常大量的信息存储和传递工作,难免出现数据安全的问题,OFFICE自带的密码设置,在什么样的场景下才有必要使用?网上所宣称的OFFICE文档密码保护不安全,随时可被破解,究竟 ...

  4. 字符串的排列(剑指offer)

    题目描述: 输入一个字符串,按字典序打印出该字符串中字符的所有排列.例如输入字符串abc,则打印出由字符a,b,c所能排列出来的所有字符串abc,acb,bac,bca,cab和cba. 思路: 利用 ...

  5. RabbitMQ(三):RabbitMQ与Spring Boot简单整合

    RabbitMQ是目前非常热门的一款消息中间件,不管是互联网大厂还是中小企业都在大量使用.Spring Boot的兴起,极大地简化了Spring的开发,本文将使用Spring Boot与RabbitM ...

  6. [算法]LeetCode 1.两数之和

    LeetCode 1.两数之和(python) 1.朴素解法 最朴素的两个for循环大法: class Solution: def twoSum(self, nums: List[int], targ ...

  7. 初始SpringMVC 完整版

    初始SpringMVC 1.SpringMVC 也叫Spring Web mvc,属于表现层的框架.Spring MVC是Spring框架的一部分,是在Spring3.0后发布的. 2.Java EE ...

  8. SpringMVC学习笔记之---深入使用

    SpringMVC深入使用 (一)基于XML配置的使用 (1)配置 1.SpringMVC基础配置 2.XML配置Controller,HandlerMapping组件映射 3.XML配置ViewRe ...

  9. centos开发环境安装

    执行 yum install gcc gcc-c++ gcc-g77 flex bison autoconf automake bzip2-devel zlib-devel ncurses-devel ...

  10. DevOps实施历程-v1.0

    ​    有AF项目的成功案例(DevOps实施历程-半自动化),公司新项目全部依此为模板,实现了从代码到安装的自动化流水线,为此我输出了Jenkins自动化指南.AF项目指南等文档,方便大家查阅和参 ...