首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >专栏 >字节跳动开源 Shmipc:基于共享内存的高性能 IPC

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

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

作者 | 字节跳动技术团队  

简  介

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 删除。

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

评论
登录后参与评论
暂无评论
推荐阅读
编辑精选文章
换一批
【Linux】详解如何利用共享内存实现进程间通信
共享内存(Shared Memory)是多进程间共享的一部分物理内存。它允许多个进程访问同一块内存空间,从而在不同进程之间共享和传递数据。这种方式常常用于加速进程间的通信,因为数据不需要在不同的进程间进行拷贝。
用户10923276
2024/04/20
2.1K0
【Linux】详解如何利用共享内存实现进程间通信
【Linux】IPC 进程间通信(二)(共享内存)
🚀 共享内存是一种进程间通信(IPC)机制,它允许多个进程直接访问同一块内存区域,从而实现高效的数据交换。
IsLand1314
2024/11/19
4030
【Linux】IPC 进程间通信(二)(共享内存)
【深入学习Linux】System V共享内存
早在设计Unix时,系统开发者就发现了一个悖论:在某些情况下,系统中的进程既要满足互相隔离又要彼此协同合作。
再睡一下就好
2025/06/11
740
【深入学习Linux】System V共享内存
Linux进程同步机制-Futex
引子 在编译2.6内核的时候,你会在编译选项中看到[*] Enable futex support这一项,上网查,有的资料会告诉你"不选这个内核不一定能正确的运行使用glibc的程序",那futex是什么?和glibc又有什么关系呢? 1. 什么是Futex Futex 是Fast Userspace muTexes的缩写,由Hubertus Franke, Matthew Kirkwood, Ingo Molnar and Rusty Russell共同设计完成。几位都是linux领域的专家,其中可能Ingo Molnar大家更熟悉一些,毕竟是O(1)调度器和CFS的实现者。 Futex按英文翻译过来就是快速用户空间互斥体。其设计思想其实 不难理解,在传统的Unix系统中,System V IPC(inter process communication),如 semaphores, msgqueues, sockets还有文件锁机制(flock())等进程间同步机制都是对一个内核对象操作来完成的,这个内核对象对要同步的进程都是可见的,其提供了共享 的状态信息和原子操作。当进程间要同步的时候必须要通过系统调用(如semop())在内核中完成。可是经研究发现,很多同步是无竞争的,即某个进程进入 互斥区,到再从某个互斥区出来这段时间,常常是没有进程也要进这个互斥区或者请求同一同步变量的。但是在这种情况下,这个进程也要陷入内核去看看有没有人 和它竞争,退出的时侯还要陷入内核去看看有没有进程等待在同一同步变量上。这些不必要的系统调用(或者说内核陷入)造成了大量的性能开销。为了解决这个问 题,Futex就应运而生,Futex是一种用户态和内核态混合的同步机制。首先,同步的进程间通过mmap共享一段内存,futex变量就位于这段共享 的内存中且操作是原子的,当进程尝试进入互斥区或者退出互斥区的时候,先去查看共享内存中的futex变量,如果没有竞争发生,则只修改futex,而不 用再执行系统调用了。当通过访问futex变量告诉进程有竞争发生,则还是得执行系统调用去完成相应的处理(wait 或者 wake up)。简单的说,futex就是通过在用户态的检查,(motivation)如果了解到没有竞争就不用陷入内核了,大大提高了low-contention时候的效率。 Linux从2.5.7开始支持Futex。 2. Futex系统调用 Futex是一种用户态和内核态混合机制,所以需要两个部分合作完成,linux上提供了sys_futex系统调用,对进程竞争情况下的同步处理提供支持。 其原型和系统调用号为     #include <linux/futex.h>     #include <sys/time.h> int futex (int *uaddr, int op, int val, const struct timespec *timeout,int *uaddr2, int val3);     #define __NR_futex              240     虽然参数有点长,其实常用的就是前面三个,后面的timeout大家都能理解,其他的也常被ignore。     uaddr就是用户态下共享内存的地址,里面存放的是一个对齐的整型计数器。     op存放着操作类型。定义的有5中,这里我简单的介绍一下两种,剩下的感兴趣的自己去man futex FUTEX_WAIT: 原子性的检查uaddr中计数器的值是否为val,如果是则让进程休眠,直到FUTEX_WAKE或者超时(time-out)。也就是把进程挂到uaddr相对应的等待队列上去。 FUTEX_WAKE: 最多唤醒val个等待在uaddr上进程。     可见FUTEX_WAIT和FUTEX_WAKE只是用来挂起或者唤醒进程,当然这部分工作也只能在内核态下完成。有些人尝试着直接使用futex系统调 用来实现进程同步,并寄希望获得futex的性能优势,这是有问题的。应该区分futex同步机制和futex系统调用。futex同步机制还包括用户态 下的操作,我们将在下节提到。 3. Futex同步机制 所有的futex同步操作都应该从用户空间开始,首先创建一个futex同步变量,也就是位于共享内存的一个整型计数器。 当 进程尝试持有锁或者要进入互斥区的时候,对futex执行"down"操作,即原子性的给futex同步变量减1。如果同步变量变为0,则没有竞争发生, 进程照常执行。如果同步变量是个负数,则意味着有竞争发生,需要调用futex系统调用的futex_wait操作休眠当前进程。 当进程释放锁或 者要离开互斥区的时候,对futex进行"up"操作,即原子性的给futex同步变量加1。如果同步变量由0变成1,则没有竞争发生,进程照常执
王亚昌
2018/08/03
15.6K0
System V IPC 共享内存详解
​ 这里我们介绍的这种通信方式也就是 system V IPC 在我们后面的使用和日常见到的其实并不多,但是包括其中的共享内存、消息队列、信号量,我们如果了解共享内存其原理的话,能够更好的帮助我们了解之前我们学过的进程地址空间的概念!
利刃大大
2023/04/12
1.1K0
System V IPC 共享内存详解
字节跳动微服务架构下的高性能优化实践
2019 年,字节跳动服务框架组针对大规模微服务架构下遇到的功能和性能痛点,以及吸收历史上旧框架下积累的经验与教训,着手开发了 RPC 框架 Kitex 以及周边一系列相关基础库,并在 2021 年正式在 Github 上开源。
深度学习与Python
2023/09/08
9010
字节跳动微服务架构下的高性能优化实践
C语言共享内存
共享内存是进程间通信中最简单的方式之一。共享内存允许两个或更多进程访问同一块内存,就如同 malloc() 函数向不同进程返回了指向同一个物理内存区域的指针。当一个进程改变了这块地址中的内容的时候,其它进程都会察觉到这个更改。
lpe234
2020/07/27
4.4K0
从IPC到分布式软总线的随笔
在Linux 系统中, 客观来说,缺乏相对开发者比较友好的进程间通信框架。谈到Linux上进程间通信,一般都会想起管道(匿名、有名)、信号/信号灯、共享内存、消息队列和socket。这些都是偏低层的技术,有没有方便开发者使用的技术或者框架呢?软件总线以及分布式软总线或许是一种不错的候选。
半吊子全栈工匠
2023/02/24
1.6K0
从IPC到分布式软总线的随笔
UNIX共享内存总结
    共享内存可以说是最有用的进程间通信方式,也是最快的IPC形式。两个不同进程A、B共享内存的意思是,同一块物理内存被映射到进程A、B各自的进程地址空间。进程A可以即时看到进程B对共享内存中数据的更新,反之亦然。由于多个进程共享同一块内存区域,必然需要某种同步机制,互斥锁和信号量都可以。采用共享内存通信的一个显而易见的好处是效率高,因为进程可以直接读写内存,而不需要任何数据的拷贝。因此,采用共享内存的通信方式效率是非常高的。
