10分钟掌握 JSON-RPC 协议,面试加分、设计不踩坑

本文已收录至GitHub,推荐阅读 👉 Java随想录

微信公众号:Java随想录

分布式系统中,不同服务之间需要一种可靠的方式来通信。远程过程调用(RPC)是一种常见的选择,而 JSON-RPC 是其中比较简单的一种。

这篇文章介绍 JSON-RPC 2.0 协议的核心内容,包括消息格式、错误处理和实际应用场景。

JSON-RPC 概述

什么是 JSON-RPC

JSON-RPC 是一种无状态、轻量级的远程过程调用协议,使用 JSON 作为数据格式。它的设计目标就是简单------协议规范只有几页文档。

JSON 作为数据交换格式,几乎所有主流编程语言都有良好的支持,包括 JavaScript、Python、Java、C#、Go 等。这使得 JSON-RPC 能够轻松实现跨语言调用。

协议本身不绑定传输层,可以跑在 HTTP、WebSocket、TCP Socket 等各种消息传输环境上。开发者可以根据业务需求选择合适的传输方式。

JSON-RPC 的发展历程

JSON-RPC 有两个主要版本:1.0 和 2.0。

1.0 版本最早提出了基于 JSON 的 RPC 概念,但在规范性方面有所欠缺。2010 年发布的 2.0 版本做了重要升级,加入了批量调用支持、统一的错误对象结构。两个版本通过 jsonrpc 字段来区分。

JSON-RPC 的核心特点

JSON-RPC 有几个值得注意的特点:

  • 简洁性:规范文档很短,请求和响应结构清晰明了。
  • 跨语言支持:任何能解析 JSON 的语言都可以实现 JSON-RPC 客户端或服务端。
  • 无状态设计:每个请求独立,协议不维护会话状态。这种设计简化了服务端的实现,也更容易水平扩展。
  • 批量处理能力:2.0 版本支持在单个请求中包含多个 RPC 调用,服务端返回结果数组。
  • 双向通信:通过长连接和通知机制,JSON-RPC 也支持服务端主动向客户端推送消息。

协议规范详解

协议约定与术语

JSON-RPC 规范中使用了 RFC 2119 定义的关键字:MUST、MUST NOT、SHOULD、SHOULD NOT、MAY。这些术语描述了实现者必须、应该或可以遵循的行为规范。

客户端(Client):发起请求的实体,负责构造请求对象并处理响应。

服务端(Server):接收请求并返回响应的实体,处理请求并生成响应。

同一个实现可以同时扮演客户端和服务端。比如在对等网络中,两个节点可能既向对方发起请求,也响应来自对方的请求。

数据类型系统

JSON-RPC 继承自 JSON 的类型系统,包含六种数据类型:

基本类型:String(字符串)、Number(数值)、Boolean(布尔值)、Null(空值)

结构化类型:Object(对象)、Array(数组)

这些类型名称首字母必须大写,包括 True 和 False。

成员名称(字段名)在客户端与服务端之间交换时必须区分大小写。函数、方法、过程这三个术语可以互换使用,都指向可以被调用的可执行单元。

消息格式深度解析

JSON-RPC 2.0 定义了三种核心消息类型:请求对象(Request Object)响应对象(Response Object)通知对象(Notification Object)

请求对象结构

请求对象是客户端向服务端发起 RPC 调用的载体:

json 复制代码
{
  "jsonrpc": "2.0",
  "method": "subtract",
  "params": [42, 23],
  "id": 1
}
  • jsonrpc 字段:协议版本标识,值必须是字符串 "2.0"。
  • method 字段 :要调用的方法名称,区分大小写。以 "rpc." 开头的方法名预留给 JSON-RPC 内部扩展,如 rpc.subscriberpc.notify
  • params 字段:方法参数,可以是数组(按顺序)或对象(按名称)。参数可选,不需要时可以省略。
json 复制代码
// 索引数组参数:参数顺序与服务端预期一致
{"jsonrpc": "2.0", "method": "subtract", "params": [42, 23], "id": 1}

// 命名对象参数:参数名需与服务端方法签名匹配
{"jsonrpc": "2.0", "method": "subtract", "params": {"minuend": 42, "subtrahend": 23}, "id": 2}
  • id 字段:客户端生成的唯一标识符,用于关联请求和响应。id 可以是字符串、数值或 null。建议避免使用 null,因为 JSON-RPC 1.0 的通知机制使用 null,可能引起混淆。使用数值作为 id 时,应避免小数。

