C#正则表达式引发的CPU跑高问题以及解决方法团队

摘要:
由于我们曾多次遇到阿里云云服务器CPU问题,我们现在对阿里云服务器有偏见。只要存在CPU问题,我们首先会怀疑云服务器的问题。然后突如其来的情况动摇了我们的“坚定”,新增加的云盘云服务器也出现了CPU高的问题。清空缓存将导致大量此类操作,这将给CPU带来压力。当OCS缓存被清除时,需要重建大量Markdown内容,并使用大量复杂的正则表达式进行处理,这将给CPU带来巨大的压力!

Web服务器CPU跑高

3月23日(周日)下午16:30左右,博客园主站负载均衡中的2台Web服务器CPU玩起了爬楼梯的游戏(见上图),一直爬到了接近100%。发现这个状况后,我们立即将这2台阿里云临时磁盘云服务器从负载均衡中摘下来,挂上1台云盘云服务器,恢复了正常。

由于曾经多次遇到过阿里云云服务器CPU问题,现在对阿里云云服务器产生了一种偏见,只要出现CPU问题,就会首先怀疑云服务器的问题。而这次出现问题时,换上云盘云服务器立即恢复正常,我们就坚定地认为临时磁盘云服务器存在某种问题。于是,我们提交了工单,向阿里云客服抱怨这个问题。

。。。

接着突然发生的状况让我们的“坚定”产生了动摇,刚加上去的那台云盘云服务器也出现了CPU跑高的问题。

云盘云服务器CPU跑高

阿里云云服务器连续出问题的可能性很小,也许是其他原因引起的。这个突发情况让我们冷静下来去回想出问题之前进行过什么操作。

想起来了——

阿里云OCS操作按钮

出现问题之前,我们进行过清空OCS实例缓存的操作(注:OCS是阿里云提供的Memcached缓存服务)。

缓存不仅能缓解数据库的压力,而且能缓解CPU的压力。比如有些数据从数据库中读取出来后需要进行一些正则表达式的处理(耗CPU的大户),如果缓存中存在,直接读取就行;如果缓存中不存在,需要先从数据库中读取,接着进行正则处理,然后放入缓存。清空缓存后会引发大量这样的操作,从而给CPU带来压力。

但是以前我们多次在周末访问低峰的时候进行过同样的清空OCS缓存的操作,增加的这点压力对Web服务器的CPU来说是小菜一碟。

为什么这次却有天壤之别?

  • 这个周末的访问量的确比之前的周末要高一些,但不致于影响这么大。
  • 在CPU跑高时,日志中记录了很多OCS缓存客户端读取数据慢的情况。难道是OCS的问题?是OCS读取缓存慢引发CPU高,还是CPU高引发OCS缓存读取速度慢?分析之后,还是觉得后者的可能性大一些。
  • 现在与之前相比,哪些变化可能引发在缓存失效的情况下需要更多的CPU消耗?

想起来了——Markdown!

C#正则表达式引发的CPU跑高问题以及解决方法团队第4张

1月份的时候我们发布了简陋的Markdown功能,现在比以前有了更多Markdown写的博文,而这些博文转换成HTML用了复杂的正则表达式。当访问一篇使用Mardown写的博文时,如果缓存中没有,会从数据库读取原始的Markdown内容,用正则表达式转换成HTML后放入缓存,后续的访问就直接从缓存中读取HMTL内容。当清空OCS缓存后,大量的Markdown内容需要重建缓存,进行大量的复杂的正则表达式处理,这会给CPU带来很大的压力!

这是就是问题的真相?难道是我们自己导演的缓存雪崩?。。。没这么简单!在访问低峰,共16个核的CPU竟然都没有撑住,不可思议!凭我们的经验,这16个核没这么弱不禁风!

继续回想。。。

又想起来了!我们曾经实际在另外一个ASP.NET应用程序中遇到过类似的情况——

C#正则表达式引发的CPU跑高问题以及解决方法团队第5张

在C#中用正则表达式处理大文本时,某种条件会触发CPU高上去,而且会一直高居不下,只有回收应用程序池才能让CPU下去。当时怎么优化正则表达式也没有用,后来没办法,使用磁盘文件进行大量缓存,减少了触发这个问题的几率。

难道.NET在正则表达式处理上隐藏着不为人所知的坑?微软从.NET Framework 4.5开始给正则表达式增加了超时设置(matchTimeout),似乎验证了这一点。

//   matchTimeout:
//     A time-out interval, or System.Text.RegularExpressions.Regex.InfiniteMatchTimeout
//     to indicate that the method should not time out.
public Regex(string pattern, RegexOptions options, TimeSpan matchTimeout);

虽然我们的应用程序已经升级到了.NET  Framework 4.5,但是还没有去使用这个特性,现在实际遇到的问题将之呼唤出来。

C#正则表达式引发的CPU跑高问题以及解决方法团队第6张

解决方法一:给Markdow转换所用的所有正则表达式加上超时设置——TimeSpan.FromSeconds(1),如果某个正则表达式处理超过1秒就会引发异常,从而不让任何一只老鼠坏了一碗汤。

示例代码如下:

private static Regex _newlinesLeadingTrailing = 
    new Regex(@"^
+|
+z", 
    RegexOptions.Compiled, 
    TimeSpan.FromSeconds(1));

但是,这样一个一个正则表达式进行修改,好麻烦!

于是有了“解决方法一”的改进版:

在Global.asax.cs中Application_Start添加如下的代码:

