news 2026/10/11 16:02:18

JSON-RPC协议详解:从原理到实战的接口通信经验

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
JSON-RPC协议详解:从原理到实战的接口通信经验

做后端这几年,我和“接口通信”这件事打过太多交道了。不管是给前端提供数据,还是服务之间互相调用,最绕不开的一个问题就是:两个系统之间,到底用什么姿势把“我要调用你某个功能”这句话说清楚。有人用 REST,有人上 gRPC,也有不少人直接在应用层塞一段 JSON 就开干——而 jsonRpc 这个老牌协议,就是我实际项目里用的最多的方案之一。它足够轻、足够直接,几乎没有学习成本,也特别适合那种“我只想调一个服务端函数”的场景。这篇就当是一份完整复盘,把我从协议原理到实战踩坑的整套经验整理出来,给正在选型或者已经被 jsonRpc 折腾过一把的同学做个参考。不管你是在搞内部微服务、写工具链,还是只是想让两个模块之间的调用更干净,这篇应该都能对得上你的需求。

1. jsonRpc 的核心价值与选型考量

1.1 它到底是什么

先把这个名词拆开。jsonRpc 全称是 JSON-RPC,一种以 JSON 为数据格式的远程过程调用协议,目前用的最多的是 2.0 版本。它的思路特别直观:客户端把“我想调用哪个函数、传什么参数”打包成一个 JSON 对象发给服务端,服务端执行完函数,再把结果或错误打包成另一个 JSON 对象返回。整个过程就像本地调用一个函数那样自然,只不过这个函数跑在了另一台机器上。

一个典型的请求长这样:

{"jsonrpc": "2.0", "method": "getUserName", "params": [1001], "id": 1}

服务端返回:

{"jsonrpc": "2.0", "result": {"name": "张三"}, "id": 1}

如果出错了,就返回 error 字段而不是 result 字段。请求对象里有个 id,用于把响应和请求对上号。这里值得强调的是,“rpc”这套东西其实在上世纪七八十年代就有了,但 JSON-RPC 因为借助了 JSON 的文本表达力,把跨语言、跨平台的调用门槛降到了极低。任何一个语言,只要能解析 JSON、能发网络请求,就能立刻实现一个 jsonRpc 服务。

1.2 为什么我会选它而不是无脑上 REST

很多人会问:现在 REST 不是已经是事实标准了吗,为什么还要用 JSON-RPC?我个人的观点不是“谁替代谁”,而是不同的场景有不同最合适的方案。REST 擅长的是资源管理,围绕 URL 和 HTTP 动词来操作资源。如果你要设计一套公开 API,需要对资源做增删改查,REST 没问题。但当你面对的是“调用服务端的某个方法”,比如让服务端计算一个值、执行一段逻辑,REST 就开始别扭了。

举个实际例子,我早期做某个后台系统的时候,需要让客户端通知服务端“触发一次报表生成”。用 REST 的话,要么 POST /api/reports,把“生成”这个动词用不同粒度拆分,要么就得自己发明一些不伦不类的路径。而用 JSON-RPC,就是一句话的事:

{"jsonrpc": "2.0", "method": "report.generate", "params": {"reportId": 1024}, "id": 5}

服务端既能明明确确地执行对应逻辑,又能在响应里直接拿到结果。调用语义和实现代码一一对应,无需为了 RESTful 的“资源”概念做额外设计。这个特点让 jsonRpc 在内部服务之间、前后端一体化的中台系统里,用起来极其顺手。

1.3 和 gRPC 比,优势与短板都在哪

gRPC 是另一个重量级选手,基于 HTTP/2 + Protobuf,性能强、强类型、自带流式传输。但 gRPC 的代价也不小:要写 .proto 文件,要生成客户端和服务端代码,要维护一堆生成的代码文件,团队没有一定规模,这套成本其实有点吃不消。

如果是小团队、轻量项目或者跨语言快速联调,jsonRpc 的优势就体现出来了:服务端只需要一个 HTTP 接口入口(或者长连接入口),接收 JSON,解析后路由到具体方法即可。客户端甚至不需要任何代码生成,直接用通用的 HTTP 工具就能调试。比起 gRPC 的强约束,jsonRpc 更像是一把“能上手就能用”的瑞士军刀。当然,如果未来业务需要双端高并发、大量流式传输、强契约保障,那么 gRPC 更合适。这两者并不冲突,甚至可以基于同一个服务抽象层,对外同时暴露 jsonRpc 和 gRPC 两种入口。

