本文已收录至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.subscribe、rpc.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
}
响应对象必须包含 result 或 error 之一,不能同时包含两者。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 是一个值得考虑的选择。