### Refactor Simplify Design # 代码简化设计文档 (Refactor & Simplify) 本文档记录对最近一轮重构(god-object 文件拆分 + 插件错误日志 + 方法白名单注册) 的**简化 review** 与落地改动。目标是**质量**,不涉及正确性 bug 修复。 ## 1. 背景 最近的提交(`HEAD~11...HEAD`)主要做了两件事: 1. **文件拆分**:把两个"上帝对象"拆成职责内聚的小文件。 - `client/xclient.go` (1212 行) → `xclient_broadcast.go` / `xclient_call.go` / `xclient_discovery.go` / `xclient_transfer.go` - `server/server.go` (799 行) → `server_conn.go` / `server_dispatch.go` / `server_response.go` / `server_shutdown.go` 2. **行为增强**:把之前 `_ = plugin.DoXxx(...)` 吞掉的错误改为记录日志;新增 `RegisterWithMethods` 方法白名单注册;空白名单守卫。 拆分本身是**逐行搬移**(byte-for-byte),四位 reviewer 一致确认搬移无漂移。 真正需要简化的是**新增逻辑**。 ## 2. 发现与处理 四个维度(Reuse / Simplification / Efficiency / Altitude)并行 review,去重后 落地如下改动。 ### 2.1 已修复 | # | 文件 | 问题 | 简化后 | |---|------|------|--------| | 1 | `server/service.go` | **三重空白名单守卫**:`RegisterWithMethods`、`RegisterNameWithMethods` 各有一次 `len(methods)==0` 检查,`register()` 内部 `else` 分支又重复了第三次(且错误文案不同)。第三处对白名单路径不可达(两个公开入口已拦截)。 | 删除 `register()` 内的死分支,改为注释说明:空 slice 会自然落到下方"no suitable methods"检查。规则收敛,文案不再三份。 | | 2 | `server/service.go` | **`RegisterWithMethods` 缺少 nil-Plugins 守卫**:`RegisterName`/`RegisterNameWithMethods` 在 `DoRegister` 前都有 `if s.Plugins == nil` 兜底,唯独 `RegisterWithMethods` 没有,与同族方法不一致(潜在 nil 解引用)。 | 补齐 `if s.Plugins == nil { s.Plugins = &pluginContainer{} }`,与同族方法对齐。 | | 3 | `server/server_shutdown.go` | **拆分留下的接缝垃圾**:`Serve` 的文档注释被搬到 shutdown 文件却无对应函数(悬空注释);`getDoneChan` 被搬来后全仓零调用(死代码)。 | 删除死函数 `getDoneChan` 与悬空注释;把 `Serve` 的文档注释还给 `server.go` 中 `Serve` 函数上方。 | | 4 | `server/service.go` | `reflect.PtrTo` 已废弃(编译器 diagnostic)。 | 替换为 `reflect.PointerTo`。 | | 5 | `server/plugin_test.go` | 新增的 `waitServerReady`(返回 bool,goroutine 安全)已用于消除 goroutine 内的 `t.Fatalf`,但 `TestPluginHeartbeat` 的子 goroutine 里还残留一处 `t.Fatalf`(`go vet` 报警)。 | 改为 `t.Errorf` + `return`,补全该模式,`go vet` 干净。 | ### 2.2 已评估但跳过(避免改变意图或超出 diff 范围) - **插件错误日志"每调用点手写"而非收敛到 `pluginContainer`**(Altitude 提出): reviewer 建议把"吞错并记日志 vs 返回错误"的策略下沉到 container 内部,让调用点 统一 `_ =` 或统一返回。这是**合理的深层改进**,但涉及重新设计 `Do*` 方法族的 错误契约,波及所有插件调用点,**远超本次 review 的 diff 范围**,且属于行为/接口 变更(`/simplify` 明确排除)。留作后续独立重构项。当前每处日志的 message 各有 上下文(`servicePath.method`),并非可折叠的 copy-paste。 - **方法白名单未复用/泛化 `suitableMethods`**(Altitude/Reuse 提出):白名单路径 自己重走 `all` 建 `picked`,并额外 `MethodByName` 探测以区分"不存在"与"签名不合适"。 reviewer 建议给 `suitableMethods` 加过滤器参数统一两条路径。这会改动核心注册机制 的签名与语义,属于设计层变更,**跳过**——现有实现正确且已复用 `suitableMethods` 的产出,`MethodByName` 探测仅用于生成更友好的错误文案,代价可接受。 - **`nil` vs `len==0` 语义**:`register` 用 `methods == nil` 表示"注册全部",非 nil 表示白名单。这是刻意的哨兵语义,公开 API 已在边界拦住空 slice,改为显式 intent 参数属于 API 变更,**跳过**。 - **doc-only / 注释冗长**(`share/context.go`、`client/discovery.go`):均为文档 措辞层面,不影响逻辑,**跳过**。 - **`xclient_discovery.go` `watch()` 去掉了 `sort.Slice`**:这是**减少**工作量而非 回归,且属搬移期的既有行为变更(前序提交 `cd34c37` 已单独处理),**不在本次范围**。 ## 3. 拆分边界评估(Altitude) 四位 reviewer 一致认为新文件边界**内聚合理**: - Server 侧:`conn`(监听/连接/单请求读取/鉴权)、`dispatch`(请求分发/函数调用/错误)、 `response`(发送响应)、`shutdown`(生命周期/优雅关闭/重启)——四个真实职责簇。 - Client 侧:`broadcast`(Broadcast/Fork/Inform 扇出)、`call`(Call/Go/SendRaw/wrap)、 `discovery`(watch/选点/缓存客户端)、`transfer`(SendFile/DownloadFile/Stream)。 无 awkward 的跨文件依赖,无"拆错位置"的接缝(除已修的 #3)。 ## 4. 验证 ``` go build ./... # 通过 go vet ./server ./client ./share # 干净(原 t.Fatalf 报警已消除) go test ./server -count=1 # ok (11.7s) ``` ## 5. 结论 拆分本身干净,无需改动。新增逻辑修掉了 5 处质量问题(死代码、不一致守卫、接缝 垃圾、废弃 API、测试 vet 报警),核心收益是**空白名单规则从三处收敛到边界一处**、 **同族注册方法的 nil-Plugins 守卫对齐**。两项更深的设计改进(插件错误策略下沉、 白名单泛化 `suitableMethods`)超出 `/simplify` 范围,记录留待后续。 --- ### CHANGELOG # [rpcx](http://rpcx.io) ## 1.9.4 - switch the experimental `rdma` transport from rsocket to gordma's rdmanet.Conn (build tag `rdma`) - add RegisterWithMethods/RegisterNameWithMethods to register only a whitelist of a struct's methods (#581) - document server plugin extension points (execution order, parameters, return values) in godoc (#645) - fix printf argument mismatches surfaced by `go vet` ## 1.9.0 - unregister all services on close automatically - add PostHTTPRequestPlugin - support io_uring - add CacheDiscovery - add Oneshot method for XClient - support RDMA ## 1.8.0 - supports distributed rate limiter based on go-redis/redis-rate - move zookeeper plugin to https://github.com/smallnest/rpcx-zookeepr - move consul plugin to https://github.com/smallnest/rpcx-consul - move redis plugin to https://github.com/smallnest/rpcx-redis - move influxd/opentelemetry plugin to https://github.com/smallnest/rpcx-plugins - you can write customized error, for example `{"code": 500, err: "internal error"}` - server support the work pool by `WithPool` - support to write services like `go std http router` style without reflect - simplify async write for service - improve performance ## 1.7.0 - move etcd support to github.com/rpcxio/rpcx-etcd - Broken API: NewXXXDiscovery returns error instead of panic - support AdvertiseAddr in FileTransfer - support Auth for OneClientPool - support Auth for XClientPool - Broken API: add meta parameter for SendFile/DownloadFile - support streaming between server side and client side - support DNS as service discovery - support rpcx flow tracing - support websocket as the transport like tcp,kcp and quic - add CMuxPlugin to allow developing customzied services by using the same single port - re-tag rpcx to make sure the version is less than 2 (for go module) - support visit grpc services by rpcx clients: https://github.com/rpcxio/rpcxplus/tree/master/grpcx - support configing grpc servicves in rpcx server side - improve rpcx performance - add Inform method in XClient - add memory connection for unit tests - supports opentelemetry ## 1.6.0 - support reflection - add kubernetes config example - improve nacos support - improve message.Encode performance - re-register services in etcd v3 - avoid duplicated client creation - add SelectNodePlugin that can interrupt the Select method - support TcpCopy by TeePlugin - support reuseport for http invoke - return reply even in case of server errors - Change two methods' name of client plugin! - Broken API: add error parameter in `PreWriteResponse`(#486) - Broken API: change ReadTimeout/WriteTimeout to IdleTimeout - Support passing Deadline of client contexts to server side - remove InprocessClient plugin - use heartbeat/tcp_keepalive to avoid client hanging ## 1.5.0 - support jsonrpc 2.0 - support CORS for jsonrpc 2.0 - support opentracing and opencensus - upload/download files by streaming - add Pool for XClient and OneClient - remove rudp support - add ConnCreated plugin. Yu can use it to set KCP UDPSession - update client plugins. All plugin returns error instead of bool - support ETCD 3.0 API - support redis as registry - support redis DB selection - fix RegisterFunction issues - add Filter for clients - remove most of build tags such as etcd, zookeeper,consul,reuseport - add Nacos as registry http://nacos.io - support blacklist and whitlist ## 1.4.0 - Support utp and rudp - Add OneClient to support invoke multile servicesby one client - Finish compress feature - Add more plugins for monitoring connection - Support dynamic port allocation - Use go module to manage dependencies - Support shutdown graceful - Add [rpcx-java](https://github.com/smallnest/rpcx-java) to support develop raw java services and clients - Support thrift codec - Setup rpcx offcial site: http://rpcx.io - Add Chinese document: http://cn.doc.rpcx.io or https://smallnest.gitbooks.io/go-rpc-programming-guide ## 1.3.1 - Add http gateway: https://github.com/rpcxio/rpcx-gateway - Add direct http invoke - Add bidirectional communication - Add xgen tool to generate codes for services automatically fix bugs. ## 1.3.0 - Rewrite rpcx. It implements its protocol and won't implemented based on wrapper of go standard rpc lib - Add go tags for pluggable plugins - Add English document: https://github.com/smallnest/rpcx-programming - Add rpcx 3.0 examples: https://github.com/rpcxio/rpcx-examples rpcx 3.0 is not compatible with rpcx 2.0 and below --- ### README - **stable branch**: v1.7.x - **development branch**: master Official site: [http://rpcx.io](http://rpcx.io/) [](https://opensource.org/licenses/Apache-2.0) [](http://godoc.org/github.com/smallnest/rpcx) [](https://github.com/smallnest/rpcx/actions) [](https://goreportcard.com/report/github.com/smallnest/rpcx) [](https://coveralls.io/github/smallnest/rpcx?branch=master) [](_documents/rpcx_dev_qq3.jpg) **Notice: etcd** since rpcx 1.7.6, some plugins have been moved to the independent project: - `etcd` plugin has been moved to [rpcx-etcd](https://github.com/rpcxio/rpcx-etcd) - `zookeeper` plugin has been moved to [rpcx-zookeeper](https://github.com/rpcxio/rpcx-zookeeper) - `consul` plugin has been moved to [rpcx-consul](https://github.com/rpcxio/rpcx-consul) - `redis` plugin has been moved to [rpcx-redis](https://github.com/rpcxio/rpcx-redis) - `influxdb` plugin has been moved to [rpcx-plugins](https://github.com/rpcxio/rpcx-plugins) - `opentelemetry` plugin has been moved to [rpcx-plugins](https://github.com/rpcxio/rpcx-plugins) ## Announce **Stable branch: v1.9.4** A tcpdump-like tool added: [rpcxdump](https://github.com/smallnest/rpcxdump)。 You can use it to debug communications between rpcx services and clients. ## Cross-Languages you can use other programming languages besides Go to access rpcx services. - **rpcx-gateway**: You can write clients in any programming languages to call rpcx services via [rpcx-gateway](https://github.com/rpcxio/rpcx-gateway) - **http invoke**: you can use the same http requests to access rpcx gateway - **Java Services/Clients**: You can use [rpcx-java](https://github.com/smallnest/rpcx-java) to implement/access rpcx services via raw protocol. - **rust rpcx**: You can write rpcx services in rust by [rpcx-rs](https://github.com/smallnest/rpcx-rs) > If you can write Go methods, you can also write rpc services. It is so easy to write rpc applications with rpcx. ## Installation install the basic features: `go get -v github.com/smallnest/rpcx/...` If you want to use `quic`、`kcp` registry, use those tags to `go get` 、 `go build` or `go run`. For example, if you want to use all features, you can: ```sh go get -v -tags "quic kcp" github.com/smallnest/rpcx/... ``` **_tags_**: - **quic**: support quic transport - **kcp**: support kcp transport - **rdma**: support the experimental RDMA transport (built on gordma's rdmanet.Conn; requires libibverbs on Linux) ## Which companies are using rpcx?

