Loading [MathJax]/jax/output/CommonHTML/config.js
首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >专栏 >字节跳动开源 Shmipc:基于共享内存的高性能 IPC

字节跳动开源 Shmipc:基于共享内存的高性能 IPC

作者头像
深度学习与Python
发布于 2023-04-10 11:11:09
发布于 2023-04-10 11:11:09
2.6K0
举报

作者 | 字节跳动技术团队  

简  介

CloudWeGo - Shmipc 是字节跳动服务框架团队研发的高性能进程间通讯库,它基于共享内存构建,具有零拷贝的特点,同时它引入的同步机制具有批量收割 IO 的能力,相对于其他进程间通讯方式能明显提升性能。在字节内部,Shmipc 应用于 Service Mesh 场景下,mesh proxy 进程与业务逻辑进程、与通用 sidecar 进程的通讯, 在大包场景和 IO 密集型场景能够取得显著的性能收益。

开源社区关于这方面的资料不多,Shmipc 的开源希望能为社区贡献一份力量,提供一份参考。本文主要介绍 Shmipc 的一些主要的设计思路以及后续的演进规划。

go 版本实现: https://github.com/cloudwego/shmipc-go 设计细节: https://github.com/cloudwego/shmipc-spec

项目背景

在字节,Service Mesh 在落地的过程中进行了大量的性能优化工作,其中 Service Mesh 的流量劫持是通过,mesh proxy 与微服务框架约定的地址进行进程间通讯来完成,性能会优于开源方案中的 iptables。但常规的优化手段已不能带来明显的性能提升。于是我们把目光放到了进程间通讯上,Shmipc 由此诞生。

设计思路

零拷贝

在生产环境中比较广泛使用的进程间通讯方式是 unix domain socket 与 TCP loopback(localhost:$PORT),两者从 benchmark 看性能差异不大。从技术细节看,都需要将通讯的数据在用户态和内核态之间进行拷贝。在 RPC 场景下,一次 RPC 流程中在进程间通讯上会有四次的内存拷贝,Request 路径两次, Response 路径两次。

虽然现代 CPU 上进行顺序的 copy 非常快,但如果我们能够消除这多达四次的内存拷贝,在大包场景下也能在一定程度上节省 CPU 使用。而基于共享内存通讯零拷贝的特性,我们可以很容易达成这一点。但为了达到零拷贝的效果,围绕共享内存本身,还会产生有许多额外的工作,比如:

  1. 深入微服务框架的序列化与反序列化。我们希望当 Request 或 Response 序列化完成时,对应的二进制数据已经存在共享内存中。而不是序列化到一块非共享内存的 buffer 中,然后再拷贝到共享内存 buffer。
  2. 实现一种进程同步机制。当一个进程把数据写入共享内存后,另外一个进程并不知道,因此需要同步机制进行通知。
  3. 高效的内存分配和回收。保证跨进程的共享内存的分配和回收机制的开销足够低,避免其掩盖零拷贝的特性带来的收益。

同步机制

分场景考虑:

  1. 按需实时同步。适用于在线场景,对时延极其敏感,每次写入操作完成后都通知对端进程。Linux 下,可做选择的比较多,TCP loopback、unix domain socket、event fd 等。event fd 的 benchmark 性能会略好,但跨进程传递 fd 会引入过多复杂性,其带来的性能提升在 IPC 上不太明显,复杂性与性能中间的权衡需要慎重考虑。在字节,我们选择了 unix domain socket 来进行进程同步。
  2. 定时同步。适用于离线场景,对时延不敏感。通过高间隔的 sleep 访问共享内存中自定义的标志位来鉴别是否有数据写入。但注意 sleep 本身也需要系统调用,开销大于 unix domain socket 的读写。
  3. 轮询同步。适用于时延非常敏感,CPU 不那么敏感的场景。可以通过单核轮询共享内存中的自定义标志位来完成。

总的来说按需实时同步和定期同步需要系统调用来完成,轮询同步不需要系统调用,但需要常态跑满一个 CPU 核心。

批量收割 IO

在线场景中按需实时同步,每次数据写入都需要进行一次进行进程同步(下图中的 4),虽然延迟问题解决了,但在性能上,需要交互的数据包需要大于一个比较大的阈值,零拷贝带来的收益才能突显。因此在共享内存中构造了一个 IO 队列的来完成批量收割 IO,使其在小包 IO 密集场景也能显现收益。核心思想是:当一个进程把请求写入 IO 队列后,会给另外一个进程发通知来处理。那么在下一个请求进来时(对应下图中的 IOEvent 2~N,一个 IOEvent 可以独立描述一个请求在共享内存中的位置),如果对端进程还在处理 IO 队列中的请求,那么就不必进行通知。因此,IO 越密集,批处理效果就越好。