这里放一张我经常拿来做决策的对照表:

维度JSON-RPCRESTgRPC
数据格式JSON,可读性好JSON 为主Protobuf 二进制
调用模型本地函数式调用资源式操作强类型存根调用
学习成本极低中中高
代码生成无无有
流式支持不支持(可配合长连接扩展)弱强
性能中,适合业务系统中高
适合场景内部系统、工具链、轻量服务对外资源 API大规模微服务、流式通信

从这张表也能看出来,jsonRpc 从来不是万能的,但它在“内部系统函数调用”这个特定场景下,确实是一个性价比很高的选择。

1.4 适用边界:什么情况我劝你别用它

作为一个用过不少方案的人,我也得坦诚说 jsonRpc 的边界。如果你要做的是公众互联网上的开放平台 API,那么建议你还是老老实实做 REST 或者更现代的规范,这样对接方的认知成本最低。如果是超大规模微服务团队,追求极致性能和服务网格协议生态,那 gRPC 更合适。此外,如果请求和响应数据动不动就十几 MB,JSON 的冗余和解析性能就会成为一个问题。

那我在什么场景下会坚定地用 jsonRpc 呢?基本是这几类:公司内部的管理系统、前后端分离内部接口、物联网网关给上层平台上报数据和下发指令、以及一些“不想引入太重框架”的工具型服务。总结成一句话:你想让服务端像一个函数库一样被调用,而且想用最小的成本把它跑起来,那么 jsonRpc 会是一个相当清爽的选择。

2. jsonRpc 协议细节逐项拆解

2.1 请求对象的四个关键字段

任何一次 jsonRpc 请求,核心都在一个 JSON 对象里,至少包含四个可控制字段:jsonrpc、method、params、id。

  • jsonrpc固定为字符串"2.0",用来告诉服务端协议版本。
  • method是服务端要调用的方法名。方法名可以自己定义命名规则,常见的是用点号分层,类似user.getById、order.create。用点号的好处是,服务端可以按前缀做权限校验、做路由分组。
  • params是传给方法的参数,可以是一个数组(按位置传参),也可以是一个对象(按名称传参)。传对象的方式可读性更好,而且能避免参数位置写错的问题,我后文会详细讲。
  • id是用来关联请求和响应的标识符。里可以放字符串、数字或 null,但实际操作中我强烈建议传一个唯一且单调递增的数字,别用 null 也别省略,否则你没法区分这个响应到底是哪个请求返回的。

这里有个细节:如果请求没有 id,那么这个请求被称为 notification,服务端执行后不会返回任何响应。我习惯把它当“只发命令、不需要往回拿结果”的接口来用。

2.2 响应对象与 error 的规范

响应对象同样是个 JSON 对象。正常情况,响应必然包含result;出错时,响应必然包含error。这两者在同一个响应里互斥出现。error的结构是:

{ "code": -32601, "message": "Method not found", "data": null }

协议预定义了一批错误码,每个错误码都有明确含义。我平时最常用的几个:

code含义触发场景
-32700解析错误服务端收到无法解析的 JSON
-32600无效请求请求 JSON 不是合法对象或字段缺失
-32601方法不存在method 指到未注册的函数
-32602无效参数params 结构不合法或参数校验失败
-32603内部错误服务端执行方法时抛出异常
-32000 到 -32099服务端自定义错误业务层错误,比如数据不存在、权限不足

自定义错误码这一块特别适合做业务层“语义化”。比如我在项目里会统一把“登录态失效”编码成 -32001,把“参数校验失败”编码成 -32002,这样客户端在通用错误处理逻辑里就能直接根据 code 分支处理,不用去解析 message 字符串,也不容易踩到“文案变了导致判断失效”这种坑。

2.3 通知机制:不需要响应的调用

刚才提到 notification 是没有 id 的请求。为什么需要这种东西?很简单:有些调用我们只关心发出去,不关心执行结果。最典型的例子就是“日志上报”和“事件通知”。

比如客户端批量上报操作日志,如果每一条都要等响应,既浪费带宽又拖慢主流程。这时候直接发一个没有 id 的 jsonRpc 请求,服务端收到了就执行,客户端也无须处理响应。再用 RPC 的思路理解,这就像一个返回值为 void 的异步函数,调用方根本不在乎它的返回结果。

