Асинхронное объединение Redis с использованием libevent - PullRequest
4 голосов
/ 31 марта 2012

Я хочу получить как можно больше от Redis + Hiredis + libevent.

Я использую следующий код (без каких-либо проверок, чтобы быть коротким)

#include <stdlib.h>
#include <event2/event.h>
#include <event2/http.h>
#include <event2/buffer.h>
#include <hiredis/hiredis.h>
#include <hiredis/async.h>
#include <hiredis/adapters/libevent.h>

typedef struct reqData {
  struct evhttp_request* req;
  struct evbuffer* buf;
} reqData;

struct event_base* base;
redisAsyncContext* c;

void get_cb(redisAsyncContext* context, void* r, void* data) {
  redisReply* reply = r;
  struct reqData* rd = data;

  evbuffer_add_printf(rd->buf, "%s", reply->str);
  evhttp_send_reply(rd->req, HTTP_OK, NULL, rd->buf);

  evbuffer_free(rd->buf);
  redisAsyncDisconnect(context);
}

void cb(struct evhttp_request* req, void* args) {
  struct evbuffer* buf;
  buf = evbuffer_new();

  reqData* rd = malloc(sizeof(reqData));
  rd->req = req;
  rd->buf = buf;

  c = redisAsyncConnect("0.0.0.0", 6380);
  redisLibeventAttach(c, base);

  redisAsyncCommand(c, get_cb, rd, "GET name");
}

int main(int argc, char** argv) {
  struct evhttp* http;
  struct evhttp_bound_socket* sock;

  base = event_base_new();
  http = evhttp_new(base);
  sock = evhttp_bind_socket_with_handle(http, "0.0.0.0", 8080);

  evhttp_set_gencb(http, cb, NULL);

  event_base_dispatch(base);

  evhttp_free(http);
  event_base_free(base);
  return 0;
}

Для компиляции используйте gcc -o main -levent -lhiredis main.c, предполагая, что libevent, redis и hiredis в системе.

Мне интересно, когда мне нужно сделать redisAsyncConnect? В main() один раз или (как показывает пример) в каждом обратном вызове. Что я могу сделать для повышения производительности?

Я получаю около 6000-7000 запросов / с. При использовании ab для сравнения это усложняется при попытке больших чисел (например, 10 000 запросов) - он не может завершить тестирование и зависает. Делая то же самое, но блокирующим образом, получаем 5000-6000 рэк / с.

Я расширил максимальный открытый файл на limit -n 10000. Я использую Mac OS X Lion.

1 Ответ

2 голосов
/ 01 апреля 2012

Конечно, гораздо лучше один раз открыть соединение Redis и попытаться использовать его как можно дольше.

Я подозреваю, что с помощью прилагаемой программы эталонный тест зависает, поскольку число свободных портов вэфемерный диапазон портов исчерпан.Каждый раз, когда новое соединение с Redis открывается и закрывается, соответствующий сокет проводит некоторое время в режиме TIME_WAIT (эту точку можно проверить с помощью команды netstat).Ядро не может перерабатывать их достаточно быстро.Когда их слишком много, дальнейшее клиентское соединение не может быть инициировано.

У вас также есть утечка памяти в программе: структура reqData выделяется для каждого запроса и никогда не освобождается.В get_cb отсутствует свободное место.

На самом деле существует 2 возможных источника сокетов TIME_WAIT: те, что используются для Redis, и те, которые открываются инструментом тестирования для подключения к серверу.Redis соединения должны быть учтены в программе.Инструмент для тестирования должен быть настроен на использование HTTP 1.1 и keepalived соединений.

Лично я предпочитаю использовать siege над ab для запуска такого типа теста.ab считается наивным инструментом для большинства людей, заинтересованных в тестировании HTTP-серверов.

На моем старом ПК с Linux первоначальная программа, работающая против осады в режиме тестирования с 50 поддерживающими соединениями, приводит к:

Transaction rate:            3412.44 trans/sec
Throughput:                     0.02 MB/sec

Когда мы полностью удаляем вызов Redis, возвращая только фиктивный результат, мы получаем:

Transaction rate:            7417.17 trans/sec
Throughput:                     0.04 MB/sec

Теперь давайте изменим программу, чтобы факторизовать соединение Redis и, естественно, извлечь выгоду из конвейеризации.Исходный код доступен здесь .Вот почему мы получаем:

Transaction rate:            7029.59 trans/sec
Throughput:                     0.03 MB/sec

Другими словами, удаляя систематические события соединения / разъединения, мы можем достичь удвоенной пропускной способности.Производительность при вызове Redis не настолько низка, как при использовании вызова Redis.

Для дальнейшей оптимизации вы можете рассмотреть возможность использования доменного сокета unix между вашим сервером и Redis и / или динамически создавать пулвыделенные объекты для уменьшения потребления ЦП.

ОБНОВЛЕНИЕ:

Чтобы поэкспериментировать с сокетом домена unix, это просто: вам просто нужно активировать поддержку в самом Redis, обновив файл конфигурации:

# Specify the path for the unix socket that will be used to listen for
# incoming connections. There is no default, so Redis will not listen
# on a unix socket when not specified.
#
unixsocket /tmp/redis.sock
unixsocketperm 755

и затем замените функцию соединения:

c = redisAsyncConnect("0.0.0.0", 6379);

на:

c = redisAsyncConnectUnix("/tmp/redis.sock");

Примечание: здесь hiredis async хорошо выполняет работу по конвейеризации команд (при условии постоянного подключения), поэтому воздействие будет низким.

...