使用java.util.regex.Pattern匹配时CPU占用过高问题排查
问题背景
在Spring框架结合Hibernate校验的场景中,使用@Pattern进行正则校验时,偶尔出现线程CPU使用率达到100%的情况,线程转储显示线程卡在Pattern类中。
相关代码片段如下:
校验逻辑代码
@PostMapping public ResponseEntity create(@RequestBody RequestObj request) { validationService.validate(request); ..... }
请求对象的正则校验注解
public class RequestObj { @Pattern(regexp = "^([a-zA-Z])+[-.'\\s]?[-a-zA-Z]*$", message = ValidationConstant.ERR_INVALID_FIRST_NAME) @NotNull(message = ValidationConstant.ERR_FIRST_NAME_EMPTY) @Size(max = 30, message = ValidationConstant.ERR_INVALID_NAME_SIZE) private String firstName; @Pattern(regexp = "^[\\sa-zA-Z0-9]+([ a-zA-Z0-9,'.?!\\-_&]+)*$", message = ValidationConstant.ERR_INVALID_COMMENT) @Size(max = 200, message = ValidationConstant.ERR_INVALID_COMMENT_SIZE) private String comment; }
问题根源:正则表达式存在回溯陷阱
你的正则表达式确实存在正则回溯陷阱,这是导致CPU飙高的核心原因。当输入不符合规则的字符串时,正则引擎会尝试大量的匹配组合,持续消耗CPU资源。
逐个分析问题正则
firstName的正则:
^([a-zA-Z])+[-.'\\s]?[-a-zA-Z]*$
问题点:([a-zA-Z])+和[-a-zA-Z]*的字符范围重叠(均包含字母),当输入类似aaaaa-这类不符合规则的字符串时,正则引擎会反复回溯:先让([a-zA-Z])+匹配所有字母,尝试匹配[-.'\\s]?后发现后续无内容,再逐步减少([a-zA-Z])+的匹配长度,尝试不同组合直到穷尽所有可能,这个过程会持续占用CPU。comment的正则:
^[\\sa-zA-Z0-9]+([ a-zA-Z0-9,'.?!\\-_&]+)*$
问题点:这个正则的回溯问题更严重,[\\sa-zA-Z0-9]+和后续组([ a-zA-Z0-9,'.?!\\-_&]+)*的字符范围几乎完全重叠。当输入较长的不符合规则的字符串时,会触发指数级的回溯,直接导致CPU被占满。
优化后的正则表达式
firstName优化版
^[a-zA-Z]+(?:[-.'\\s][a-zA-Z]+)?$
解释:用非捕获组(?:...)定义可选的后缀部分,只有当存在分隔符[-.'\\s]时,后面才必须跟字母,彻底消除了重叠匹配带来的回溯问题,同时保证规则的正确性(开头为字母,可包含一个分隔符加字母结尾)。
comment优化版
^[\\sa-zA-Z0-9,'.?!\\-_&]+$
解释:将原来嵌套的重复匹配合并为单次的范围匹配,既满足“所有字符都属于指定范围”的规则,又完全避免了回溯陷阱。
额外建议
结合@Size注解的长度限制,校验框架会先校验字符串长度,再执行正则匹配,这可以减少超长字符串进入正则匹配环节,进一步提升校验效率。
内容的提问来源于stack exchange,提问作者afsa

