使用libevent进行异步Redis池化

我希望尽可能多地从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 req / s.使用ab对此进行基准测试,在尝试使用大数字(例如10k reqs)时,事情变得复杂 – 它无法完成基准测试和冻结.做同样的事情但阻塞方式结果是5000-6000 req / s.

我已经通过限制-n 10000扩展了最大文件.我正在使用Mac OS X Lion.

最佳答案 当然,打开Redis连接一次会更好,并尝试尽可能地重用它.

使用提供的程序,我怀疑基准冻结,因为临时端口范围内的空闲端口数量已耗尽.每次打开和关闭与Redis的新连接时,相应的套接字都会在TIME_WAIT模式下花费一些时间(可以使用netstat命令检查此点).内核无法足够快地回收它们.如果它们太多,则无法启动其他客户端连接.

您在程序中也有内存泄漏:为每个请求分配reqData结构,并且永远不会释放. get_cb中缺少免费.

实际上,有两种可能的TIME_WAIT套接字源:用于Redis的套接字,以及由基准工具打开以连接到服务器的套接字. Redis连接应该在程序中分解.必须将基准测试工具配置为使用HTTP 1.1和keepalived连接.

就个人而言,我更倾向于使用siege而不是ab来运行这种基准测试.对于对HTTP服务器进行基准测试感兴趣的大多数人来说ab被认为是一种天真的工具.

在我的旧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电话.

要进一步优化,您可以考虑在服务器和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在管道命令方面做得很好(假设连接是永久性的),因此影响很小.

点赞