2026年的今天,微服务、分布式系统早已是行业标配。但拨开Spring Cloud、Dubbo、gRPC这些繁杂的生态,追问一个最朴素的问题:远程过程调用(RPC)的核心,究竟是什么?
答案远比想象中简单——它不过是想让开发者像调用本地函数一样,调用另一台机器上的函数。而“从零实现”的意义,正是为了撕开优雅封装的外衣,亲眼看看网络通信、序列化、代理和注册这四块积木,是如何咬合成现代分布式基石的。
假设你在写一个计算器服务,本地调用是:
result = add(3, 5) # 8,瞬时完成但如果 add 函数运行在千里之外的服务器上呢?你必须手动处理:
RPC框架的本质,就是把这四步封装成一个“本地代理”。调用者面对代理对象,浑然不觉背后是Socket在收发数据。而我们要从零搭建的,正是这个“魔术帽子”。
为了避开第三方重量级库,我们用最原生的 Python socket + JSON 实现一个迷你RPC。它虽简陋,却五脏俱全。
我们将请求和响应都封装成字典,用 JSON 序列化后通过 TCP 发送。注意,真实框架需处理粘包问题,此处为清晰仅做简易收发。
import socket
import json
def send_request(host, port, request_dict):
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as sock:
sock.connect((host, port))
sock.sendall(json.dumps(request_dict).encode('utf-8'))
response = sock.recv(1024).decode('utf-8') # 简易场景,假设数据包足够小
return json.loads(response)服务端需要维护一个“函数名 → 函数实体”的字典,收到请求后,根据方法名查表、反射调用、返回结果。
class RpcServer:
def __init__(self, host, port):
self.host = host
self.port = port
self.functions = {} # 服务注册表
def register(self, func):
"""将函数按名称注册到表中"""
self.functions[func.__name__] = func
return func # 方便装饰器用法
def run(self):
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as sock:
sock.bind((self.host, self.port))
sock.listen(5)
print(f"RPC Server running on {self.host}:{self.port}")
while True:
conn, _ = sock.accept()
data = json.loads(conn.recv(1024).decode())
# 查表并调用
func = self.functions.get(data['method'])
if func:
result = func(*data['params'])
conn.sendall(json.dumps({'result': result}).encode())
else:
conn.sendall(json.dumps({'error': 'Method not found'}).encode())
conn.close()这是RPC最精妙的一环——利用 Python 的 __getattr__ 拦截未定义属性的访问,动态构造请求并发送。
class RpcClient:
def __init__(self, host, port):
self.host = host
self.port = port
def __getattr__(self, name):
"""当调用 client.add 时,此处被触发,name 即为 'add'"""
def wrapper(*args):
request = {'method': name, 'params': args}
return send_request(self.host, self.port, request)
return wrapper把三块拼起来,奇迹发生了:
# ---------- 服务端 ----------
server = RpcServer('localhost', 8888)
@server.register
def add(a, b):
return a + b
@server.register
def greet(name):
return f"Hello, {name}!"
# 在终端启动服务(示意,实际运行需开新线程)
# server.run()
# ---------- 客户端(另一个进程) ----------
client = RpcClient('localhost', 8888)
# 宛如本地函数!
print(client.add(100, 200)) # 输出: 300
print(client.greet("World")) # 输出: Hello, World!短短几十行,一个完整的RPC骨架已然立起。 客户端不知道网络,服务端不知道谁在调用——抽象的胜利,莫过于此。
如果你的老板说“把它上线,扛住百万QPS”,上面的代码会在瞬间崩塌。因为现实世界的残酷,恰恰隐藏在“从零到生产”的巨大鸿沟中:
recv(1024) 极其脆弱。实际框架必须设计协议头(Header) 指示载荷长度,或使用特殊的界定符。你看,从零实现让我们获得了极致的透明感,但也让我们深刻理解:分布式领域没有银弹。每一层优雅的封装背后,都沉淀着数十年的工程妥协。
当我们敲下 client.add(3,5) 时,底层其实发生了这些事:
{"method":"add","params":[3,5]}。RPC 不仅仅是一项技术,更是一种宏大的编程哲学——它试图让程序员遗忘“网络”这个不可靠的物理事实,坚信计算机之间可以像大脑神经元一样,无缝协作。
而我们亲手写出的这几段代码,就是对这种哲学最朴素、最诚实的注脚。下次当你使用 Dubbo 注解或 gRPC 拦截器时,愿你能回想起那个由 socket、json 和 __getattr__ 构建的微小世界——那里没有复杂的 SPI 机制,没有冗长的 XML 配置,只有函数跨越进程边界时,那一声清脆的呼唤与回响。
(全文核心代码仅4段,总计约60行,却勾勒出了RPC从诞生到工业演进的完整脉络。)
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。