Главная
Блог разработчиков phpBB
 
+ 17 предустановленных модов
+ SEO-оптимизация форума
+ авторизация через соц. сети
+ защита от спама

Стремительный фреймворк для стресс-тестирования приложений

Anna | 5.06.2014 | нет комментариев

Веб-приложений под высокие нагрузки в последнее время создается все огромнее, но вот с фреймворсками дозволяющими эластично их стресс тестировать — не густо. Их безусловно много различных (см. голосование в конце поста) но кто-то куки не поддерживает, кто-то нагрузку слабую дает, кто-то дюже массивен, да и годятся они в основном для вовсе однотипных запросов, т.е. динамически генерить всякий запрос применяя свою логику и при этом еще максимально стремительно (и в идеале на java Дабы допилить если что) — не обнаружил таких.

Следственно было решено писать свой. Основные требования: быстрота и динамическая генерация запросов. При этом быстрота это не легко тысячи RPS, а в идеале — когда стресс упирается только в пропускную способность сети и работает с всякий свободной машины. 

Движок

С требованиями ясно, сейчас необходимо решить на чем это все будет трудиться, т.е. какой http/tcp заказчик применять. Безусловно мы не хотим применять устаревшую модель thread-per-connection (нить на соединение), потому что сразу упремся в несколько тысяч rps в зависимости от мощности машины и быстроты переключения контекстов в jvm. Т.о. apache-http-client и им сходственные отметаются. Тут нужно глядеть на т.н. неблокирующие сетевые заказчики, построенные на NIO.

К счастью в java мире в этой нише давным-давно присутствует эталон де-факто опенсорсный Netty, тот, что к тому дюже универсален и низкоуровневый, разрешает трудиться с tcp и udp.

Зодчество

Для создания своего отправщика нам потребуется ChannelUpstreamHandler обработчик в терминах Netty, из которого и будет посылать наши запросы.

Дальше необходимо предпочесть высокопроизводительный таймер для отправки максимально потенциального числа запросов в секунду (rps). Тут дозволено взять типовой ScheduledExecutorService, он в тезисе с этим справляется, впрочем на слабых машинах отменнее применять HashedWheelTimer (входит в состав Netty) из-за меньших убыточных затрат при добавлении задач, только требует некоторого тюнинга. На сильных машинах между ними фактически нет разницы.

И последнее, Дабы выжать максимум rps с всякий машины, когда незнакомы какие лимиты по соединениям в данной ОСи либо всеобщая нынешняя нагрузка, вернее каждого воспользоваться способом проб и ошибок: задать вначале какое-нибудь запредельное значение, скажем миллион запросов в секунду и дальше ожидать на каком числе соединений начнутся ошибки при создании новых. Навыки показали что предельное число rps обыкновенно чуть поменьше этой цифры.
Т.е. берем эту цифру за исходное значение rps и потом если ошибки повторяются сокращаем ее на 10-20%.

Реализация

 

Генерация запросов

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

public interface RequestSource {
    /**
     * @return request contents
     */
    ChannelBuffer next();
}

ChannelBuffer — это абстракция потока байтов в Netty, т.е. тут должно возвращаться все содержимое запроса в виде потока байт. В случае http и других текстовых протоколов — это легко байтовое представление строки (текста) запроса.
Также в случае http нужно ставить 2 символа новой строки в конце запроса(nn), это является знаком конца запроса и для Netty (не пошлет запрос в отвратном случае)

Отправка

Дабы отправлять запросы в Netty — вначале необходимо очевидно подключиться к удаленному серверу, следственно на старте заказчика запускаем периодические подключения с частотой в соответствие с нынешним rps:

