В своих продуктах мы используем AMQP. Это удобно, это позволяет лучше масштабироваться и позволяет добавлять в сложную систему новые модули и расширения практически без проблем. В качестве брокера используется RabbitMQ. В качестве фронтендов используется Nginx. До недавнего времени мы везде использовали связку php-amqp и librabbitmq для работы с брокером. Но в некоторых частях системы это нам показалось избыточным.
Задача
Научиться отправлять сообщения в брокер 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::connectmyrabbit;
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("disconnectedn<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 completedn<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 и сильно мало ресурсов, приходится изобретать и не такое.
Будем также рады любой помощи и советам!
Автор: sn00p