当前所在位置: 首页 > 域名购买

小心踩雷!一个小小的正则表达式竟把CPU拖垮……小米农如何进行域名投资?

2021-10-11 本站作者 【 字体:

域名投资者起步有很多种方式,但需要足够多的人民币:

1、手动注册一些域名,进行交易;

2、购买自己看中某类域名,如双拼,3数、4数等;

3、通过抢注平台抢注到自己满意的域名再进行转卖域名投资;

小心踩雷!一个小小的正则表达式竟把CPU拖垮……

通过 Java 自带的线程 Dump 工具,我们导出了出问题的堆栈信息。

4、通过竞价获得自己认准的域名进行域名投资。

无论是哪一种方式,只有买入、卖出后才算投资成功。而在买入而未卖出之前,都算投入,所以需要足够多的人民币进行起步。当然这里的足够多只是一个相对概念,仅仅针对自己投入的大小,可以是几百、几千甚至几万,

小心踩雷!一个小小的正则表达式竟把CPU拖垮……

我们可以看到所有的堆栈都指向了一个名为 validateUrl 的方法,这样的报错信息在堆栈中一共超过 100 处。

投资域名需要有足够好的眼光和创意,对于域名,肯定是越短越好,后缀越国际化越好,但是这样的好域名已经在前面十多年的过程中消耗得差不多了,现在你想注册双拼的COM米,含义不错的5字母是很不容易的,而要想注册到4数字、4字母那更是不可能完成的任务。所以要想投资成功,就得要足够好的眼光和创意了。眼光:看的是趋势,比如我现在是选择以低价注册其它后缀的短数字米投资,还是选择相对高价收购主流后缀.com/net/org的双拼米呢? 创意:创意值钱,但是是一种可能。

方法:

1、傍名牌,抢在国际公司进入中国之前就将品牌抢注成域名,趁国内知名企业尚未意识到域名价值之前先下手为强,这是很多投资者心目中成功的捷径。但是这种投资方式往往会引发争议,闹上法庭也时有发生。想靠傍名牌赚钱需要具备吃官司的心理准备,前期投入也不止注册费那么简单。法院一旦判定是恶意抢注,很可能一无所获。

通过排查代码,我们知道这个方法的主要功能是校验 URL 是否合法。很奇怪,一个正则表达式怎么会导致 CPU 利用率居高不下。

为了弄清楚复现问题,我们将其中的关键代码摘抄出来,做了个简单的单元测试。

public static void main(String[] args) { String badRegex = "^([hH][tT]{2}[pP]://|[hH][tT]{2}[pP][sS]://)(([A-Za-z0-9-~]+).)+([A-Za-z0-9-~\\\\/])+$"; String bugUrl = "http://www.fapiao.com/dddp-web/pdf/download?request=6e7JGxxxxx4ILd-kExxxxxxxqJ4-CHLmqVnenXC692m74H38sdfdsazxcUmfcOH2fAfY1Vw__^DadIfJgiEf"; if (bugUrl.matches(badRegex)) { System.out.println("match!!"); } else { System.out.println("no match!!"); } }

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

小心踩雷!一个小小的正则表达式竟把CPU拖垮……

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

2、抢终端,与第一条有些类似,但主要针对国内公司进行创意,随着互联网产业发展,有公司要进行抢注业务,他将进行域名收购,面临的选择有贵点但是短些的双拼和便宜且涵义更精确的三拼,这两者任选其一,都会让域名拥有者大赚一笔。

3、投趋势,这主要是扫米,前些天某公司一次性注册所有四声.cn就是此类。

4、投唯一,主要是选择数字域名。数字域名4数、5数就如同手机号码,用一个少一个,利用唯一赚钱。

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

作为初入米市的小米农,切勿盲目跟风,米市有风险、投资需谨慎,当然投资域名还是要上靠谱的域名交易平台,爱名网就是不错的选择。

