refactor(discover): 对齐 Redis 类型化命令(by AI)

This commit is contained in:
Star 2026-07-19 08:48:55 +08:00
parent a1bc49adcc
commit 9c64bc2f80
7 changed files with 86 additions and 53 deletions

View File

@ -1,5 +1,10 @@
# CHANGELOG
## [v1.5.8] - 2026-07-19
- **Redis API 对齐**: 服务注册、心跳、注销和节点拉取统一改用 `redis v1.5.11` 类型化命令,减少通用结果转换。
- **依赖升级**: 对齐 `cast``config``crypto``encoding``file``http``id``log``rand``safe``shell` 的当前 patch 版本。
- **文档与测试**: 补齐全局配置和负载均衡 API 说明,新增默认负载均衡器 Benchmark。
## [v1.5.7] - 2026-07-18
- **依赖校验修复**: 升级 `redis``v1.5.11`,并将间接依赖 `redigo` 固定为稳定版 `v1.9.3`,移除撤回版本。

View File

@ -96,8 +96,8 @@ func Start(addr string) bool {
}
// 注册节点
if serverRedisPool.Do("HSET", Config.App, addr, Config.Weight).Error == nil {
serverRedisPool.Do("SETEX", Config.App+"_"+addr, 10, "1")
if serverRedisPool.HSET(Config.App, addr, Config.Weight) {
serverRedisPool.SETEX(Config.App+"_"+addr, 10, "1")
logInfo("registered")
serverRedisPool.PUBLISH("CH_"+Config.App, fmt.Sprintf("%s %d", addr, Config.Weight))
daemonRunning = true
@ -132,14 +132,14 @@ func daemon() {
}
if isServer && serverRedisPool != nil {
if !serverRedisPool.Do("HEXISTS", Config.App, myAddr).Bool() {
if !serverRedisPool.HEXISTS(Config.App, myAddr) {
logInfo("lost app registered info, re-registering")
if serverRedisPool.Do("HSET", Config.App, myAddr, Config.Weight).Error == nil {
serverRedisPool.Do("SETEX", Config.App+"_"+myAddr, 10, "1")
if serverRedisPool.HSET(Config.App, myAddr, Config.Weight) {
serverRedisPool.SETEX(Config.App+"_"+myAddr, 10, "1")
serverRedisPool.PUBLISH("CH_"+Config.App, fmt.Sprintf("%s %d", myAddr, Config.Weight))
}
} else {
serverRedisPool.Do("SETEX", Config.App+"_"+myAddr, 10, "1")
serverRedisPool.SETEX(Config.App+"_"+myAddr, 10, "1")
}
}
}
@ -201,8 +201,8 @@ func Stop() {
if isServer {
daemonRunning = false
if serverRedisPool != nil {
serverRedisPool.Do("HDEL", Config.App, myAddr)
serverRedisPool.Do("DEL", Config.App+"_"+myAddr)
serverRedisPool.HDEL(Config.App, myAddr)
serverRedisPool.DEL(Config.App + "_" + myAddr)
serverRedisPool.PUBLISH("CH_"+Config.App, fmt.Sprintf("%s %d", myAddr, 0))
}
isServer = false
@ -360,12 +360,12 @@ func fetchApp(app string) {
return
}
results := pool.Do("HGETALL", app).ResultMap()
results := pool.HGETALL(app)
// 检查存活
for addr := range results {
if !pool.Do("EXISTS", app+"_"+addr).Bool() {
pool.Do("HDEL", app, addr)
if !pool.EXISTS(app + "_" + addr) {
pool.HDEL(app, addr)
delete(results, addr)
}
}

View File

@ -8,9 +8,9 @@ import (
"testing"
"time"
"github.com/gorilla/websocket"
"apigo.cc/go/discover"
"apigo.cc/go/redis"
"github.com/gorilla/websocket"
)
func TestDiscover(t *testing.T) {
@ -138,3 +138,19 @@ func TestEasyStart(t *testing.T) {
fmt.Printf("EasyStart: %s:%d\n", ip, port)
discover.Stop()
}
func BenchmarkDefaultLoadBalancer(b *testing.B) {
loadBalancer := &discover.DefaultLoadBalancer{}
appClient := &discover.AppClient{}
nodes := []*discover.NodeInfo{
{Addr: "127.0.0.1:18001", Weight: 100},
{Addr: "127.0.0.1:18002", Weight: 200},
{Addr: "127.0.0.1:18003", Weight: 300},
}
b.ReportAllocs()
b.ResetTimer()
for b.Loop() {
loadBalancer.Next(appClient, nodes, nil)
}
}

View File

@ -21,6 +21,13 @@ discover:
## API 指南
### 全局配置
- `Config.Registry / App / Weight`: 注册中心、当前应用名与节点权重。
- `Config.Calls`: 被调用应用及调用配置。
- `Config.CallRetryTimes`: 单次调用的最大节点尝试次数。
- `Config.IpPrefix`: `EasyStart` 选择监听地址时使用的 IP 前缀。
- `RelayHeaders`: 跨服务调用时自动透传的 Header 列表。
### 初始化与启动
- `Start(addr string) bool`: 启动服务发现,指定当前节点的外部访问地址。
- `EasyStart() (string, int)`: 自动监听可用端口并启动服务发现。返回 IP 和端口。
@ -42,6 +49,9 @@ discover:
### 负载均衡与路由
- `SetLoadBalancer(lb LoadBalancer)`: 自定义全局负载均衡策略。
- `LoadBalancer.Next`: 从可用节点中选择本次调用节点。
- `LoadBalancer.Response`: 请求完成后更新节点状态或统计信息。
- `DefaultLoadBalancer`: 内置的权重得分负载均衡实现。
- `SetRoute(route func(ac *AppClient, r *http.Request))`: 设置全局路由拦截规则。
- `Caller.DoWithNode / ManualDoWithNode`: 指定或返回实际调用节点。

View File

@ -5,6 +5,7 @@
2. **实时同步**: 验证通过 Redis PUBLISH 更新节点信息后,客户端能实时感知并更新本地节点列表。
3. **故障剔除**: 验证当节点调用持续失败时,能自动从本地列表中剔除。
4. **环境变量配置**: 验证 `EasyStart` 结合环境变量的启动流程。
5. **负载均衡性能**: 验证默认负载均衡器在多节点集合上的选择性能与内存分配。
## 测试结果
- **Unit Tests**: `go test -v ./...`
@ -12,6 +13,7 @@
- `TestEasyStart`: PASS
- **Module Graph**: `go list -m all` 通过,不再出现 checksum mismatch。
- **v1.5.7 Module Graph**: 使用 `redis v1.5.11``redigo v1.9.3`,不再包含撤回版本。
- **v1.5.8 Module Graph**: `apigo.cc/go/*` 基础依赖已对齐模块索引中的当前 patch 版本。
## Benchmark
- 待补充Discover 主要性能开销在负载均衡算法选择,单次选择耗时极低)。
- `BenchmarkDefaultLoadBalancer-16`: `32.35 ns/op``0 B/op``0 allocs/op`Apple M3 Max)。

22
go.mod
View File

@ -3,23 +3,23 @@ module apigo.cc/go/discover
go 1.25.0
require (
apigo.cc/go/cast v1.5.4
apigo.cc/go/config v1.5.3
apigo.cc/go/http v1.5.3
apigo.cc/go/id v1.5.6
apigo.cc/go/log v1.5.8
apigo.cc/go/cast v1.5.5
apigo.cc/go/config v1.5.4
apigo.cc/go/http v1.5.4
apigo.cc/go/id v1.5.7
apigo.cc/go/log v1.5.9
apigo.cc/go/redis v1.5.11
github.com/gorilla/websocket v1.5.3
)
require (
apigo.cc/go/crypto v1.5.4 // indirect
apigo.cc/go/encoding v1.5.5 // indirect
apigo.cc/go/file v1.5.5 // indirect
apigo.cc/go/crypto v1.5.5 // indirect
apigo.cc/go/encoding v1.5.6 // indirect
apigo.cc/go/file v1.5.6 // indirect
apigo.cc/go/jsmod v1.5.3 // indirect
apigo.cc/go/rand v1.5.3 // indirect
apigo.cc/go/safe v1.5.2 // indirect
apigo.cc/go/shell v1.5.4 // indirect
apigo.cc/go/rand v1.5.4 // indirect
apigo.cc/go/safe v1.5.3 // indirect
apigo.cc/go/shell v1.5.5 // indirect
github.com/gomodule/redigo v1.9.3 // indirect
golang.org/x/crypto v0.52.0 // indirect
golang.org/x/net v0.54.0 // indirect

44
go.sum
View File

@ -1,29 +1,29 @@
apigo.cc/go/cast v1.5.4 h1:OH5Vkrc1K19wuazf9VecjwwMwKDFgLQkI3UvwB4umcs=
apigo.cc/go/cast v1.5.4/go.mod h1:GMjjrYn93tWat1U409G7h1jR3ejfLLI7r0efBo9Sbd4=
apigo.cc/go/config v1.5.3 h1:peq1FM2xO+vzPHJf8Dwg3DXm8PtFQMfTFKQj6fpoG7A=
apigo.cc/go/config v1.5.3/go.mod h1:ZiOAjWa1mQIzszaJZN+kO6YU4GXreng+NxkcK/TAkqQ=
apigo.cc/go/crypto v1.5.4 h1:7rFcF9Mns+7BIB13lystvLVsAeYapKsrD26KOrQq2Zk=
apigo.cc/go/crypto v1.5.4/go.mod h1:PheYKHEXmoEFI1AK5PpY1borQWcRlkkSaWncT3cWbhE=
apigo.cc/go/encoding v1.5.5 h1:kduNLWQgtcQqHYobOuu1djbgg8LedkGOe8f18ZMfqzs=
apigo.cc/go/encoding v1.5.5/go.mod h1:Big9q1Zwy4071dXtnrQ3SJDzfa/G7/A60KE/5+M//P8=
apigo.cc/go/file v1.5.5 h1:/+HmDumLu6Qk2KuQL63M9lpgzHTDL+QJ8dStOl7e9gs=
apigo.cc/go/file v1.5.5/go.mod h1:xRVNhctvqOKeBemmcRW/BQfgkc3B+vT/UZVdSc7duUo=
apigo.cc/go/http v1.5.3 h1:nvJh9bqPPcPRv6p8WEw7bJAd0UC+r2zvQA8/QioVLTQ=
apigo.cc/go/http v1.5.3/go.mod h1:cFrPK61y9f1PrsNSJscZT/QVOgkT15o9OP7O8cuMb8Q=
apigo.cc/go/id v1.5.6 h1:Z3PPp8H8FgNHMwHerxtT2GgstueDv4aCDnUUCoQZoFQ=
apigo.cc/go/id v1.5.6/go.mod h1:HcJK691qfBPzvQ/lt8bo/incKUFtG5vENX/rDEgzJh0=
apigo.cc/go/cast v1.5.5 h1:DMbfK3uPhPjRaXutj3StIZIkfjFIATSXfuAOeNOd4Fw=
apigo.cc/go/cast v1.5.5/go.mod h1:GMjjrYn93tWat1U409G7h1jR3ejfLLI7r0efBo9Sbd4=
apigo.cc/go/config v1.5.4 h1:1c/OarGwbz3+6ikurE+a6LJLjtzXvGvbxw3HV/Nk54M=
apigo.cc/go/config v1.5.4/go.mod h1:oN+D2F8ETIyqKp+Yu8R4PRQlUoqR44o35jAHwLrrAq0=
apigo.cc/go/crypto v1.5.5 h1:YQHumieqviNGMhwoxDtMuUdGWAQiGrkVhz9hZRHOhWs=
apigo.cc/go/crypto v1.5.5/go.mod h1:z/FXt0HE7fSMJKF2MF7jY2fSjlHUYYaSffETJ7ZzJmE=
apigo.cc/go/encoding v1.5.6 h1:v02swVfbFGidD4QcX2ktuHHbCjdSbOB85fhzAXay+7M=
apigo.cc/go/encoding v1.5.6/go.mod h1:Big9q1Zwy4071dXtnrQ3SJDzfa/G7/A60KE/5+M//P8=
apigo.cc/go/file v1.5.6 h1:Y7w3Tyu4e16VuED7rF2pzba+dzGE+hjDnBlIPVHIfzA=
apigo.cc/go/file v1.5.6/go.mod h1:9sdW4ylSOA0HWc8Yt8qdnmMf6nn5SUEmjoPKyXpYXIQ=
apigo.cc/go/http v1.5.4 h1:Tm95WMsXyazFnaaPopdMXv1k9TiaPenLxDQ40WCrsEQ=
apigo.cc/go/http v1.5.4/go.mod h1:cFrPK61y9f1PrsNSJscZT/QVOgkT15o9OP7O8cuMb8Q=
apigo.cc/go/id v1.5.7 h1:Y5Sx6sQBCAdYMCQPTjODZyGMMd1+WRWCy2dWHVq11XQ=
apigo.cc/go/id v1.5.7/go.mod h1:fugudFBqfVNakfm91zZzuzU0P4PULzo6sylB8hRMqxA=
apigo.cc/go/jsmod v1.5.3 h1:S3W317bH0QV2NMeRO1E0v6ySIBOfMWYv/NuQJbvqKWU=
apigo.cc/go/jsmod v1.5.3/go.mod h1:bmyeZtOAP/j5am+YRnaiM89smysK24K7ebk0koFtsSw=
apigo.cc/go/log v1.5.8 h1:/IYtGPWhRjT3OayylDIphkWZIQbpLjqVeSnFEiD3Dy0=
apigo.cc/go/log v1.5.8/go.mod h1:HfFPANMYxJx197SSTXB21Pgxcz/gGqPP8nlSErgd5WE=
apigo.cc/go/rand v1.5.3 h1:O4bPIwyaOWEBCr0nL9A4G4qG48AqiGTCzfPeckm3Ius=
apigo.cc/go/rand v1.5.3/go.mod h1:q1BTFkY/cXE229dDD5Q22lF7T0DoKPV6xAu+6bCrDH4=
apigo.cc/go/log v1.5.9 h1:g8JehZrpVJyesesv+JFig5szEELsO4UjShsjlEihCfc=
apigo.cc/go/log v1.5.9/go.mod h1:b4f/UB5Kk7oiFtkXvG4GVilmHGygWgxELRAdEZrIyfs=
apigo.cc/go/rand v1.5.4 h1:eessFBsKQuoOYdzrStldOGw9f4HqbeO87X95bI4jIBQ=
apigo.cc/go/rand v1.5.4/go.mod h1:q1BTFkY/cXE229dDD5Q22lF7T0DoKPV6xAu+6bCrDH4=
apigo.cc/go/redis v1.5.11 h1:r/kiY3EUD+3YwTd+zMZaRD7eOQZEIvSJwAVnzG96zoQ=
apigo.cc/go/redis v1.5.11/go.mod h1:sCzxIWN+nNIRvR5ePaHsP19hjR52NPfXsq7L1c6A5ec=
apigo.cc/go/safe v1.5.2 h1:EnuEOW/SGwf/5A0nw9LnqfKJE071+TIc6ez8HI9R9Lg=
apigo.cc/go/safe v1.5.2/go.mod h1:2GqCCLLGex4OAhdET3iBWm1R+LIYtmTrvHP8W0iESSw=
apigo.cc/go/shell v1.5.4 h1:Kn6lP6I6d9U0hbyUjpKKFdFZ8RPo4vi4V6AYW8YFzrc=
apigo.cc/go/shell v1.5.4/go.mod h1:FdZWUrcXHGJXo725oSyHqAeFoX0E9yY3PDhrz9hujgY=
apigo.cc/go/safe v1.5.3 h1:9p/BmdlVWLbekpKByZIFC09Qn8Wdhik2eINiwunBxPs=
apigo.cc/go/safe v1.5.3/go.mod h1:Ay8kEPL76DeXH4ifsVTc/3/sfGHlWLQjAp4vi7GA9AI=
apigo.cc/go/shell v1.5.5 h1:sf1QZiL7IHkoOLdrYX1qT4JWIlYZIAAYGJJXorjscB4=
apigo.cc/go/shell v1.5.5/go.mod h1:FdZWUrcXHGJXo725oSyHqAeFoX0E9yY3PDhrz9hujgY=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/gomodule/redigo v1.9.3 h1:dNPSXeXv6HCq2jdyWfjgmhBdqnR6PRO3m/G05nvpPC8=