响应对象结构

响应对象是服务端返回给客户端的执行结果:

json 复制代码
// 成功响应
{
  "jsonrpc": "2.0",
  "result": 19,
  "id": 1
}

// 错误响应
{
  "jsonrpc": "2.0",
  "error": {
    "code": -32601,
    "message": "Method not found",
    "data": "详细错误信息"
  },
  "id": 1
}

响应对象必须包含 resulterror 之一,不能同时包含两者。id 必须与对应请求中的 id 保持一致。

如果请求本身存在错误(如无效的 JSON 格式或非法请求结构),导致服务端无法确定原始 id 时,响应中的 id 必须为 null。

通知机制

通知是一种不需要响应的请求,通过省略 id 字段来标识:

json 复制代码
{
  "jsonrpc": "2.0",
  "method": "update",
  "params": [1, 2, 3, 4, 5]
}

通知适用于日志记录、事件发布、进度通知等场景。客户端只负责发送消息,不需要处理响应。

由于没有响应机制,客户端无法得知通知是否被成功处理。在批量请求中,通知请求不会产生对应的响应。

错误处理机制

错误对象结构

JSON-RPC 2.0 定义了标准化的错误对象:

json 复制代码
{
  "code": -32603,
  "message": "Internal error",
  "data": { "details": "数据库连接失败" }
}
  • code 字段:整数错误码,标识错误类型。
  • message 字段:简短的人类可读错误描述,通常是一句话。
  • data 字段:可选,携带错误的附加信息,可以是字符串、对象或任何有效的 JSON 值。

预定义错误码

JSON-RPC 2.0 在 -32768 到 -32000 范围内预留了预定义错误码:

错误码 名称 说明
-32700 Parse Error 解析错误,服务端收到的 JSON 格式无效
-32600 Invalid Request 无效请求,发送的不是有效的请求对象
-32601 Method Not Found 方法不存在或不可调用
-32602 Invalid Params 参数无效
-32603 Internal Error JSON-RPC 内部错误

-32000 到 -32099 范围内的错误码保留给服务端自定义使用。应用程序也可以定义自己的错误码(通常为负数且绝对值小于 32767)。

错误处理建议

  • 合理使用 data 字段:code 和 message 提供错误的基本分类,data 可以包含调试信息、堆栈跟踪或业务相关的详细信息。生产环境中注意控制敏感信息暴露。
  • 统一错误处理中间件:在服务端统一拦截和处理错误,将业务异常转换为标准化的 JSON-RPC 错误格式,避免暴露内部实现细节。
  • 区分错误类型:某些错误(如参数校验失败)是客户端问题,可以提示用户修正;另一些错误(如数据库故障)需要服务端介入处理。

批量调用与性能优化

批量请求机制

JSON-RPC 2.0 支持在单个请求中发送多个 RPC 调用,服务端返回相应的响应数组。这一机制在高并发场景下可以减少网络往返次数。

json 复制代码
// 批量请求
[
  {"jsonrpc": "2.0", "method": "sum", "params": [1, 2, 4], "id": "1"},
  {"jsonrpc": "2.0", "method": "notify_hello", "params": [7]},
  {"jsonrpc": "2.0", "method": "subtract", "params": [42, 23], "id": "2"},
  {"jsonrpc": "2.0", "method": "get_data", "id": "9"}
]

对应的批量响应:

json 复制代码
[
  {"jsonrpc": "2.0", "result": 7, "id": "1"},
  {"jsonrpc": "2.0", "result": 19, "id": "2"},
  {"jsonrpc": "2.0", "result": ["hello", 5], "id": "9"}
]

通知请求(没有 id 字段)不会产生响应。响应数组中各元素的顺序与请求顺序无关,客户端通过匹配 id 来关联请求和响应。

批量调用的边界情况

规范对批量调用中的边界情况有明确定义:

如果批量请求本身不是有效的 JSON 或不是包含至少一个值的数组,服务端应返回单对象响应而非数组。空数组 [] 被视为无效请求。

如果批量请求中的所有请求都是通知,服务端不需要返回任何响应。其他情况下,即使某些请求处理失败,响应数组中仍应包含对应请求的错误响应。

性能优化策略

  • 连接复用:使用持久化连接(如 HTTP Keep-Alive 或 WebSocket)可以避免频繁建立和销毁连接的开销。
  • 批量聚合:将多个相关的小请求合并为一个批量请求,减少网络往返次数。
  • 压缩传输:启用 HTTP 压缩(如 gzip)可以减少大型 JSON 响应体的传输体积。
  • 异步处理:使用异步调用和通知机制,提高客户端的并发处理能力。

