2012-03-31 12 views
4

Ich möchte so viel wie möglich von Redis + Hiredis + Libevent bekommen.Async Redis Pooling mit Libevent

Ich verwende folgenden Code (ohne Kontrollen kurz zu sein) in System

#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; 
} 

zu kompilieren, verwenden gcc -o main -levent -lhiredis main.c vorausgesetzt libevent, redis und hiredis.

Ich bin neugierig, wenn ich redisAsyncConnect tun muss? In main() einmal oder (wie zum Beispiel) in jedem Callback. Kann ich etwas tun, um die Leistung zu steigern?

Ich bekomme über 6000-7000 req/s. Mit ab, um dies zu benchmarken, kompliziert Sachen beim Versuch große Zahlen (z. B. 10k Reqs) - es kann Benchmark nicht abgeschlossen und friert. Die gleiche Sache machend, aber in blockierender Weise sind die Resultate 5000-6000 req/s.

Ich habe Max Datei geöffnet von limit -n 10000 erweitert. Ich benutze Mac OS X Lion.

Antwort

2

Es ist natürlich viel besser, die Redis-Verbindung einmal zu öffnen und zu versuchen, sie so weit wie möglich wiederzuverwenden.

Mit dem mitgelieferten Programm, ich vermute, der Benchmark friert ein, weil die Anzahl der freien Ports im kurzlebigen Port-Bereich erschöpft ist. Jedes Mal, wenn eine neue Verbindung zu Redis geöffnet und geschlossen wird, verbringt der entsprechende Socket einige Zeit im TIME_WAIT-Modus (dieser Punkt kann mit dem Befehl netstat überprüft werden). Der Kernel kann sie nicht schnell genug recyceln. Wenn Sie zu viele davon haben, kann keine weitere Clientverbindung initiiert werden.

Sie haben auch einen Speicherverlust im Programm: Die reqData-Struktur wird für jede Anfrage zugeordnet und nie freigegeben. Ein free fehlt in get_cb.

Tatsächlich gibt es 2 mögliche Quellen für TIME_WAIT-Sockets: die für Redis verwendeten und die, die vom Benchmark-Tool für die Verbindung mit dem Server geöffnet wurden. Redis-Verbindungen sollten im Programm faktorisiert werden. Das Benchmark-Tool muss für die Verwendung von HTTP 1.1- und Keepalived-Verbindungen konfiguriert sein.

Persönlich bevorzuge ich siege über ab, um diese Art von Benchmark zu betreiben. ab wird von den meisten Leuten, die sich für das Benchmarking von HTTP-Servern interessieren, als naives Werkzeug angesehen.

Auf meinem alten Linux-PC, dem ursprünglichen Programm, laufen gegen Belagerung im Benchmark-Modus mit 50 keepalived Verbindungen, ergibt:

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

Wenn wir vollständig um den Anruf zu Redis zu entfernen, nur ein Dummy-Ergebnis zurückkehrt, Wir bekommen:

Nun, lassen Sie uns das Programm ändern, um die Redis-Verbindung zu faktorisieren, und natürlich von Pipelining profitieren. Der Quellcode ist verfügbar here. Hier ist, warum wir erhalten:

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

Mit anderen Worten, durch Entfernen der systematischen Verbindung/Trennung Ereignisse können wir den doppelten Durchsatz erreichen. Die Leistung mit dem Redis-Anruf ist nicht so weit wie die Leistung bekommen wir ohne Redis-Aufruf.

Zur weiteren Optimierung könnten Sie einen Unix-Domain-Socket zwischen Ihrem Server und Redis verwenden und/oder die dynamisch zugewiesenen Objekte zusammenfassen, um den CPU-Verbrauch zu reduzieren.

UPDATE:

mit einem Unix-Domain-Socket zu experimentieren, es ist ganz einfach: Sie müssen nur die Unterstützung in Redis aktivieren sich durch die Konfigurationsdatei zu aktualisieren:

# 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 

und dann die Verbindung ersetzen Funktion:

c = redisAsyncConnect("0.0.0.0", 6379); 

von:

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

Hinweis: Hier leistet mendis async gute Arbeit bei der Pipelining der Befehle (sofern die Verbindung permanent ist), so dass die Auswirkungen gering sind.

+0

Kann mir keine bessere und wertvollere Antwort vorstellen. Vielen, vielen Dank Didier! Kannst du mir auch mehr über weitere Optimierung mit Domain Socket erzählen? Oder verlinke mich mit einigen Ressourcen? –

+0

Ich habe meine vorherige Antwort entsprechend aktualisiert. –

+0

Danke! Ich kann es kaum erwarten, die Benchmarks zu überprüfen. –