понедельник, 8 июля 2013 г.

Балансировка HTTP запросов NGINX'ом

    Про то как балансировать нагрузку nginx'ом написано множество статей - поэтому я не буду повторяться и рассказывать про директивы upstream и proxy_pass. Расскажу о таком тонком нюансе поведения, который порой ускользает от внимания  даже опытных сисадминов, а именно  об эффекте дублирования запросов.  
     Дело в том что мало кто задумывается - а что происходит с HTTP запросом, если он, по какой-либо причине не обрабатывается бэкэндом ?  Дожен ли он передаваться следующему бэкэену описанному в директиве upstream или же нужно просто возвращать 5xx ошибку ?  По умолчанию nginx пытается отправить этот запрос следующему бэкэнду и если он его обрабатывает - то пользователь вообще никогда не узнает что при обработке его запроса возникли какие-либо трудности. Казалось бы - вот она - отказоустойчивая система, мечта всех программистов...  
       Но не тут то было.... Описанное выше поведение идеально при отдаче контента,  когда нам нужно во что бы то ни стало показать пльзователю запрошенную страницу.  Но представьте что это POST запрос, который выполняет какое-либо важное, с точи зрения приложения, действие. Например оформление заказа в интернет магазине.  Представим себе ситуацию когда обработка подобного запроса завершается по таймауту или просто ошибкой. Nginx как честный балансировщик направляет этот запрос к следующему бэкэнду. Но что это означает с точки зрения приложения ? С точки зрения приложения запрос просто сдуплировался, был оди запрос - а стало два. Если при  обработке этого POST запроса не используются данные из сессии - то можно сказать что мы  легко отделались - ситуация более менее предсказуемая. 
      В слуае отсутствия уникального ключа данные дублируются - я думаю практически все когда-нибуть сталкивались с этим. К примеру не вовремя оставленный комментарий может задвоится. Этим грешат даже очень хорошо тестируемые системы типа вконтакта. 
      Если этим POST запросом оказался к примеру запрос на регистрацию пользователя, при котором как минимум одно поле должно быть уникальным - будь то email или логин - то один из запросов закончится успешно, второй завершится ошибкой - с сообщением вида "email/login должен быть уникальным". При этом пользователь на самом деле был создан. Естественно что такое поведение системы не вызовет восторга у клиентов.
      Если же при обработке POST запроса(как в первом примере с отправкой заказа) используется некоторое состояние приложения, хранящееся в сессии(список товаров в корзине пользователя) - то мы в полной жопе. Что в этом случае может случится - зависит от очень многих факторов - и это в свою очередь,  порождает очень широкий спектр глюков. Начиная от полной потери заказа до дублирования заказа. Разработчики в таких ситуациях как правило винят интернет эксплорер... якобы кнопка не всегда блокируется и пользователи умудряются отправить заказ два раза... QA разводят руками - воспроизвети это не возможно. В общем боротся с этими глюками можно очень долго... и про то что в этом может быть виновен  nginx - мы задумываемся в последнюю очередь.
           Что это исправить, нужно отключить передачу POST запросов следующему backend серверу с помощью директивы proxy_next_upstream 
       

Комментариев нет:

Отправить комментарий