co-uring-http: 基于 C++ 无栈协程与 io_uring 的高性能 HTTP 服务器

2023-05-02 08:44:58 +08:00
 xAsiimov

前言

GitHub: xiaoyang-sde/co-uring-http

前段时间我在实现 rust-kernel-riscv (使用 Rust 无栈协程进行上下文切换的操作系统内核) 时, 跟进了一些 Linux Kernel 的特性, 其中印象最深的就是 io_uring. io_uring 作为最新的高性能异步 I/O 框架, 支持普通文件与网络套接字的异步读写, 解决了传统 AIO 的许多问题. 在 Linux 通过隔离内核页表来应对 Meltdown 攻击后, 系统调用的开销是不可忽略的, 而 io_uring 通过映射一段在用户态与内核态共享的内存区域, 显著减少系统调用的次数, 缓解了刷新缓存开销. 关于 io_uring 的使用方法可以参考迟先生的博客: io_uring 的接口与实现.

C++ 20 引入的无栈协程让编写异步程序容易了不少, 之前通过回调函数实现的功能可以全部通过类似同步代码的写法来实现. 协程的性能很优秀, 创建的开销几乎可以忽略不记, 但是当前的标准只提供了基础功能, 还并没有实现易于使用的协程高级库, 导致我尝试自己封装了一套协程原语, 例如 task<T>sync_wait<task<T>>.

为了体验这些特性, 我用 C++ 20 协程与 io_uring 重新实现了一个烂大街项目: HTTP 服务器. 鉴于以前没用过 C++ 写项目, 再加上 GitHub 常见的 HTTP 服务器项目是基于 Reactor 模式与 epoll 实现的, 以至于我在开发的过程中能借鉴 (指复制) 的机会并不多, 希望各位包容一下我的逆天代码. 我会持续维护这个项目, 争取添加更多特性并进一步优化性能.

主要特性

编译环境

.devcontainer/Dockerfile 提供了基于 ubuntu:lunar 的容器镜像, 已经配置好了编译环境, 可以直接在 Linux 或者 WSL 上使用. WSL 用户可以参考 Update WSL Kernel 的步骤将 Linux Kernel 升级到 6.3, 但是 Docker Desktop on Mac 用户似乎没办法升级.

cmake -DCMAKE\_BUILD\_TYPE=Release -DCMAKE\_C\_COMPILER:FILEPATH=/usr/bin/clang -DCMAKE\_CXX\_COMPILER:FILEPATH=/usr/bin/clang++ -B build -G "Unix Makefiles"
make -C build -j$(nproc)
./build/co\_uring\_http

性能测试

为了测试 co-uring-http 在高并发情况的性能, 我用 hey 这个工具向它建立 1 万个客户端连接, 总共发送 100 万个 HTTP 请求, 每次请求大小为 1 KB 的文件. co-uring-http 每秒可以 88160 的请求, 并且在 0.5 秒内处理了 99% 的请求.

测试环境是 WSL (Ubuntu 22.04 LTS, Kernel 版本 6.3.0-microsoft-standard-WSL2), i5-12400 (6 核 12 线程), 16 GB 内存, PM9A1 固态硬盘.

./hey -n 1000000 -c 10000 <http://127.0.0.1:8080/1k>

Summary:
  Total:        11.3429 secs
  Slowest:      1.2630 secs
  Fastest:      0.0000 secs
  Average:      0.0976 secs
  Requests/sec: 88160.9738

  Total data:   1024000000 bytes
  Size/request: 1024 bytes

Response time histogram:
  0.000 [1]     |
  0.126 [701093]        |■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■
  0.253 [259407]        |■■■■■■■■■■■■■■■
  0.379 [24843] |■
  0.505 [4652]  |
  0.632 [678]   |
  0.758 [1933]  |
  0.884 [1715]  |
  1.010 [489]   |
  1.137 [5111]  |
  1.263 [78]    |

设计文档

组件简介

// `thread_worker::handle_client()` 协程的简化版代码逻辑
// 省略了许多用于处理 `http_request` 并构造 `http_response` 的代码
// 实现细节请参考源代码
auto thread_worker::handle_client(client_socket client_socket) -> task<> {
  http_parser http_parser;
  buffer_ring &buffer_ring = buffer_ring::get_instance();
  while (true) {
    const auto [recv_buffer_id, recv_buffer_size] = co_await client_socket.recv(BUFFER_SIZE);
    const std::span<char> recv_buffer = buffer_ring.borrow_buffer(recv_buffer_id, recv_buffer_size);
    // ...
    if (const auto parse_result = http_parser.parse_packet(recv_buffer); parse_result.has_value()) {
      const http_request &http_request = parse_result.value();
      // ...
      std::string send_buffer = http_response.serialize();
      co_await client_socket.send(send_buffer, send_buffer.size());
    }

    buffer_ring.return_buffer(recv_buffer_id);
  }
}

工作流程

参考文献

2020 次点击
所在节点    分享创造
5 条回复
Nazz
2023-05-02 09:42:42 +08:00
很犀利
artnowben
2023-05-02 10:17:04 +08:00
我看到一些评论说, io_uring 对大吞吐有比较好的效果,对 latency 敏感的业务肯能帮助不大,不知道楼主是否测试过 latency ?
J1sen
2023-05-04 02:43:02 +08:00
最近也想做这个协程+io_uring 的, 搜 github 的时候也看到这个项目
beetlerx
2023-05-04 12:01:32 +08:00
Kernel 6.3 要求有点高啊, 好多新系统也才 5.几
xAsiimov
2023-05-05 15:16:40 +08:00
@beetlerx 这是个以实验 kernel 新功能为主的项目, 所以选了最新的 kernel 版本

这是一个专为移动设备优化的页面(即为了让你能够在 Google 搜索结果里秒开这个页面),如果你希望参与 V2EX 社区的讨论,你可以继续到 V2EX 上打开本讨论主题的完整版本。

https://www.v2ex.com/t/936812

V2EX 是创意工作者们的社区,是一个分享自己正在做的有趣事物、交流想法,可以遇见新朋友甚至新机会的地方。

V2EX is a community of developers, designers and creative people.

© 2021 V2EX