问题的答案。
问题的历史
Swift 的并发演变始于结构化并发和本地 actors,以消除单个进程内的数据竞争。随着语言扩展到服务器端和分布式系统,开发者需要一种方法来维护 Swift 严格的内存安全和隔离保证,当 actors 位于不同机器上时。DistributedActor 提案引入了一种编译器验证的分布式计算模型,确保网络调用遵循与本地方法调用相同的 async/await 合同。
问题描述
传统的远程过程调用依赖于运行时代码生成或动态代理,这些都绕过了 Swift 的类型检查器,从而导致在客户端和服务器之间 API 合同漂移时出现故障。语言需要一种机制在编译时强制跨进程边界的方法显式处理序列化、网络延迟和传输故障。挑战在于如何区分本地同步执行和远程异步调度,而不破坏 actor 编程模型或牺牲零成本抽象原则。
解决方案
distributed actor 声明隐式合成了一个 ActorSystem 属性,为每个实例注入传输机制。带有 distributed 关键字的方法在编译时验证,确保所有参数和返回值符合 Codable 或 Sendable,编译器生成一个分布式 thunk 来拦截调用。当发生远程调用时,ActorSystem 负责传输参数,通过其传输层发送,并在反序列化完成之前挂起调用者,同时保留 Swift 的结构化并发和错误处理语义。
日常生活中的情况
问题描述
一家金融科技初创公司需要在 iOS 客户端和后端匹配引擎之间同步高频交易状态。现有的 REST 实现引入了序列化开销,并且缺乏协议版本的编译时验证,导致在市场波动期间,当消息模式发生偏差时出现运行时解码错误。
考虑的第一个解决方案:gRPC 与 Protocol Buffers
这种方法提供了类型安全的代码生成和高效的跨语言边界的二进制序列化。然而,它需要维护单独的 .proto 定义文件和复杂的构建管道集成,造成与 Swift 的本地并发模型之间的阻抗不匹配。开发者必须手动将 gRPC 的基于回调的 API 与 Swift 的 async/await 衔接,导致代码冗长且模糊了业务逻辑。
考虑的第二个解决方案:基于 WebSocket 的自定义二进制协议
构建定制协议提供了最大的性能控制和与 Swift 结构化并发的紧密集成。缺点是完全缺乏对远程接口的编译器强制执行,这需要进行全面的集成测试以捕捉参数不匹配。此外,缺乏位置透明性迫使开发者维护本地缓存与远程引擎的并行代码路径,增加了维护负担和错误率。
选择的解决方案和结果
团队采用了 Swift DistributedActors,并在 WebSocket 之上实现了自定义 ActorSystem。这使得交易 actors 能够使用本地 Swift 语法来定义,编译器验证所有分布式方法的参数都是可序列化的,并且方法被标记为 async throws。distributed 关键字使网络边界显式,而 actor 系统透明地处理传输机制。结果是一个统一的代码库,与远程匹配引擎的交互使用与本地状态访问相同的语法,消除了运行时 API 不匹配,减少了 40% 的分布式系统复杂性。
候选人常常忽略的内容
为什么分布式方法必须声明为 throws,即使实现看似完美无缺?
Swift 的分布式 actor 模型将网络故障视为基本物理现象,而不是实现错误。编译器在每个分布式方法周围合成一个抛出 thunk,以处理 ActorSystem 错误、传输超时和反序列化失败。即使业务逻辑从不抛出,底层传输可能无法到达远程主机或接收到损坏的数据包。此要求强迫开发者使用 Swift 的 do-catch 错误处理来处理失败模式,防止未捕获的异常在网络分区期间使客户端崩溃。throws 注解成为分布式方法的 ABI 合同的一部分,确保调用者意识到不可靠的网络边界。
如何解决 ActorSystem 的分布式 actor 的物理位置,以及当本地 actor 引用被传递到远程进程时会发生什么?
每个 DistributedActor 拥有一个由其创建的 ActorSystem 分配的唯一 ActorID,作为能力令牌,表示 actor 的位置。当跨网络边界传递分布式 actor 时,Swift 的运行时不会传输对象指针;而是使用 actor 的 encode(to:) 方法编码 ActorID。接收进程创建一个代理 actor 实例,该实例共享相同的 ActorID,但绑定到其本地 ActorSystem。当代理收到方法调用时,系统会查询其路由表;如果 ActorID 指向一个远程节点,则调用会透明地转发。这确保了 actors 在网络中不会按值复制,从而维护 Swift 的并发安全所必需的单一拥有者语义。
什么使 distributed 方法与同一分布式 actor 内的常规方法区别开来,为什么后者不能被远程调用?
DistributedActor 内的常规方法在本地线程上同步执行,并直接访问隔离状态,绕过分布式 thunk 机制。这些方法不会通过 ActorSystem 进行序列化,这意味着它们无法容忍网络延迟或故障模式。编译器将远程调用限制为 distributed 方法,因为这些方法经过额外验证:它们必须是 async 和 throws,并且所有参数必须符合 Sendable 或 Codable。尝试在远程 actor 引用上调用常规方法会导致编译时错误,因为编译器无法保证该方法处理序列化或遵循分布式执行语义。这个区别保留了仅本地操作的性能,同时对网络绑定调用强制执行严格的合同。