rpcx — 无需 IDL 的 RPC
pkg/core/rpcx 管 rpcx 的生命周期。它和 gRPC 最大的区别:
没有 IDL、没有代码生成——一个服务就是一个普通 struct,导出方法签名对上
就能被调用。服务端由 verticle.Run 按 rpcx: 段创建,
客户端在 Setup 里用 env.RPCXClient 建立。
运行
完全离线,不需要 make dev。同时开 HTTP :8080 与
rpcx :8972(默认 network: tcp)。配置文件是目录里的
config.yaml,-conf 默认指向它。
go run ./examples/rpcx # 等价于 make example NAME=rpcx
# http :8080 + rpcx :8972
curl -i localhost:8080/api/v1/greet/world # 本地调用
curl -i localhost:8080/api/v1/greet-remote/world # 真实 rpcx 往返(target 默认指向自己)
它演示了什么
- 服务即 struct:方法签名
func(ctx, *Args, *Reply) error, 参数与返回值是导出字段的 struct(默认 gob 编码)。 - 注册走 hook:实现
RegisterRPCX,由verticle.Run在 serve 之前调用,且只在配了rpcx:段时才调用。 - 本地调用 vs 远程调用:
greetLocal直接调服务方法(无网络),greetRemote走真实 rpcx 客户端。 - 懒连接:和 gRPC 不同,
RPCXClient不在 Setup 时探测连通性, 第一次Call才拨号——所以 target 填自己也能跑通。 - 错误跨边界只剩消息:
errs.Kind与业务 code 传不过去, 客户端要把错误再映射回自己的 code。
服务端:struct 与注册
来自 examples/rpcx/main.go。Greet 是普通方法,
RegisterRPCX 用 RegisterName("Greeter", …) 把它挂上——
注册名要和客户端的 servicePath 一致。
func (g *Greeter) Greet(ctx context.Context, args *GreetArgs, reply *GreetReply) error {
if args.Name == "" {
// Only the message survives the RPC boundary — the client side
// maps it back to a business code.
return errs.Invalid(40001, "name is required")
}
reply.Greeting = g.prefix + args.Name
obs.Info(ctx, "greet over rpcx", "name", args.Name)
return nil
}
// RegisterRPCX is called by verticle.Run before serving, and only when the
// `rpcx:` section is configured.
func (s *rpcxService) RegisterRPCX(srv *rpcxserver.Server) error {
return srv.RegisterName("Greeter", s.greeter, "")
}
客户端:懒连接调用
来自同一文件的 greetRemote。客户端在 Setup 里建立,
但只有 Target 非空时才建;没配就是 503。
if s.client == nil {
return nil, errs.Unavailable(50301, "no rpcx peer configured (set rpcxdemo.target)")
}
var reply GreetReply
if err := s.client.Call(ctx, "Greet", &GreetArgs{Name: r.PathValue("name")}, &reply); err != nil {
return nil, errs.Unavailable(50302, "rpcx call failed: %v", err)
}
return map[string]any{"greeting": reply.Greeting, "where": "remote"}, nil
sequenceDiagram
participant H as HTTP handler
participant C as traceXClient(RPCXClient)
participant M as 请求 metadata
participant S as tracePlugin(rpcx server)
participant G as Greeter.Greet
H->>C: Call(ctx, "Greet", args, reply)
Note over C: 开 client span
C->>M: 注入 W3C traceparent
M->>S: 线上传输(rpcx 复制 req metadata)
S->>S: Extract 出父 span,开 server span
S->>G: 调用服务方法
G-->>S: reply
S->>S: PostCall 结束 span
S-->>C: reply
C->>C: 结束 client span
验证
curl -i localhost:8080/api/v1/greet/world # 本地调用
curl -i localhost:8080/api/v1/greet-remote/world # 真实 rpcx 往返(target 默认指向自己)
两个端点都返回 {"code":0,"msg":"ok","data":{"greeting":"Hello, world",…}},
差别在 where 字段:本地是 "local",远程是
"remote"。空名字调用服务方法会返回
errs.Invalid(40001, …)。
测试
main_test.go 完全离线:在临时端口上起真实 rpcx.New
server(Network: "tcp", Addr: "127.0.0.1:0"),注册
Greeter,用 rpcx.PeerClient 连上并发起真实调用,
断言 Greeting == "Hi world";另有两例断言空名字会报错,以及
服务端返回 503 路径。rpcx 在 Serve 内部才绑定监听,
所以测试有一个 waitForAddr 轮询等监听出现——
没有 broker、没有网络。
go test ./examples/rpcx
相关
- gRPC 与 rpcx:IDL、契约与传输的对比。
- 可观测性:traceparent 如何跨进程。
- examples/grpc:需要强契约与多语言互通时的选择。
- Skill:
skills/matex-setup-rpcx/SKILL.md。 - 示例索引。