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 与注册

来自 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
				
traceparent 跨进程传播。客户端把 W3C trace 上下文写进 rpcx 的请求 metadata, 服务端插件从中提取并开一个父级为调用方的 span。关闭 tracing 时两侧都是 no-op, 所以这段逻辑无条件接线。

验证

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

相关