Задача
Научиться отправлять сообщения в брокер RabbitMQ напрямую с фронтендов, минуя php. По возможности асинхронно. Свести к минимуму программирование и вбивание костылей.
Сразу оговорюсь, что мы хотели оптимизировать совсем служебную часть системы, которая формирует некую статистику, передает данные дальше и больше ничем не занимается. Тут нет никакого контента и ничего не надо отдавать пользователю. Также нет медленных бекендов и ничего нигде не блокируется.
Пути решения.
Для Nginx написано немалое количество модулей. Мы сейчас уже используем или планируем использовать модули из сборки openresty.
Но вот конкретно для работы с amqp нет почти ничего готового.
В интернете тоже практически пусто, такая тема была популярна в 2010 году, но в этом году же и заглохла.
Есть модуль Nginx для 0MQ и плагин для RabbitMQ, который собирается, но не работает. И опять 2010 год.
— можно написать свой модуль для Nginx.
— можно использовать lua, но нет готовых биндингов, надо писать.
— можно использовать что-то еще, например thrift и заточить его для такой задачи.
Но это все долго и мы все же не программисты )))
Был найден wrapper для librabbitmq на perl. К счастью, nginx умеет embedded perl.
Мы все пакетируем в deb, проблем со сборкой именно этого модуля для nginx=1.2.4 не возникло.
Нужно лишь убедиться, что используется ключ —with-http_perl_module. И что файлы собранного модуля ищутся в путях. У нас, например, получилось так:
objs/src/http/modules/perl/blib/lib/nginx.pm usr/lib/perl/5.14 objs/src/http/modules/perl/blib/arch/auto/nginx/nginx.so usr/lib/perl/5.14/auto/nginx
Net::RabbitMQ мы брали отсюда.
Кусок nginx.conf
http { perl_modules /etc/nginx/perl; perl_require rmqsux.pm; ...
Кусок testsite.conf
server { listen 80; server_name test01.dev; location / { perl rmqsux::amqppull; } }
Пример скрипта:
whoareyou@test01:/etc/nginx/perl# cat rmqsux.pm package rmqsux; use nginx; use Net::RabbitMQ; use vars; rmxsux::connectmyrabit; sub try (&@) { my($try,$catch) = @_; eval { &$try }; if ($@) { local $_ = $@; &$catch; } } sub catch (&) { $_[0] } sub connectmyrabbit { try { $mq = Net::RabbitMQ->destroy(); } catch { $mq = Net::RabbitMQ->new(); }; $mq->connect("192.168.1.100", { user => "test", password => "test", vhost => "testhost" }); $mq->channel_open(1); } sub disconnectmyrabbit { $mq->disconnect(); my $r = shift; $r->send_http_header("text/html"); $r->print("disconnected\n<br/>"); $r->status(200); } sub amqppull { my $r = shift; try { $mq->publish(1, "test", "teststringteststringteststringteststringteststring"); } catch { rmqsux::connectmyrabbit; $mq->publish(1, "test", "oops died here"); }; $r->send_http_header("text/html"); return OK if $r->header_only; $r->print("send completed\n<br/>"); $r->status(200); $r->flush; } 1; __END__
Бахнули синтетический тест. Можно бахнуть больше, но rabbitmq надо специально тюнить.
silenkov@sn00p:/home$ ab -c 300 -n 100000 http://test01.dev/ This is ApacheBench, Version 2.3 <$Revision: 655654 $> Copyright 1996 Adam Twiss, Zeus Technology Ltd, http://www.zeustech.net/ Licensed to The Apache Software Foundation, http://www.apache.org/ Benchmarking test01.dev (be patient) Completed 10000 requests Completed 20000 requests Completed 30000 requests Completed 40000 requests Completed 50000 requests Completed 60000 requests Completed 70000 requests Completed 80000 requests Completed 90000 requests Completed 100000 requests Finished 100000 requests Server Software: nginx Server Hostname: test01.dev Server Port: 80 Document Path: / Document Length: 20 bytes Concurrency Level: 300 Time taken for tests: 10.402 seconds Complete requests: 100000 Failed requests: 0 Write errors: 0 Total transferred: 13500000 bytes HTML transferred: 2000000 bytes Requests per second: 9613.40 [#/sec] (mean) Time per request: 31.206 [ms] (mean) Time per request: 0.104 [ms] (mean, across all concurrent requests) Transfer rate: 1267.39 [Kbytes/sec] received Connection Times (ms) min mean[+/-sd] median max Connect: 0 14 126.9 2 3007 Processing: 1 14 30.0 9 1543 Waiting: 1 13 30.0 8 1543 Total: 3 29 136.8 11 3230 Percentage of the requests served within a certain time (ms) 50% 11 66% 15 75% 16 80% 16 90% 20 95% 49 98% 56 99% 1009 100% 3230 (longest request)
От количества итераций результаты меняются в пределах 5%.
Связка php+librabbitmq выдавала нам 300-600 rps, но тут дело, в основном, в конечном числе воркеров php.
Проблемы:
— под нагрузкой connect зачастую рвется, поэтому такой вот хитрый ход в скрипте ) Определить порог срабатывания этой ошибки пока не смогли. До 6000 рпс точно ничего не рвется.
— непонятно, будет ли работать асинхронно? Можно все переписать на lua, тогда точно будет.
— непонятны пока проблемы такого подхода в целом.
— нужен механизм сдерживания этой машины. Nginx все равно быстрее, чем любой бекенд. Он прожует хоть сто тысяч рпс, но вот бекенд уже нет. limit_req_zone тут вроде бы подходит, но необходимо подбирать burst под профиль нагрузки.
Производительность в целом сильно радует. Сообщения не теряются, ничего пока не утекает уже четвертый день. Сейчас закончатся все тесты, будем дальше делать consume. Там надо все распарсить и запихать в Postgres, у Nginx этот модуль тоже есть и все с ним отлично.
Просьба от комментариев про велосипеды воздержаться. У нас на таких частях системы сильно много rps и сильно мало ресурсов, приходится изобретать и не такое.
Будем также рады любой помощи и советам!
ссылка на оригинал статьи http://habrahabr.ru/post/155225/
Добавить комментарий