一、事件背景
1.1 需求说明
我们的各个业务系统上报了用户的登录日志(包括成功和失败的),这些登录日志我们要存起来,对用户的行为进行分析,以便发现一些异常的用户;也为用户画像提供更多的基础数据支持。
但是我们的登录是可以使用邮箱和用户名两种情况登录的,上报的日志是从多个业务场景上报,高峰量还是非常大的,并且我们并没有用户数据库的直连权限,这里有需要将使用用户名登录的和使用邮箱登录的分开存储。
所以在存储之前我们得弄清楚用户是使用哪种场景登录的。
这里的邮箱登录还是用户名登录,我们就是使用的正则来做的一个简单判断。
1.2 事件发生时间及发现过程
发生时间还好是上班时间,钉钉告警出来,正好在工位就立马发现能解决了;避开了悲催的半夜被告警铃声吵醒美梦的悲剧。
下面是使用top
命令查看当时CPU使用情况的截图,也是后背一凉。

二、问题排查过程
2.1 初步定位
实际上面1.2的图就可以看到是哪一个进程导致的CPU飙高,这就非常方便我们定位了,直接找到对应的程序,然后使用jstack
命令查看一下堆栈情况。

2.2 详细排查
打印出来的堆栈情况,可以清晰的看到大量的java.util.regex.Pattern
对象,这个时候如果想不到我们可以dump
一下具体分析,万幸的是这个项目从头到尾都是我负责的,里面只有唯一的一个地方使用到了正则,所以我立马定位到了具体的代码(疯狂的牛马)。
下面是我真正正则校验的代码,实际看起来还是非常清晰明了的。
java
/** 正则表达式:验证邮箱 */
public static final String REGEX_EMAIL = "^([a-z0-9A-Z]+[-|\\.]?)+[a-z0-9A-Z]@([a-z0-9A-Z]+(-[a-z0-9A-Z]+)?\\.)+[a-zA-Z]{2,}$";
/**
* 校验邮箱
*
* @param email 邮箱
* @return 校验通过返回true,否则返回false
*/
public static boolean isEmail(String email) {
return Pattern.matches(REGEX_EMAIL, email);
}
单独从上面实际是不怎么看的出我们的问题的。
三、问题原因分析
3.1 问题分析
找到了问题产生点,问题分析说起来就非常简单了;哪怕我想不到,我也可以直接问问AI。

哎,看了AI的回答,相信你也猜到了问题产生的核心原因。
看到前面的正则,我们就知道,我这里是符合贪婪匹配与长文本 和贪婪匹配与长文本 这两点的,至于并发问题,因为我的实际处理程序是通过MQ
削峰的,所以这里并不满足。
当然,如果你并不能直接看出来,那也简单,你把上面的正则丢给AI分析分析。

咋样,强大的AI立马告诉了你结果。并且他还可以给出一些解决办法。
四、问题解决措施
4.1 按照业务合理解决
前面已经说过AI会给你提供一些解决办法,但是AI并不是万能的,每个业务场景都有自己的特殊,我们可以根据自己的业务来综合考虑。
先看看AI的优化建议。

AI的优化建议非常标准,但是我考虑这里业务场景实际有很大限制,所以没必要按照它的来。
业务场景补充描述:
-
邮箱最长长度是80
-
用户名最长长度是256(你别说,还真有人定格设置)
4.2 解决办法
前面补充了业务场景,相信很多人已经猜到了解决办法。
没有,就是限制一下触发正则的前提。
java
if (username.length() <= 80 && username.contains("@")) {
// 进行正则校验
}
4.3 影响范围
虽然排查到解决,看着都非常简单,但是实际操作起来还是很繁琐的,一顿骚操作,再经过测试到发布到正式环境,也耗时1小时多。

五、总结
任何代码都不是万能的,这个正则校验工具类,经过了不知道多长时间的使用,在本项目就是踩坑了,要是不从堆栈去仔细排查,你怕是脑子想爆炸都找不到原因。
在开发过程中真的需要多多思考,结合业务场景仔细去设计代码,没有万能的代码,但是有万能的程序员,相信你看了这篇文章也会有不一样的收获。