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. DEEP LEARNING 大满贯课程表

    Reinforcement Learning post by ISH GIRWAN Courses/Tutorials Deep Reinforcement Learning, Spring 2017 ...

  2. Git入门体验

    Git这个东西我也是最近才知道的,然后知道后却发现一个事实:自己真的是太LOW啦!竟然连Git都不知道!!!??? Git 在实际协同工作时会为我们提供巨大帮助, 下面简单介绍一下Git的用法: 一. ...

  3. Django QuestSet API (官方文档)

    1.返回新查询集的方法 (1)filter():滤指定条件的结果 Entry.objects.filter(pub_date__gt=datetime.date(2005, 1, 3), headli ...

  4. Uva 11235 RMQ问题

    RMQ: 有一个不变的数组,不停的求一个区间的最小值. 使用倍增的思想优化到logN; d(i,j) 表示从 i 开始的,长度为2j的一段元素中的最小值. 那么状态转移方程: d(i,j) = min ...

  5. Linux内存管理 —— 内核态和用户态的内存分配方式

    1. 使用buddy系统管理ZONE我的这两篇文章buddy系统和slab分配器已经分析过buddy和slab的原理和源码,因此一些细节不再赘述.所有zone都是通过buddy系统管理的,buddy ...

  6. 配置SSIS 包部署

    包配置是干嘛滴! 使用包配置可以从开发环境的外部设置运行时属性和变量.         把用户变量转换成Config文件 步骤: 准备工作 把第一个例子中的userinfo.txt复制两份,放到同一个 ...

  7. 重定向跳出父Frame

    当session过期后可以用过滤器来设置重定向页面 代码如下: public class ActionFilter extends HttpServlet implements Filter {pri ...

  8. img适配问题解决方法

    将img放到background中,将background-size设置为100%:只需要适配背景为img的元素的宽和高即可.

  9. 2、SpringBoot+MybatisPlus整合-------BaseCRUD

    开发工具:STS 代码下载链接:GitHub管理代码 版本: Springboot:1.5.14.RELEASE 使用2.0以上的Springboot,会报出一些异常.欢迎知道异常原因的大牛解惑. M ...

  10. Windows/Linux下查看系统CPU使用最高的线程

    参考:https://blog.csdn.net/qq_27818157/article/details/78688580 jstack -l 31372 > c:/31372.stack