不过要提醒一句:因为 notification 没有响应,如果服务端执行出错,客户端是完全感知不到的。所以只有“执行失败不致命”的场景才适合用 notification,真正重要的操作还是老老实实带上 id,确保能拿到执行结果再做下一步决策。

2.4 批量请求:一次发多条

jsonRpc 2.0 还支持批量操作:客户端可以把多个请求放在一个 JSON 数组里发送,服务端按顺序执行,最后也返回一个数组。这里有一个兼容陷阱:如果传入的是一个空数组,服务端应该返回一个解析错误,因为从语义上“空数组不包含任何请求”,属于无效请求。

批量请求在执行时,服务端会为每个请求单独生成对应响应,并且响应数组里的每一项都会带上原请求的 id。如果单个请求本身就是 notification,那么该请求在返回数组里就没有对应项。这个机制在处理“一批状态更新”的时候特别有用,能显著减少 HTTP 交互次数,但服务端实现时要特别注意处理顺序和部分失败的组合逻辑,这点我在后面排查章节会展开讲。

3. 从零实现一套 jsonRpc 服务

3.1 技术选型与工程结构

协议讨论完了,接下来就是动手环节。我选 Python 标准库来做一版最小实现,这样没有第三方依赖,任何机器装了 Python 就能跑,能最大限度展示 jsonRpc 本身的逻辑。更复杂项目里我也会用类似框架,但底层的这套路由和错误处理思想是通用的。

先想清楚服务端需要干几件事:

  1. 暴露一个 HTTP 入口,接收 POST 请求。
  2. 读 body,解析成 JSON。
  3. 判断是单个请求还是批量数组。
  4. 校验jsonrpc字段和method字段。
  5. 从注册表找到对应的方法并调用。
  6. 捕获异常并统一转成协议错误响应。
  7. 序列化结果返回给客户端。

基于这个流程,我通常会把工程拆成三个文件:protocol.py负责错误码和异常定义,server.py负责路由和分发,client.py封装客户端请求逻辑。这种拆分的好处是以后如果要加鉴权、加日志,都有明确的位置可以插进去。

3.2 错误对象与自定义异常

先定义协议里需要用的异常类型。这里要处理的核心是:业务异常和系统异常要分开。系统异常(如方法不存在、参数类型不对)是代码问题,应该返回标准错误码;业务异常(如用户不存在、订单状态不允许操作)则应该走自定义错误码。

class JsonRpcError(Exception): def __init__(self, code, message, data=None): self.code = code self.message = message self.data = data super().__init__(message) class ParseError(JsonRpcError): def __init__(self): super().__init__(-32700, "Parse error") class InvalidRequest(JsonRpcError): def __init__(self): super().__init__(-32600, "Invalid Request") class MethodNotFound(JsonRpcError): def __init__(self): super().__init__(-32601, "Method not found") class InvalidParams(JsonRpcError): def __init__(self): super().__init__(-32602, "Invalid params") class InternalError(JsonRpcError): def __init__(self, message="Internal error"): super().__init__(-32603, message)

把异常类拆分出来,最大的价值在于路由分发时只要捕获JsonRpcError就能精确转成响应,其他未知异常在兜底逻辑里转成通用的内部错误。这样客户端收到的错误对象永远是足够规范的。

3.3 服务端路由与分发逻辑

路由是核心中的核心。我维护一个字典,把方法名映射到实际的 Python 函数。分发函数根据请求里的method去字典里查找,找不到就抛MethodNotFound;找到后把params解包传给目标函数。如果目标函数内部抛了业务异常,它会自动冒泡到分发层被捕获并返回。

