java正则表达式处理器_正则表达式引起的异常,CPU100%

正则表达式造成CPU100%的问题回溯

线上正常运行,突然在某个瞬间发现线上几十台机器突然全部出现CPU100%的报警,马上手动使用工具下载jstack日志,发现已经无法下载,接着联系运维获取jstack日志;得到jstack日志马上查看是否存在业务代码的包,比如 com.xxx.xxx.appUrl.getUrl等类似代码

8cfaaa61cf600f3a0379c4dd93dc13d6.png

ff09c8b53affadf435ec92e6158bfb75.png

马上发现urlconstants.getAppUrl出现问题造成线程状态RUNNABLE;

9da19be21f2804fa32c786803724b483.png

从此处分析代码发现另外一个系统录入的URL存在非法字符,并且系统中的正则表达式定义的不规范,造成线程出现假死,很快便能定位到CPU100%的原因。

不规范的匹配正则表达式: http(s)?://m.wc.id/promo_((?!.*?/).*)+.html

匹配到的数据:

正则表达式死循环的原因:

现在深入分析正则表达式为什么会造成CPU100%,首先我们要了解java的正则的匹配模式,默认匹配模式为贪婪模式,但同时支持贪婪、懒惰与独占3种不同的匹配模式;

下面的几个特殊字符相信大家都知道它们的用法:

1. ?: 告诉引擎匹配前导字符0次或一次。事实上是表示前导字符是可选的。

2. +: 告诉引擎匹配前导字符1次或多次。

3. *: 告诉引擎匹配前导字符0次或多次。

4. {min, max}: 告诉引擎匹配前导字符min次到max次。min和max都是非负整数。如果有逗号而max被省略了,则表示max没有限制;如果逗号和max都被省略了,则表示重复min次。

贪婪

懒惰

独占

X?

X??

X?+

X*

X*?

X*+

X+

X+?

X++

X{n}

X{n}?

X{n}+

X{n,}

X{n,}?

X{n,}+

X{n,m}

X{n,m}?

X{n,m}+

从http(s)?://m.wc.id/promo_((?!.*?/).*)+.html匹配规则分析,第一处(S)?分析其只匹配0次或者1次,并且不会发生回溯;(?!.*?/)不匹配/符号使用了懒惰模式也不会发生回溯现象造成死循环;第三处((?!.*?/).*)+使用了独占模式告诉系统尽可能多滴匹配导入字符,导致正则匹配发生回溯现象,出现死循环。原因找到了,最简单的方式改变方式则是将((?!.*?/).*)+改为((?!.*?/).*)?的规则,当然这样这时候上面的例子就会匹配false,原因在html后实际URL会带入参数,这样就不匹配是正确的,接着修改匹配规则

http(s)?://m.jd.id/promo_((?!.*?/).*).html(.*)?

新的匹配规则符合了业务需求,到此我们的CPU100%就解决了。当然在java系统尽量不要使用复杂的正则表达式其原因是(1)不熟悉正则表达式容易造成业务死循环(2)正则表达式的复杂度代表着性能不优秀。

什么是回溯? 正则表达式引擎

说起回溯陷阱,要先从正则表达式的引擎说起。正则引擎主要可以分为基本不同的两大类:一种是DFA(确定型有穷自动机),另一种是NFA(不确定型有穷自动机)。简单来讲,NFA 对应的是正则表达式主导的匹配,而 DFA 对应的是文本主导的匹配。DFA从匹配文本入手,从左到右,每个字符不会匹配两次,它的时间复杂度是多项式的,所以通常情况下,它的速度更快,但支持的特性很少,不支持捕获组、各种引用等等;而NFA则是从正则表达式入手,不断读入字符,尝试是否匹配当前正则,不匹配则吐出字符重新尝试,通常它的速度比较慢,最优时间复杂度为多项式的,最差情况为指数级的。但NFA支持更多的特性,因而绝大多数编程场景下(包括java,js),我们面对的是NFA。以下面的表达式和文本为例,

text= ‘after tonight’ regex = ‘to(nite|nighta|night)’

在NFA匹配时候,是根据正则表达式来匹配文本的,从t开始匹配a,失败,继续,直到文本里面的第一个t,接着比较o和e,失败,正则回退到 t,继续,直到文本里面的第二个t,然后 o和文本里面的o也匹配,继续,正则表达式后面有三个可选条件,依次匹配,第一个失败,接着二、三,直到匹配。而在DFA匹配时候,采用的是用文本来匹配正则表达式的方式,从a开始匹配t,直到第一个t跟正则的t匹配,但e跟o匹配失败,继续,直到文本里面的第二个 t 匹配正则的t,接着o与o匹配,n的时候发现正则里面有三个可选匹配,开始并行匹配,直到文本中的g使得第一个可选条件不匹配,继续,直到最后匹配。可以看到,DFA匹配过程中文本中的字符每一个只比较了一次,没有吐出的操作,应该是快于NFA的。另外,不管正则表达式怎么写,对于DFA而言,文本的匹配过程是一致的,都是对文本的字符依次从左到右进行匹配,所以,DFA在匹配过程中是跟正则表达式无关的,而 NFA 对于不同但效果相同的正则表达式,匹配过程是完全不同的。

回溯

说完了引擎,我们再来看看到底什么是回溯。对于下面这个表达式,相信大家很清楚它的意图,

ab{1,3}c

也就是说中间的b需要匹配1~3次。那么对于文本“abbbc”,按照第1部分NFA引擎的匹配规则,其实是没有发生回溯的,在表达式中的a匹配完成之后,b恰好和文本中的3个b完整匹配,之后是c发生匹配,一气呵成。如果我们把文本换成“abc”呢?无非就是少了一个字母b,却发生了所谓的回溯。匹配过程如下图所示(橙色为匹配,黄色为不匹配),

4794c0b03ec0c80444f637a9b6bd7b67.png

1~2步应该都好理解,但是为什么在第3步开始,虽然已经文本中已经有一个b匹配了b{1,3},后面还会拉着字母c跟b{1,3}做比较呢?这个就是我们下面将要提到的正则的贪婪特性,也就是说b{1,3}会竭尽所能的匹配最多的字符。在这个地方我们先知道它一直要匹配到撞上南墙为止。 在这种情况下,第3步发生不匹配之后,整个匹配流程并没有走完,而是像栈一样,将字符c吐出来,然后去用正则表达式中的c去和文本中的c进行匹配。这样就发生了一次回溯。

参考实例:

https://blog.csdn.net/weixin_42516949/article/details/80858913

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值