^([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 自动机会读取正则表达式的一个一个字符,然后拿去和目标字符串匹配,匹配成功就换正则表达式的下一个字符,否则继续和目标字符串的下一个字符比较。

或许你们听不太懂,没事,接下来我们以上面的例子一步步解析:

首先,拿到正则表达式的第一个匹配符:d。于是拿去和字符串的字符进行比较,字符串的第一个字符是 T,不匹配,换下一个。

第二个是 o,也不匹配,再换下一个。第三个是 d,匹配了,那么就读取正则表达式的第二个字符:a。

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

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

NFA 自动机的回溯

了解了 NFA 是如何进行字符串匹配的,接下来我们就可以讲讲这篇文章的重点了:回溯。

为了更好地解释回溯,我们同样以下面的例子来讲解:

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

上面这个例子的目的比较简单,匹配以 a 开头,以 c 结尾,中间有 1-3 个 b 字符的字符串。

NFA 对其解析的过程是这样子的:

首先,读取正则表达式第一个匹配符 a 和字符串第一个字符 a 比较,匹配了。于是读取正则表达式第二个字符。读取正则表达式第二个匹配符 b{1,3} 和字符串的第二个字符 b 比较,匹配了。但因为 b{1,3} 表示 1-3 个 b 字符串,以及 NFA 自动机的贪婪特性(也就是说要尽可能多地匹配)。

所以此时并不会再去读取下一个正则表达式的匹配符,而是依旧使用 b{1,3} 和字符串的第三个字符 b 比较,发现还是匹配。

于是继续使用 b{1,3} 和字符串的第四个字符 c 比较,发现不匹配了。此时就会发生回溯。

发生回溯是怎么操作呢?发生回溯后,我们已经读取的字符串第四个字符 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__^DadIfJgiEf

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

校验协议:^([hH][tT]{2}[pP]://|[hH][tT]{2}[pP][sS]://)

校验域名:(([A-Za-z0-9-~]+).)+校验参数:([A-Za-z0-9-~\\/])+$我们可以发现正则表达式校验协议 http:// 这部分是没有问题的,但是在校验 www.fapiao.com 的时候,使用了 xxxx. 这种方式。

那么匹配过程是这样的:

匹配到 www.匹配到 fapiao.匹配到 com/dzfp-web/pdf/download?request=6e7JGm38jf.....,你会发现因为贪婪匹配的原因,所以程序会一直读后面的字符串进行匹配,最后发现没有点号,于是就一个个字符回溯回去了。

这是这个正则表达式存在的第一个问题;另外一个问题是在正则表达式的第三部分。

我们发现出现问题的 URL 是有下划线(_)和百分号(%)的,但是对应第三部分的正则表达式里面却没有。

这样就会导致前面匹配了一长串的字符之后,发现不匹配,最后回溯回去。这是这个正则表达式存在的第二个问题。

解决方案

明白了回溯是导致问题的原因之后,其实就是减少这种回溯,你会发现如果我在第三部分加上下划线和百分号之后,程序就正常了。

public static void main(String[] args) { String badRegex = "^([hH][tT]{2}[pP]://|[hH][tT]{2}[pP][sS]://)(([A-Za-z0-9-~]+).)+([A-Za-z0-9-~_%\\\\/])+$"; String bugUrl = "http://www.fapiao.com/dddp-web/pdf/download?request=6e7JGxxxxx4ILd-kExxxxxxxqJ4-CHLmqVnenXC692m74H38sdfdsazxcUmfcOH2fAfY1Vw__^DadIfJgiEf"; if (bugUrl.matches(badRegex)) { System.out.println("match!!"); } else { System.out.println("no match!!"); } }

运行上面的程序,立刻就会打印出 match!!。但这是不够的,如果以后还有其他 URL 包含了乱七八糟的字符呢,我们难不成还再修改一遍。肯定不现实嘛!

其实在正则表达式中有这么三种模式:贪婪模式、懒惰模式、独占模式。

在关于数量的匹配中,有 + ? * {min,max} 四种两次,如果只是单独使用,那么它们就是贪婪模式。

如果在他们之后加多一个 ? 符号,那么原先的贪婪模式就会变成懒惰模式,即尽可能少地匹配。但是懒惰模式还是会发生回溯现象的。

TODO 例如下面这个例子:

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

正则表达式的第一个操作符 a 与字符串第一个字符 a 匹配,匹配成功。于是正则表达式的第二个操作符 b{1,3}? 和字符串第二个字符 b 匹配,匹配成功。

因为最小匹配原则,所以拿正则表达式第三个操作符 c 与字符串第三个字符 b 匹配,发现不匹配。

于是回溯回去,拿正则表达式第二个操作符 b{1,3}? 和字符串第三个字符 b 匹配,匹配成功。

于是再拿正则表达式第三个操作符 c 与字符串第四个字符 c 匹配,匹配成功。于是结束。

如果在他们之后加多一个 + 符号,那么原先的贪婪模式就会变成独占模式,即尽可能多地匹配,但是不回溯。

于是乎,如果要彻底解决问题,就要在保证功能的同时确保不发生回溯。我将上面校验 URL 的正则表达式的第二部分后面加多了个 + 号,即变成这样:

^([hH][tT]{2}[pP]://|[hH][tT]{2}[pP][sS]://) (([A-Za-z0-9-~]+).)++ --->>> (这里加了个+号) ([A-Za-z0-9-~\\/])+$

这样之后,运行原有的程序就没有问题了。

最后推荐一个网站,这个网站可以检查你写的正则表达式和对应的字符串匹配时会不会有问题。

Online regex tester and debugger:PHP,PCRE,Python,Golang and JavaScript

例如我本文中存在问题的那个 URL 使用该网站检查后会提示:catastrophic backgracking(灾难性回溯)。

小心踩雷!一个小小的正则表达式竟把CPU拖垮……

当你点击左下角的「regex debugger」时,它会告诉你一共经过多少步检查完毕,并且会将所有步骤都列出来,并标明发生回溯的位置。

小心踩雷!一个小小的正则表达式竟把CPU拖垮……

本文中的这个正则表达式在进行了 11 万步尝试之后,自动停止了。这说明这个正则表达式确实存在问题,需要改进。

但是当我用我们修改过的正则表达式进行测试,即下面这个正则表达式:

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

工具提示只用了 58 步就完成了检查,如下图:

小心踩雷!一个小小的正则表达式竟把CPU拖垮……

一个字符的差别,性能就差距了好几万倍。

总结

一个小小的正则表达式竟然能够把 CPU 拖垮,也是很神奇了。这也给平时写程序的我们一个警醒,遇到正则表达式的时候要注意贪婪模式和回溯问题,否则我们每写的一个表达式都是一个雷。

通过查阅网上资料,我发现深圳阿里中心 LAZADA 的同学也在 2017 年遇到了这个问题。

他们同样也是在测试环境没有发现问题,但是一到线上的时候就发生了 CPU 100% 的问题,他们遇到的问题几乎跟我们的一模一样。

虽然把这篇文章写完了,但是关于 NFA 自动机的原理方面,特别是关于懒惰模式、独占模式的解释方面还是没有解释得足够深入。

因为 NFA 自动机确实不是那么容易理解,所以在这方面还需要不断学习加强。欢迎有懂行的朋友来学习交流,互相促进。

作者:陈树义

出处:转载自「陈树义」微信公众号,一个有情怀的技术公众号,立志用最简单的语言,让复杂的技术不再难懂。目前专注 Java 领域的技术分享,包括但不限于 Java 源码、SSM、ElasticSearch、JVM、MySQL、MyCat 等技术领域。关注树义君,让你的技术成长不再困难。

阅读全文
id_1广告位-300*300
相关推荐

一文搞懂什么是vlan三层交换机、网关、DNS、子网掩码、MAC地址什么是vlan、三层交换机、网关、DNS、子网掩码、MAC地址

一文搞懂什么是vlan三层交换机、网关、DNS、子网掩码、MAC地址什么是vlan、三层交换机、网关、DNS、子网掩码、MAC地址
一、什么是VLANVLAN中文是“虚拟局域网”。LAN可以是由少数几台家用计算机...

阿里云服务器购买教程有吗,谁有阿里云服务器的购买教程阿里云域名备案需要哪些文件

阿里云服务器购买教程有吗,谁有阿里云服务器的购买教程阿里云域名备案需要哪些文件
如果备案主体为个人,则基本资料中的主体负责人证件、网站负责人证件及主办单位证件均...

最强内网穿透工具frpF5负载均衡器如何通过irules实现应用的灵活转发?

最强内网穿透工具frpF5负载均衡器如何通过irules实现应用的灵活转发?
F5是非常强大的商业负载均衡器。除了处理性能强劲,以及高稳定性之外,F5还可以通...

只做url转发的域名怎样备案?-“米发(MFPad)URL部署服务域名注册提供商”。 (转载)

只做url转发的域名怎样备案?-“米发(MFPad)URL部署服务域名注册提供商”。 (转载)
在百度搜索“米发”,第一条就是“米发(MFPad)URL部署服务提供商”。所谓U...

用自己的域名,解析指向到别人的网站,应该怎么设置?域名解析,我购买了一个域名,想指向京东,是否把域名解析成www.jd.com或者京东网站的IP即可?

用自己的域名,解析指向到别人的网站,应该怎么设置?域名解析,我购买了一个域名,想指向京东,是否把域名解析成www.jd.com或者京东网站的IP即可?
首先可以明确的告诉大家,即使我们将自己的域名解析至京东的节点IP或域名上,依旧是...

短网址:只有前世,没有今生新媒体运营人,有哪些必备工具呢?(推荐收藏)

短网址:只有前世,没有今生新媒体运营人,有哪些必备工具呢?(推荐收藏)
点上面的蓝字 航通社 订阅我们本文首发于百度百家原创文章,未经授权,请勿转载使用...

twitter推自动缩略网址服务 将使用t.co域名(转载)top域名选择和解析(转载)

twitter推自动缩略网址服务 将使用t.co域名(转载)top域名选择和解析(转载)
域名是站长建站过程中最基本元素,域名的基本知识站长们懂多少,是否能正确掌握帮助自...

2006医药代理商与域名转让Top新顶级域名改变站长业态

2006医药代理商与域名转让Top新顶级域名改变站长业态
我们从事医药信息整理工作,整理了数量巨大的全国各地医药代理商信息资料,为方便各地...

有什么软件统计过期域名聚名网域名过期域名查?聚名网域名过期能看到几天

有什么软件统计过期域名聚名网域名过期域名查?聚名网域名过期能看到几天
过期域名查询点击首页导航条“域名查询”,进入过期域名查询页面。过期域名查询功能可...

如何查询域名是否被停了如何查看一个域名的到期时间

如何查询域名是否被停了如何查看一个域名的到期时间
出现域名突然不能访问有很多原因<a href="http://w...