Рубрики
Без рубрики

Регулярное выражение, которое сломало сервер

Автор оригинала: Vlad Mihalcea.

Я никогда не думал, что увижу не отвечающий сервер из-за плохого сопоставления регулярных выражений, но это просто случилось с одной из наших служб, из-за чего она не отвечает.

Давайте предположим, что мы проанализируем некоторую информацию об автомобиле внешнего дилера. Мы пытаемся найти все эти автомобили с надписью “без кондиционера” среди различных доступных шаблонов ввода (но без соответствующих шаблонов, таких как “моно кондиционер”).

Регулярное выражение, которое нарушило работу нашего сервиса, выглядит следующим образом:

String TEST_VALUE = "ABS, traction control, front and side airbags, Isofix child seat anchor points, no air conditioning, electric windows, \r\nelectrically operated door mirrors";
double start = System.nanoTime();
Pattern pattern = Pattern.compile("^(?:.*?(?:\\s|,)+)*no\\s+air\\s+conditioning.*$");
assertTrue(pattern.matcher(TEST_VALUE).matches());
double end = System.nanoTime();
LOGGER.info("Took {} micros", (end - start) / (1000 ));

Через 2 минуты этот тест все еще продолжался, и одно ядро процессора было полностью перегружено.

Во-первых, метод соответствует использует все входные данные, поэтому нам не нужны разделители начала(^) или конца ($), и из-за новых символов строки во входной строке мы должны указать нашему шаблону регулярных выражений работать в МНОГОСТРОЧНОМ режиме:

Pattern pattern = Pattern.compile(
    "(?:.*?(?:\\s|,)+)*no\\s+air\\s+conditioning.*?", 
    Pattern.MULTILINE
);

Давайте посмотрим, как ведут себя несколько версий этого регулярного выражения:

“(?:.*?(?:\\s|,)+)*нет\\s+кондиционера\ \ s+. *?” Это слишком медленно 35699.334
“(?:.*?(?:\\s|,)+)?нет\\s+кондиционер\ \ s+. *?” Группе без захвата не нужен множитель один или много (+), поэтому мы можем заменить его нулем или единицей(?) 108.686
“(?:.*? \\b)?нет\\s+кондиционер\ \ s+. *?” Он работает для большего количества входных данных, чем предыдущий, в котором используется только пробел(\s) и запятая (,) для разделения соответствующего шаблона 153.636
“\\bno\\s+кондиционер\ \ s+кондиционер” Найти намного быстрее, чем совпадения, и нас интересует только первое появление этого шаблона. 78.831

Почему бы вместо этого не использовать String.indexOf ()?

Хотя это было бы намного быстрее, чем использование регулярных выражений, нам все равно пришлось бы учитывать начало строки, шаблоны, такие как “моно кондиционер”, вкладки или несколько пробелов между нашими маркерами шаблонов. Пользовательские реализации как таковые могут быть быстрее, но менее гибкими и требуют больше времени для реализации.

Регулярное выражение – прекрасный инструмент для сопоставления шаблонов, но вы не должны воспринимать его как должное, так как небольшие изменения могут привести к большим различиям. Причина , по которой первое регулярное выражение было контрпродуктивным, связана с катастрофическим возвратом , явлением, о котором должен знать каждый разработчик, прежде чем начинать писать регулярные выражения.