четверг, 31 мая 2012 г.

Высокая нагрузка

В компании я работаю над проектом Континент. Серверное ПО, жесткие требования. Как добиться качества? В тестовых условиях достаточно проблематично организовать тысячи клиентских машин. Что уж говорить про десятки тысяч, которые мы хотим поддерживать.

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

Эти мысли долго терзали меня, пока я наконец не сел, и не написал свой фреймворк сетевого нагрузочного тестирования. Правильный. :)

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

Однопоточность дает несколько плюсов. Во первых нам не нужно ничего синхронизировать между потоками. Во вторых нам не нужны никакие блокировки. Есть и другие.

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

Традиционные приложения пишутся так, что системный вводо-вывод оказывается в самой глубине иерархии классов. Поэтому код типичных приложений в качестве клиентов нагрузки использовать не получается. Надо почти все переписывать. Дескриптор необходимо иметь отдельно от клиента.

Однопоточность так же позволяет расставить четкие приоритеты в клиентских операциях. Нет необходимости писать в сокет, если мы не успеваем обрабатывать то, что приходит. Нет необходимости тратить процессорное время (например на шифрование), если мы не успеваем отправлять данные.

Еще интересный момент с протоколированием. Нет смысла с тысячи клиентов все валить на стандартный вывод. Информации будет столько, что толку от нее не будет никакого. Вопрос очень сложный в том плане, что объем протоколируемой информации зависит от того, как мы собираемся эту информацию обрабатывать. В примитивном варианте меня устраивает стандартный вывод, но при этом я вывожу на экран активность только одного клиента из всех. Но если мне захочется отследить какие-то другие характеристики, возможно мне понадобиться запись большого количества информации в файл для последующей обработки. Это все можно настроить. Не знаю только, как удовлетворить сразу все интересы из коробки.  С другой стороны избыток протоколирования приведет к снижению полезной нагрузки от приложения. Дилемма.

Первый тестовый модуль, который я выложил на bitbucket, тестирует http.
К серверу nginx удалось одновременно подключить 25000 тестовых http клиентов. Если установить nginx на отдельной машине, наверное можно и больше. http клиенты упираются только в сеть.

Старался все сделать так, чтобы можно было легко писать своих клиентов, и собирать с них интересующую информацию. Будет здорово, если кому-то окажется полезным, хотя я еще не определился с форматом проекта... то ли это либа, то ли тестсьют? Как его лучше оформить?

8 коммент.:

izlesa комментирует...

так асинхронный это подход, нет? boost.asio, completition ports на винде, на никсах вроде kevent или, чтото типа. Но вот обычно стараються все-таки сделать количество потоков сделать равным кол-ву ядер (впрочем там есть и другие эмпирические правила). Впрочем я не сильно разбираюсь в высокой нагрузке )

Andrey Valyaev комментирует...

Асинхронный, но boost::asio я не использую. В принципе можно использовать kevent, но пока хватает обычного poll. kevent - это linux specific, без лишней необходимости нет смысла использовать. тем более что poll вполне справляется с десятками тысяч дескрипторов.

Исходная посылка в том, что клиенты нагрузки никак не зависят друг от друга. Нет необходимости в нескольких потоках. Один поток справляется с десятками тысяч клиентов. Если мало, можно запустить второй экземпляр приложения, он утилизирует второе яро.. запусти 10, утилизируют 10.

Однопоточность быстрее. :)

PS: Поскольку пишу на новом c++, я приучаю себя не использовать boost... Не всегда получается. :)

Анонимный комментирует...

На самом деле, всё относительно.
Мы, например, писали систему по приёму с сети событий по UDP, их обработке и вставке данных в БД Oracle.
Что могу сказать... 15 млн. записей за 10 минут вставить/проапдейтить в БД - это не хухры-мухры. Один поток (получил порцию - вставил - вызвал commit) тут явно не справится. Распараллелили на 5 потоков, заработало нормально. Но всё в итоге упёрлось в дисковое I/O на сервере БД. Остальной весь софт отрабатывает куда шустрее.

Andrey Valyaev комментирует...

Понимаешь Владимир, если идет речь про один поток, это вовсе не означает, что вся работа построена на зачитал-записал-закоммитил...

Моя задача стояла именно в одновременных подключениях, которые висят долго. Поэтому вся логика строится на контроле ввода вывода... типа есть данные для клиента #1234 - пусть зачитает.. Сокет для клиента #4321 освободился, он может записать, осталось время - они могут нагенерировать еще что нибудь. типа того.

Я тут подумал, что наверное лучше это оформить в виде либы.

Dmitriy T. комментирует...

Логгирование всё же необходимо. Под нагрузкой всякие чудеса могут начаться - надо иметь возможность проверить что "баланс" сойдётся. Проверять же "баланс" на лету обычно неправильно при асинхроне(ну если там что-то сложнее сравнения пары байт) - так что остаётся проверка по логам.

Ну и надо понимать что иногда полезно иметь возможность потестировать нагрузкой с нескольких машин. Так подробный логгинг иногда жрёт порядочно и нагрузить по полной иногда получается только с нескольких машин. Иногда только под длительным тестированием в полную нагрузку проявляются некоторые редкие плавающие баги.

Andrey Valyaev комментирует...

Я и не говорю, что логгинг не нужен.
У меня есть трассировка...

Если служба работает как надо - нет смысла протоколировать. Просто запускаешь и убеждаешься.

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

Тут, скорее всего, готовой утилитой уже не обойдешься, надо брать в руки компилятор и выводить нужную информацию в нужные места.

С другой стороны я предпочитаю не выводить много информации, а выводить содержательную информацию по минимуму. Пусть программа проанализирует, просуммирует, промеряет и скажет мне что точно происходит не так. :) на то и нужен компьютер.

Проект я наверное сделаю в виде либы, с парой-тройкой примеров... так будет удобнее им пользоваться. В ближайшее время...

Yury Schkatula комментирует...

Помнится, у одного бывшего Заказчика был продукт на подобную тему - Avalanche. Там проблему "упирания в сеть" решили специализированным железом. Цель аналогичная - http testing.

Andrey Valyaev комментирует...

Мы уже вот ждем ixia... для тестирования вроде замечательная штука...