4

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ですか?1main()回または (例が示すように) コールバックごとに。パフォーマンスを向上させるためにできることはありますか?

私は約6000-7000リクエスト/秒を取得しています。これをベンチマークに使用abすると、大きな数 (例: 10k リクエスト) を試すと複雑になり、ベンチマークを完了できず、フリーズします。同じことを行っていますが、結果は 5000-6000 req/s です。

で最大ファイルオープンを拡張しましたlimit -n 10000。Mac OS X Lion を使用しています。

4

1 に答える 1

2

もちろん、Redis 接続を一度開いて、できるだけ再利用することをお勧めします。

提供されたプログラムでは、エフェメラル ポート範囲の空きポートの数が使い果たされているため、ベンチマークがフリーズしていると思われます。Redis への新しい接続が開かれたり閉じられたりするたびに、対応するソケットが TIME_WAIT モードで時間を費やします (この点は netstat コマンドを使用して確認できます)。カーネルはそれらを十分に速くリサイクルできません。それらが多すぎると、それ以上クライアント接続を開始できなくなります。

また、プログラムにメモリ リークがあります。reqData 構造体は要求ごとに割り当てられ、割り当てが解除されることはありません。get_cb に free がありません。

実際には、TIME_WAIT ソケットには 2 つのソースが考えられます: Redis に使用されるものと、サーバーに接続するためにベンチマーク ツールによって開かれるものです。Redis 接続は、プログラムで因数分解する必要があります。ベンチマーク ツールは、HTTP 1.1 およびキープアライブ接続を使用するように構成する必要があります。

個人的には、この種のベンチマークを実行するには、ab よりもsiegeを使用することを好みます。ab は、HTTP サーバーのベンチマークに関心のあるほとんどの人にとって単純なツールと見なされています。

私の古い Linux PC で、50 の keepalived 接続を使用してベンチマーク モードで初期プログラムを siege に対して実行すると、次の結果が得られます。

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

つまり、体系的な接続/切断イベントを削除することで、スループットを 2 倍にすることができます。Redis 呼び出しを使用した場合のパフォーマンスは、Redis 呼び出しを使用しない場合よりもはるかに優れています。

さらに最適化するには、サーバーと Redis の間で UNIX ドメイン ソケットを使用したり、動的に割り当てられたオブジェクトをプールして CPU 消費を削減したりすることを検討できます。

アップデート:

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 はコマンドのパイプライン化で適切に機能するため (接続が永続的である場合)、影響は少なくなります。

于 2012-04-01T09:40:12.230 に答える