行车记+翻车记:.NET Core 新车改造,C# 节能降耗,docker swarm 重回赛道

非常抱歉,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: 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

时间: 2024-11-03 17:02:18

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

记一次企业级爬虫系统升级改造(三):文本分析与数据建模规则化处理

SupportYun当前状况: 博主的SupportYun系统基本已经完成第一阶段预期的底层服务开发啦~~~自己小嘚瑟一下. 有对该系统历史背景与功能等不明白的可先看该系列的第1/2篇文章: 1.记一次企业级爬虫系统升级改造(一) 2.记一次企业级爬虫系统升级改造(二):基于AngleSharp实现的抓取服务 再贴一次博主对这个系统的简要整体规划图: 博主第一阶段主要会做独立的爬虫服务+数据规则化引擎以及内容归类处理这一块. 再简单粗暴一点就是把大量类似下图的网页抓取,然后分析数据,得到活动城市

NOIp(2017)——翻车记

noip2017结束了,不知道该说些什么,感觉翻车了. Day0 12:00学校出发坐车,什么鬼车,颠来颠去,然后,不知怎么就静止不动了,导致很长一段时间我们被堵在高速公路上一动不动,车里闷热,就不知怎么的睡着了,醒来时已经过了一个小时,刚到成都,司机想抄近道,解过被被一根栏杆封锁了去路,和交警协调了好半天,才放我们过去,到酒店时,感觉屁股都坐平了. 晚上浪浪浪,和以前的同学交流心得,然后鬼畜的发现他们都好强啊,QAQ,就我好像很蒟蒻,一种不好的预感油然而生,晚上早早的睡觉了.好像才9:50多分

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

昨天晚上,我们将博客站点的生产环境从 docker swarm 集群切换到了 k8s 集群,开船到目前,航行非常平稳,可以说首航成功! k8s 集群是我们用10台阿里云服务器自己搭建的,1台 master 配置是2核4G,9台 nodes 配置都是4核8G,kubernetes 版本是 1.16.3 . 博客站点请求入口没有走 ingress ,直接通过 service 监听 30080 端口,阿里云负载均衡转发请求到该端口. apiVersion: v1 kind: Service metad

ASP.NET CORE做的网站运行在docker上(不用dockerfile文件部署)

原文:ASP.NET CORE做的网站运行在docker上(不用dockerfile文件部署) 按网上的做法用dockerfile文件是可以弄得出来的,http://www.docker.org.cn/article/119.html, 不过我想把网站文件放在外面硬盘目录,再映射进去,这样只要在硬盘目录中修改CSHTML文件后重启一下容器就行了 步骤如下: 1. vs中建立ASP.NET CORE网站,类名为coreweb1 2. 发布到c:\temp\coreweb1目录 3. 先在本地CMD

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 项目借助devops和github实现CI 项目源码在GitHub里,点击这里获取 下面是录了一些视频,视频全部在B站,做了一下简单的介绍 asp.net core webapi 单元测试控制器(一) 点击这里浏览 asp.net core webapi 单元测试控制器(二) 点击这里浏览 asp.net cor

Codeforces Round#500 Div.2 翻车记

A:签到 #include<iostream> #include<cstdio> #include<cmath> #include<cstdlib> #include<cstring> #include<algorithm> using namespace std; int read() { int x=0,f=1;char c=getchar(); while (c<'0'||c>'9') {if (c=='-') f=

记一次企业级爬虫系统升级改造(二):基于AngleSharp实现的抓取服务

爬虫系统升级改造正式启动: 在第一篇文章,博主主要介绍了本次改造的爬虫系统的业务背景与全局规划构思: 未来Support云系统,不仅仅是爬虫系统,是集爬取数据.数据建模处理统计分析.支持全文检索资源库.其他业务部门和公司资讯系统重要数据来源.辅助决策等功能于一身的企业级Support系统. 介于好多园友对博主的任务排期表感兴趣,便介绍一下博主当时针对这个系统做的工作任务排期概要(排期表就是更加详细细分外加估算工时的一份excel表格,就不贴出来了): 1.总分四大阶段,逐步上线,最终达到预期规划

记一次 OpenIPMI core的分析

现象 gdb对OpenIPMI core的分析表明 domain_id 未被初始化好就被使用,具体信息如下: Thread debugging using libthread_db enabled]> Using host libthread_db library "/lib64/libthread_db.so.1".> Core was generated by `/usr/lsd/bin/tcmeoer'.> Program terminated with sig

记一次Java Core Dump分析过程

#背景提要 很久没有亲自动手部署代码了,命令行的亲切感越来越低.放飞了键盘,习惯了鼠标操作的windows环境.冷不丁实操部署也是不错的. 常常在部署时,运维同学对于[hs_err_pid]文件视而不见.殊不知这是Java 虚拟机崩溃日志. #这次是如何分析问题的? 一.首先查看日志头文件 # # A fatal error has been detected by the Java Runtime Environment: # # SIGSEGV (0xb) at pc=0x00007f90e