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

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

。。。

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

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

想起来了——

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

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

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

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

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

想起来了——Markdown!

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

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

继续回想。。。

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

在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,但是还没有去使用这个特性,现在实际遇到的问题将之呼唤出来。

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

示例代码如下:

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

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

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

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

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

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

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

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

最终解决方法

将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跑高问题以及解决方法的更多相关文章

  1. 实际遭遇GC回收造成的Web服务器CPU跑高

    今天下午有段时间访问园子感觉不如以前那么快的流畅,上Web服务器一看,果然,负载均衡中的1台云服务器CPU跑高. 上图中红色曲线表示的是CPU占用率.正常情况下,CPU占用率一般在40%以下. 这台云 ...

  2. ADF控件ID变化引发JS无法定位控件的解决方法

    原文地址:ADF控件ID变化引发JS无法定位控件的解决方法作者:Nicholas JSFF定义的控件ID到了客户端时往往会改变.例如在JSFF中的一个的ID为"ot1",但是当这个 ...

  3. IntelliJ IDEA编辑文件的时候CPU飙高问题的解决

    原文地址:https://www.javatang.com/archives/2018/04/26/25582403.html 上篇文章中说明了解决IntelliJ IDEA中文输入法无提示的问题,最 ...

  4. C#中异常:“The type initializer to throw an exception(类型初始值设定项引发异常)”的简单分析与解决方法

    对于C#中异常:“The type initializer to throw an exception(类型初始值设定项引发异常)”的简单分析,目前本人分析两种情况,如下: 情况一: 借鉴麒麟.NET ...

  5. 正则表达式回溯-导致CPU偏高

    最近了解了下有关正则表达式回溯的内容,想想就写下来,方便自己. 正则表达式匹配算法是建立在正则表达式引擎的基础上的,目前有两种引擎:DFA(确定型有穷自动机)和NFA(不确定型有穷自动机).这两种引擎 ...

  6. PHP 网站大流量与高并发的解决方法

    php 网站如何应对大流量与高并发呢? 首先,确认服务器硬件是否足够支持当前的流量. 普通的P4服务器一般最多能支持每天10万地理IP,如果访问量比这个还要大,则请配置一台更高性能的专用服务器. 否则 ...

  7. IOS中input光标跑偏问题的解决方法

    ios端兼容input光标高度处理 在最近的项目中遇到一个问题,input输入框光标,在安卓手机上显示没有问题,但是在苹果手机上 当点击输入的时候,光标的高度和父盒子的高度一样.造成的原因就是给父盒子 ...

  8. 使用BasicDataSource引发的数据库连接中断的问题和解决方法

    http://blog.csdn.net/itbasketplayer/article/details/44198963 http://blog.sina.com.cn/s/blog_9e3e5499 ...

  9. Java进程占用内存过高,排查解决方法

    最近收到邮件报警,说内存使作率达到84%.如下图: 解决方法: A:可能是代码原因导致的问题: 1.使用命令:top 查看当前进程的状态 2.从上图可以看到PID:916的java进程占用内存较大.定 ...

随机推荐

  1. April 11 2017 Week 15 Tuesday

    Love is hard to get into, but harder to get out of. 相爱不易,相忘更难. The past are hurt, but I think we can ...

  2. thinkphp 去掉URL 里面的index.php

    例如你的原路径是 http://localhost/test/index.php/home/goods/index.html 那么现在的地址是 http://localhost/test/home/g ...

  3. 使用selenium启动火狐浏览器,解决Unable to create new remote session问题

    今天用火狐浏览器来做自动化,才启动就报错,提示不能创建新的session,不能启动浏览器 问题原因: 火狐driver与火狐浏览器与selenium版本的不兼容 我使用的火狐driver是0.21.0 ...

  4. ajax实现分页页签

    在一些搜索列表的页面中,我们会遇到一些需要处理页签的需求,一般这样的页面,要么是在JSP中处理,每次都跳页.这样做是个很方便的方法.但是如果页面上有很多和列表无关,每次都需要重新渲染是不是显得慢了一些 ...

  5. Spring 注解中@Resource 和 @Authwired 的区别

    @Resource的作用相当于@Autowired,只不过@Autowired按byType自动注入,而@Resource默认按 byName自动注入罢了.@Resource有两个属性是比较重要的,分 ...

  6. ipython notebook超级好用

    这个东西超级好用,以后要以c++和python为主要沟通语言了.

  7. iOS内存管理部分内容

    Objective-C 高级编程 iOS与OS X多线程和内存管理第一章部分讲述了关于ARC的内容,还讲述了关于修饰符的问题,还讲了好多底层的实现的内容,这些底层实现却往往是在面试的过程中经常被遇到的 ...

  8. Lucene检索提高性能的几个方式

    1.采用最新版本的Lucene 2.索引文件存储采用本地文件系统,如果需要挂载远程系统,请采用 readonly方式. 3.当然采用更好的硬件,更高I/O的磁盘 4.提高OS 缓存,调整参数 5.提高 ...

  9. 学习vue-cli3的项目搭建

    安装 关于旧版本 Vue CLI 的包名称由 vue-cli 改成了 @vue/cli. 如果你已经全局安装了旧版本的 vue-cli(1.x 或 2.x),你需要先通过 npm uninstall ...

  10. leetcode第221题(最大正方形)的本地IDE实现及变形

    问题描述: 在一个由 0 和 1 组成的二维矩阵内,找到只包含 1 的最大正方形,并返回其面积.PS:本文也对只包含0的最大正方形面积进行了运算 示例: 输入: 1 0 1 0 0 1 0 1 1 1 ...