王亚昌
2018/08/03
2.5K0
一种C程序使用IPC多进程共享内存并实现热迁移的方法
这篇文章讨论如何使用CRIU迁移使用了共享内存的程序,主要讨论其中的前两种共享内存方法,最终介绍一种支持热迁移的C程序共享内存使用方法。
宋天伦
2020/12/31
1.4K0
OpenHarmony内核源码分析(进程通讯篇) | 九种进程间通讯方式速揽
鸿蒙内核默认支持 64个进程和128个任务,由进程池和任务池统一管理.内核设计尽量不去打扰它们,让各自过好各自的日子, 但大家毕竟在一口锅里吃饭, 不可能不与外界联系, 联系就得有渠道,有规矩.
小帅聊鸿蒙
2025/03/13
1150
OpenHarmony内核源码分析(进程通讯篇) | 九种进程间通讯方式速揽
【Linux】「共享内存揭秘」:高效进程通信的终极指南
探索Python数据结构与算法:解锁编程的无限可能:https://cloud.tencent.com/developer/article/2471556
Yui_
2024/11/27
4980
【Linux】「共享内存揭秘」:高效进程通信的终极指南
进程间通信学习小结(共享内存)
要使用共享内存,应该有如下步骤:1.开辟一块共享内存shmget()2.允许本进程使用共某块共享内运维
Java架构师必看
2021/03/22
9520
System|IPC|Rethinking IPC
IPC,进程间通信,是打破地址空间隔离的必经之路。本文按照个人理解对于IPC进行了一些分类与整理。
朝闻君
2021/11/22
8500
System|IPC|Rethinking IPC
共享内存进阶指南:深入学习mmap和shm*的用法与技巧
文件是存储在磁盘上的,要快速的读写一个大文件,可以通过共享内存的方式(mmap等)。mmap内部是使用的DMA技术,DMA是内存和磁盘之间的传输方式,有自己的指令,不需要CPU的参与。
Lion 莱恩呀
2024/10/07
6370
共享内存进阶指南:深入学习mmap和shm*的用法与技巧
Linux进程间通信(四) - 共享内存
共享内存的优势 采用共享内存通信的一个显而易见的好处是效率高,因为进程可以直接读写内存,而不需要任何数据的拷贝。对于像管道和消息队列等通信方式,则需要在内核和用户空间进行四次的数据拷贝,而共享内存则只拷贝两次数据:一次从输入文件到共享内存区,另一次从共享内存区到输出文件。实际上,进程之间在共享内存时,并不总是读写少量数据后就解除映射,有新的通信时,再重新建立共享内存区域。而是保持共享区域,直到通信完毕为止,这样,数据内容一直保存在共享内存中,并没有写回文件。共享内存中的内容往往是在解除映射时才写回文件的。因
三丰SanFeng
2018/01/16
7.3K0
Linux进程间通信(四) - 共享内存
【Linux】进程间通信——共享内存
共享内存(Shared Memory)是一种 进程间通信(IPC) 机制,允许多个进程共享同一块物理内存,从而提高数据交换效率。相比其他 IPC 方式(如管道、消息队列等),共享内存具有 速度快、低开销 的优势,因为数据直接存储在内存中,而无需通过内核进行数据拷贝。
用户11305458
2025/02/28
5380
【Linux】进程间通信——共享内存
Linux笔记(16)| 进程同步机制——管道和IPC
今天要分享的是Linux进程的同步机制,包括管道和IPC。之前学习的信号也有控制进程同步的作用,但是信号仅仅传输很少的信息,而且系统开销大,所以这里再介绍几种其他的进程同步机制。在之前的一篇文章中有提到相关内容,但是当时没有详细展开,可以回顾一下:Linux笔记(10)| 进程概述。
飞哥
2020/11/25
2.2K0
Linux笔记(16)| 进程同步机制——管道和IPC
进程通信之共享内存「建议收藏」
共享内存 共享内存就是同意两个不相关的进程訪问同一个逻辑内存。共享内存是在两个正在执行的进程之间共享和传递数据的一种很有效的方式。不同进程之间共享的内存通常安排为同一段物理内存。
全栈程序员站长
2022/07/08
6840
Linux 进程间通信 : 共享内存(上)
邹立巍
2017/07/26
11.7K3
相关推荐
【Linux】详解如何利用共享内存实现进程间通信
更多 >
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档