ARTICLE DETAIL

资讯详情

深耕网站视觉设计与运营推广的一线实战洞察。

Linux进程间通信八种方式详解:原理、代码与选型指南

Linux进程间通信八种方式详解:原理、代码与选型指南 打开任务管理器你经常会看到几十个进程在跑浏览器一个标签页一个进程IDE 后台一堆守护进程。但它们不是孤岛要协作就要传数据、发通知、抢资源这就是进程间通信IPC。很多刚开始接触 Linux 多进程开发的工程师容易被“8种IPC”这种说法唬住觉得是八套独立且复杂的技术。实际拆开看无非就是传输通道、共享空间、事件通知这几类组合着用而已。这篇文章我会从原理讲到实操把 8 种主流 IPC 方式逐项拆开给出代码、参数、选型建议和坑点。文章默认以 Linux 为主要场景但其中有几个方法在 Windows、macOS 上同样能找到对应物。不管你是写服务端、做嵌入式、还是在 Electron 里折腾主进程和渲染进程的通信应该都能找到可以直接抄作业的部分。1. 先理清思路为什么进程间通信是绕不开的坎1.1 进程隔离与协作操作系统给每个进程分配了独立的虚拟地址空间A 进程的变量在物理内存里的位置B 进程根本不知道也不允许直接访问。这个隔离机制是稳定和安全的基础否则某个进程越界写坏内存整个系统都跟着遭殃。但现实需求往往是客户端要把请求交给服务进程采集程序要把数据发给分析程序多个 worker 之间要互相同步进度。这时候就需要操作系统提供一套公开的“门路”让进程之间可以交换信息。进程间通信IPC就是这套门路的统称。要理解 IPC 的底层先要建立三个基本概念数据通道数据从 A 进程流入 B 进程可以走内核缓冲区也可以走共享内存映射。控制信息比如“数据好了”“可以开始了”“请停止”这类消息往往只有几个字节但要求及时到达。同步机制多进程同时读写一块区域时必须保证互斥和顺序不然数据就乱了。很多人容易把 IPC 的“通信”和“同步”混在一起。严格说信号量、文件锁这些本身不传业务数据它们解决的是“谁先谁后、能不能进”的问题属于 IPC 的辅助手段。实际项目里常用方案是共享内存 信号量搭配使用一个负责传数据一个负责保护数据。1.2 选 IPC 之前的三个核心问题选型前先别急着查 API先回答三个问题通信双方是不是在同一台机器上数据量有多大是几十字节的消息还是几十 MB 的流延迟和吞吐的预算有多严格这三个问题几乎能直接筛掉一半选项。同一台机器、超大流量、低延迟优先考虑共享内存跨主机、异构系统只能走网络 socket少量结构化消息消息队列或 Unix Domain Socket 就够用只是通知对方“事情做完了”一个信号也许就解决了。我见过不少新人在项目里闭眼用 TCP/HTTP 做本机进程通信结果绕了一圈。本机通信用 loopback 地址虽然也能通但每次都要走完整的 TCP 协议栈外加端口管理和粘包拆包性能和复杂度都不划算。Unix Domain Socket 和共享内存才是本机通信的正解。2. 8种主流IPC方式逐项拆解2.1 管道最简单的单向数据流管道Pipe是 Unix 系统里最古老也最基础的 IPC 方式Shell 里的cat file | grep keyword用的就是它。管道本质上是内核里的一段缓冲区一个进程往里写另一个进程从里读数据按先进先出的顺序流动。关键特征是单向。如果你想让两个进程互相通信就得建两个管道。另一个特征是字节流没有消息边界读端会连续读到写端塞进去的字节。比如写端先写了 10 字节再写 20 字节读端可能一次读走全部 30 字节也可能分两次读走 15 和 15这完全取决于调度。用 Python 模拟这种场景非常直观import os r, w os.pipe() pid os.fork() if pid 0: os.close(w) data os.read(r, 1024) print(子进程收到:, data.decode()) os.close(r) os._exit(0) else: os.close(r) os.write(w, bhello from parent) os.close(w) os.waitpid(pid, 0)管道适合父子进程之间、方向固定的简单数据流缺点是数据会经过内核缓冲区拷贝吞吐不高而且没命名时就只能在有亲缘关系的进程之间用。2.2 命名管道FIFO让不相关进程也能传话管道只能在 fork 出来的父子进程间用因为子进程继承了文件描述符。没有亲缘关系的两个进程怎么共用一条管道解决办法是给管道一个文件系统里的名字这就是命名管道也叫 FIFO。FIFO 用mkfifo创建以后进程可以像操作普通文件一样 open、read、write但数据不落盘仍然在内核缓冲区里流动。它的读写行为比普通管道更讲究open读端时如果还没有写端打开调用通常会阻塞反过来也一样。这个特性既是优点也是坑很多新手第一次跑 FIFO 程序时直接卡死在 open 上就是因为一端先 open 了却没有另一端来配对。一个典型的双向 CLI 场景进程 A 负责收命令进程 B 负责发命令。可以建两个 FIFO一个cmd.in一个cmd.out互为反方向。Shell 里测一下最直接mkfifo /tmp/myfifo echo hello /tmp/myfifo # 另开一个终端 cat /tmp/myfifo你会看到 echo 会一直挂着直到 cat 打开读端数据才真正被读走。这说明 FIFO 的写入动作是“等到有人读才算数”。FIFO 适合在同一台机器的不相关进程间传数据比 socket 更简单。但它也是流式数据没有消息边界多写者并发写的时候容易互相穿插通常需要配合锁或协议头使用。2.3 信号最轻量但也最粗暴的通知信号是 IPC 里最“轻”的一种它不传数据只传一个编号。比如SIGINT、SIGTERM、SIGUSR1。进程收到信号后可以执行默认动作也可以注册 handler 自定义处理逻辑。信号的优点是异步和快速。缺点也很明显信息量太小只能表达“发生了一个信号”表达不了具体的业务数据而且信号处理函数里不能随便调用非异步安全的函数比如printf、malloc这些都不安全。标准做法是信号 handler 里只做一件事置一个全局标志位然后让主循环去检查。#include signal.h #include stdio.h volatile sig_atomic_t flag 0; void handler(int sig) { flag 1; } int main(void) { signal(SIGUSR1, handler); while (1) { if (flag) { puts(got signal); flag 0; } } return 0; }sig_atomic_t是唯一保证在信号处理函数里读写安全的类型。这个细节很多人不留意直接用int在大多数平台没事但从标准严谨性来说用sig_atomic_t更稳。信号常用于进程退出、挂起重启、配置刷新这类控制场景。不要把业务大对象塞进信号机制里那不是它的赛道。2.4 消息队列带格式的中转站管道传的是无边界字节流如果我希望每次读到的就是“一条完整消息”而不是被切成两半的半截消息就需要消息队列。System V 消息队列和 POSIX 消息队列是 Linux 上常见的两套接口。每条消息有类型和长度读方可以按类型取消息天然带边界而且多个进程可以同时往同一个队列里投递消息。内核负责暂存消息解耦了生产者和消费者之间的实时关系。用 System V 消息队列的典型流程就是msgget创建、msgsnd发送、msgrcv接收。消息结构体必须首字段是long类型表示消息类型struct msg_buf { long mtype; char mtext[256]; };mtype不只是标识还能用来实现优先级。读方指定mtype1就只读类型为 1 的消息指定mtype0就读队列里最早的一条。这种按类型定向接收的能力是管道和 socket 不容易替代的。消息队列的痛点是数据需要在内核空间和用户空间之间拷贝两次消息长度通常还有上限。msgrcv如果给的长度小于实际消息长度默认会报错而不是截断这也是一个容易踩的坑。现代高吞吐场景里消息队列的出镜率下降了不少但在传统 Unix 服务和嵌入式系统里仍然很常见。2.5 共享内存性能之王如果两个进程要高频交换大量数据管道和消息队列都会有问题每次读写都要经过内核数据在用户态和内核态之间来回拷贝。共享内存的思路完全不一样把同一块物理内存映射到多个进程的虚拟地址空间里A 进程写入B 进程立刻就能看到不需要内核转手。在 Linux 上常用的是 POSIX 共享内存流程是shm_open创建或打开一个命名对象然后ftruncate设置大小再用mmap映射到进程地址空间。#include sys/mman.h #include sys/stat.h #include fcntl.h #include unistd.h int fd shm_open(/myshm, O_CREAT | O_RDWR, 0666); ftruncate(fd, 4096); void *ptr mmap(NULL, 4096, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);之后直接往ptr指向的地址写数据就完事。读端同样打开/myshm映射后就能读到同样的数据。共享内存的性能来自“零拷贝”但它的天花板恰恰是同步。谁在写写完了没读的时候会不会读到一半这些问题共享内存自己一概不管。所以生产环境里的共享内存几乎必须配套信号量、互斥锁或序列号机制。共享内存的另一个隐蔽坑是生命周期。shm_open创建的对象如果没人清理会在/dev/shm下留下文件进程退出后仍然存在。重启服务时如果旧数据还在可能干扰新进程的初始化逻辑需要显式shm_unlink。2.6 信号量不是通信是同步信号量是一个计数器用来控制同时访问某资源的进程数。它可以分为二进制信号量和计数信号量。它的核心动作有两个P操作等待/减一和V操作释放/加一。Linux 上好用的是 POSIX 命名信号量sem_open、sem_wait、sem_post三件套。为什么它算 IPC因为信号量本身是可以跨进程共享的。多个进程访问同一个共享内存或文件时用信号量锁住临界区保证同一时间只有一个进程在写。sem_t *sem sem_open(/mysem, O_CREAT, 0666, 1); sem_wait(sem); // 临界区写共享内存 sem_post(sem);我见过不少人把信号量当互斥锁用能满足一部分需求但它和互斥锁有个区别信号量可以在一个进程中 V 操作在另一个进程中 wait 操作适合“生产者唤醒消费者”这种栅栏场景而互斥锁严格要求“谁锁谁解”。选型时看清楚自己要的是单纯的互斥还是能力释放的同步。信号量的坑集中在初始值、命名冲突和死锁。两个进程拿的顺序不一致就容易死锁比如 A 先拿锁1再拿锁2B 先拿锁2再拿锁1。避免方法就是所有进程都按固定顺序申请多把锁。2.7 套接字跨机器的通信老大哥套接字可能是应用最广的 IPC 了因为它不只管本机还能跨机器。TCP、UDP、Unix Domain Socket 都是套接字体系里的成员。TCP面向连接的可靠字节流适合跨主机。UDP无连接、不可靠但延迟低适合对实时性要求高、能容忍丢包的业务。Unix Domain Socket同机专用不走网络协议栈比 TCP loopback 更快、更轻。很多工程师容易忽略 Unix Domain Socket。它在本机场景下几乎是“带外话”级别的存在。创建时用的地址是文件路径比如/tmp/app.sock业务代码结构跟 TCP socket 几乎一样只是地址族和地址参数不同。用 Python 的socket模块写 Unix Domain Socket 服务端import socket server socket.socket(socket.AF_UNIX, socket.SOCK_STREAM) server.bind(/tmp/app.sock) server.listen(16) while True: conn, _ server.accept() data conn.recv(4096) conn.sendall(bok) conn.close()Unix Domain Socket 之所以快是因为同机通信不经过网卡和 IP 协议栈数据直接在内核内部路由。不过要注意它有种特殊行为如果是SOCK_DGRAM模式报文有边界但要注意发送缓冲区大小限制如果是SOCK_STREAM模式又要处理粘包。2.8 内存映射mmap文件即通信严格来说mmap 不是一种独立的 IPC 机制更像是“把文件或文件的一部分映射到进程地址空间”的手段。但当多个进程通过MAP_SHARED映射同一个文件时它就成了一个隐形的通信通道。跟 POSIX 共享内存相比mmap 文件的一个好处是数据会落盘。即使所有进程都退出了文件内容还在重启后还能恢复。共享内存则是易失的只存在内存文件系统里。int fd open(/tmp/data.bin, O_CREAT | O_RDWR, 0666); ftruncate(fd, 4096); char *p mmap(NULL, 4096, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);有了映射区一个进程写p[0] x另一个进程读p[0]就能立即看到。这种方式的效率也很高因为页面缓存会被内核统一管理。mmap 的坑点在于文件大小、同步和数据一致性。映射的区域不能超过文件实际大小写之前得先ftruncate多个进程同时写同一个区域同样需要锁。还有一个容易忽略的问题msync并不是必须调但如果你希望数据及时刷到磁盘应该主动调用否则进程崩溃时可能丢数据。3. 实操细节与参数选择3.1 共享内存的绑定与同步很多人第一次跑共享内存例子会发现读端要么读不到数据要么读到半截数据。原因就是没有同步。常见做法是共享内存里放一个结构体结构体里既有数据区又有控制字段。用原子变量或者信号量做“数据就绪”标志。设计一个简单可靠的方案struct shm_block { sem_t lock; int ready; char payload[1024]; };写端先加锁写完payload后把ready置 1再解锁。读端加锁后检查ready为 1 才读。这里的lock必须用sem_init的时候指定pshared参数为非 0才能跨进程使用。我在实际项目里更喜欢把ready设计成“序列号递增”而不只是 0/1 标志。消费者每读一次记录下上次的序列号下次发现序列号变了就说明有新数据。这样即使消费者处理速度跟不上也不会把旧数据重复当新数据处理。还有一个容易被忽略的是内存对齐。共享内存里的结构体如果包含锁和缓冲不同机器、不同编译器可能有不同的 padding。跨架构共享同一个内存布局时务必用固定宽度类型显式控制对齐。3.2 Unix Domain Socket 和 TCP 怎么选同一台机器上的两个服务通信Unix Domain Socket 几乎是默认最优解。它的延迟通常比 TCP loopback 低而且不需要占用端口权限管理也可以直接映射到文件权限。缺点是它绑定的是文件路径调试起来没有netstat -an | grep port那么顺手但ss -x可以看。TCP 也有它的不可替代性如果未来可能要拆到两台机器或者通信方是不受你控制的客户端那就直接用 TCP 或 HTTP避免后面改造。一位做过网关的老同事总结得很到位“本机高性能用 Unix Socket跨机标准互通用 TCP资源紧张、能容忍丢数据再用 UDP。”写 Unix Domain Socket 时要注意清理 socket 文件。服务端退出后/tmp/app.sock不会自动删除下次启动 bind 会报地址已占用。你必须在启动时先判断文件是否存在存在就unlink然后再 bind。但这也有安全问题生产环境建议把 socket 放在专属目录下启动前只清理自己确认管理的文件。3.3 消息队列的长度和权限System V 消息队列有几个参数经常让人翻车msgsnd数据块大小不能超过msg_qbytes。msgrcv读取缓冲区小于实际消息长度时默认报E2BIG。队列资源属于内核进程退出后不会自动清理需要msgctl删除。来看一个读消息的细节struct msg_buf { long mtype; char mtext[512]; }; if (msgrcv(qid, msg, sizeof(msg.mtext), 0, 0) -1) { perror(msgrcv); }这里sizeof(msg.mtext)传的是 512而不是整个结构体大小。如果实际消息超过 512默认会报错。如果不希望报错而是截断读取最后一个参数可以加上MSG_NOERROR这是个很少被提到但很实用的标志位。权限方面msgget的权限位和打开文件一样但如果两个进程的用户不同很容易出现“创建者能收发另一个进程权限不够”的问题。调试时可以先用0666但要清楚这是宽松做法生产环境应当用更严格的权限模型。3.4 高并发场景下io_uring 给 IPC 带来了什么近两年 Linux 高性能 I/O 绕不开 io_uring。它的核心思路是用一个共享内存环形队列做用户态和内核态之间的指令提交与完成收割减少系统调用次数。很多文章讨论 io_uring 都集中在文件读写和网络事件上但它在 IPC 上的潜力同样不小。最典型的组合是 io_uring 接管 socket 读写尤其是在大量短连接、高频小消息的场景下省掉大量read/write/accept系统调用能明显降低 CPU 开销。不过 io_uring 的编程模型比 epoll 更复杂你得管 SQ 和 CQ 两个队列处理IORING_OP_READ、IORING_OP_WRITE的完成事件。我在实际压测里看到的结论是当单机连接数过万、每秒消息量数十万条时io_uring 的收益才会明显放大。如果只是几百个连接的小服务用 epoll 反而更简单、更不折腾。io_uring 本质上是给那些足够大的规模准备的不要为了炫技而引入复杂度。4. 选型策略与场景对照4.1 同一台机器上的高频数据交换假设有两个服务一个采集行情数据一个做实时计算。每秒要传 10 万条消息每条 200 字节左右延迟要求微秒级。这种场景无论是管道还是消息队列都会被拷贝开销拖后腿最佳方案是共享内存 无锁环形队列ring buffer。无锁环形队列的思路是生产者只管写 head消费者只管读 tail通过原子操作更新位置。这个方案在网络中间件、游戏服务器、量化系统里都非常常见。它比用信号量更高效但实现上要求严格遵循单一生产者和单一消费者原则或者用 CAS 处理多生产者。共享内存 ring buffer 的坑集中在“容量边界”上。如果生产者写太快、消费者读太慢缓冲区满了要么阻塞要么丢数据。生产方案一定要把“满了怎么办”定义清楚是覆盖旧数据还是丢弃新数据直接决定了业务的语义。4.2 分布式和跨主机场景跨机器的进程通信没有太多选择余地主流就是 TCP、UDP 和建立在它们之上的 RPC 框架。如果你要的是“像调本地函数一样调远程服务”可以选择 gRPC、Thrift 这类成熟框架它们把序列化、连接管理、超时重试都封装好了。但底层依然是 socket。理解这一点很重要很多框架出了问题最后还是得回到 TCP 的粘包、半包、连接超时这些基础问题去排查。框架只是减少重复劳动不能替代对传输层的理解。UDP 适合直播流、游戏同步这类允许丢包但不允许延迟波动的场景。如果要用 UDP 做可靠传输就得自己实现 ACK 和重传复杂度会急剧上升。除非是 QUIC 这类现成方案否则一般业务不建议自己造可靠 UDP 的轮子。4.3 Electron 主进程和渲染进程的 IPC 到底和 Vue 有没有关系很多前端同学搜 Electron IPC 时会看到 Vue 相关的内容第一反应是“Electron 主渲染进程通信和 Vue 是什么关系”。答案是没有直接关系。Electron 的 IPC 是 Chromium 架构带来的主进程是 Node.js 环境渲染进程是浏览器页面环境两者各自独立。为了让页面能够调用文件读写、系统弹窗等能力Electron 提供了ipcMain和ipcRenderer这两组 API。主进程用ipcMain.handle注册能力渲染进程用ipcRenderer.invoke调用本质上是进程间的一问一答。Vue 只是渲染进程里的 UI 框架。你在 Vue 组件里写ipcRenderer.invoke和在普通 JS 里写没有任何区别。Vue 的作用是帮你把 UI 状态管理好不参与底层 IPC 传输。如果有人说“Vue 和 Electron IPC 强绑定”那不是准确的说法顶多是把两者放进同一个项目里用而已。一个常见的坑是渲染进程直接require(electron)会报错。这是因为默认情况下渲染进程的 Node 集成被关掉了。要用 IPC应该在 preload 脚本里暴露 API比如const { contextBridge, ipcRenderer } require(electron) contextBridge.exposeInMainWorld(api, { loadConfig: () ipcRenderer.invoke(load-config) })然后在 Vue 组件里调用window.api.loadConfig()。这样既保证了安全又不和 Vue 的开发模型冲突。4.4 嵌入式强实时系统QNX 的 IPC 为什么特殊QNX 是微内核实时操作系统很多车载、医疗、工控设备里都用它。它的进程间通信跟我们熟知的 Linux IPC 不太一样。QNX 的核心 IPC 机制是消息传递message passing进程之间通过MsgSend、MsgReceive、MsgReply三个原语通信。这套机制把“通信”和“同步”合二为一发送方MsgSend会一直阻塞直到接收方MsgReply天然形成同步点。这在实时系统里很讨喜因为没有异步的数据竞争问题也不用额外加锁。QNX 的 IPC 是系统设计的一等公民很多服务之间的调用都建立在消息传递之上而不是靠共享内存。如果从 Linux 切换到 QNX最先要适应的就是这种思维先想消息怎么发再想数据怎么组织。共享内存在 QNX 里也存在一般用于大数据量传输但仍要配合其他机制做同步。5. 常见问题与排查实录5.1 共享内存写端崩溃读端一直等锁共享内存最怕的事之一写进程拿锁之后突然崩溃锁没释放读进程sem_wait直接卡死。这是用进程内同步原语做跨进程保护的经典问题。解决办法有几条路尽量缩短临界区降低崩溃概率用 robust 互斥锁比如pthread_mutexattr_setrobust但 POSIX 信号量没有 robust 版本在共享内存结构体里放一个“心跳时间戳”读端如果发现写端超过阈值没更新就自行复位锁或者退出等待。我在工控项目里用过“看门狗 锁地址记录”的方案写端每 100ms 更新一个时间戳读端发现超时后用sem_post把信号量值强行恢复同时把锁持有者标志清零。这个办法不够优雅但胜在简单可靠。如果条件允许直接把消费逻辑做成无锁数据结构才能从根本上规避这个问题。5.2 管道写了一半读端关闭了给管道写数据时如果读端已经关闭写进程会收到SIGPIPE信号默认行为是直接终止进程。这就是为什么你对一个管道进程write时程序会“莫名其妙”退出。避免方式很简单要么忽略SIGPIPE要么注册自定义 handler。很多网络服务都会在初始化时调用signal(SIGPIPE, SIG_IGN)然后让write返回EPIPE错误自己决定怎么处理。忽略信号后写操作返回 -1你再去判断errno是EPIPE说明对端已经走了再做清理就行。对管道和 socket 编程来说这是一个必须养成肌肉记忆的细节。我见过线上服务因为SIGPIPE直接崩溃日志里什么都没留下排查半天才发现是客户端提前断开导致的。5.3 消息队列报 E2BIG 或者 EMSGSIZE消息队列的每条消息长度有限制超过限制会直接报错。System V 的msg_qbytes默认值不大通过msgctl和IPC_SET可以调大但前提是有权限。POSIX 消息队列可以使用mq_open时指定mq_maxmsg和mq_msgsize默认上限受内核参数限制。这类问题最有效的排查方式就是先查配置cat /proc/sys/kernel/msgmax cat /proc/sys/kernel/msgmnb如果业务消息本来就大与其调队列上限不如换共享内存。消息队列的设计初衷是传递“小而频繁”的消息硬塞大块数据是逆着它的设计走。另外System V 消息队列对象不会随进程退出自动销毁。如果反复创建服务而不清理队列ipcs -q会看到一堆残留队列。这些残留会占内核内存而且可能导致新的消息队列创建失败。5.4 Unix Domain Socket 连接数限制Unix Domain Socket 的监听队列长度由listen决定但系统对文件描述符数量和连接状态也有整体限制。如果服务端处理不过来连接会在内核队列里堆积。listen的第二个参数是已完成握手但未accept的连接队列长度设太大没有意义因为应用处理能力跟不上队列反而加剧延迟。用ss -x可以查看 Unix socket 的状态比netstat更直观。如果发现大量Send-Q堆积基本能判断是应用层处理速度跟不上。另一个实际问题是 socket 文件路径长度。Unix socket 地址有长度上限路径不能太长。把 socket 文件放在深层目录下时bind 可能失败报AF_UNIX地址过长的错误。解决方法是改用短路径或者放到/tmp下。5.5 多进程 IPC 问题用什么工具排查排查 IPC 问题时不需要一上来就写一堆调试代码先看系统状态。ipcs查看共享内存、消息队列、信号量对象的创建者、大小、使用状态。ss -x查看 Unix Domain Socket 连接状态。ls -l /dev/shm查看 POSIX 共享内存对象。strace -p pid跟踪进程的系统调用能直接看到某次write、read、shmget是否返回错误。perf trace更高频采样适合分析消息队列和共享内存的竞争热点。我排查过一次诡异问题两个进程用共享内存通信数据偶尔错乱但代码逻辑看起来没问题。最后用strace发现其中一个进程根本没有正确映射共享内存而是映射了一个同名但不同大小的旧对象。这类问题不看底层状态很难定位。6. 最后聊几句实际的体会把这些 IPC 方式都亲自跑一遍之后我的体会是没有“最好”的 IPC只有“最合适”的 IPC。管道和信号适合轻量控制消息队列适合结构化消息共享内存适合性能敏感的大流量场景Unix Domain Socket 适合本机服务间高可靠互通TCP 则是跨机器的兜底方案。如果你刚开始接触建议不要直接冲共享内存先把管道和 Unix Domain Socket 用熟理解“阻塞”“边界”“同步”这几个关键词再上手共享内存会顺畅很多。做项目选型时多问一句“数据量、延迟、跨机与否”答案自然会浮现出来。这些底层知识看上去琐碎但每一个线上诡异问题的背后几乎都能归结到某条 IPC 的基本原理上。
返回列表