正则的回溯

我们先了解一下什么是回溯

https://zhuanlan.zhihu.com/p/27417442

这篇文章将的比较详细,我选取其中一个例子给大家简单介绍一下

当用 /".*"/  匹配字符串 "acd"ef 时,下面是匹配过程

简单总结一下

贪婪匹配 (.*) 后面紧跟字符 g,(.*)的匹配范围是 g匹配范围的父集,这种写法必定会导致字符 g 的匹配失败而产生回溯

字符串中 g 到 (.*) 所匹配的字符结尾间的字符越多,回溯次数越多。如果字符串中没有g,则会全部回溯

我们线上nginx 都采用的 正则匹配,平时我们提单新增location的时候如果正则写地很随意就很容易写回溯的正则影响nginx匹配的效率。下面我举一个例子

location ~ ^/re/(.*)/g 

这个正则就是一个不好的写法

用 .* 匹配范围太广,不够明确。这个正则可以匹配多种 url 比如 /re/aaa/g/eee   /re/aaa/bbb/g/eee /re/aaa/geee  等

此外使用 .* 会引起回溯, g 后面的字符越多,回溯越严重,造成性能损耗

假如业务的url是  /re/aaa/g/eee   /re/aaa/bbb/g/eee /re/aaa/geee 这三种都有,那实际更好的写法是

location ~ ^/re/[^g]+/g 

我在nginx上分别压测了一下上面两种配置,url 为 /re/aa/gaaaaaaaaa ,g后面有10个字符,第一种写法54336 第二种是 55964 ,第二比第一种性能提高了3%,如果url更长性能差距会更大

灾难性回溯

如果说上面的回溯造成的一点性能上的损失还可以接受的话,那灾难性回溯造成的影响就很难忽略了

灾难性回溯简单来说就是在正则表达式中过于粗暴得使用了 *、+ 和 ?等量词限定符的组合和嵌套,导致在匹配某些特定类型的字符串的时候,回溯次数随着字符串长度的增加而指数级上升。这会造成服务的cpu资源占用很高而影响正常的业务逻辑执行

下面举两个的例子

regex = /^(\w+s?)*$/
test_str = "An input string that takes a long time or even makes this regexp to hang!"regex = /a?a?a?a?a?a?a?a?a?a?a?a?a?a?a?a?a?a?a?a?a?a?a?a?a?a?a?a?a?a?aaaaaaaaaaaaaaaaaaaaaaaaaaaaaa/
test_str = "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"

大家可以用自己常用的语言试一下上面两个例子,看看自己所用语言的正则引擎有没有回溯的问题

https://zh.javascript.info/regexp-catastrophic-backtracking

这篇文章介绍了灾难性回溯的过程,以 (\d+)*$ 匹配 12345! 为例,由于 * 的存在,正则引擎匹配到!失败后,会尝试

\d+\d+
\d+\d+\d+

......

多种方式排列组组合来匹配字符串,随着数字长度的增加,回溯次数会成指数级增长

https://regex101.com/r/LbX3JI/1

大家可以在这里开启debug 工具来直观的感受一下回溯过程

之前说到nginx 使用的是pcre库,这就会存在灾难性回溯的问题

好消息是在测试中发现nginx 对回溯次数是有限制的

虽然没有查到具体的值是多少,翻了nginx源码,nginx在调用pcre_exec() 的是时候貌似没有特别设置限制次数,根据pcre 文章的介绍,默认值是 1000万,经过测试差不多是这个值,回溯次数超过1000万的时候,nginx 会中断请求 返回500,error log 中报错pcre_exec() failed: -8

nginx lua 模块则可以通过设置 lua_regex_match_limit 100000; 来手动限制lua函数中的回溯次数

坏消息是这并不能根本性的解决回溯的问题, nginx 默认1000万的回溯次数仍会消耗大量cpu资源。

nginx 中易出现灾难性回溯的正则表达式特征

在nginx配置中我们不太容易写出 /a?a?a?a?a?a?a?a?a?a?a?a?a?a?a?a?a?a?a?a?a?a?a?a?a?a?a?a?a?a?aaaaaaaaaaaaaaaaaaaaaaaaaaaaaa/ 这种正则,但是会比较容易写出这种类型的正则 /^(\w+s?)*$/。我简单总结一下出现灾难性回溯正则及其匹配的字符串特征

