2012-03-31 6 views
4

Я хочу получить как можно больше от Redis + Hiredis + libevent.Async Redis pooling using 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 req/s. Используя ab, чтобы сравнить это, материал усложняется при попытке больших чисел (например, 10k reqs) - он не может завершить тесты и замораживать. Выполнение одного и того же результата, но с блокировкой результатов 5000-6000 req/s.

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

ответ

2

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

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

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

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

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

На мой старый Linux PC, первоначальная программа, бегите от осады в тесте режиме с 50 keepalived соединений, приводит к:

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

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

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

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

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

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

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

UPDATE:

Экспериментировать с доменным Сокет, это просто: вы просто должны активировать поддержку в самой 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 отлично справляется с конвейерными командами (при условии, что соединение является постоянным), поэтому воздействие будет низким.

+0

Не могу представить лучшего и более ценного ответа. Многие, большое спасибо Дидье! Также вы могли бы рассказать мне больше о дальнейшей оптимизации с помощью сокета домена? Или ссылку на некоторые ресурсы? –

+0

Я обновил свой предыдущий ответ соответственно. –

+0

Спасибо! Не могу дождаться, чтобы проверить тесты. –