另外就是离线场景中,定时同步本身就是批量处理 IO 的,批处理的效果能够有效减少进程同步带来的系统调用,sleep 间隔越高,进程同步的开销就越低。

对于轮询同步则不需要考虑批量收割 IO,因为这个机制本身是为了减少进程同步开销。而轮询同步直接占满一个 CPU 核心,相当于默认把同步机制的开销拉满以获取极低的同步延迟。

性能收益

Benchmark

其中 X 轴为数据包大小,Y 轴为一次 Ping-Pong 的耗时,单位为微秒,越小越好。可以看到在小包场景下,Shmipc 相对于 unix domain socket 也能获得一些收益,并且随着包大小越大性能越好。

数据源:git clone https://github.com/cloudwego/shmipc-go && go test -bench=BenchmarkParallelPingPong -run BenchmarkParallelPingPong

生产环境

在字节生产环境的 Service Mesh 生态中,我们在 3000+ 服务、100w+ 实例上应用了 Shmipc。不同的业务场景显现出不同的收益,其中收益最高的风控 业务降低了整体 24% 的资源使用,当然也有无明显收益的甚至劣化的场景出现。但在大包和 IO 密集型场景均能显现出显著收益

采坑记录

在字节实际落地的过程中我们也踩了一些坑,导致一些线上事故,比较具有参考价值。

  1. 共享内存泄漏。IPC 过程共享内存分配和回收涉及到两个进程,稍有不慎就容易发生共享内存的泄漏。问题虽然非常棘手,但只要能够做到泄漏时主动发现,以及泄漏之后有观测手段可以排查即可。
    1. 主动发现。可以通过增加一些统计信息然后汇总到监控系统来做到主动发现,比如总分配和总回收的内存大小。
    2. 观测手段。在设计共享内存的布局时增加一些元信息,使得在发生泄漏之后,我们可以通过内置的 debug 工具 dump 泄漏时刻的共享内存来进行分析。能够知道所泄漏的内存有多少,里面的内容是什么,以及和这部分内容相关的一些元信息。
  2. 串包。串包是最头疼的问题,出现的原因是千奇百怪的,往往造成严重后果。我们曾在某业务上发生串包事故,出现的原因是因为大包导致共享内存耗尽,fallback 到常规路径的过程中设计存在缺陷,小概率出现串包。排查过程和原因并不具备共性,可以提供更多的参考是增加更多场景的集成测试单元测试将串包扼杀在摇篮中。
  3. 共享内存踩踏。应该尽可能使用 memfd 来共享内存,而不是 mmap 文件系统中的某个路径。早期我们通过 mmap 文件系统的路径来共享内存,Shmipc 的开启和共享内存的路径由环境变量指定,启动过程由引导进程注入应用进程。那么存在一种情况是应用进程可能会 fork 出一个进程,该进程继承了应用进程的环境变量并且也集成了 Shmipc,然后 fork 的进程和应用进程 mmap 了同一块共享内存,发现踩踏。在字节的事故场景是应用进程使用了 golang 的 plugin 机制从外部加载 .so 来运行,该 .so 集成了 Shmipc,并且跑在应用进程里,能看到所有环境变量,于是就和应用进程 mmap 了同一片共享内存,运行过程发生未定义行为。
  4. Sigbus coredump。早期我们通过 mmap /dev/shm/ 路径(tmpfs)下的文件来共享内存,应用服务大都运行在 docker 容器实例中。容器实例对 tmpfs 有容量限制(可以通过 df -h 观测),这会使得 mmap 的共享内存如果超过该限制就会出现 Sigbus,并且 mmap 本身不会有任何报错,但在运行期,使用到超过限制的地址空间时才会出现 Sigbus 导致应用进程崩溃。解决方式和第三点一样,使用 memfd 来共享内存。

后续演进

  1. 整合至微服务 RPC 框架 CloudWeGo/Kitex。
  2. 整合至微服务 HTTP 框架 CloudWeGo/Hertz。
  3. 开源 Rust 版本的 Shmipc 并整合至 Rust RPC 框架 CloudWeGo/Volo。
  4. 开源 C++ 版本的 Shmipc。
  5. 引入定时同步机制适用于离线场景。
  6. 引入轮询同步的同步机制适用于对延迟有极致要求的场景。
  7. 赋能其他 IPC 场景, 比如 Log SDK 与 Log Agent, Metrics SDK 与 Metrics Agent 等。

总  结

希望本文能让大家对于 Shmipc 有一个初步的了解,知晓其设计原理,更多实现细节以及使用方法请参考文章开头给出的项目地址。欢迎各位感兴趣的同学向 Shmipc 项目提交 Issue 和 PR,共同建设 CloudWeGo 开源社区,也期望 Shmipc 在 IPC 领域助力越来越多开发者和企业构建高性能云原生架构。

