【问题标题】:Async Redis pooling using libevent使用 libevent 的异步 Redis 池
【发布时间】:2012-03-31 18:37:04
【问题描述】:

我想从 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 对此进行基准测试,在尝试大数字(例如 10k 请求)时,事情会变得复杂 - 它无法完成基准测试并冻结。做同样的事情,但以阻塞方式,结果是 5000-6000 req/s。

我已经扩展了limit -n 10000 打开的最大文件。我正在使用 Mac OS X Lion。

【问题讨论】:

    标签: c asynchronous redis libevent


    【解决方案1】:

    Redis连接打开一次当然好很多,尽量复用。

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

    程序中还有内存泄漏:reqData 结构是为每个请求分配的,并且永远不会被释放。 get_cb 中缺少一个 free。

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

    就个人而言,我更喜欢使用 siege 而不是 ab 来运行这种基准测试。大多数有兴趣对 HTTP 服务器进行基准测试的人认为 ab 是一个幼稚的工具。

    在我的旧 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 连接,自然会受益于流水线。源代码可用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 在流水线化命令方面做得很好(假设连接是永久的),所以影响会很小。

    【讨论】:

    • 无法想象更好、更有价值的答案。非常非常感谢迪迪埃!您还可以告诉我更多有关使用域套接字进行进一步优化的信息吗?或者将我链接到一些资源?
    猜你喜欢
    • 1970-01-01
    • 2015-12-01
    • 2014-06-17
    • 1970-01-01
    • 2013-02-15
    • 2023-03-22
    • 1970-01-01
    • 1970-01-01
    • 2020-06-20
    相关资源
    最近更新 更多