Seastar:多核机器上编写高效复杂的服务器应用程序的 C++ 库

目录

官网

联网

为什么选择网络替代品?

无共享设计

核心数量增长,时钟速度保持恒定

同时,I / O继续提高速度

海星模式:无共享

核心之间的明确通信

讯息传递

从程序员的角度

简化测试和故障排除

简介

Seastar

性能

性能

Seastar 应用


官网

http://seastar.io/

Seastar是一个先进的开源C ++框架,用于现代硬件上的高性能服务器应用程序。Seastar用于Scylla(与Apache Cassandra兼容的高性能NoSQL数据库)。使用Seastar的应用程序可以在Linux 或OSv上运行。


联网

Seastar在两个平台上支持四种不同的联网模式,所有这些都无需更改应用程序代码。可以将同一应用程序构建为专用服务器设备或基于unikernel的VM。

  • Linux上的DPDK网络在Linux主机上运行的Seastar应用程序可以直接访问物理网络设备。当以访客身份运行,通过设备分配或在裸机上运行时,Seastar应用程序可以使用DPDK。此模式提供了低延迟,高吞吐量的网络。通信不需要系统调用,也不会进行数据复制。这是获得最佳性能的首选。
  • Linux标准套接字API:为了简化应用程序开发,可以将Seastar应用程序构建为使用普通的Linux网络。
  • Linux上的Seastar本机堆栈虚拟主机:将Linux virtio-net设备专用于Seastar应用程序,并绕过Linux网络堆栈。这主要用于开发Seastar TCP / IP堆栈本身。
  • OSv上的Virtio设备:在OSv平台而不是Linux上运行的本机堆栈网络:OSv将虚拟设备分配给Seastar应用程序。

为什么选择网络替代品?

Linux中可用的常规网络功能功能齐全,成熟且性能卓越。但是,对于真正的网络密集型应用程序,Linux堆栈受到限制:

  • 将网络堆栈的内核空间实现分隔为内核空间,意味着需要昂贵的上下文切换来执行网络操作,并且必须执行数据复制才能将数据从内核缓冲区传输到用户缓冲区,反之亦然。
  • 分时 Linux是一个分时系统,因此必须依靠缓慢而昂贵的中断来通知内核有新的数据包要处理。
  • 线程模型 Linux内核具有大量线程,因此所有数据结构均受锁保护。尽管付出了巨大的努力,Linux才具有很好的可扩展性,但这并非没有局限性,争用发生在大量内核上。即使没有争用,锁定原语本身也相对较慢并且会影响网络性能。

通过使用使用Seastar基本原语实现的用户空间TCP / IP堆栈,可以避免这些约束。Seastar本机网络享有零复制,零锁定和零上下文切换性能。

另一种用户空间网络工具包DPDK专为快速数据包处理而设计,通常每个数据包少于80个CPU周期。它与Linux无缝集成,以利用高性能硬件。

Seastar专为面向未来的开发而设计:您可以构建和运行相同的应用程序,以在部署时最有效的联网模式下运行,而不必事先做出经济上无法预测的技术选择。


无共享设计

http://seastar.io/shared-nothing/

现代工作负载必须在其上运行的硬件与当前编程范例所依赖的硬件以及当前软件基础结构所设计的硬件明显不同。

核心数量增长,时钟速度保持恒定

各个内核时钟速度的性能提升已停止。内核数量的增加意味着性能取决于多个内核之间的协调,而不再取决于单个内核的吞吐量。

在新硬件上,标准工作负载的性能更多地取决于跨内核的锁定和协调,而不是单个内核的性能。软件架构师面临两种不具吸引力的选择:粗粒度锁定(将看到应用程序线程争夺数据控制权,然后等待而不是产生有用的工作)和细粒度锁定(除了难以编程和调试之外)由于锁定原语本身,即使没有争用发生,开销也很大。

同时,I / O继续提高速度

现代系统上可用的网络和存储设备的速度也在持续增长。但是,CPU内核无法在任何一个内核上处理数据包。

在10GBps网络上以线速处理1024字节数据包的2GHz处理器,每个数据包只有1670个时钟周期。(来源:英特尔DPDK概述

海星模式:无共享

由于跨内核共享信息需要昂贵的锁定,因此Seastar使用无共享模型将所有请求分片到各个内核。

Seastar每个内核运行一个应用程序线程,并且取决于显式消息传递,而不是线程之间的共享内存。这种设计避免了缓慢的,不可扩展的锁原语和缓存反弹。

跨内核的任何资源共享都必须明确处理。例如,当两个请求是同一会话的一部分,并且两个CPU各自获得依赖于同一会话状态的请求时,一个CPU必须将请求显式转发给另一个。任何一个CPU都可以处理任何一个响应。Seastar提供的功能会限制跨核通信的需求,但是当不可避免的通信时,它会提供高性能的非阻塞通信原语以确保性能不会下降。

核心之间的明确通信

Seastar提供了几个相关功能,用于内核之间的通信。最简单的是:

smp::submit_to(cpu, lambda)

这是一个承诺。它返回一个Future,即lambda的返回值。它在指定的cpu上运行lambda并返回结果。例如:

smp::submit_to(neighbor, [key] {
	return local_database[key];
}).then([key, neighbor] (sstring value) {
	print("The value of key %s on shard %d is %s\n", key, neighbor, value);
});

线程环境中的等效方法需要锁定数据库对象。锁定操作本质上是昂贵的,并且根据所使用的锁定方案,还可能迫使上下文切换或浪费CPU周期进行旋转。

跨核通信的其他变体允许将值广播到所有CPU,或允许将lambda发送到所有CPU,收集结果并应用转换以减小为单个值的映射/归约操作。


讯息传递

http://seastar.io/message-passing/

线程化应用程序固有地需要昂贵的锁定操作,而Seastar模型可以完全避免跨CPU通信的锁定

从程序员的角度来看,Seastar使用期货,承诺和延续(f / p / c)。使用epoll和用户空间库(如libevent)的传统事件驱动编程很难编写复杂的应用程序,而使用f / p / c可以更轻松地编写复杂的异步代码。

例如,发送方核心C0和接收方核心C1之间的以下交互可以发生,而无需锁定。* C0:发送者->等待队列输入(通常是立即进入)->排队请求,分配承诺。* C1:出队请求;执行->将结果移动到请求对象->将请求加入响应队列* C0:出队请求;提取响应,用它来实现承诺;销毁请求。

每个实际队列,一个用于请求,一个返回队列,用于满足请求,是一个简单的指针队列。

系统上每对CPU内核对有一个请求队列和一个返回队列。因为一个核心不与自身配对,所以一个16核心的系统将具有240个请求队列和240个返回队列。

从程序员的角度

Seastar提供了一组通用的编程结构来管理内核之间的通信。例如:

[1]  return conn->read_exactly(4).then(temporary_buffer<char> buf) {
[2]      int id = buf_to_id(buf);
[3]      return smp::submit_to(other_core, [id] {
[4]           return lookup(id);
[5]      });
[6]  }).then([this] (sstring result) {
[7]      return conn->write(result);
[8]  });

