Ruby Regex导致高CPU负载,导致Rails不响应

Ruby Regex导致高CPU负载,导致Rails不响应,ruby,regex,ruby-1.8.7,Ruby,Regex,Ruby 1.8.7,我有一个Ruby 1.8.7脚本来解析iOS本地化文件: singleline_comment = /\/\/(.*)$/ multiline_comment = /\/\*(.*?)\*\//m string_line = /\s*"(.*?)"\s*=\s*"(.*?)"\s*\;\s*/xm out = decoded_src.scan(/(?:#{singleline_comment}|#{multiline_comment})?\s*?#{string_line}/) 它过去工作得

我有一个Ruby 1.8.7脚本来解析iOS本地化文件:

singleline_comment = /\/\/(.*)$/
multiline_comment = /\/\*(.*?)\*\//m
string_line = /\s*"(.*?)"\s*=\s*"(.*?)"\s*\;\s*/xm

out = decoded_src.scan(/(?:#{singleline_comment}|#{multiline_comment})?\s*?#{string_line}/)
它过去工作得很好,但今天我们用一个800Kb的文件测试了它,而这个文件没有;在每行的末尾。结果是CPU负载很高,Rails服务器没有响应。我的假设是,它将整个文件作为捕获组中的单个字符串,从而阻止了服务器

解决办法是添加?正则表达式量化器,0或1倍于;文字字符:

  /\s*"(.*?)"\s*=\s*"(.*?)"\s*\;?\s*/xm
现在,即使是那些旧iOS格式的文件,它也能正常工作,但我现在担心的是,如果用户提交了一个格式错误的文件,比如一个没有结尾的文件,该怎么办。我的服务器会再次被阻止吗


我该如何防止这种情况?有没有办法试着只运行五秒钟?我能做些什么来避免停止我的整个Rails应用程序呢?

看起来您试图像解析字符串一样解析整个配置。虽然这是可行的,但很容易出错。正则表达式引擎需要做很多向前和向后的工作,写得不好的模式最终会浪费大量的CPU时间。有时,一个小的调整可以解决问题,但是处理的文本越多,表达式越复杂,发生事情的可能性就越大,这会让你陷入困境

通过对我自己工作中获取数据的不同方法进行基准测试,我了解到锚定regexp模式可以在速度上产生巨大的差异。如果你不能以某种方式锚定一个模式,那么你将遭受模式的回溯和贪婪,除非你可以限制引擎在默认情况下想要做什么

我必须解析很多设备配置,但我没有试图将它们作为单个字符串处理,而是将它们分解为由行数组组成的逻辑块,然后我可以提供逻辑,根据块包含某些类型信息的知识从这些块中提取数据。小块搜索速度更快,编写可锚定的模式也更容易,提供了巨大的加速

另外,不要犹豫使用Ruby的字符串方法,比如拆分来拆分行,子字符串匹配来查找包含所需内容的行。它们速度非常快,不太可能导致减速

如果我有一个字符串,比如:

config = "name:\n foo\ntype:\n thingie\nlast update:\n tomorrow\n"
chunks = config.split("\n").slice_before(/^\w/).to_a
# => [["name:", " foo"], ["type:", " thingie"], ["last update:", " tomorrow"]]

command_blocks = chunks.map{ |k, v| [k[0..-2], v.strip] }.to_h

command_blocks['name'] # => "foo"
command_blocks['last update'] # => "tomorrow"
对于这类任务来说,是一种非常有用的方法,因为它允许我们定义一个模式,然后用于测试主数组中的中断,并根据这些中断进行分组。模块中有很多有用的方法,所以一定要仔细阅读

可以解析相同的数据

当然,如果没有你想做的事情的样本数据,就很难提出更好的建议,但想法是,将你的输入分解成可管理的小块,然后从那里开始

作为对如何定义模式的评论

不要使用/\/…/即使用%r来定义不同的分隔符:

singleline_comment = /\/\/(.*)$/     # => /\/\/(.*)$/
singleline_comment = %r#//(.*)$#     # => /\/\/(.*)$/

multiline_comment = /\/\*(.*?)\*\//m # => /\/\*(.*?)\*\//m
multiline_comment = %r#/\*(.*?)\*/#m # => /\/\*(.*?)\*\//m
上面每个示例中的第一行是您如何操作,第二行是我如何操作。它们产生相同的regexp对象,但第二个对象更容易理解

您甚至可以让Regexp帮助您,为您转义:

NONGREEDY_CAPTURE_NONE_TO_ALL_CHARS = '(.*?)'
GREEDY_CAPTURE_NONE_TO_ALL_CHARS = '(.*)'
EOL = '$'

Regexp.new(Regexp.escape('//') + GREEDY_CAPTURE_NONE_TO_ALL_CHARS + EOL) # => /\/\/(.*)$/
Regexp.new(Regexp.escape('/*') + NONGREEDY_CAPTURE_NONE_TO_ALL_CHARS + Regexp.escape('*/'), Regexp::MULTILINE) # => /\/\*(.*?)\*\//m
这样,您可以迭代地构建极其复杂的表达式,同时使它们相对易于维护


至于停止Rails应用程序,不要尝试在同一个Ruby进程中处理文件。运行一个单独的作业,监视文件并对其进行处理,并存储您希望在以后需要时访问的任何内容。这样,服务器将继续响应,而不是锁定。我不会在线程中这样做,但会编写一个单独的Ruby脚本来查找传入的数据,如果没有找到任何数据,则会休眠一段时间,然后再次查找。Ruby的sleep方法将对此有所帮助,或者您可以使用操作系统的cron功能。

在尝试匹配regex之前,您可以检查文件的格式是否错误。一个简单的检查就是计算字符数,看看它是偶数还是奇数。至于超时,看看Ruby的超时库:它看起来有一个变体,因为你有惰性量词,组每次都在扩展,而不是变小。很难推荐一个通用的修复方案,这取决于数据的格式。一般来说,您最好尽可能具体地使用正则表达式,在本例中使用[^]*而不是。*?甚至[^]*+在减少回溯方面也会更安全。计数不一定能说明什么有用的东西1\'.count'=>3会产生误导。这并不能回答问题,而且是一个很长的注释。