class JsonRpcServer: def __init__(self): self._methods = {} def register(self, name=None): def decorator(func): self._methods[name or func.__name__] = func return func return decorator def _invoke_single(self, request): if isinstance(request, list): return self._invoke_batch(request) if not isinstance(request, dict): raise InvalidRequest() if request.get("jsonrpc") != "2.0": raise InvalidRequest() method = request.get("method") if not isinstance(method, str) or method == "": raise InvalidRequest() params = request.get("params", []) if not isinstance(params, (list, dict)): raise InvalidParams() func = self._methods.get(method) if func is None: raise MethodNotFound() try: if isinstance(params, dict): result = func(**params) else: result = func(*params) except JsonRpcError: raise except Exception as e: # 记录日志后转为内部错误,避免把堆栈细节暴露给客户端 raise InternalError(f"Server internal error: {e}") if "id" in request and request["id"] is not None: return {"jsonrpc": "2.0", "result": result, "id": request["id"]} return None def _invoke_batch(self, requests): if len(requests) == 0: raise InvalidRequest() return [resp for resp in (self._invoke_single(req) for req in requests) if resp is not None] def handle(self, raw_body): try: data = json.loads(raw_body) except json.JSONDecodeError: data = None if data is None: return {"jsonrpc": "2.0", "error": ParseError().to_dict(), "id": None} try: result = self._invoke_single(data) if result is None: return None return result except JsonRpcError as e: return {"jsonrpc": "2.0", "error": e.to_dict(), "id": data.get("id", None) if isinstance(data, dict) else None}

这段代码里有两个容易忽略的细节。第一,空的 batch 数组要抛InvalidRequest,如果不处理,客户端发了[]你回一个空数组,就把协议语义弄歪了。第二,notification 的返回是None,但 HTTP 层依然要回 200 空 body,不能因为业务层没有响应就断开连接。我在 HTTP 包装层会做区分:如果handle返回None,就响应 204。

3.4 HTTP 层封装

既然走的是 RPC over HTTP,那么入口函数只需要做一件事:把请求体传给JsonRpcServer.handle,然后把结果以 JSON 响应。这里我固定用application/json作为 Content-Type。虽然 JSON-RPC 协议本身不限定传输层,但在 HTTP 上,很多框架默认的 POST 表单处理会干扰原始 body 的读取,所以我都会在代码里显式指定request.get_data()拿原始字节流,避免被中间层解析掉。

from http.server import BaseHTTPRequestHandler, HTTPServer class RpcHttpHandler(BaseHTTPRequestHandler): server_version = "JsonRpcServer/1.0" def do_POST(self): content_length = int(self.headers.get("Content-Length", 0)) raw = self.rfile.read(content_length) response = rpc_server.handle(raw) if response is None: self.send_response(204) self.end_headers() return body = json.dumps(response, ensure_ascii=False).encode("utf-8") self.send_response(200) self.send_header("Content-Type", "application/json; charset=utf-8") self.send_header("Content-Length", str(len(body))) self.end_headers() self.wfile.write(body) def log_message(self, format, *args): # 减少调试噪音 pass

如果不是自研而是引入现有框架,整体结构也一样。Flask 里就是@app.post("/rpc")一个路由,FastAPI 里甚至可以用Request拿原生 body 再做分发。最核心的handle方法完全可以复用。

3.5 客户端封装:id 生成与超时控制

客户端相对简单,核心就三步:构造请求、发送、解析响应。但我踩过不少坑,这里分享几个容易出问题的地方。

第一个坑是 id 生成策略。很多人直接用当前时间戳当 id,如果同一毫秒内并发发多个请求,id 就重复了,响应回来容易错乱。我推荐用一个进程内自增计数器,配合进程号做前缀,比如100001、100002这样单调递增。虽然 JSON-RPC 不要求 id 单调,但越排他越好排查问题。

第二个坑是超时。发送 RPC 时如果不设置超时,一旦网络波动,客户端请求可能挂很久。我的习惯是区分连接超时和读超时:连接 3 秒,读超时按业务复杂度给 10 到 30 秒。对于长时间任务,不要在客户端死等同步结果,交给异步通知或任务查询接口更稳健。

一个最小实现如下:

import json import urllib.request class JsonRpcClient: def __init__(self, endpoint, timeout=10): self.endpoint = endpoint self.timeout = timeout self._id = 0 def _next_id(self): self._id += 1 return self._id def call(self, method, params=None, timeout=None): payload = { "jsonrpc": "2.0", "method": method, "params": params, "id": self._next_id(), } req = urllib.request.Request( self.endpoint, data=json.dumps(payload).encode("utf-8"), headers={"Content-Type": "application/json"}, method="POST", ) with urllib.request.urlopen(req, timeout=timeout or self.timeout) as resp: data = json.loads(resp.read().decode("utf-8")) if "error" in data: raise JsonRpcError(data["error"]["code"], data["error"]["message"], data["error"].get("data")) return data.get("result")

