avva: (Default)
[personal profile] avva
Вот тут написано, чем я занимался в основном последние неделю-две.

Сегодня был забавный баг:

mod_rewrite - это такой стандартный модуль для вебсервера (apache), позволяющий менять URLи, задавая всякие сложные правила. В частности, он поддерживает переписывание URLей внешней программой: apache её запускает, и все процессы-дети, обрабатывающие запросы, говорят с ней через stdin/stdout (сериализируя свой доступ через lockfile): посылают ей строку URLя, пришедшую от клиента, и получают обратно новую. Я написал такую внешнюю программу-сервер для нашей новой системы load balancing: переписывание в данном случае заключается в подставлении нужного сервера. Вот как это выглядит в общих чертах: HTTP-запрос приходит на внешний load balancer и отправляется им на одну из машин-прокси. На этих машинах бегут mod_rewrite и mod_proxy: mod_rewrite посылает моему серверу URL, скажем, "users/avva/friends/"; мой сервер находит относительно свободный веб-сервер во внутренней сетке, скажем, 10.0.0.2, и выдаёт обратно строку "http://10.0.0.2/users/avva/friends/"; этот переписанный URL переходит к mod_proxy, которая работает как прокси, пересылает запрос юзера этому вебсерверу, получает ответ и пересылает его юзеру.

(есть причины, по которым выгодно работать по такой двух-уровневой схеме, через внутренние прокси - не буду сейчас вдаваться в них)

Мой сервер читает запросы со своего stdin и отвечает на них на stdout, и одновременно слушает на определённом UDP-порте; а все веб-серверы постоянно посылают сообщения broadcast'ом на этот порт о том, сколько на них есть свободных детей для обработки запросов (отсылкой статистики занимается специальный модуль, к-й я написал на Перле, он бежит внутри mod_perl на веб-серверах). Сначала этот сервер был написан на Перле, но он не справлялся просто с потоком этой статистики и одновременно с запросами от mod_rewrite по stdin/stdout; поэтому я переписал его на C, и сейчас работает очень хорошо. Но вот какой был баг. Главный цикл моего сервера - select() на два дескриптора: UDP-socket'а и stdin; когда на одном из дескрипторов есть новые данные, он читает их и обрабатывает. Если эти данные приходят из stdin, он ожидает увидеть там целую строку (URL плюс \n, завершающий её). Код сервера делает read() с дескриптора stdin до тех пор, пока есть что читать, так что если даже строка URLа разобьётся на несколько вызывов read(), он её правильно сложит вместе (это возможно, т.к. строки URL могут быть очень длинными в случае больших GET-запросов; некоторые сумасшедшие прокси и серверы переписывают POST-запросы как GET-запросы - насколько я понял, Hotmail грешит этим, в частности). Но вот если строка URL оказывалась разорванной между вызовами select(): т.е., например, пришли какие-то данные, он читает, читает, читает, получает в конце концов "users/avva/friends", но без \n в конце, и read() ему говорит, что читать больше нечего - и только после ещё одного вызова select() придут новые данные - вот в этом случае код не справлялся и вёл себя неправильно. Но казалось бы, как такое может произойти? - ведь модуль mod_rewrite пишет всю строку для внешнего сервера сразу, трудно представить, зачем бы он вёл себя иначе. Тем не менее это происходило, но довольно редко.

Оказалось вот что. mod_rewrite посылает данные в stdin моего сервера так (копирую из его исходников):

write(fpin, key, strlen(key));
write(fpin, "\n", 1);

Само по себе разделение этой записи на два вызова write() мне ничем не мешает, т.к. я всё равно вызываю read() столько раз, сколько нужно, соединяя результаты в одном буфере. Но если между этими двумя вызовами write() произойдёт task switch — именно в этом месте! — и процессор переключится на мой процесс, то я получу всю строку за исключением "\n", а остаток получу уже только после следующего вызова select(). Понятно теперь стало и то, почему это происходило, и то, почему происходило так редко. А если бы mod_rewrite объединял ключ (так URL называется в терминологии этого модуля) с "\n" каким-нибудь несчастным sprintf'ом перед отсылкой в write(), никакой проблемы у меня бы не возникло, и не пришлось бы переписывать заново весь главный цикл прослушивания данных и обработки запросов с stdin. Но я не жалею, что пришлось, на самом деле. Просто забавно.