与 RESTful API 的对比分析

设计理念差异

JSON-RPC 和 RESTful 代表了两种不同的 API 设计哲学。JSON-RPC 是过程导向的,关注的是"做什么操作";RESTful 是资源导向的,关注的是"操作什么资源"。

以用户操作为例:

json 复制代码
// JSON-RPC: 直接调用方法
{"jsonrpc": "2.0", "method": "createUser", "params": {"name": "张三", "email": "zhang@example.com"}, "id": 1}

// RESTful: 操作资源
POST /users
{"name": "张三", "email": "zhang@example.com"}

通信模式对比

维度 JSON-RPC RESTful
语义抽象 方法调用 资源操作
URL 角色 仅作为端点地址 表示资源路径
HTTP 方法 仅使用 POST 充分利用 GET/POST/PUT/DELETE
状态管理 可维护会话状态 倡导无状态设计
灵活性 更紧凑,适合精确控制 更灵活,适合通用场景

选型建议

适合使用 JSON-RPC 的场景

  • 内部服务间通信,接口相对稳定
  • 需要精确的方法语义和参数控制
  • 对带宽敏感,需要紧凑的消息格式
  • 团队对 RPC 模式更熟悉

适合使用 RESTful 的场景

  • 公开 API,需要良好的可发现性
  • 资源概念明确的 CRUD 操作
  • 需要利用 HTTP 缓存机制
  • 追求 API 的自我描述能力

安全性考量

常见安全威胁

JSON-RPC 的简洁设计虽然降低了实现复杂度,但也带来了一些安全考量:

  • 数据泄露:JSON-RPC 通常通过 HTTP 传输,未加密的通信可能被中间人攻击截获。
  • 方法枚举:如果服务端没有正确限制可调用方法,攻击者可能通过枚举方法名发现未公开的接口。
  • 拒绝服务:恶意客户端可能发送大量请求或构造超大的批量请求,耗尽服务端资源。
  • 注入攻击:不安全的参数处理可能导致 SQL 注入、命令注入等安全漏洞。
  • 重放攻击:截获的有效请求可能被攻击者重放,造成非预期的重复操作。

安全加固措施

  • 传输层加密:使用 HTTPS 协议,确保通信内容的机密性和完整性。
  • 身份认证与授权:实现身份验证机制(如 JWT、OAuth 2.0),在方法层面实施细粒度的授权控制。
  • 输入验证:对所有参数进行严格的类型检查和值域验证。
  • 速率限制:实施请求速率限制,防止恶意高频访问。
  • 方法白名单:仅暴露必要的方法,对未公开的方法返回 -32601 错误。
  • 日志与监控:记录详细的请求日志,监控异常模式,及时发现潜在攻击。

结语

JSON-RPC 2.0 协议设计简洁,在分布式系统通信中有其适用场景。规范本身很短,学习和实现成本都不高,JSON 格式的请求/响应便于调试和日志记录。

当然,JSON-RPC 也有局限性。它缺少内置的元数据机制,类型系统相对简单。在需要高度标准化、复杂类型系统或强类型安全的场景中,可以考虑 gRPC、Thrift 等方案。

如果你的业务场景需要简单、透明的服务间通信方式,JSON-RPC 是一个值得考虑的选择。

相关推荐
凤年徐2 小时前
C++手撕红黑树:从0到200行,拿下STL map底层核心
c++·后端·算法
IT_陈寒2 小时前
Python的列表推导式里藏了个坑,差点让我加班到凌晨
前端·人工智能·后端
卷无止境3 小时前
podman与docker的区别和生产环境最佳实践
后端
程途知微3 小时前
ConcurrentHashMap线程安全实现原理全解析
java·后端
Mars酱3 小时前
1分钟编写贪吃蛇 | JSnake贪吃蛇单机版
java·后端·开源
卷卷说风控3 小时前
养了10年风控,今年开始养「虾」了
后端
王码码20354 小时前
Go语言中的Elasticsearch操作:olivere实战
后端·golang·go·接口
小旭95274 小时前
Spring Security 实现权限控制(认证 + 授权全流程)
java·后端·spring
weixin_408099674 小时前
【完整教程】天诺脚本如何调用 OCR 文字识别 API?自动识别屏幕文字实战(附代码)
前端·人工智能·后端·ocr·api·天诺脚本·自动识别文字脚本