用的时候一行调用就行:

client = JsonRpcClient("http://127.0.0.1:8000/rpc") user = client.call("user.getById", {"userId": 1001})

需要强调的还是要看error字段优先于result。我遇到过一些客户端实现先看 result 再看 error,导致服务器返回 error 时居然把 error 对象当成结果返回。正确的检查顺序永远是:先判断有没有 error,有 error 就抛异常或者走错误分支。

3.6 关于参数传递风格的最佳实践

jsonRpc 的 params 既支持数组又支持对象。我自己的项目里,新建接口一律推荐用对象传参。数组传参会带来一个严重问题:调用方必须记得每个位置的含义,一旦后续服务端参数顺序调整,客户端旧版本传参就会错位,而且不会产生任何编译期报错,只能在运行时暴露。

对象传参让每个参数都有名字,比如{"userId": 1001, "includeProfile": true}。就算服务端以后增加了新参数,旧客户端不传也不影响。配合服务端的默认值,兼容性非常好。只有那种参数极少的接口才建议用数组,比如一个ping不带任何参数,或者add(1, 2)这种纯算数方法。

4. 生产环境里的坑与排查技巧

4.1 id 类型引起的诡异对不上

这个问题的表现是:服务端明明把 results 返回了,客户端却认为响应和请求对不上。原因很直接,id 用了「不同格式但在 JSON 里会被混淆」的值。比如一个客户端发字符串"1",服务端返回数字1,虽然看着都是 1,但类型不一致,严格比较就会失败。

解决办法是在客户端统一 id 类型,服务端也统一不修改请求的 id。实现时,服务端响应里的 id 必须原样从请求里复制,不能做任何类型转换。我项目里的规范就是:永远用整型自增 id,杜绝字符串和整型混用。这样排查日志的时候也方便直接按 id 搜。

4.2 批量请求里的部分失败

批量请求最大的坑在于“一条失败要不要影响其他条”。协议本身对此没有强制规定,有些实现是“有一条出错就整体返回错误”,有些是“每条请求单独处理,错误条目留在对应的响应位上”。实际开发中,我建议按独立处理来设计:每个请求单独执行,单独返回 result 或 error。如果某一项出错,不影响其他项执行。这更符合批量调用的直觉,也方便客户端针对每一项分别判断。

典型的批量响应长这样:

[ {"jsonrpc": "2.0", "result": 1, "id": 1}, {"jsonrpc": "2.0", "error": {"code": -32601, "message": "Method not found"}, "id": 2}, {"jsonrpc": "2.0", "result": 3, "id": 3} ]

但这里有个细节:批量请求通常意味着多条业务操作可能不是并发安全的。如果同一批请求里既要查又要改同一条数据,服务端实现时必须考虑事务边界。我就遇到过客户端发一个批量请求,先减库存后查库存,结果因为执行顺序问题查到了旧数据。所以凡是批量里有“写后读”依赖的,我建议拆成两次独立调用,或者服务端对批量请求加一个可选的串行化事务开关。

4.3 服务端吞掉异常导致空响应

写服务端时,最常见的错误是最外层直接try-except捕获了所有异常,然后 an empty 200 返回。客户端等半天,拿到的却是空 body,解码 JSON 时报错,一时不知道是服务端崩了还是网络异常。

排查思路也很简单:首先确认服务端是否在 HTTP 层把异常转换成了-32603错误响应。如果发现返回了空 body,优先查日志,看看服务端是否在构造响应体之前就写入了 header。另一个常见原因是框架的异常中间件把异常直接吞掉,在业务代码外边返回了一个默认的 HTTP 500。我习惯是在入口处包一层统一错误处理:

try: response = rpc_server.handle(raw) except Exception: traceback.print_exc() response = {"jsonrpc": "2.0", "error": InternalError().to_dict(), "id": None}

不管什么异常,至少保证客户端能收到结构合法的 JSON-RPC 错误响应,而不是一堆堆栈文本或者空 body。这对客户端排查问题有实质帮助:能少一层猜测。

4.4 超大 JSON 和深层嵌套的安全问题

jsonRpc 虽然只用 JSON,但 JSON 本身可能被构造得极其复杂,比如嵌套 10000 层数组、超大字符串。如果服务端不做任何限制,一个恶意请求或一个 bug 客户端就可能直接把服务端的内存和 CPU 打满。