第1行要求TCP告知我们4个字节何时可用,并将它们放在缓冲区中。它将尝试为我们提供一个直接指向包含数据的数据包区域的指针,但是如果不可能的话,它将分配一个缓冲区(例如,因为4个字节到达了不同的数据包)。

第2行在我们的四个字节到达时执行,并对请求进行解码(在这里,转换为项目ID)。

第3行要求other_core代表我们做某事,并在完成时通知我们。

第4行在另一个内核上执行。它调用查找函数来查询本地数据存储,并返回结果。

第5行让other_core的响应返回给我们。

第6行(在第3行之后立即执行)告诉第3行,当我们得到响应时该怎么做。

第7行收到来自other_core的响应,并将其写回到连接中。

在幕后,发生了很多事情。甲.then()功能可以决定立即执行其块(对于read_exactly(),如果数据恰好是可用; write()如果TCP发送缓冲器未满)。它可能决定推迟执行(如果数据不可用),在这种情况下,它会分配一小块内存,并将块的捕获存储在其中(这些捕获出现在方括号内,例如[id]第3行),并将其附加到任何内容这是我们在等待的诺言。

submit_to()做了类似但更专业的事情:它将块移动到新分配的内存区域,等待smp队列不太忙,将请求排队在核心到核心队列中,然后返回。最终,调度程序将轮询响应队列,在返回的路径中拾取相同的对象,并将结果移入Promise,从而触发继续。

简化测试和故障排除

并行编程是一种罕见的开发人员技能,随着CPU数量和工作负载并行化的增加,对并行编程的需求也越来越高。尽管必须有效利用CPU时间,但对于大多数项目而言,有效利用程序员时间就显得尤为重要。Seastar是实现世界一流性能和可理解,可测试的开发人员经验的唯一框架。


简介

https://www.jianshu.com/p/b4eb84c8dbc5

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

  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库,为用户提供了复杂的编译时功能和对性能的完全控制,而无需运行时间开销。

Seastar

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

  • 合作型微任务调度器:每个核心都运行一个协作式任务调度器,而不是运行线程。每个任务通常都非常轻量级 - 只需要处理最后一次I / O操作的结果并提交新结果即可运行。
  • 无共享SMP体系结构:每个内核独立于SMP系统中的其他内核运行。内存,数据结构和CPU时间不共享; 相反,核心间通信使用明确的消息传递。Seastar核心通常被称为碎片。TODO:更多https://github.com/scylladb/seastar/wiki/SMP
  • 基于未来的API:期货允许您提交I / O操作并链接完成I / O操作时执行的任务。并行运行多个I / O操作非常简单 - 例如,响应来自TCP连接的请求,您可以发出多个磁盘I / O请求,将消息发送到同一系统上的其他内核,或发送请求到集群中的其他节点,等待部分或全部结果完成,汇总结果并发送响应。
  • 无共享TCP堆栈:尽管Seastar可以使用主机操作系统的TCP堆栈,但它还提供了自己的高性能TCP / IP堆栈,该堆栈构建在任务调度器和无共享架构之上。堆栈在两个方向上提供零拷贝:您可以直接从TCP堆栈的缓冲区处理数据,并将您自己的数据结构的内容作为消息的一部分发送,而不会发生副本。阅读更多...
  • 基于DMA的存储API:与网络堆栈一样,Seastar提供零拷贝存储API,允许您将数据存储在存储设备中或从存储设备中存取数据。

本教程面向已经熟悉C ++语言的开发人员,并将介绍如何使用Seastar创建新应用程序。

欢迎访问我的 个人博客 程序员的冷浪漫
作者:ColdRomantic
链接:https://www.jianshu.com/p/b4eb84c8dbc5
来源:简书
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。


性能

https://www.jianshu.com/p/b4eb84c8dbc5

Seastar 是一个高级,开源的 C++ 框架,支持现代化硬件的高性能服务器应用。Seastar 支持 Linux 和 OSv。

性能

上面的示例都是在 Linux 下运行,Memcached 版本是 1.4.17。

Seastar 是第一个集成了大量的架构创新的框架:

Seastar 应用

Seastar 当前专注于高吞吐量,低延迟 I/O 应用:

 

已标记关键词 清除标记
©️2020 CSDN 皮肤主题: 酷酷鲨 设计师:CSDN官方博客 返回首页