当前位置: 首页 > news >正文

操作系统:RPC 中可能遇到的问题(Issues in RPC)

目录

常见的 RPC 问题及其解决方案

问题 1:客户端和服务器对数据的表示方式不同

问题 2:远程调用可能失败或重复执行

问题 3:RPC 中的“绑定”(Binding)是怎么实现的?

RPC的执行全过程(Execution of a Remote Procedure Call)

一、客户端发起调用

二、客户端收到端口号,重新发送调用


现在我们继续来讲解:RPC 中可能遇到的问题(Issues in RPC)以及它们是如何被解决的。

常见的 RPC 问题及其解决方案

当客户端和服务器不在同一台机器时,会遇到很多实际问题,比如机器结构不同、数据表达不同、连接失败等等。我们一一来看。

问题 1:客户端和服务器对数据的表示方式不同

举个例子:

假设客户端和服务器运行在两种不同架构的计算机上:

系统类型存储顺序说明
Big-endian高位字节存在低地址(最重要的字节排在前面)
Little-endian低位字节存在低地址(最不重要的字节排在前面)

比如数字 0x12345678,在两种机器中会被以不同顺序存储。

❗问题:如果客户端把数字按自己的方式发给服务器,而服务器直接按自己方式解读,数据就会错乱!
想象一下你给别人发了“1234”,对方看到的却是“3412”。

解决方案:使用统一的外部数据表示格式(XDR)

🔑 XDR(External Data Representation)

是一种与平台无关的中立数据格式,双方都用它来交换信息。

RPC 系统的做法是:

操作
 客户端在发送前,把数据 转换成 XDR 格式(这一过程叫 marshalling)
 网络上传输全部用 XDR
 服务器端收到后,把 XDR 数据 转换回本地格式(这一过程叫 unmarshalling)

这样,不管你是 big-endian 还是 little-endian,大家都说“共同的语言”。

一个形象的类比:

你可以把 XDR 理解成“英语”:

  • 客户端说“中文”,但翻译成“英语”发给对方;

  • 服务器是“西班牙人”,但他也会“英语”,所以能理解;

  • 最终双方沟通无障碍。


问题 2:远程调用可能失败或重复执行

本地函数调用 vs 远程调用

当我们在一台电脑上调用本地函数时,比如:

result = add(3, 5)

这个调用几乎永远不会失败,除非:

  • 程序崩溃;

  • 内存出错;

  • 系统关机。

通常这种失败是“极端情况”。

❗ 但在 RPC 中,不确定因素太多了:

远程调用时,函数在另一台机器上,调用过程可能因为以下“常见网络问题”而失败:

网络问题可能的影响
网络延迟客户端等待超时,以为失败
网络中断请求根本没有到达
服务器繁忙请求被延迟处理,结果返回慢
响应丢失函数执行了,但客户端没有收到返回值

导致的两种危险情况:

情况举例后果
函数根本没被执行请求丢失用户点击按钮没有反应
函数被执行多次客户端没收到回应,以为失败,于是重发请求用户可能“被重复扣款”或“重复提交表单”

 操作系统应尽量实现:Exactly Once 语义

所谓 Exactly Once(刚好一次),指的是:

不管网络出了什么问题,RPC 的目标函数要么执行一次,要么根本不执行,但绝不会执行多次

这是最理想的状态。

但问题是:

本地调用天然就是 "exactly once",因为它运行在同一台机器上;
而在 RPC 中,要达到 exactly once 很难,因为:

  • 网络不稳定;

  • 客户端可能重发请求;

  • 服务器不知道这个请求是不是“新来的”还是“重复的”;

  • 网络响应可能丢失但函数其实已经执行了。

为了尽量实现 “exactly once”,RPC 系统可以用这些技术:

方法说明
唯一请求编号(Request ID)每次调用附带一个唯一编号,服务器记录已处理的请求 ID,如果重复收到,就不再执行,只返回结果。
幂等操作设计让函数重复执行不会有坏结果(如多次提交都得到相同结果),比如“查询余额”而不是“扣款”
超时重试机制客户端设定超时,如果过了时间没收到响应,可以重发请求,但要搭配请求 ID 使用
事务日志服务器记录处理历史,保证请求即使中断也可以恢复状态

问题 3:RPC 中的“绑定”(Binding)是怎么实现的?

什么是“绑定”?

在我们调用本地函数时,比如:

int result = add(3, 5);

背后会发生“绑定”:

编译器会在编译时或运行时,把 add() 这个名字替换成它在内存中的具体地址。

所以程序运行时就知道“去哪儿找这个函数”。

❗问题来了:RPC 中的函数不在本地,是在远程的另一台机器上!

RPC 函数调用时,需要发消息到远程服务器,但该发到哪个端口呢?

你总得知道“对方在哪儿”,才能把请求发出去 —— 这就是 RPC 中需要解决的“绑定问题”。

❓为什么麻烦?

因为客户端和服务器:

  • 不共享内存;

  • 不知道对方具体位置;

  • 网络连接的“地址”和“端口”得事先知道,或者在运行时动态获得。

 两种绑定方式:静态绑定 vs 动态绑定

方式 1:静态绑定(Static Binding)

💡 也叫编译时绑定

  • 客户端和服务器事先约定好端口号。

  • 比如:Web 服务器使用端口 80,FTP 使用 21,Telnet 使用 23。

  • 在写程序的时候就已经写死了这个端口号。

send("192.168.1.5", port=8080, message)

一旦编译好程序,服务端口就不能改了。

✅ 优点:

  • 实现简单;

  • 没有额外的“查找开销”。

