在写 Demo 时,我们通常只处理简单的 Echo 逻辑。但在真实业务中,我们需要查询数据库、调用下游微服务。这些操作往往耗时较长,如果直接在 Event Loop 中同步调用,会阻塞整个服务。
本篇探讨如何在 Libevent 中集成复杂的业务逻辑。
1. 数据库交互
1.1. 方案 A: 原生异步驱动 (推荐)
这是性能最好的方案。许多现代数据库驱动都提供了非阻塞 API,甚至直接支持 Libevent。
Redis:
hiredis库自带了adapters/libevent.h。redisAsyncContext *c = redisAsyncConnect("127.0.0.1", 6379); redisLibeventAttach(c, base); // 绑定到 event_base redisAsyncCommand(c, getCallback, NULL, "GET key");MySQL/MariaDB: MariaDB Connector/C 提供了非阻塞 API (
mysql_real_connect_start等)。PostgreSQL:
libpq支持异步查询 (PQsendQuery)。
1.2. 方案 B: 线程池卸载 (通用)
如果你的数据库驱动只支持阻塞 API(如 ODBC, 老旧的 Oracle 驱动),或者业务逻辑包含大量 CPU 计算,必须使用线程池。
架构流程: 1. 主线程:
收到请求,封装成 Task 对象,推入
RequestQueue。 2. Worker 线程:
从队列取出 Task,执行阻塞的 DB 查询。 3.
通知: Worker 得到结果后,将结果写入
ResponseQueue,并通过 pipe 或
event_active 通知主线程。 4.
主线程: 收到通知,从
ResponseQueue 取出结果,发送给客户端。
Libevent 源码包中的 sample/http-server.c
其实并没有演示这一点,但在生产环境中这是标配。
2. RPC 与微服务集成
在微服务架构中,服务往往需要调用其他 HTTP/gRPC 服务。
2.1. 使用 evhttp 客户端
Libevent 自带的 evhttp 不仅是 Server,也是
Client。
struct evhttp_connection *conn = evhttp_connection_base_new(base, NULL, "127.0.0.1", 8080);
struct evhttp_request *req = evhttp_request_new(response_cb, arg);
evhttp_make_request(conn, req, EVHTTP_REQ_GET, "/api/v1/user");这种方式完全非阻塞,非常适合高并发调用。
2.2. 避免回调地狱 (Callback Hell)
当业务逻辑变成:查询 Redis -> 没命中 -> 查询 MySQL -> 调用 RPC -> 返回,你会发现代码里嵌套了无数层回调。
解决方案: 1. 有限状态机
(FSM): 定义 STATE_INIT,
STATE_REDIS, STATE_DB,
STATE_RPC。所有回调都调用同一个
drive_machine(ctx)
函数,根据当前状态决定下一步操作。 2. C++20 协程
(Coroutines): 如果使用 C++,可以将 Libevent
的回调封装成 awaitable
对象,写出同步风格的异步代码。
// 伪代码:C++20 风格
Task handle_request(Request req) {
auto val = co_await redis.get(req.key);
if (!val) {
val = co_await db.query(req.key);
}
co_await respond(val);
}3. 总结
- 能用异步驱动就用异步驱动。
- 不能用异步驱动(或有 CPU 密集计算)就上线程池。
- 复杂逻辑用状态机或协程来管理,保持代码整洁。
上一篇: 04-architecture/cpp-modern.md - C++ 现代化封装 下一篇: 05-protocols/http-server.md - 内置 HTTP Server