例子: /(a*)*$/

1. 正则采用了 * + 嵌套比如 (a*)*,并且可以匹配较长的目标字符串,比如 aaaaaaaaaab中,可以匹配到aaaaaaaaaa

2.  正则嵌套之后有会匹配到的字符,比如上面的$。注意$ 也可以是其他字符

3.  1、2 中的正则之间存在会匹配失败的字符串,且只有在匹配失败后才会导致回溯。比如上述正则在匹配字符串  aaaaaaaaaab 时,字符"b"匹配失败导致前面嵌套的正则发生回溯

/(a*)*$/ 这个例子比较简单,类似的还有

/(.*)*[^b]$/ 在匹配字符串 aaaaaaaaaaaaaaaaaaaab 时

/(.*)*[^b]e/ 在匹配字符串 aaaaaaaaaaaaaaaaaaaabe 时

稍微特别的,里面有类似-的分隔符

/(-a*)*[^b]e/ 在匹配字符串 -a-a-a-a-a-a-abe 时这个时候字符串中分隔符 - 的数量将会指数级影响回溯的次数

上面有字符 a* 来举例方便了解,实际上这种只有在特定字符才会异常。而实际生产中一般会用 \w* 甚至 .* 这种形式,这会极大地增加出现灾难性回溯的概率

nginx 发生灾难性回溯造成影响

会消耗大量CPU资源

经过 https://regex101.com/ 中测试

此正则 /(a*)*$/ 匹配特定字符串是所需的匹配次数

字符串   匹配次数
 ax  20    
 aax  40
 aaax  80
 aaaax  160

匹配次数为20*2^(字符a的数量)

这个例子比较极端,但是可以反映出回溯导致匹配次数的指数级增长趋势

会造成集群故障

由于灾难性回溯会消耗大量cpu资源,单个nginx worker 7qps的异常请求就能把cpu 使用到100%,对于一个10台40核服务器的nginx集群,几千qps的请求就能干崩整个集群

异常定位非常困难

灾难性回溯造成线上影响的排查很困难,故障的产生需要一些特定流量才会触发,流量的增加和配置的上线时间节点不一定一致,对于缺乏经验的同学来说想要定位到异常配置需要花费大量时间

几种nginx配置中不推荐的正则写法

下面几种配置的写法不一定会造成灾难性回溯,但是我们极不推荐这些写法,他们很有可能在某些需求下错写成易造成灾难性回溯的正则

我都以location 配置为例

lcoation ~ ^/test(/.*)*$

这个写法如果不熟悉nginx同学可能觉得没啥问题,但实际上他等价于 lcoation ~ ^/test

location ~ ^/test/(-\w+)*$ 

这种写法括号里有分隔符 - ,他会造成灾难性回溯,但是对应灾难性回溯的字符串要求稍微苛刻一些需要含有大量分隔符,但是如果改成 location ~ ^/test/(-?w+)*$  就很容易出现灾难性回溯,而写正则的同学可能完全意识不到这二者影响大小的区别

类似的还有这种写法

lcoation ~ ^/test/(\w+/?)*$

这个乍一看去好像很合理,没有用 .* ,甚至还考虑到了url随后有无斜杠两种情况,但它会造成灾难性回溯

如何解决

禁止使用 量词限定符 * + 的正则嵌套

根本的解决办法就是跟换正则引擎,但这对于我们nginx来说成本太高,并且有兼容现有正则的问题

避免 .* 的使用,减少回溯

尽量避免线上nginx中使用复杂正则

以上就是本篇文章的全部内容。正则的实现原理是很复杂的,我们平时在写正则的时候往往就是能匹配到就行而忽略了正则的匹配原理,这样时间长了就可能会踩到意想不到的坑里

