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
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 vet1.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 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
<a href="https://rpcx.io/"><img height="160" src="http://rpcx.io/logos/rpcx-logo-text.png"></a>
Official site: 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
- zookeeper plugin has been moved to rpcx-zookeeper
- consul plugin has been moved to rpcx-consul
- redis plugin has been moved to rpcx-redis
- influxdb plugin has been moved to rpcx-plugins
- opentelemetry plugin has been moved to rpcx-plugins
Announce
Stable branch: v1.9.4
A tcpdump-like tool added: 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
- http invoke: you can use the same http requests to access rpcx gateway
- Java Services/Clients: You can use rpcx-java to implement/access rpcx services via raw protocol.
- rust rpcx: You can write rpcx services in rust by 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:
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?
<p float="left">
<img style="padding-bottom: 20px;" src="https://user-images.githubusercontent.com/865763/102993220-b967f000-4557-11eb-9747-703a6cbb9fb1.png" width="200" />
<img style="padding-bottom: 20px;" src="https://user-images.githubusercontent.com/865763/113414067-a8e4d280-93ee-11eb-9f42-1373d7e766c1.png" width="200" />
<img style="padding-bottom: 20px;" src="https://user-images.githubusercontent.com/865763/102993433-267b8580-4558-11eb-9e45-4e1a86d61688.png" width="200" />
<img style="padding-bottom: 20px;" src="https://user-images.githubusercontent.com/865763/102993530-4743db00-4558-11eb-9f76-1ee69e992b82.png" width="200" />
<img style="padding-bottom: 20px;" src="https://user-images.githubusercontent.com/865763/102993612-722e2f00-4558-11eb-849a-3264c430aef9.png" width="200" />
<img style="padding-bottom: 20px;" src="https://user-images.githubusercontent.com/865763/102993785-c20cf600-4558-11eb-82b9-27b801aca4ff.png" width="200" />
</p>
Features
rpcx is a RPC framework like Alibaba Dubbo and Weibo 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 and KCP
- Support multiple codecs such as JSON, Protobuf, MessagePack and raw bytes.
- Service discovery. Support peer2peer, configured peers, zookeeper, etcd, consul and mDNS.
- 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.
Performance
Test results show rpcx has better performance than other rpc framework except standard rpc lib.
The benchmark code is at 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
<table><tr><th>Throughputs</th><th>Mean Latency</th><th>P99 Latency</th></tr><tr><td width="30%"><img src="http://colobu.com/2018/01/31/benchmark-2018-spring-of-popular-rpc-frameworks/p0-throughput.png"></td><td width="30%"><img src="http://colobu.com/2018/01/31/benchmark-2018-spring-of-popular-rpc-frameworks/p0-latency.png"></td><td width="30%"><img src="http://colobu.com/2018/01/31/benchmark-2018-spring-of-popular-rpc-frameworks/p0-p99.png"></td></tr></table>
mock 10ms process time
<table><tr><th>Throughputs</th><th>Mean Latency</th><th>P99 Latency</th></tr><tr><td width="30%"><img src="http://colobu.com/2018/01/31/benchmark-2018-spring-of-popular-rpc-frameworks/p10-throughput.png"></td><td width="30%"><img src="http://colobu.com/2018/01/31/benchmark-2018-spring-of-popular-rpc-frameworks/p10-latency.png"></td><td width="30%"><img src="http://colobu.com/2018/01/31/benchmark-2018-spring-of-popular-rpc-frameworks/p10-p99.png"></td></tr></table>
mock 30ms process time
<table><tr><th>Throughputs</th><th>Mean Latency</th><th>P99 Latency</th></tr><tr><td width="30%"><img src="http://colobu.com/2018/01/31/benchmark-2018-spring-of-popular-rpc-frameworks/p30-throughput.png"></td><td width="30%"><img src="http://colobu.com/2018/01/31/benchmark-2018-spring-of-popular-rpc-frameworks/p30-latency.png"></td><td width="30%"><img src="http://colobu.com/2018/01/31/benchmark-2018-spring-of-popular-rpc-frameworks/p30-p99.png"></td></tr></table>
Examples
You can find all examples at rpcxio/rpcx-examples.
The below is a simple example.
Server
// 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:
// 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
// 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
<a href="https://github.com/smallnest/rpcx/graphs/contributors">
<img src="https://contrib.rocks/image?repo=smallnest/rpcx" />
</a>
Contribute
see 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
---