我的做法是在入口层加两个保护:max_body_size和max_depth。比如 body 超过 1MB 直接拒绝,重试也没必要;JSON 解析后递归深度超过 100 层也直接返回InvalidRequest。Python 里解析 JSON 默认有递归深度限制,但总会抛一个很底层的异常,不如自己可控。此外,方法执行前对关键参数做类型校验,别让一个字符串被误当成列表迭代,否则容易产生比较奇怪的异常。

4.5 鉴权、日志与追踪的最小实践

这些虽然不属于协议本身,但生产环境离不开。我的最小实践是三层:

  • 传输层鉴权:HTTP 入口统一校验Authorizationheader,比如内部服务之间用固定的 token,校验失败直接返回 401。
  • 方法级鉴权:某些方法只允许特定角色调用。配合方法名的点号前缀,可以做一个简单的权限表,比如admin.*只允许管理员调用。
  • 全链路日志:记录每个请求的id、method、params摘要、耗时、返回状态。响应失败时把 error 对象完整记录。

日志格式我习惯用 JSON 一行一条,包含trace_id、method、params_hash、cost_ms、code这些字段。排查慢调用和错误时,直接按trace_id过滤就能快速定位问题。这套东西虽然简单,但能解决绝大多数“不知道哪个方法出错了”的诉求。

4.6 常见错误速查表

现象可能原因处理办法
收到 -32700body 不是合法 JSON 或者被中间层转码确认 Content-Type,用原始字节解析
收到 -32600请求对象非 dict 或 jsonrpc 字段错误检查客户端封装是否少了 jsonrpc 字段
收到 -32601method 名称写错或未注册核对服务端注册表,检查前后缀
收到 -32602params 类型不对或参数名不匹配检查是位置传参还是命名传参
收到 -32001自定义业务错误(如鉴权失败)根据服务端约定的 data 做细分处理
空 body / 连接断开服务端异常未走统一错误处理检查入口 try-except,先保证错误响应
响应 id 和请求 id 不一致服务端或客户端做了类型转换统一 id 类型并原样回传
批量请求整体失败批量实现没有逐条独立处理改为逐条调用,单条失败不影响其他

5. 进一步扩展:从单机到服务化

jsonRpc 做单个服务很简单,但一旦服务多了,还是会遇到“怎么发现服务地址”“怎么做负载均衡”“服务挂了怎么切换”的问题。这里不展开讲服务网格,只说低成本的做法:可以在 jsonRpc 外面包一层代理服务,客户端只连代理,代理按 method 前缀把请求转发到不同的后端实例。这样的好处是,客户端不需要每个服务都配一个地址。

我之前做过一个类似的内部网关,用一张静态路由表维护 method 前缀和后端地址的映射:

user.* -> 10.0.0.11:8000/rpc order.* -> 10.0.0.12:8000/rpc report.* -> 10.0.0.13:8000/rpc

代理剥掉传输层细节后,把原始 jsonRpc body 原封不动转发给后端,再把后端响应原样传回。这个方法本质上就是在协议层没有做任何改动,只是多跳了一跳。如果后面服务更多了,再引入注册中心做动态发现也不难:路由表从静态配置变成“服务名 + 实例列表”,转发时按轮询或加权策略选实例。这套思路在中小型团队里已经足够稳定,比直接上全套微服务框架轻量太多。

5.1 超时策略和熔断怎么落地

当服务多了以后,客户端“一刀切”式的超时就不够用了。我给不同方法配不同超时,比如查询类 5 秒,报表生成类 30 秒,然后对失败率高的方法做熔断:连续失败 N 次后直接快速失败,不再请求后端,过一会儿再放一部分流量试探。这个思想的落地点在于,所有错误最终都统一聚合成 JsonRpcError,所以熔断器只需要依赖 error code 就行,比如只对 -32603 或网络错误计数,而对业务类的 -32001 不计数,因为它们不是服务端故障。

5.2 和异步消息系统的配合

不是所有调用都适合同步等待结果。比如用户上传了一批订单,客户端希望服务端处理完再通知结果,这时同步 RPC 会让调用方等太久。我的做法是把 jsonRpc 和消息队列配合起来:入口方法收到请求后,把任务投递到队列,立刻返回一个taskId,客户端稍后用另一个 jsonRpc 方法查询任务状态。这样既保留了 jsonRpc 的调用语义,又避免了长时间占用连接。这个模式在文件处理、批量导入、异步报表的场景里特别实用。