## Features rpcx is a RPC framework like [Alibaba Dubbo](http://dubbo.io/) and [Weibo Motan](https://github.com/weibocom/motan). **rpcx** is created for targets: 1. **Simple**: easy to learn, easy to develop, easy to integrate and easy to deploy 2. **Performance**: high performance (>= grpc-go) 3. **Cross-platform**: support _raw slice of bytes_, _JSON_, _Protobuf_ and _MessagePack_. Theoretically it can be used with java, php, python, c/c++, node.js, c# and other platforms 4. **Service discovery and service governance**: support zookeeper, etcd and consul. It contains below features - Support raw Go functions. There's no need to define proto files. - Pluggable. Features can be extended such as service discovery, tracing. - Support TCP, HTTP, [QUIC](https://en.wikipedia.org/wiki/QUIC) and [KCP](https://github.com/skywind3000/kcp) - Support multiple codecs such as JSON, Protobuf, [MessagePack](https://msgpack.org/index.html) and raw bytes. - Service discovery. Support peer2peer, configured peers, [zookeeper](https://zookeeper.apache.org), [etcd](https://github.com/coreos/etcd), [consul](https://www.consul.io) and [mDNS](https://en.wikipedia.org/wiki/Multicast_DNS). - Fault tolerance:Failover, Failfast, Failtry. - Load banlancing:support Random, RoundRobin, Consistent hashing, Weighted, network quality and Geography. - Support Compression. - Support passing metadata. - Support Authorization. - Support heartbeat and one-way request. - Other features: metrics, log, timeout, alias, circuit breaker. - Support bidirectional communication. - Support access via HTTP so you can write clients in any programming languages. - Support API gateway. - Support backup request, forking and broadcast. rpcx uses a binary protocol and platform-independent, which means you can develop services in other languages such as Java, python, nodejs, and you can use other prorgramming languages to invoke services developed in Go. There is a UI manager: [rpcx-ui](https://github.com/smallnest/rpcx-ui). ## Performance Test results show rpcx has better performance than other rpc framework except standard rpc lib. The benchmark code is at [rpcx-benchmark](https://github.com/rpcx-ecosystem/rpcx-benchmark). **Listen to others, but test by yourself**. **_Test Environment_** - **CPU**: Intel(R) Xeon(R) CPU E5-2630 v3 @ 2.40GHz, 32 cores - **Memory**: 32G - **Go**: 1.9.0 - **OS**: CentOS 7 / 3.10.0-229.el7.x86_64 **_Use_** - protobuf - the client and the server on the same server - 581 bytes payload - 500/2000/5000 concurrent clients - mock processing time: 0ms, 10ms and 30ms **_Test Result_** ### mock 0ms process time
ThroughputsMean LatencyP99 Latency
### mock 10ms process time
ThroughputsMean LatencyP99 Latency
### mock 30ms process time
ThroughputsMean LatencyP99 Latency
## Examples You can find all examples at [rpcxio/rpcx-examples](https://github.com/rpcxio/rpcx-examples). The below is a simple example. **Server** ```go // define example.Arith …… s := server.NewServer() s.RegisterName("Arith", new(example.Arith), "") s.Serve("tcp", addr) ``` #### Registering only selected methods `Register`/`RegisterName` expose every suitable exported method of a struct as RPC. To expose only some of them, use `RegisterWithMethods` / `RegisterNameWithMethods` with a whitelist of method names: ```go // Arith has exported methods Mul, Add and Sub, but only Mul/Add // should be callable remotely. s := server.NewServer() err := s.RegisterWithMethods(new(example.Arith), []string{"Mul", "Add"}, "") // err := s.RegisterNameWithMethods("Arith", new(example.Arith), []string{"Mul", "Add"}, "") s.Serve("tcp", addr) ``` Rules of the whitelist: - **Whitelist only**: methods not listed are not registered, so a newly added method stays private unless you opt it in. - **Errors on bad names**: a name that does not exist on the receiver, or that exists but is not a suitable RPC method, makes registration fail (the two cases give different error messages). Nothing is partially registered. - **Empty whitelist errors**: pass a non-empty list, or use `Register` / `RegisterName` to register all methods. **Client** ```go // prepare requests …… d, err := client.NewPeer2PeerDiscovery("tcp@"+addr, "") xclient := client.NewXClient("Arith", client.Failtry, client.RandomSelect, d, client.DefaultOption) defer xclient.Close() err = xclient.Call(context.Background(), "Mul", args, reply, nil) ``` ## Contributors ## Contribute see [contributors](https://github.com/smallnest/rpcx/graphs/contributors). Welcome to contribute: - submit issues or requirements - send PRs - write projects to use rpcx - write tutorials or articles to introduce rpcx ## License Apache License, Version 2.0 ---