draft: true title: “正则表达式回溯问题” date: 2019-07-09T19:12:59+08:00 slug: “” lastmod: 2019-07-09T19:12:59+08:00 keywords: [] description: “” tags: [] categories: [] series: [] author: “heguangfu” katex: true

comment: true toc: true autoCollapseToc: true postMetaInFooter: true hiddenFromHomePage: false contentCopyright: “
感谢阅读,如果有问题请您留言,我会及时改正
本博客所有原创文章版权归hgf所有,转载请注明出处hgfkeep.github.io” reward: false mathjax: true mathjaxEnableSingleDollar: true mathjaxEnableAutoNumber: true

flowchartDiagrams: enable: false options: “”

sequenceDiagrams: enable: false options: “”

typora-root-url: ../../static


发现问题

监控预警CPU占用100%。

  1. 查看java进程中那个线程造成CPU100%: top -p<pid> -H
  2. 依据线程id,生成jstack信息中的nid(n表示native,线程id,十六进制表示): python -c"print hex(9757)”
  3. dump JVM堆栈,查看造成CPU 100%问题的线程堆栈。

仅排查,发现:

image-20190710141217967

通过查找堆栈信息,发现是正则表达式匹配时,出现了问题。

问题复现

image-20190710104618039

image-20190710104722499

多添加一个异常信息的x,就会造成程序匹配的步数极具增加。且y前面每多一个x,步数就会翻倍。造成计算步数指数级上升。

问题分析

将上述的常量 <html></html>用简单的字母替换, 例如正则表达式 a(x*x*)*yb, 简单匹配axxyb 需要 9步,如下图:

1

但是待匹配数据无法正确匹配时,如 axxyxb,那么匹配步数变为88步,如下图:

2

如果需要较长的匹配才能发现无法匹配时,例如待匹配字符串为 axxxyxb 所需步数会更长,如下图:

3

经过测试,统计结果如下表(正则表达式为 a(x*x*)*yb):

字符串 匹配步数
axxyb 9
axxyxb 88
axxxyxb 300
axxxxyxb 1024
axxxxxyxb 3496
axxxxxxyxb 11936
axxxxxxxyxb 40752
axxxxxxxxyxb 139136
axxxxxxxxxyxb 475040

计算步数成指数级上升。可想而知,用正则表达式处理网页信息,会出现占用CPU引起性能问题。

解决办法

  1. 优化正则表达式
    1. 有限量词(Possessive Quantifiers)
    2. 原子分组(Atomic Grouping)
  2. 避免导致性能问题的回溯:
    1. 避免前后重复的模式,例如 /x*x*/
    2. 避免嵌套的量词,例如 /(x*)*/
  3. 消极方案:如果能查看匹配的步数,那么可以在匹配的步数远大于(例如100倍)待匹配字符串长度时,直接退出。
  4. 如果能避免用正则去处理上述无法匹配的字符串,也是一种解决方法。

参考

  1. regexp buddy 讨论正则回溯问题
  2. 正则表达式回溯造成的计算时间复杂度指数上升
  3. 正则表达式测试网站
  4. 详解正则表达式回溯
  5. 正则表达式教程
comments powered by Disqus