今日好文推荐

警方通报网传中电科加班事件调查结果;拼多多解散恶意功能团队;逼死程序员诈骗千万的“翟欣欣案”一审宣判 | Q资讯

谷歌正式发布WebGPU!90多位贡献者研发6年,浏览器终于可以利用底层硬件了

新手用ChatGPT仅需数小时轻松构建零日漏洞,69家专业公司都检测不出来:“不仅能调用开源库,还能彻底重写源代码”

揭秘 ChatGPT 背后的技术栈:OpenAI 如何将 Kubernetes 扩展到了 7500 个节点

本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2023-04-09,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 InfoQ 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
暂无评论
推荐阅读
编辑精选文章
换一批
UNIX共享内存总结
    共享内存可以说是最有用的进程间通信方式,也是最快的IPC形式。两个不同进程A、B共享内存的意思是,同一块物理内存被映射到进程A、B各自的进程地址空间。进程A可以即时看到进程B对共享内存中数据的更新,反之亦然。由于多个进程共享同一块内存区域,必然需要某种同步机制,互斥锁和信号量都可以。采用共享内存通信的一个显而易见的好处是效率高,因为进程可以直接读写内存,而不需要任何数据的拷贝。因此,采用共享内存的通信方式效率是非常高的。
王亚昌
2018/08/03
2.5K0
共享内存进阶指南:深入学习mmap和shm*的用法与技巧
文件是存储在磁盘上的,要快速的读写一个大文件,可以通过共享内存的方式(mmap等)。mmap内部是使用的DMA技术,DMA是内存和磁盘之间的传输方式,有自己的指令,不需要CPU的参与。
Lion 莱恩呀
2024/10/07
7190
共享内存进阶指南:深入学习mmap和shm*的用法与技巧
字节跳动微服务架构下的高性能优化实践
2019 年,字节跳动服务框架组针对大规模微服务架构下遇到的功能和性能痛点,以及吸收历史上旧框架下积累的经验与教训,着手开发了 RPC 框架 Kitex 以及周边一系列相关基础库,并在 2021 年正式在 Github 上开源。
深度学习与Python
2023/09/08
9770
字节跳动微服务架构下的高性能优化实践
【Linux】IPC 进程间通信(二)(共享内存)
🚀 共享内存是一种进程间通信(IPC)机制,它允许多个进程直接访问同一块内存区域,从而实现高效的数据交换。
IsLand1314
2024/11/19
5080
【Linux】IPC 进程间通信(二)(共享内存)
【Linux】进程间通信——共享内存
共享内存(Shared Memory)是一种 进程间通信(IPC) 机制,允许多个进程共享同一块物理内存,从而提高数据交换效率。相比其他 IPC 方式(如管道、消息队列等),共享内存具有 速度快、低开销 的优势,因为数据直接存储在内存中,而无需通过内核进行数据拷贝。
用户11305458
2025/02/28
8430
【Linux】进程间通信——共享内存
【深入学习Linux】System V共享内存
早在设计Unix时,系统开发者就发现了一个悖论:在某些情况下,系统中的进程既要满足互相隔离又要彼此协同合作。
再睡一下就好
2025/06/11
1000
【深入学习Linux】System V共享内存
UNIX(进程间通信):11 共享内存到底是什么
共享内存是System V版本的最后一个进程间通信方式。共享内存,顾名思义就是允许两个不相关的进程访问同一个逻辑内存,共享内存是两个正在运行的进程之间共享和传递数据的一种非常有效的方式。不同进程之间共享的内存通常为同一段物理内存。进程可以将同一段物理内存连接到他们自己的地址空间中,所有的进程都可以访问共享内存中的地址。如果某个进程向共享内存写入数据,所做的改动将立即影响到可以访问同一段共享内存的任何其他进程。
用户3479834
2021/03/04
1.8K0
UNIX(进程间通信):11 共享内存到底是什么
进程间通信学习小结(共享内存)
要使用共享内存,应该有如下步骤:1.开辟一块共享内存shmget()2.允许本进程使用共某块共享内运维
Java架构师必看
2021/03/22
9620
POSIX共享内存
几种进程间的通信方式:管道,FIFO,消息队列,他们的共同特点就是通过内核来进行通信(假设POSIX消息队列也是在内核中实现的,因为POSIX标准并没有限定它的实现方式)。向管道,FIFO,消息队列写入数据需要把数据从进程复制到内核,从这些IPC读取数据的时候又需要把数据从内核复制到进程。所以这种IPC方式往往需要2次在进程和内核之间进行数据的复制,即进程间的通信必须借助内核来传递。如下图所示:
WindSun
2019/08/31
3.1K1
POSIX共享内存
共享内存同步机制_共享内存通信机制
共享内存是System V版本的最后一个进程间通信方式。共享内存,顾名思义就是允许两个不相关的进程访问同一个逻辑内存,共享内存是两个正在运行的进程之间共享和传递数据的一种非常有效的方式。不同进程之间共享的内存通常为同一段物理内存。进程可以将同一段物理内存连接到他们自己的地址空间中,所有的进程都可以访问共享内存中的地址。如果某个进程向共享内存写入数据,所做的改动将立即影响到可以访问同一段共享内存的任何其他进程。
全栈程序员站长
2022/11/01
2.2K0
共享内存同步机制_共享内存通信机制
进程通信之共享内存「建议收藏」
共享内存 共享内存就是同意两个不相关的进程訪问同一个逻辑内存。共享内存是在两个正在执行的进程之间共享和传递数据的一种很有效的方式。不同进程之间共享的内存通常安排为同一段物理内存。
全栈程序员站长
2022/07/08
6920
Linux 进程间通信 : 共享内存(上)
邹立巍
2017/07/26
11.7K3
System|IPC|Rethinking IPC
IPC,进程间通信,是打破地址空间隔离的必经之路。本文按照个人理解对于IPC进行了一些分类与整理。
朝闻君
2021/11/22
8680
System|IPC|Rethinking IPC
System V IPC 共享内存详解
​ 这里我们介绍的这种通信方式也就是 system V IPC 在我们后面的使用和日常见到的其实并不多,但是包括其中的共享内存、消息队列、信号量,我们如果了解共享内存其原理的话,能够更好的帮助我们了解之前我们学过的进程地址空间的概念!
利刃大大
2023/04/12
1.1K0
System V IPC 共享内存详解
C语言共享内存
共享内存是进程间通信中最简单的方式之一。共享内存允许两个或更多进程访问同一块内存,就如同 malloc() 函数向不同进程返回了指向同一个物理内存区域的指针。当一个进程改变了这块地址中的内容的时候,其它进程都会察觉到这个更改。
lpe234
2020/07/27
4.5K0
[操作系统] 进程间通信:system V共享内存
System V 是UNIX操作系统的一个版本,它定义了一系列的标准,包括进程间通信(IPC)的标准。Linux操作系统的内核实现了 <font style="color:rgb(6, 6, 7);">System V</font> 定义的IPC标准,并为此专门设计了一个模块来处理进程间的通信。进程间通信(IPC)的核心目的是允许不同的进程能够访问和操作同一份资源。这样,进程之间就可以通过共享资源来交换信息。不同的IPC机制可能在接口和原理上有相似之处,使得开发者可以更容易地理解和使用这些机制。
DevKevin
2025/05/30
1450
[操作系统] 进程间通信:system V共享内存
一种C程序使用IPC多进程共享内存并实现热迁移的方法
这篇文章讨论如何使用CRIU迁移使用了共享内存的程序,主要讨论其中的前两种共享内存方法,最终介绍一种支持热迁移的C程序共享内存使用方法。
宋天伦
2020/12/31
1.4K0
【在Linux世界中追寻伟大的One Piece】System V共享内存
共享内存区是最快的IPC形式。一旦这样的内存映射到共享它的进程的地址空间,这些进程间数据传递不再涉及到内核,换句话说是进程不再通过执行进入内核的系统调用来传递彼此的数据。
枫叶丹
2024/10/03
1650
【在Linux世界中追寻伟大的One Piece】System V共享内存
Linux进程间通信——使用共享内存
顾名思义,共享内存就是允许两个不相关的进程访问同一个逻辑内存。共享内存是在两个正在运行的进程之间共享和传递数据的一种非常有效的方式。不同进程之间共享的内存通常安排为同一段物理内存。进程可以将同一段共享内存连接到它们自己的地址空间中,所有进程都可以访问共享内存中的地址,就好像它们是由用C语言函数malloc分配的内存一样。而如果某个进程向共享内存写入数据,所做的改动将立即影响到可以访问同一段共享内存的任何其他进程。
全栈程序员站长
2022/11/01
4K0
Linux笔记(16)| 进程同步机制——管道和IPC
今天要分享的是Linux进程的同步机制,包括管道和IPC。之前学习的信号也有控制进程同步的作用,但是信号仅仅传输很少的信息,而且系统开销大,所以这里再介绍几种其他的进程同步机制。在之前的一篇文章中有提到相关内容,但是当时没有详细展开,可以回顾一下:Linux笔记(10)| 进程概述。
飞哥
2020/11/25
2.2K0
Linux笔记(16)| 进程同步机制——管道和IPC
相关推荐
UNIX共享内存总结
更多 >
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档