ARTICLE DETAIL

资讯详情

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

用打电话的方式理解系统通信:同步、异步与事件驱动

用打电话的方式理解系统通信:同步、异步与事件驱动 电话一响有人秒接有人看一眼来电号码直接把手机扣在桌上。接通之后有人第一句就是“你说我听着”有人花了三分钟寒暄还没进入正题。挂断时也一样有人干脆利落地“就这样拜拜”有人反复确认“你明白我意思吧”才肯放手。如果把这些表现拆开看你会发现每个人都在无意识地遵循一套自己的“通信协议”。这里说的“希人”我更愿意理解成那些性格鲜明、沟通方式极具辨识度的人——在电话这种实时、强制、带有一定压力的通信场景里每个人的“协议”都会被暴露得很彻底。这篇文章想做的事情是把“不同人打电话的方式”做一次系统化拆解然后把人的沟通差异映射到软件系统的通信设计上。读完你会得到两样东西第一一套判断“沟通方式是否高效”的分析框架第二在实际系统设计中如何选择同步调用、异步消息、事件驱动这三种“打电话”模式。1. 先给结论打电话是最能暴露“通信协议”的场景为什么偏偏拿打电话说事而不是发消息、写邮件因为电话是典型的同步通信。它有几个特征非常鲜明双方必须同时在线建立一条独占的“连接”说话是实时的一方说完另一方立刻响应无法精确回放说出去的话就是泼出去的水语气、停顿、抢话这些“元信息”会严重干扰内容本身。从通信工程的角度看电话是一套“低时延、高成本、强交互”的协议。发消息是异步的写邮件是带持久化存储的而电话更像是 TCP 长连接先拨号握手接通后持续传输数据最后有一方主动挂断断开连接。正因为它“重”所以每个人的真实沟通风格才会在电话里暴露无遗。有人把电话当成“紧急通道”只有重要的事才拨有人把电话当成“默认入口”任何事都先打过去再说。这两种人如果互为同事会产生巨大的摩擦前者觉得被冒犯后者觉得对方冷漠。放到系统设计里也一样。同步 RPC 和异步消息没有绝对的好坏只有适不适合当前业务场景。判断的维度就是你拨出电话那一刻最关心什么——是立刻知道结果还是只要消息送达就行。2. 你身边一定有这几类“希人”先做一个小型分类。这些类型未必覆盖所有人但放在一起对比能很清楚地看出“通信协议”的差异。2.1 产品经理型目标明确以结论开场这类人打电话的第一个特点是不问“在吗”开口就是“关于上周的需求我确认一下”。他们会提前想好要问什么电话过程控制在三分钟以内结束时会复述关键结论。跟这类人打电话你会感觉节奏被牢牢掌控。他们的“协议”是高结构化的开场说明目的中间只讨论和目的相关的问题结尾确认下一步动作。从效率角度看这是非常优秀的通信方式。但缺点是缺少缓冲如果被叫方状态不好很容易觉得压力大。2.2 程序员型能文字就不语音打了就直入主题程序员接到电话的第一反应往往不是接而是先想“能不能发消息”。如果迫不得已接了通常会直接问“什么事说重点”。他们不喜欢在电话里讨论方案因为需要看代码、看文档电话里根本记不住。这类人的“协议”其实是偏异步的默认先通过即时消息传递上下文只有在信息已经同步得差不多、只剩少数需要实时确认的点时才拨电话。跟程序员打电话最好提前把相关文档链接通过消息发过去再接通电话时直接讨论效率高很多。2.3 老板型突袭式来电先听结果再布置任务老板型打电话的模式一般是这样手机突然震动来电显示是领导接通之后没有寒暄直接问“上次说的那个事怎么样了”。如果你回答“还在做”他会追问“做到哪一步了什么时候能好”。这类人在通信协议上讲究“状态同步优先”。他打电话的核心目的不是获取完整信息而是快速确认“事情是否在预期轨道上”。所以给老板打电话之前一定要先想好“当前状态 风险 下一步 需要什么支持”这个四段式结构否则很容易被问住。2.4 运维型半夜来电先确认故障等级再说话运维人员打电话有一套近乎条件反射的规则半夜接到告警电话接起来先确认“是哪个系统、什么级别、有没有人处理过”然后再决定要不要叫醒其他人。他们的电话“协议”是典型的分级响应机制。不是所有电话都值得立即处理所以要有一套快速判定优先级的方法。这套逻辑放到系统里就是告警分级、值班 escalation、on-call 轮询。如果你身边有做运维的朋友观察他们接电话的顺序能学会一套非常实用的故障通信规则。2.5 销售型话术驱动擅长在对话中引导方向销售型的人打电话你会发现他们非常擅长用提问控制节奏。“您目前最关注的是哪一块”“如果是这样的话那确实需要调整方案您觉得呢”每一句话都在把对话往预设方向推。这类人的“协议”特征是交互式引导。他们不只传递信息更在意对方的反应并实时调整自己的话术。放到系统设计里这种模式很像“带反馈的闭环控制”——不是简单发一个请求而是根据响应动态修正策略。客服机器人、智能外呼系统本质上都在模仿这种能力。3. 电话沟通的六个维度对应通信协议设计把不同人的打电话风格拆到底会发现无非是六个维度上的差异。这六个维度恰好和通信协议设计中的核心要素一一对应。3.1 同步与异步有人打电话必须等对方接不接就一直打有人习惯了发消息对方什么时候回都行。前者是同步阻塞后者是异步非阻塞。系统设计里这个选择直接决定架构形态同步 RPC 适合“请求-响应”模型异步 MQ 适合“发出去就完事”的模型。没有哪个更高级只看业务上能不能接受延迟。3.2 连接建立方式我们经常在微信问一句“在吗”其实就是拨号前的“握手”。但有些人觉得“在吗”是废话直接说事才是高效。这一点差异对应的是 HTTP 这种无状态协议与 WebSocket 这种长连接协议的取舍。无状态协议的好处是简单、易于横向扩展长连接的好处是省去反复握手开销但需要维护连接状态复杂度更高。你会选择哪种方式取决于你的业务是“偶尔打一两次电话”还是“通了之后要一直保持在线”。3.3 消息格式有人打电话讲大白话有人习惯先用编号罗列“第一、第二、第三”。如果把电话内容转成文本前者是自由格式后者是结构化 JSON。放到系统通信里就是协议格式的选择纯文本、XML、JSON、Protobuf。结构化程度越高解析越稳定但灵活性越差。团队协作时也一样如果双方对“消息格式”没有共识通话中就会出现大量“我说东你说西”的歧义。3.4 超时处理拨出电话后对方一直不接你会怎么办有人等三十秒就挂断有人会一直听到忙音。系统设计里这就是超时时间、重试次数和熔断策略的取舍。一个容易踩坑的地方是不少系统把超时设置为“够久就能成功”结果上游一旦变慢所有调用方都被拖死。真正合理的策略应该是“设定上限 快速失败 有限重试 推入异步队列”。3.5 重试与补偿电话没打通很多人会再打一次。但如果是重要的事情有些人会留言有些人会发消息确认有些人会找一个双方都认识的人转达。这就是重试和补偿机制。在分布式系统中消息丢失、消费失败几乎是必然的。关键是设计重试时要考虑幂等性对方可能已经处理了一次你重试时不能造成重复扣款、重复下单之类的问题。3.6 优雅关闭通话结束时一个成熟的人会说“那就这样我先挂了后续邮件同步”。突然挂断是一种极其不友好的体验。在系统通信里这对应连接关闭、消息 ACK、事务提交和最终一致性的设计。很多线上故障恰恰出在“没有优雅关闭”上任务执行到一半应用重启没有 ACK消息被重复消费数据库写了一半事务提交失败没有补偿逻辑数据就脏了。4. 从人的电话到系统的通信三种典型“打电话”模式现在把镜头从人切换到系统。软件架构里的通信方式虽然花样繁多但抽象到“打电话”这个层面其实只有三大类。4.1 同步调用像打电话主调方拨号发起请求被调方接听处理请求并返回结果主调方一直保持等待。这就是 RPC、HTTP 调用的典型模式。优点链路清晰结果立等可取排错简单。 缺点调用方被阻塞一旦被调方变慢或者不可用调用方也会跟着遭殃。适用场景用户注册、登录校验、实时查询等对结果有时效性要求的操作。4.2 异步消息像语音留言主叫方拨号过去对方没接于是留了一段语音自己该干嘛干嘛。对方看到留言后自行处理处理完再说一声也可能不说。这就是消息队列的典型模式生产者把消息投递到 Broker消费者异步消费。两边不需要同时在线系统之间解耦。优点削峰填谷、调用方无阻塞、系统可用性高。 缺点链路变长处理结果不能立刻返回需要额外设计结果回查或回调机制。适用场景订单超时关闭、日志采集、通知推送、离线任务处理。4.3 事件驱动像“发了朋友圈自动通知感兴趣的人”这种模式更像“订阅-发布”你发布一个事件不关心谁接收、接收后做什么。订阅了该事件的服务会自行响应。优点扩展性强新增消费者无需修改生产者。 缺点事件流难以追踪可能出现“事件风暴”需要额外的事件治理能力。适用场景用户行为埋点、跨系统状态同步、微服务间的领域事件。4.4 三种模式对比对比维度同步调用异步消息事件驱动打电话类比实时通话语音留言广播通知调用方是否等待等待不等待不等待系统耦合度高中低结果可预期性立即返回延迟返回不保证有返回故障影响面调用方阻塞消息堆积订阅方连锁反应适用场景实时查询任务解耦状态同步与扩展如果你的系统还在争论“该用 Feign 还是 MQ”不如先回到业务本质这通电话到底需要对方立刻给你答案还是只需要告知对方一声5. 代码示例用 Python 模拟三种“打电话”方式为了让这三种模式能直观对比下面用一个不依赖任何第三方库的 Python 脚本做模拟。代码分为三个主体类分别对应同步通话、异步留言、事件发布。5.1 公共基础类先定义一个“被叫人”的类型它负责模拟每个人的处理耗时。import time import queue import threading class Person: 模拟一个可以被通知的人 def __init__(self, name: str, reply_time: float 1.0): self.name name self.reply_time reply_time def receive(self, message: str) - str: # 模拟处理耗时 time.sleep(self.reply_time) return f{self.name} 已处理: {message} def on_event(self, event: str) - None: # 事件驱动模式下的回调 print(f {self.name} 收到事件: {event})文件路径call_simulation.py注意receive方法返回的是一个字符串这样可以模拟“被叫人处理完后的回复”。5.2 同步模式实时通话class SyncCall: 同步打电话主叫方阻塞等待被叫方返回结果 def call(self, target: Person, message: str) - str: print(f[同步] 拨号给 {target.name}等待接通...) result target.receive(message) print(f[同步] 通话结束得到回复: {result}) return result这段代码的逻辑很简单调用target.receive(message)时主叫方会一直等待直到receive返回。这个等待过程对应的是同步通信中的阻塞等待。5.3 异步模式语音留言class AsyncMailbox: 异步语音留言主叫方发完消息后立即返回被叫方稍后处理 def __init__(self): self.mailbox queue.Queue() def call(self, target: Person, message: str, callbackNone) - str: print(f[异步] 拨号给 {target.name}未接转语音留言) self.mailbox.put((target, message, callback)) print(f[异步] 留言成功主叫方可以继续做其他事情) return queued def process_all(self): 模拟被叫方稍后处理所有留言 while not self.mailbox.empty(): target, message, callback self.mailbox.get() result target.receive(message) print(f[异步] 被叫方处理完成: {result}) if callback: callback(result)异步模式下call方法只是把消息放入队列然后立即返回queued。这就是“发完消息不等回复”的效果。process_all方法模拟了一个后台消费者它稍后统一处理所有留言。5.4 事件驱动朋友圈广播class EventHub: 事件发布订阅发布事件后所有订阅者各自处理 def __init__(self): self.subscribers [] def subscribe(self, target: Person) - None: self.subscribers.append(target) def publish(self, event: str) - None: print(f[事件] 发布: {event}) for target in self.subscribers: target.on_event(event)这里的事件驱动和异步留言的区别在于发布者完全不关心谁处理、处理得怎么样。订阅者列表里每分钟都能加新成员但发布者不需要改任何代码。5.5 组装运行if __name__ __main__: alice Person(Alice, reply_time1.0) bob Person(Bob, reply_time0.5) print( * 50) print(场景一同步电话) print( * 50) sync_mode SyncCall() sync_mode.call(alice, 线上订单查询接口超时了排查一下) print() print( * 50) print(场景二异步语音留言) print( * 50) async_mode AsyncMailbox() async_mode.call(bob, 待办明天凌晨发布新版本请确认回滚方案) # 模拟主叫方不等待直接执行后续代码 print([异步] 主叫方没有等待开始做其他任务...) time.sleep(0.2) # 消费者稍后统一处理 async_mode.process_all() print() print( * 50) print(场景三事件广播) print( * 50) event_hub EventHub() event_hub.subscribe(alice) event_hub.subscribe(bob) event_hub.publish(用户购买成功) print() print(模拟结束)运行方式python3 call_simulation.py预期输出中你能很清楚地看到三种模式的差异同步模式下每一行输出都是严格顺序的异步模式下主叫方没有等待这句会出现在被叫方处理完成之前事件驱动模式下所有订阅者都会各自打印一行。6. 运行效果与验证上面的代码把三种模式放在同一个脚本里便于直观对比。运行后你至少应该看到以下关键现象同步模式中“通话结束”这一行必然出现在“主叫方”后续任何操作之前因为它是阻塞的。异步模式中“主叫方继续做其他事情”会先出现说明调用方没有被阻塞。事件驱动模式中subscribe的先后顺序决定了打印顺序但发布者本身不做任何等待。如果想确认自己理解正确可以做一个小实验把Person.reply_time调成 5 秒再跑一次。同步模式会明显卡住 5 秒异步模式几乎不会感知到这个耗时因为处理过程已经放到后台了。判断一个系统应该用哪种方式可以在代码里问自己三个问题我能不能接受“等不到结果”如果被调方挂了我的主流程还能不能继续跑我这通“电话”发出去有多少个人需要知道这三个问题的答案基本就指向了应该选同步、异步还是事件驱动。7. 常见问题与排查思路在实战中这三种通信模式都会遇到一些典型问题。下面列几个最常出现的并给出排查方向。问题现象可能原因排查方式解决方案同步调用经常超时被调方响应变慢或未设置合理超时查看调用链耗时分布、GC 是否频繁缩短超时时间增加熔断和降级策略异步消息大量堆积消费者处理能力不足或者消费逻辑卡死查看 MQ 积压数量、消费者线程状态扩容消费者实例优化单条消息处理逻辑消息被重复消费消费者没有做幂等处理或消费后未及时 ACK检查消费者日志和消息去重表引入业务幂等键消费前先查重事件广播后多个服务连锁故障事件订阅关系过深形成调用链风暴查看事件拓扑确认哪些服务订阅了事件细化事件类型按需订阅增加开关降级电话打不通但系统没报错异步模式下失败被吞掉检查消息投递状态、死信队列增加死信队列与失败告警线上故障时无法快速定位是哪个环节出的问题缺少 traceId 透传查看日志打印是否带全局链路编号在消息体和 RPC 请求中透传 traceId这中间最容易被忽视的是幂等性。很多同学在做异步消费时只处理了“正常消费一次”的路径没有处理“消费一半重启”“消费者拉取到消息后但 ACK 前宕机”的情况。最好的办法是每条消息带一个业务内唯一的requestId消费前先查状态表如果已经处理过就直接返回成功。8. 最佳实践让团队成员像高效“打电话”一样协作通信协议的设计不能脱离团队协作中的真实沟通。下面几条建议既适用于技术系统也适用于团队日常协作。8.1 明确“电话”的优先级不是所有事情都值得拨一通电话。如果信息可以异步传递且没有时效要求优先用文档或消息。如果事情紧急、影响面大、需要立即确认才打电话。团队可以约定紧急程度的定义例如“生产故障”“线上资损”属于必须电话联系的事“方案评审”“日常进度同步”优先写在文档里。8.2 给每次“通话”设定超时建议团队内部的电话尽量控制在十分钟内。超时了说明问题过于复杂不适合在电话里强行推进应该转为有记录的会议或设计文档。落到系统上就是所有同步 RPC 必须设置超时不能无限制等待。8.3 关键信息必须“留痕”打电话高效但有一个致命缺点没有记录。电话里说定的内容如果不落到文档几天后就变成“没说过”。因此建议执行“电话 记录”的组合重要通话结束后由发起方整理纪要发到群里或写到工单系统中。技术系统也一样关键操作不能只靠内存传递必须有日志和审计。8.4 为异步处理设置“回调”机制异步消息不是发完就结束。如果业务方需要知道处理结果必须设计回调或结果查询接口。否则就会出现“活干了但没人知道”“活没干也没人发现”的情况。可以把异步任务的状态设计成状态机待处理→处理中→成功/失败并提供状态查询入口。8.5 事故响应时通信协议要“降级为同步”日常开发可以大量使用异步沟通但在线上故障排查时异步通信非常低效。这时候团队应该切换成“同步模式”拉群、打电话、共享桌面所有关键信息实时同步。这也是很多团队在故障演练里专门训练的部分——切换沟通模式本身是一种能力。9. 一个实用的收尾建议写到最后想给出一个可以直接在手边使用的建议下次你再拨一通工作电话之前先花十秒钟想清楚四件事——当前状态是什么、需要对方做什么、对方不做的风险是什么、需要对方何时给答复。这四件事想明白后你的电话会自动变得高效。同样在给系统设计通信方式的时候也别急着上某个中间件。先想清楚这是同步请求、异步任务还是事件通知接下来再决定用 RPC、消息队列还是事件总线。通信方式选错后面加再多监控和排查工具都只是补救。不同的人打电话有不同的方式不同的系统也一样。真正重要的是在动手拨号之前想清楚这一通“电话”在你整个协作链路里扮演的角色。这样人和人之间、系统和系统之间才不会因为“沟通方式不一致”而反复消耗彼此。
返回列表