5.3 遇到性能瓶颈怎么优化

jsonRpc 的性能瓶颈通常不在协议本身,而在序列化和网络 IO。简单实测下来,Python 用标准 json 库序列化一个小请求,耗时大概在几十微秒到上百微秒级别,HTTP 往返才是大头。如果压测发现吞吐不够,我一般按顺序做这几件事:开连接复用、合并批量请求、换更快的 JSON 库(比如用 C 扩展实现的那几个)、必要时再考虑上异步框架。记住一条原则:先用最简单的方案跑通,再根据压测数据决定要不要优化。很多项目一上来就用重度框架,反而把问题复杂化了。

最后再分享一点项目中的个人体会

jsonRpc 这套东西,我实际用了好几年,最大的感受就是:它把“服务间沟通”变成了一件自然且可控的事。要说它有什么魔力,其实没有,它就是一个极简的、能被任何语言解析的调用协议。但因为简单,它的坑往往藏在实现细节里:id 怎么回传、错误怎么区分、批量怎么处理、超时怎么兜底。这些细节决定了一次调用在真实环境里是不是足够稳。我踩过很多次 id 类型不一致的坑,也看过服务端吞异常导致客户端等超时的现场,所以特别想把这些经验记录下来。如果你现在正准备给内部系统设计 RPC,或者被现有接口的调用方式绕得头疼,不妨先拿 jsonRpc 搭一个最小原型,让服务端的“函数”能真正被远程调用起来,再逐步填充鉴权、日志、监控这些周边能力。它的上手速度绝对能让你意外,而且,一旦你熟悉了这套思路,后续再接触 gRPC 这类更重的方案时,心里也会更清楚“强约束”到底解决的是哪一部分问题。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/11 16:02:12

玉米生长阶段检测数据集实战:从标注格式转换到YOLO训练落地

简介:这份玉米生长阶段目标检测数据集面向农业视觉算法开发者与目标检测学习者,用于训练和验证玉米生育期识别模型。数据集将玉米生长划分为幼苗、初期生长、快速生长、成熟前期、成熟阶段及成熟期不健康状态共6个类别,覆盖从植株矮小到穗部发…

作者头像 李华
网站建设 2026/10/11 15:59:07

个人博客 1:AI 模块开发环境搭建 + 代码拉取运行 + 项目可行性分析与 AI 模型选型说明(TaoToken 统一 Key 接入篇)

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/10/11 15:58:47

AnyPS5:面向PS5开发者的轻量级用户态仿真调试方案

1. 项目概述:这不是一个“破解”工具,而是一套面向PS5开发者的本地化调试与模拟验证方案“AnyPS5”这个名称在近期技术社区中频繁出现,但它的实际定位常被误读。我接触过多个使用该名称的内部项目,它们共同指向一个明确目标&#…

作者头像 李华
网站建设 2026/10/11 15:58:44

AnyPS5多场景适配方案:从SSD扩展到HDMI 2.1的全面调优

很多朋友看到“AnyPS5”这个项目代号时,第一反应都会问:这是什么意思?是给PS5做破解?还是搞一套万能模拟器?先泼一盆冷水:都不是。AnyPS5 的核心思路,是一套围绕 PS5 主机的“多场景通用适配方案…

作者头像 李华
网站建设 2026/10/11 15:56:21

数组与链表深度拆解:内存布局、复杂度真相与Java工程选型实战

1. 先从一道很普通的面试题说起 数组和链表,几乎是每个Java开发者在初学阶段就会碰到的“一对儿”数据结构。你可能早就背过它们的区别:数组是连续内存,链表是离散节点;数组查询快、增删慢,链表增删快、查询慢。考试、…

作者头像 李华
网站建设 2026/10/11 15:55:12

联软发布企业级MCP中台:让AI连接业务系统更简单、更可控

随着AI Agent逐步进入办公、研发、运营和生产等业务场景,企业需要连接的系统越来越多。OA、CRM、知识库、WMS等系统往往拥有不同的接口和认证方式,传统的分散式MCP接入不仅配置复杂,也容易带来权限失控、调用难追溯等管理问题。近日&#xff…

作者头像 李华