1.概述

转载:正则表达式引发的惨案

导读:正则表达式是程序员经常使用的工具之一。本文作者通过一个正则表达式的陷阱,先深入剖析了出现问题的原因,后给出怎么处理这类问题的方法。最后还给出了一些检测常见正则表达式问题的工具,十分值得深入研究。

前几天线上一个项目监控信息突然报告异常,上到机器上后查看相关资源的使用情况,发现 CPU 利用率将近 100%。通过 Java 自带的线程 Dump 工具,我们导出了出问题的堆栈信息。


我们可以看到所有的堆栈都指向了一个名为 validateUrl 的方法,这样的报错信息在堆栈中一共超过 100 处。通过排查代码,我们知道这个方法的主要功能是校验 URL 是否合法。

很奇怪,一个正则表达式怎么会导致 CPU 利用率居高不下。为了弄清楚复现问题,我们将其中的关键代码摘抄出来,做了个简单的单元测试。


当我们运行上面这个例子的时候,通过资源监视器可以看到有一个名为 java 的进程 CPU 利用率直接飙升到了 91.4% 。


看到这里,我们基本可以推断,这个正则表达式就是导致 CPU 利用率居高不下的凶手!

于是,我们将排错的重点放在了那个正则表达式上:

^([hH][tT]{2}[pP]://|[hH][tT]{2}[pP][sS]://)(([A-Za-z0-9-~]+).)+([A-Za-z0-9-~\\/])+$

这个正则表达式看起来没什么问题,可以分为三个部分:

第一部分匹配 http 和 https 协议,第二部分匹配 www. 字符,第三部分匹配许多字符。我看着这个表达式发呆了许久,也没发现没有什么大的问题。

其实这里导致 CPU 使用率高的关键原因就是:Java 正则表达式使用的引擎实现是 NFA 自动机,这种正则表达式引擎在进行字符匹配时会发生回溯(backtracking)。而一旦发生回溯,那其消耗的时间就会变得很长,有可能是几分钟,也有可能是几个小时,时间长短取决于回溯的次数和复杂度。

看到这里,可能大家还不是很清楚什么是回溯,还有点懵。没关系,我们一点点从正则表达式的原理开始讲起。

正则表达式引擎

正则表达式是一个很方便的匹配符号,但要实现这么复杂,功能如此强大的匹配语法,就必须要有一套算法来实现,而实现这套算法的东西就叫做正则表达式引擎。简单地说,实现正则表达式引擎的有两种方式:DFA 自动机(Deterministic Final Automata 确定型有穷自动机)和 NFA 自动机(Non deterministic Finite Automaton 不确定型有穷自动机)。

对于这两种自动机,他们有各自的区别,这里并不打算深入将它们的原理。简单地说,DFA 自动机的时间复杂度是线性的,更加稳定,但是功能有限。而 NFA 的时间复杂度比较不稳定,有时候很好,有时候不怎么好,好不好取决于你写的正则表达式。但是胜在 NFA 的功能更加强大,所以包括 Java 、.NET、Perl、Python、Ruby、PHP 等语言都使用了 NFA 去实现其正则表达式。

那 NFA 自动加到底是怎么进行匹配的呢?我们以下面的字符和表达式来举例说明。

text="Today is a nice day."regex="day"

要记住一个很重要的点,即:NFA 是以正则表达式为基准去匹配的。也就是说,NFA 自动机会读取正则表达式的一个一个字符,然后拿去和目标字符串匹配,匹配成功就换正则表达式的下一个字符,否则继续和目标字符串的下一个字符比较。或许你们听不太懂,没事,接下来我们以上面的例子一步步解析。

  1. 首先,拿到正则表达式的第一个匹配符:d。于是那去和字符串的字符进行比较,字符串的第一个字符是 T,不匹配,换下一个。第二个是 o,也不匹配,再换下一个。第三个是 d,匹配了,那么就读取正则表达式的第二个字符:a。

  2. 读取到正则表达式的第二个匹配符:a。那着继续和字符串的第四个字符 a 比较,又匹配了。那么接着读取正则表达式的第三个字符:y。

  3. 读取到正则表达式的第三个匹配符:y。那着继续和字符串的第五个字符 y 比较,又匹配了。尝试读取正则表达式的下一个字符,发现没有了,那么匹配结束。

上面这个匹配过程就是 NFA 自动机的匹配过程,但实际上的匹配过程会比这个复杂非常多,但其原理是不变的。

NFA自动机的回溯

了解了 NFA 是如何进行字符串匹配的,接下来我们就可以讲讲这篇文章的重点了:回溯。为了更好地解释回溯,我们同样以下面的例子来讲解。

text="abbc"
regex="ab{1,3}c"

上面的这个例子的目的比较简单,匹配以 a 开头,以 c 结尾,中间有 1-3 个 b 字符的字符串。NFA 对其解析的过程是这样子的:

  1. 首先,读取正则表达式第一个匹配符 a 和 字符串第一个字符 a 比较,匹配了。于是读取正则表达式第二个字符。

  2. 读取正则表达式第二个匹配符 b{1,3} 和字符串的第二个字符 b 比较,匹配了。但因为 b{1,3} 表示 1-3 个 b 字符串,以及 NFA 自动机的贪婪特性(也就是说要尽可能多地匹配),所以此时并不会再去读取下一个正则表达式的匹配符,而是依旧使用 b{1,3} 和字符串的第三个字符 b 比较,发现还是匹配。于是继续使用 b{1,3} 和字符串的第四个字符 c 比较,发现不匹配了。此时就会发生回溯。

  3. 发生回溯是怎么操作呢?发生回溯后,我们已经读取的字符串第四个字符 c 将被吐出去,指针回到第三个字符串的位置。之后,程序读取正则表达式的下一个操作符 c,读取当前指针的下一个字符 c 进行对比,发现匹配。于是读取下一个操作符,但这里已经结束了。

下面我们回过头来看看前面的那个校验 URL 的正则表达式:

^([hH][tT]{2}[pP]://|[hH][tT]{2}[pP][sS]://)(([A-Za-z0-9-~]+).)+([A-Za-z0-9-~\\/])+$

出现问题的 URL 是:

http://www.fapiao.com/dzfp-web/pdf/download?request=6e7JGm38jfjghVrv4ILd-kEn64HcUX4qL4a4qJ4-CHLmqVnenXC692m74H5oxkjgdsYazxcUmfcOH2fAfY1Vw__%5EDadIfJgiEf

我们把这个正则表达式分为三个部分:

  1. 第一部分:校验协议。^([hH][tT]{2}[pP]

    【正则表达式】正则表达式引发的惨案 回溯 超时 cpu 100相关推荐

    1. 线上服务 CPU 100%?一键定位 so easy!

      0.背景 经常做后端服务开发的同学,或多或少都遇到过 CPU 负载特别高的问题.尤其是在周末或大半夜,突然群里有人反馈线上机器负载特别高,不熟悉定位流程和思路的同学可能登上服务器一通手忙脚乱,定位过程 ...

    2. 一键定位java 线上服务 CPU 100%

      传统方法: top oder by with P:1040 // 首先按进程负载排序找到 axLoad(pid) top -Hp 进程PID:1073 // 找到相关负载 线程PID printf & ...

    3. 线上服务 CPU 100% ?一键定位 so easy!

      点击上方 "编程技术圈"关注, 星标或置顶一起成长 后台回复"大礼包"有惊喜礼包! 每日英文 A man should have one dream at le ...

    4. 一个purge参数引发的惨案——从线上hbase数据被删事故说起

      一个purge参数引发的惨案--从线上hbase数据被删事故说起 在写这篇blog前,我的心情久久不能平静,虽然明白运维工作如履薄冰,但没有料到这么一个细小的疏漏会带来如此严重的灾难.这是一起其他公司 ...

    5. C#正则表达式 — 正则表达式类

      来自:http://www.dreamdu.com/blog/2008/10/07/cs_regex_class/ C#正则表达式 - 正则表达式类 又重新学了一遍C#的正则表达式,温故而知新,C#的 ...

    6. mysql修改字符集utf8mb4引发的惨案

      mysql修改字符集utf8mb4引发的惨案 环境配置: Linux CentOS 7 mysql5.7字符编码是utf8: 惨案原因:数据库表需要支持表情很符号,表情符号一般是4个字符,utf8最多 ...

    7. 一个 Google 汉堡引发的惨案

      Emoji,即表情符号,最早是由日本人栗田穰崇创作,在日本网络及手机用户中流行.自苹果公司发布的 iOS 5 输入法中加入了 emoji 后,这种表情符号开始席卷全球.小伙伴们也都热衷于在聊天信息中使 ...

    8. [故障公告]14:40-15:00博客站点web服务器雪崩似的CPU 100%

      非常抱歉,今天下午14:40-15:00左右,web服务器突然雪崩似的出现CPU 100%问题,造成博客站点无法访问(503).由此给您带来很大的麻烦,请您谅解. 在问题出现之前,博客站点运行着5台w ...

    9. Java死锁和Java进程Java CPU 100%排查

      三板斧:top -> top -Hp ->jstack 通过 top 命令找到 CPU 消耗最多的进程号: 通过 top -Hp 进程号 命令找到 CPU 消耗最多的线程号(列名仍然为 P ...

    最新文章

    1. 贸易保护主义不能解决德国光伏企业的问题
    2. asp.net 页面中点击按钮后无反应的解决方法
    3. jQuer or js 插件aptana studio 3.4.0)教你玩转eclipse配置(全世
    4. excel最常用的八个函数_Excel中最常用的快捷键
    5. js mysql 住宿系统_[源码和文档分享]基于JavaScript和MySQL实现的酒店管理系统
    6. Redis介绍使用及进阶
    7. android 播放MP3实例
    8. Angular jasmine fixture.detectChanges如何触发directive的set方法
    9. PHP面向对象2之变量、方法
    10. 雷蛇用户计算机不满足系统要求,我的RazerBook13已经升级Windows11了
    11. python 短视频_短视频篇 | Python 带你进行短视频二次创作
    12. DataSetProvider的Option属性
    13. Linux:进入文件夹提示Permission denied错误
    14. ISO9001-2008标准(中英文对照)
    15. CentOS系统下文件夹目录结构及其作用
    16. 病毒木马入侵招数大曝光
    17. missing external certificate的解决方案
    18. android加载ftp图片,按键安卓版网络访问之 图片处理,FTP,HTTP 实现
    19. 【手把手教你】趋势跟踪交易策略的量化回测
    20. 快手、抖音、视频号交战内容付费

    热门文章

    1. 雷军曾被马云夫人张瑛数落一个多小时?当事人回应:都是谣言
    2. 华为技术有限公司申请“荣耀视频”、“荣耀钱包”等商标
    3. 中芯国际科创板上市发行价定为27.46元/股,募资或超500亿元
    4. 5年内,创始人父子相继因意外去世,这家市值百亿公司怎么了?
    5. 百度元老黯然离职是被开除?向海龙回应...
    6. 开局说丑说拒绝,开售抢的贼快!iPhone11预约超百万
    7. 1小时搞定马化腾,却巨亏200亿!近五年最火的创业明星,正在沦为网红
    8. 华硕ROG游戏手机2跑分曝光:骁龙855 Plus加持 性能难逢对手
    9. 因低俗色情网络文学作品 多个知名小说平台停更整改
    10. 结合中国古典文化取名 华为意在把传说化为现实奇迹