❌ 缺点:

  • 不灵活:端口固定;

  • 如果服务迁移到其他端口或机器,客户端就无法找到;

  • 多个服务可能冲突。

 方式 2:动态绑定(Dynamic Binding)

 使用“协调者”(Rendezvous / Matchmaker)

  • 操作系统运行一个叫 rendezvous 守护进程(协调者)的服务,监听在一个固定的端口。

  • 客户端并不直接访问目标服务,而是:

调用流程:

  1. 客户端向 rendezvous 守护进程 发请求,说明自己需要调用哪个远程过程(函数);

  2. 守护进程查找这个服务是否已经运行,并返回其端口号;

  3. 客户端随后用这个端口号继续发起实际的 RPC 调用;

  4. 一旦连接建立,客户端可以一直用这个端口,直到:

    • 调用结束;

    • 连接断开;

    • 服务崩溃。

✅ 优点:

  • 灵活:服务端口可以动态分配;

  • 可扩展:多个服务共享基础设施;

  • 更适合分布式和微服务系统。

❌ 缺点:

  • 实现更复杂;

  • 有轻微性能开销(多一次请求);

  • 依赖 rendezvous 守护进程的可靠性。

假设你打电话找“张三”:

静态绑定

你手上已经有张三的电话号码(写在程序里了),直接拨过去

动态绑定你先打给“114 电话查询台”(rendezvous),问张三的号码,然后再打过去

RPC的执行全过程(Execution of a Remote Procedure Call)

当你写下一个 RPC 函数调用,比如:

result = remote_add(3, 5);

背后其实系统自动帮你完成了非常多的事情。我们接下来一步一步拆解这个过程。

操作系统:远程过程调用( Remote Procedure Call,RPC)-CSDN博客

一、客户端发起调用

  1. 用户程序调用函数

    • 你调用的是一个看似本地的函数 remote_add(),实际你调用的是一个 Stub 函数。

    • Stub 会准备 RPC 请求消息,其中包括:

      • 要调用的函数名(比如 remote_addX);

      • 参数;

      • 唯一编号;

      • 此时还不知道目标端口 P(目标服务端口)!

  2. 内核把请求转发给“协调者”

    • 协调者又称 matchmaker,在一个固定端口上监听所有“服务查找”请求。

    • 于是:

      • 内核将该 RPC 消息发送到 matchmaker 进程;

      • matchmaker 的作用是查找哪个服务提供了函数 X,并返回它的真实端口号 P

消息往返流程如下:

发出接收方内容
客户端Matchmaker请求端口号:请问函数 X 对应的服务端口是多少?
Matchmaker客户端回复端口号 P:该服务正在端口 P 上监听

 

二、客户端收到端口号,重新发送调用

  1. 客户端拿到目标端口 P 之后:

    • 把 RPC 消息(调用函数 X + 参数)重新发往端口 P。

    • 这时,消息就送到了 提供服务的远程服务器 上。

  2. 服务器收到消息后:

    • 服务器上的 Stub 解包消息;

    • 执行目标函数 X(3, 5)

    • 获取结果,比如 8

    • 把结果打包发回客户端。

最终结果返回:

发出接收方内容
服务器 Stub客户端 Stub返回值(比如结果 8)

客户端 stub 收到响应后,把结果交还给原始程序,你的变量就得到了返回值:

result = 8; // 完成
  • 使用这个端口 P 发送调用请求;

  • 服务器收到请求,执行函数,生成输出;

  • 输出结果返回客户端,整个调用结束。

全流程图示

客户端程序(调用)│▼
客户端 stub 打包参数│▼
查找端口号:└─> Matchmaker:请求函数 X 的端口?◄── 回复:端口号 P│▼
发送调用消息到端口 P│▼
服务器 stub 解包 + 执行函数│▼
打包结果,发回客户端│▼
客户端 stub 解包,返回结果
http://www.dtcms.com/a/314255.html

相关文章:

  • Ubuntu系统VScode实现opencv(c++)图像一维直方图
  • Git如何同步本地与远程仓库并解决冲突
  • C#利用unity游戏引实现开发设备仿真系统步骤
  • 《解构Angular组件变化检测:从自动到手 动的效能突破》
  • Unity Shader编程完全入门指南:从零到实战 C# 实战案例
  • 雷达系统工程学习:自制极化合成孔径雷达无人机
  • 【OpenGL】LearnOpenGL学习笔记03 - 着色器
  • 2025年半导体探针卡市场深度调研:规模数据、竞争格局
  • 防火墙的进阶练习
  • PVE环境对网口和wifi的配置
  • Neo4j 基础语法指南
  • 基于Spring Cloud Gateway和Resilience4j的微服务容错与流量控制实战经验分享
  • javacc学习笔记 03、编译原理实践 - JavaCC解析表达式并生成抽象语法树
  • MySQL5.0数据库管理系统安装部署
  • PCB反焊盘的样子越诡异,高速过孔的性能越好?
  • [自动化Adapt] 父子事件| 冗余过滤 | SQLite | SQLAlchemy | 会话工厂 | Alembic
  • 【物联网】基于树莓派的物联网开发【23】——树莓派安装SQLite嵌入式数据库
  • 秋招笔记-8.4
  • 小实验:按键点灯(中断法)
  • QT的UDP
  • 【数据结构入门】链表
  • Solidity智能合约开发全攻略
  • Java基础-斗地主游戏
  • ArrayDeque双端队列--底层原理可视化
  • ubuntu修改时区
  • 【HZ-T536开发板免费体验】Cangjie Magic调用视觉语言大模型(VLM)真香,是不是可以没有YOLO和OCR了?
  • 通用 PDF 文件流 OCR 到文本 API 接口
  • 使用 Aspose.OCR 将图像文本转换为可编辑文本
  • 基础14-Java集合框架:掌握List、Set和Map的使用
  • 十字滑台:精密制造的“心脏“如何跳动?