scheduler.startAtFixedRate(new Runnable() {
    @Overrid
    public void run() {
       try {
            ChannelFuture future = bootstrap.connect(addr);
            connected.incrementAndGet();
        } catch (ChannelException e) {
            if (e.getCause() instanceof SocketException) {
                processLimitErrors();            
            }
            ...
        }, rpsRate);

Позже удачного подключения, сразу посылаем сам запрос, следственно наш Netty обработчик комфортно будет наследовать от SimpleChannelUpstreamHandler где для этого есть особый способ. Но есть один нюанс: новое подключение обрабатывается т.н. основном потоке («boss»), где не обязаны присутствовать длинные операции, чем может являться генерация нового запроса, следственно придется перекладывать в иной поток, в выводе сама отправка запроса будет выглядеть приблизительно так:

private class StressClientHandler extends SimpleChannelUpstreamHandler {        
        ....
        @Override
        public void channelConnected(ChannelHandlerContext ctx, final ChannelStateEvent e) throws Exception {
            ...
            requestExecutor.execute(new Runnable() {
                @Override
                public void run() {
                    e.getChannel().write(requestSource.next());
                }
            });
            ....
        }
    }

 

Обработка ошибок

Дальше — обработка ошибок создания новых соединений когда нынешняя частота отправки запросов слишком огромная. И это самая нетривиальная часть, точнее трудно сделать это платформонезависимо, т.к. различные операционные системы ведут себя по различному в этой обстановки. Скажем linux выкидывает BindException, windows — ConnectException, а MacOS X — либо одно из этих, либо вообще InternalError (Too many open files). Т.о. на мак-оси стресс ведет себя особенно непредсказуемо.

В связи с этим, помимо обработки ошибок при подключении, в нашем обработчике тоже нужно это делать (заодно подсчитывая число ошибок для статистики):

private class StressClientHandler extends SimpleChannelUpstreamHandler {        
        ....
        @Override
        public void exceptionCaught(ChannelHandlerContext ctx, ExceptionEvent e) throws Exception {
            e.getChannel().close();

            Throwable exc = e.getCause();
            ...
            if (exc instanceof BindException) {
                be.incrementAndGet();
                processLimitErrors();
            } else if (exc instanceof ConnectException) {
                ce.incrementAndGet();
                processLimitErrors();
            } 
            ...
        }
            ....
   }

 

Результаты сервера

Напоследок, нужно решить что будем делать с результатами от сервера. От того что это стресс тест и нам значима только пропускная способность, тут остается только считать статистику:

private class StressClientHandler extends SimpleChannelUpstreamHandler {
  @Override
        public void messageReceived(ChannelHandlerContext ctx, MessageEvent e) throws Exception {
            ...
            ChannelBuffer resp = (ChannelBuffer) e.getMessage();
            received.incrementAndGet();
            ...
        }
}

Тут же может быть и подсчет типов http результатов (4xx, 2xx)

Каждый код

Каждый код с дополнительными плюшками как бы чтения http образцов из файлов, шаблонизатором, таймаутами и тп. лежит в виде готового maven плана на GitHub (ultimate-stress). Там же дозволено скачатьготовый дистрибутив (jar файл).

Итоги

Все безусловно упирается в лимит открытых соединений. Скажем на linux при увеличении некоторых настроек ОС (ulimit и т.п.), на локальной машине получалось добиться около 30K rps, на современном железе. Теоритечески помимо лимита соединений и сети огромнее ограничений быть не должно, на практике все же убыточные расходы jvm дают о себе знать и фактический rps на 20-30% поменьше заданного.

Что вы используете для стресс-тестирования java приложений?

Только зарегистрированные пользователи могут участвовать в опросе. Войдите, пожалуйста.

Проголосовало 18 человек. Воздержалось 19 человек.

Какую нагрузку получалось сделать (rps)?

Только зарегистрированные пользователи могут участвовать в опросе. Войдите, пожалуйста.

Проголосовало 10 человек. Воздержалось 27 человек.

Источник: programmingmaster.ru

Оставить комментарий
Форум phpBB, русская поддержка форума phpBB
Рейтинг@Mail.ru 2008 - 2017 © BB3x.ru - русская поддержка форума phpBB