protected void Application_Start(object sender, EventArgs e)
{
    AppDomain.CurrentDomain.SetData("REGEX_DEFAULT_MATCH_TIMEOUT", TimeSpan.FromSeconds(1));
}

这样就可以全局设置所有正则表达式的默认超时时间。

采用了解决方法一之后,我们又仔细考虑了一下,学得这不是最终解决方案。解决方法一虽然解决了一只老鼠坏一锅汤的问题,但是假如一百只、一千只老鼠接连出现呢?也会给CPU带来压力,这种压力会影响主站对其他请求的响应速度。

更好的解决方法应该是——不管Markdown的正则表达式处理消耗多少CPU,即使把CPU跑爆了,也不要影响主站。所以,将这部分处理分出去,隔离开来,才是最终解决方法。

C#正则表达式引发的CPU跑高问题以及解决方法团队第7张

最终解决方法

将Markdown的正则表达式处理放在独立的站点、独立的服务器,然后在博客程序中需要处理Markdown的时候,将文本内容post给这个独立站点进行处理。

之前在博客程序中是这样处理Markdown的:

if (entry.IsMarkdown)
{
    body = new MarkdownSharp.Markdown().Transform(body);
}

现在用了一台单独的云服务器跑ASP.NET MVC程序进行Markdown处理,MVC代码如下:

public class MarkdownController : Controller
{
    [HttpPost]
    public ActionResult Transform()
    {
        using (var reader = new StreamReader(Request.InputStream))
        {
            var bodyText = reader.ReadToEnd();
            return Content(new MarkdownSharp.Markdown().Transform(bodyText));
        }            
    }
}

上面的代码中,为了减少MVC的处理工作,直接从http post body中获取Markdown文本。

然后博客程序中用HttpClient将Markdown文本post给这个独立MVC站点进行处理。示例代码如下:

if (entry.IsMarkdown)
{
    var httpClient = new HttpClient();
    var httpContent = new StringContent(body);
    var response = httpClient.PostAsync("http://markdown.s.cnblogs.com/markdown/transform", httpContent).Result;
    if (response.StatusCode == System.Net.HttpStatusCode.OK)
    {
        body = response.Content.ReadAsStringAsync().Result;
    }
    else
    {
        body = new MarkdownSharp.Markdown().Transform(body);                        
    }                
}

上面的代码也考虑了一定的容错,假如处理Markdown的站点down掉了,博客程序会暂时辛苦一下,自己进行Markdown的正则处理。

这个最终解决方案已经实际部署到我们的主站(www.cnblogs.com)中。

免责声明:文章转载自《C#正则表达式引发的CPU跑高问题以及解决方法团队》仅用于学习参考。如对内容有疑问,请及时联系本站处理。

上篇select模式学习(二)之:客户端MySQL在linux上的rpm包方式安装方法下篇

宿迁高防,2C2G15M,22元/月;香港BGP,2C5G5M,25元/月 雨云优惠码:MjYwNzM=

相关文章

Spring Cache的基本使用与分析

概述 使用 Spring Cache 可以极大的简化我们对数据的缓存,并且它封装了多种缓存,本文基于 redis 来说明。 基本使用 1、所需依赖 <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spr...

MySQL的瑞士军刀(转)

这里主要讲mysql运维中的一些主要工具,这些工具可能大家都用过,特别是系统管理员或者做linux服务器维护的同学可能都知道这些小工具,这 里讲得会比较多一些,除了系统监控的小工具,还包括一些mysql的工具,甚至深入一些的工具也会讲到,重点是大家听完了后,一定要去自己动手实践一下, 这样才有意义,熟能生巧。 光听说过是不行的,还得都要去实践,实践出真知,...

java进程占用CPU高的问题

一. 上节回顾怎么查看CPU使用率? top:显示了系统总体的CPU和内存使用情况,以及各个进程的资源使用情况。默认每隔3s刷新一次 ps:只显示每个进程的资源使用情况 top并没有细分进程的用户态CPU和内核态CPU pidstat:可以分析每个进程的CPU使用情况 通过top,ps,pidstat这些工具,能够很快找到CPU使用率较高的进程。要知道CP...

Tomcat启动时加载数据到缓存---web.xml中listener加载顺序(例如顺序:1、初始化spring容器,2、初始化线程池,3、加载业务代码,将数据库中数据加载到内存中)

  最近公司要做功能迁移,原来的后台使用的Netty,现在要迁移到在uap上,也就是说所有后台的代码不能通过netty写的加载顺序加载了。   问题就来了,怎样让迁移到tomcat的代码按照原来的加载顺序进行加载(例如顺序:1、初始化spring容器,2、初始化线程池,3、加载业务代码,将数据库中数据加载到内存中)。我个人首先想到的是在tomcat中加监听...

【转】CUDA程序优化要点

CUDA程序优化应该考虑的点:精度:只在关键步骤使用双精度,其他部分仍然使用单精度浮点以获得指令吞吐量和精度的平衡;    目前 GPU 的单精度性能要远远超过双精度性能,整数乘法、求模、求余等运算的指令吞吐量也较为有限。在科学计算中,由于需要处理的数据量巨大,往往采用双精度或者四精度才能获得可靠的结果,目前的 Tesla 架构还不能很好的满足高精度计算的...

elasticsearch 性能优化

转载: https://www.cnblogs.com/jajian/p/10465519.html 硬件选择 Elasticsearch(后文简称 ES)的基础是 Lucene,所有的索引和文档数据是存储在本地的磁盘中,具体的路径可在 ES 的配置文件../config/elasticsearch.yml中配置,如下: # ---------------...