1 what is Seastar

这篇文章将介绍一下 Seastar : 一个在现在多核机器上编写高效复杂的服务器应用程序的 C++ 库。

有些框架非常高效,但只允许构建简单的应用程序(eg: DPDK 允许单独处理数据包的应用程序), 而其他框架则允许构建极其复杂的应用程序,代价是运行时效率。Seastar 是我们尝试获得两全其美的方法:创建一个允许构建高度复杂的服务器应用程序并实现最佳性能的库。

2 起源

Seastar 的灵感和首例使用案例是ScyllaDB,重写了Apache Cassandra,Cassandra 是一个分厂复杂的应用,同时通过 Seastar,我们能够重新实现吞吐量提高10倍,以及显着降低和更一致的延迟。 Seastar提供了一个完整的异步编程框架,它使用两个概念 - 期货和延续 - 统一表示和处理每种类型的异步事件,包括网络I/O,磁盘 I/O 以及其他事件的复杂组合。

由于现代多核和多插槽机器在核心之间共享数据(原子指令,高速缓存行反弹和内存隔离)具有陡峭的惩罚,Seastar程序使用无共享编程模型,即可用内存在内核之间分配,每个内核都在其内存部分进行数据处理,内核之间的通信通过显式消息传递进行(当然,这本身就是使用SMP的共享内存硬件发生的)。

具有同步设计的服务器仍然具有令人不满意的性能,并且随着并发连接数量的增长而缩小。1999年,Dan Kigel普及了 “C10K问题” —— 需要在一台服务器上有效处理 10,000个并发连接 —— 其中大多数是缓慢, 甚至是不活跃。(大四的时候,美团面试官也问过我这个问题

对应的解决方案,在接下来的数十年中很流行,它将放弃舒适但低效的同步服务器设计,并转而采用 新型服务器设计 - 异步或事件驱动,服务器设计。 事件驱动的服务器只有一个线程,或者更准确地说,每个CPU有一个线程。这个单线程运行一个紧密的循环,在每次迭代中检查,使用poll()(或更有效epoll)用于许多打开的文件描述符上的新事件,例如套接字。例如,一个事件可以是一个可读的套接字(新数据已经从远程端到达)或变得可写(我们可以在这个连接上发送更多的数据)。应用程序通过执行一些非阻塞操作来处理此事件,修改一个或多个文件描述符并保持其对此连接状态的了解。 然而,异步服务器应用程序的作者在今天仍面临着两大挑战:

  • 复杂性:编写简单的异步服务器非常简单。但编写复杂的异步服务器是非常困难的。单个连接的处理,而不是一个简单易读的函数调用,现在涉及大量的小型回调函数,以及一个复杂的状态机,用于记忆每个事件发生时需要调用哪个函数。

  • 非阻塞:每个内核只有一个线程对于服务器应用程序的性能很重要,因为上下文切换很慢。但是,如果我们每个内核只有一个线程,则事件处理函数不能阻塞,否则内核将保持空闲状态。但是一些现有的编程语言和框架让服务器作者别无选择,只能使用阻塞函数,因此也不能使用多线程。例如,Cassandra被编写为异步服务器应用程序; 但是因为磁盘 I/O 是用mmaped文件实现的,mmaped文件在访问时可能不受控制地阻塞整个线程,所以它们被迫每个CPU运行多个线程。 而且,当需要最佳性能时,服务器应用程序及其编程框架别无选择,只能考虑以下几点:

  • 现代机器:现代机器与十年前的机器非常不同。它们具有许多内核和深层存储器层次结构(从L1缓存到NUMA),这些层次会奖励某些编程实践并惩罚其他人:不可扩展编程实践(如锁定)可能会破坏许多内核的性能; 共享内存和无锁同步原语可用(即原子操作和内存排序的屏蔽),但比仅涉及单个内核高速缓存中的数据的操作要慢得多,并且还会阻止应用程序扩展到多个内核。

  • 编程语言:诸如Java,Javascript和类似的“现代”语言等高级语言很方便,但每种语言都有自己的一组假设,这些假设与上面列出的要求相冲突。这些旨在便携式的语言也使程序员无法控制关键代码的性能。为了获得最佳性能,我们需要一种编程语言,它为程序员提供完全控制,零运行时开销,另一方面 - 复杂的编译时代码生成和优化。

Seastar是一个用于编写异步服务器应用程序的框架,旨在解决上述所有四个难题:它是编写涉及网络和磁盘I / O的复杂异步应用程序的框架。该框架的快速路径完全是单线程(每核心),可扩展到多个内核,并最大限度地减少了在内核之间使用昂贵的内存共享。它基于 C++14 的一些新特性,为用户提供了复杂的编译时功能和对性能的完全控制,而无需运行时间开销。

3 Features

Seastar是一个事件驱动的框架,允许您以相对直接的方式编写非阻塞的异步代码(一旦理解)。它的API基于future。Seastar利用以下概念实现卓越性能:

  • 合作型微任务调度器:每个核心都运行一个协作式任务调度器,而不是运行线程。每个任务通常都非常轻量级 - 只需要处理最后一次I / O操作的结果并提交新结果即可运行。

  • 无共享SMP体系结构:每个内核独立于SMP系统中的其他内核运行。内存,数据结构和CPU时间不共享; 相反,核心间通信使用明确的消息传递。Seastar核心通常被称为碎片。TODO:更多https://github.com/scylladb/seastar/wiki/SMP

  • 基于 future 的API:期货允许您提交I / O操作并链接完成I / O操作时执行的任务。并行运行多个I / O操作非常简单 - 例如,响应来自TCP连接的请求,您可以发出多个磁盘I / O请求,将消息发送到同一系统上的其他内核,或发送请求到集群中的其他节点,等待部分或全部结果完成,汇总结果并发送响应。

  • 无共享TCP堆栈:尽管Seastar可以使用主机操作系统的TCP堆栈,但它还提供了自己的高性能TCP / IP堆栈,该堆栈构建在任务调度器和无共享架构之上。堆栈在两个方向上提供零拷贝:您可以直接从TCP堆栈的缓冲区处理数据,并将您自己的数据结构的内容作为消息的一部分发送,而不会发生副本。阅读更多…

  • 基于DMA的存储API:与网络堆栈一样,Seastar提供零拷贝存储API,允许您将数据存储在存储设备中或从存储设备中存取数据。

<且听下回分解>