Date: 2003-09-19 02:34 pm (UTC)
From: [identity profile] meshko.livejournal.com
стоп, а разве есть гарантия, что если послано одним write(), то такого не произойдет? Он что, атомик?

Date: 2003-09-19 02:37 pm (UTC)
From: [identity profile] avva.livejournal.com
Он, конечно, не atomic в том смысле, что никто не гарантирует, что его не разорвёт на несколько read()-ов. Но насколько я понимаю архитектуру ядра, один посланный write() на pipe (не на сетевой сокет) не может разорвать между select()-ами, т.е. другой конец трубы придёт в состояние готовности чтения и уже из него не уйдёт, пока всё содержимое этого write()'а не будет вычитано.

Date: 2003-09-20 07:23 pm (UTC)
From: [identity profile] meshko.livejournal.com
В pipe буфер вряд ли растет -- что если write() его переполнит, заблокируется и будет ждать, пока read() прочитает? По-меому с read()/select() никогда нельзя ни на что такое полагаться.

Date: 2003-09-19 02:34 pm (UTC)
From: (Anonymous)
snprintf()' ом конечно же.

Date: 2003-09-19 02:34 pm (UTC)
From: [identity profile] avva.livejournal.com
Да :)

Date: 2003-09-19 02:38 pm (UTC)
From: [identity profile] gera.livejournal.com
В последние вечера (в Америке) были жуткие проблемы (сервера не отзывались).
Как говорил Ленин, главный критерий теории - практика. Посмотрим, как теперь будет работать :)

Date: 2003-09-19 04:18 pm (UTC)
From: [identity profile] dimrub.livejournal.com
I'm sorry for a dumb question (and for English), "sami my ne mestnye", but why, except for the cost, won't you use one of the many standard load balancing solutions available on the market (and probably in the open source community)? Surely not the NIH syndrom?...

BTW, this bug is exceptional in that it is one of the few that is easier to find on a single CPU machine, than on a multi-CPU.

Date: 2003-09-19 04:35 pm (UTC)
From: [identity profile] avva.livejournal.com
We are using "one of the many standard load balancing solutions available on the market": F5's BIG-IP. Have been using them forever, and they're very good. BIG-IP load balances our network: both external requests coming to it from the outside, and several kinds of internal requests from one server to the other within our network. The scheme I outlined in my entry is largely orthogonal to load balancing done by our BIG-IP.

In our situation, where one of the primary bottlenecks is CPU on the web servers eaten by CPU-hungry mod_perl processes, BIG-IP's balancing of requests meant for mod_perl to execute is not good enough. It does a great job of load-balancing other kinds of requests (like images, static pages, or whatever else) but the dynamic perl-built pages load up web servers pretty heavily, and some of the web server machines end up more stressed than others (sometimes for no real reason other than random distribution of requests). the BIG-IP doesn't know anything about that, apache doesn't let it know how busy it is (apache has no framework for doing that, with the exception of mod_backhand which broadcasts apache stats, but it's primarily used for internal load balancing achieved by transferring requests from one internal webserver to another, less busy one. That's too inefficient in our heavy dynamic mod_perl context). Other load balancers, while often pretty good at what they do, also don't do that AFAIK. So we felt we needed a custom solution, and we built one.

Date: 2003-09-20 03:42 am (UTC)
From: [identity profile] dimrub.livejournal.com
ОК, понял :).

Кстати, "внутренние прокси" == "reverse proxy"?

Date: 2003-09-20 03:44 am (UTC)
From: [identity profile] avva.livejournal.com
Да. Т.е. я их назвал внутренними прокси, не подразумевая это как перевод термина, просто имея в виду, что они работают как прокси внутри нашей сетки. Но они именно reverse proxies.

December 2025

S M T W T F S
  123 4 56
78 9 10 11 1213
1415 1617181920
21 22 23 24 2526 27
2829 30 31   

Most Popular Tags

Style Credit

Expand Cut Tags

No cut tags
Page generated Jan. 1st, 2026 09:25 pm
Powered by Dreamwidth Studios