nginx的正则回溯和灾难性回溯相关推荐

  1. 正则的灾难性回溯?没想到我们真的踩到了……

    前言 今年7月28日,我发布了一篇文章 nginx的正则回溯和灾难性回溯​​​​​​​ 万万没想到,不到3个月之后,我们尽然真的差点踩到了我当时描述的那个坑里-- 起因 15:51 开始,我们陆续收到 ...

  2. 由Java正则表达式的灾难性回溯引发的高CPU异常:java.util.regex.Pattern$Loop.match

    问题与分析 某天领导report了一个问题:线上的CPU自从上一个版本迭代后就一直处于居高不下的状况,领导看着这段时间的曲线图判断是有两条线程在不停的死循环. 接到任务后去查看了AWS的CloudWa ...

  3. java回溯算法_回溯算法讲解--适用于leetcode绝大多数回溯题目

    什么是回溯算法? 回溯法是一种系统搜索问题解空间的方法.为了实现回溯,需要给问题定义一个解空间. 说到底它是一种搜索算法.只是这里的搜索是在一个叫做解空间的地方搜索. 而往往所谓的dfs,bfs都是在 ...

  4. 回溯法之递归回溯和迭代回溯

      回溯法有通用解题法之称,它可以系统的搜索一个问题的所有解或者任意解.它在问题的解空间树中,按深度优先策略从根节点出发搜索解空间树,算法搜索至解空间树的任意一个结点时,先判断该节点如(子树)是否包含 ...

  5. 在nginx的正则配置中使用大括号花括号引起的问题解决

    在nginx的正则配置中使用大括号花括号引起的问题解决 问题现象 问题解决 示例 错误示例 正确示例 问题现象 大括号在nginx配置中经常用到,所以在正则表达里直接使用会导致格式错误. 问题解决 把 ...

  6. python回溯算法_回溯算法经典问题及python代码实现

    2. 0-1背包问题 # 0-1 bag problem import sys def f(no, cur_mass, things, num): global cur_max if no == nu ...

  7. Nginx rewrite正则匹配重写

    Nginx的rewrite功能支持正则匹配重写,即将URL地址临时或永久重新指向某个新的位置,类似于重定向.这个特性有利用当网站结构做出重大调整,如之前的网站mp3资源使用URL为www.site1. ...

  8. 深搜回溯与不回溯的区别

    一.需要不断尝试以达到最终目的时需要回溯,比如数独.全排列. 以下为全排列代码: #include <iostream> #include <vector> #include ...

  9. 【算法思想:回溯法】回溯算法入门级详解

    回溯法是一种非常重要的算法思想,在大厂面试中频繁出现,所以做了一个笔记,记录了一下. 回溯算法与深度优先遍历 以下是维基百科中「回溯算法」和「深度优先遍历」的定义. 回溯法 采用试错的思想,它尝试分步 ...

最新文章

  1. 不止临床应用,AI还要帮不懂编程的医生搞科研
  2. redis做简单mq的高可用
  3. 用命令行非交互改密码
  4. windows 技术篇 - cmd命令查看当前目录下的所有文件和文件夹以及所有子目录下的文件,dir命令的使用方法
  5. 使用Azure应用服务本地验证安卓脸书
  6. PTA浙大版python程序设计题目集--第2章-3 阶梯电价 (15 分)
  7. 如何写_如何写博士论文?博士生如何写期刊论文?
  8. SAP Fiori应用的搜索问题
  9. Orchard Core Framework:ASP.NET Core 模块化,多租户框架
  10. maven deploy上传私服出错
  11. 微星小飞机界面翻译_/|黑科技高效快捷的多引擎翻译软件
  12. rf扫描枪_RF枪(RF手持扫描枪)是什么,有什么用途?
  13. 盘点史上最经典的电影台词
  14. flink 作业提交流程
  15. Codeforces1389 E. Calendar Ambiguity(数论)
  16. java里的if语句怎么写_Java中的if怎么用?
  17. 猿创征文|我的后端成长之路(985科班两年,我发现了大学正确打开方式)
  18. Mybatis+spring知识点
  19. 二元灰狼优化(BGWO)应用于特征选择任务(Matlab代码实现)
  20. 电脑公司GHOST WIN7 装机旗舰版 2013 09

热门文章

  1. AES解密类,解密案例 解决No matching distribution found for Crypto
  2. mac系统计算机名,苹果电脑系统各版本名字该怎么念?
  3. 《机器学习实战》第四章
  4. Omnigraffle Pro 注册码/许可证
  5. 如何在PowerPoint演示文稿中突出显示文本
  6. 2018第一次校队集训题解
  7. 【转载】《仙剑OL》主题曲_玩家版
  8. python爬取旅游信息_Python 爬取 13 个旅游城市,告诉你五一大家最爱去哪玩?
  9. 三大c4d人物角色模型素材网站 实用 精选
  10. maven 搭建ssm多模块项目(web+service)