Compare commits

..

No commits in common. "v1.0.1" and "master" have entirely different histories.

11 changed files with 84 additions and 184 deletions

View File

@ -8,30 +8,27 @@ import (
// AppClient 用于管理单个请求的重试和负载均衡状态 // AppClient 用于管理单个请求的重试和负载均衡状态
type AppClient struct { type AppClient struct {
excludes map[string]bool // 本次请求已排除的节点 excludes map[string]bool
attempts int // 本次请求的重试次数 tryTimes int
Logger *log.Logger // 用于日志记录的 Logger Logger *log.Logger
App string // 目标应用名称 App string
Method string // 请求方法 Method string
Path string // 请求路径 Path string
Data map[string]any // 请求数据 Data *map[string]any
Headers map[string]string // 请求头 Headers *map[string]string
} }
// logError 记录 Discover 客户端错误 func (ac *AppClient) logError(error string, extra ...any) {
func (ac *AppClient) logError(msg string, extra ...any) {
if ac.Logger == nil { if ac.Logger == nil {
ac.Logger = log.DefaultLogger ac.Logger = log.DefaultLogger
} }
ac.Logger.Error("Discover Client: "+msg, extra...) ac.Logger.Error("Discover Client: "+error, extra...)
} }
// Next 获取下一个可用节点
func (ac *AppClient) Next(app string, request *http.Request) *NodeInfo { func (ac *AppClient) Next(app string, request *http.Request) *NodeInfo {
return ac.NextWithNode(app, "", request) return ac.NextWithNode(app, "", request)
} }
// CheckApp 检查并尝试添加应用
func (ac *AppClient) CheckApp(app string) bool { func (ac *AppClient) CheckApp(app string) bool {
nodes := getAppNodes(app) nodes := getAppNodes(app)
if nodes == nil { if nodes == nil {
@ -43,7 +40,6 @@ func (ac *AppClient) CheckApp(app string) bool {
return true return true
} }
// NextWithNode 获取下一个可用节点,支持指定节点
func (ac *AppClient) NextWithNode(app, withNode string, request *http.Request) *NodeInfo { func (ac *AppClient) NextWithNode(app, withNode string, request *http.Request) *NodeInfo {
if ac.excludes == nil { if ac.excludes == nil {
ac.excludes = make(map[string]bool) ac.excludes = make(map[string]bool)
@ -55,15 +51,15 @@ func (ac *AppClient) NextWithNode(app, withNode string, request *http.Request) *
return nil return nil
} }
ac.attempts++ ac.tryTimes++
if withNode != "" { if withNode != "" {
ac.excludes[withNode] = true ac.excludes[withNode] = true
return allNodes[withNode] return allNodes[withNode]
} }
readyNodes := make([]*NodeInfo, 0, len(allNodes)) readyNodes := make([]*NodeInfo, 0)
for _, node := range allNodes { for _, node := range allNodes {
if ac.excludes[node.Addr] || node.FailedTimes.Load() >= int32(Config.CallRetryTimes) { if ac.excludes[node.Addr] || node.FailedTimes >= Config.CallRetryTimes {
continue continue
} }
readyNodes = append(readyNodes, node) readyNodes = append(readyNodes, node)
@ -87,7 +83,7 @@ func (ac *AppClient) NextWithNode(app, withNode string, request *http.Request) *
} }
if node == nil { if node == nil {
ac.logError("no available node", "app", app, "attempts", ac.attempts) ac.logError("no available node", "app", app, "tryTimes", ac.tryTimes)
} }
return node return node

View File

@ -1,15 +1,5 @@
# CHANGELOG # CHANGELOG
## v1.0.1 (2026-05-05)
- 优化代码规范修复变量名冲突Shadowing改进 `tryTimes` -> `attempts` 等语义命名。
- 性能优化:优化 `AppClient` 中的 `Data``Headers` 类型(从指针改为直接引用),减少内存寻址开销。
- 性能优化:优化 `NextWithNode` 中的切片分配。
- 架构优化:导出 `log.FillBase`,支持外部模块实现高效自定义日志。
- 功能增强:引入 `DiscoverLog`,实现基于对象池的高性能发现过程日志记录。
- 标准对齐:统一使用 `apigo.cc/go/http` 中定义的 Header 常量。
- 文档完善:为所有导出类型和方法添加详细文档注释。
- 测试增强:添加 `BenchmarkDiscover` 基准测试。
## v1.0.0 ## v1.0.0
- 从 `ssgo/discover` 迁移至 `apigo.cc/go/discover` - 从 `ssgo/discover` 迁移至 `apigo.cc/go/discover`
- 采用全新的 `apigo.cc/go` 基础设施log, redis, http, cast, u - 采用全新的 `apigo.cc/go` 基础设施log, redis, http, cast, u

View File

@ -41,58 +41,48 @@ func getHttpClient(app string, timeout time.Duration, h2c bool) *gohttp.Client {
return c return c
} }
// Caller 用于发起服务间调用
type Caller struct { type Caller struct {
Request *http.Request // 原始请求,用于透传 Header Request *http.Request
NoBody bool // 是否不发送请求体 NoBody bool
logger *log.Logger // 用于日志记录的 Logger logger *log.Logger
} }
// NewCaller 创建一个新的调用器
func NewCaller(request *http.Request, logger *log.Logger) *Caller { func NewCaller(request *http.Request, logger *log.Logger) *Caller {
return &Caller{Request: request, logger: logger} return &Caller{Request: request, logger: logger}
} }
// logError 记录 Discover 调用器错误 func (c *Caller) logError(error string, extra ...any) {
func (c *Caller) logError(msg string, extra ...any) {
if c.logger == nil { if c.logger == nil {
c.logger = log.DefaultLogger c.logger = log.DefaultLogger
} }
c.logger.Error("Discover Caller: "+msg, extra...) c.logger.Error("Discover Caller: "+error, extra...)
} }
// Get 发起 GET 请求
func (c *Caller) Get(app, path string, headers ...string) *gohttp.Result { func (c *Caller) Get(app, path string, headers ...string) *gohttp.Result {
return c.Do("GET", app, path, nil, headers...) return c.Do("GET", app, path, nil, headers...)
} }
// Post 发起 POST 请求
func (c *Caller) Post(app, path string, data any, headers ...string) *gohttp.Result { func (c *Caller) Post(app, path string, data any, headers ...string) *gohttp.Result {
return c.Do("POST", app, path, data, headers...) return c.Do("POST", app, path, data, headers...)
} }
// Put 发起 PUT 请求
func (c *Caller) Put(app, path string, data any, headers ...string) *gohttp.Result { func (c *Caller) Put(app, path string, data any, headers ...string) *gohttp.Result {
return c.Do("PUT", app, path, data, headers...) return c.Do("PUT", app, path, data, headers...)
} }
// Delete 发起 DELETE 请求
func (c *Caller) Delete(app, path string, data any, headers ...string) *gohttp.Result { func (c *Caller) Delete(app, path string, data any, headers ...string) *gohttp.Result {
return c.Do("DELETE", app, path, data, headers...) return c.Do("DELETE", app, path, data, headers...)
} }
// Head 发起 HEAD 请求
func (c *Caller) Head(app, path string, headers ...string) *gohttp.Result { func (c *Caller) Head(app, path string, headers ...string) *gohttp.Result {
return c.Do("HEAD", app, path, nil, headers...) return c.Do("HEAD", app, path, nil, headers...)
} }
// Do 发起通用请求
func (c *Caller) Do(method, app, path string, data any, headers ...string) *gohttp.Result { func (c *Caller) Do(method, app, path string, data any, headers ...string) *gohttp.Result {
r, _ := c.DoWithNode(method, app, "", path, data, headers...) r, _ := c.DoWithNode(method, app, "", path, data, headers...)
return r return r
} }
// Open 发起 WebSocket 连接
func (c *Caller) Open(app, path string, headers ...string) *websocket.Conn { func (c *Caller) Open(app, path string, headers ...string) *websocket.Conn {
r, _ := c.doWithNode(false, "WS", app, "", path, nil, headers...) r, _ := c.doWithNode(false, "WS", app, "", path, nil, headers...)
if v, ok := r.(*websocket.Conn); ok { if v, ok := r.(*websocket.Conn); ok {
@ -101,7 +91,6 @@ func (c *Caller) Open(app, path string, headers ...string) *websocket.Conn {
return nil return nil
} }
// DoWithNode 发起请求并返回结果及节点地址
func (c *Caller) DoWithNode(method, app, withNode, path string, data any, headers ...string) (*gohttp.Result, string) { func (c *Caller) DoWithNode(method, app, withNode, path string, data any, headers ...string) (*gohttp.Result, string) {
r, nodeAddr := c.doWithNode(false, method, app, withNode, path, data, headers...) r, nodeAddr := c.doWithNode(false, method, app, withNode, path, data, headers...)
if v, ok := r.(*gohttp.Result); ok { if v, ok := r.(*gohttp.Result); ok {
@ -110,7 +99,6 @@ func (c *Caller) DoWithNode(method, app, withNode, path string, data any, header
return nil, nodeAddr return nil, nodeAddr
} }
// ManualDoWithNode 发起请求(手动处理响应)并返回结果及节点地址
func (c *Caller) ManualDoWithNode(method, app, withNode, path string, data any, headers ...string) (*gohttp.Result, string) { func (c *Caller) ManualDoWithNode(method, app, withNode, path string, data any, headers ...string) (*gohttp.Result, string) {
r, nodeAddr := c.doWithNode(true, method, app, withNode, path, data, headers...) r, nodeAddr := c.doWithNode(true, method, app, withNode, path, data, headers...)
if v, ok := r.(*gohttp.Result); ok { if v, ok := r.(*gohttp.Result); ok {
@ -119,7 +107,7 @@ func (c *Caller) ManualDoWithNode(method, app, withNode, path string, data any,
return nil, nodeAddr return nil, nodeAddr
} }
func (c *Caller) doWithNode(manual bool, method, app, withNode, path string, data any, headers ...string) (any, string) { func (c *Caller) doWithNode(manualDo bool, method, app, withNode, path string, data any, headers ...string) (any, string) {
callerHeaders := make(map[string]string) callerHeaders := make(map[string]string)
for i := 1; i < len(headers); i += 2 { for i := 1; i < len(headers); i += 2 {
callerHeaders[headers[i-1]] = headers[i] callerHeaders[headers[i-1]] = headers[i]
@ -143,8 +131,8 @@ func (c *Caller) doWithNode(manual bool, method, app, withNode, path string, dat
App: app, App: app,
Method: method, Method: method,
Path: path, Path: path,
Data: callData, Data: &callData,
Headers: callerHeaders, Headers: &callerHeaders,
} }
if settedRoute != nil { if settedRoute != nil {
@ -174,7 +162,7 @@ func (c *Caller) doWithNode(manual bool, method, app, withNode, path string, dat
break break
} }
node.UsedTimes.Add(1) node.UsedTimes++
startTime := time.Now() startTime := time.Now()
scheme := "http" scheme := "http"
if callInfo != nil && callInfo.SSL { if callInfo != nil && callInfo.SSL {
@ -206,13 +194,13 @@ func (c *Caller) doWithNode(manual bool, method, app, withNode, path string, dat
res = &gohttp.Result{Error: err, Response: resp} res = &gohttp.Result{Error: err, Response: resp}
} else { } else {
if c.Request != nil { if c.Request != nil {
if manual { if manualDo {
res = hc.ManualDoByRequest(c.Request, method, url, data, settedHeaders...) res = hc.ManualDoByRequest(c.Request, method, url, data, settedHeaders...)
} else { } else {
res = hc.DoByRequest(c.Request, method, url, data, settedHeaders...) res = hc.DoByRequest(c.Request, method, url, data, settedHeaders...)
} }
} else { } else {
if manual { if manualDo {
res = hc.ManualDo(method, url, data, settedHeaders...) res = hc.ManualDo(method, url, data, settedHeaders...)
} else { } else {
res = hc.Do(method, url, data, settedHeaders...) res = hc.Do(method, url, data, settedHeaders...)
@ -221,11 +209,10 @@ func (c *Caller) doWithNode(manual bool, method, app, withNode, path string, dat
} }
responseTime := time.Since(startTime) responseTime := time.Since(startTime)
usedTimeMs := float32(responseTime.Nanoseconds()) / 1e6
settedLoadBalancer.Response(&appClient, node, res.Error, res.Response, responseTime) settedLoadBalancer.Response(&appClient, node, res.Error, res.Response, responseTime)
if res.Error != nil || (res.Response != nil && res.Response.StatusCode >= 502 && res.Response.StatusCode <= 504) { if res.Error != nil || (res.Response != nil && res.Response.StatusCode >= 502 && res.Response.StatusCode <= 504) {
node.FailedTimes.Add(1) node.FailedTimes++
errStr := "" errStr := ""
if res.Error != nil { if res.Error != nil {
errStr = res.Error.Error() errStr = res.Error.Error()
@ -233,19 +220,18 @@ func (c *Caller) doWithNode(manual bool, method, app, withNode, path string, dat
errStr = res.Response.Status errStr = res.Response.Status
} }
c.logError(errStr, "app", app, "node", node.Addr, "path", path, "attempts", appClient.attempts) c.logError(errStr, "app", app, "node", node.Addr, "path", path, "tryTimes", appClient.tryTimes)
appClient.Log(node.Addr, usedTimeMs, fmt.Errorf("%s", errStr))
// 仅做本地隔离,不再篡改全局注册中心状态 if node.FailedTimes >= Config.CallRetryTimes {
if node.FailedTimes.Load() >= int32(Config.CallRetryTimes) { logError("node removed due to high failures", "app", app, "node", node.Addr)
logError("node isolated locally due to high failures", "app", app, "node", node.Addr) if clientRedisPool != nil {
clientRedisPool.Do("HDEL", app, node.Addr)
clientRedisPool.PUBLISH("CH_"+app, fmt.Sprintf("%s 0", node.Addr))
}
} }
continue continue
} }
// 请求成功,重置失败计数
node.FailedTimes.Store(0)
appClient.Log(node.Addr, usedTimeMs, nil)
if strings.ToUpper(method) == "WS" { if strings.ToUpper(method) == "WS" {
return wsConn, node.Addr return wsConn, node.Addr
} }

View File

@ -1,27 +1,35 @@
package discover package discover
import (
gohttp "apigo.cc/go/http"
)
const ( const (
HeaderFromApp = gohttp.HeaderFromApp HeaderFromApp = "X-Discover-From-App"
HeaderFromNode = gohttp.HeaderFromNode HeaderFromNode = "X-Discover-From-Node"
HeaderClientIP = gohttp.HeaderClientIP HeaderClientIp = "X-Client-Ip"
HeaderForwardedFor = gohttp.HeaderForwardedFor HeaderForwardedFor = "X-Forwarded-For"
HeaderUserID = gohttp.HeaderUserID HeaderUserId = "X-User-Id"
HeaderDeviceID = gohttp.HeaderDeviceID HeaderDeviceId = "X-Device-Id"
HeaderClientAppName = gohttp.HeaderClientAppName HeaderClientAppName = "X-Client-App-Name"
HeaderClientAppVersion = gohttp.HeaderClientAppVersion HeaderClientAppVersion = "X-Client-App-Version"
HeaderSessionID = gohttp.HeaderSessionID HeaderSessionId = "X-Session-Id"
HeaderRequestID = gohttp.HeaderRequestID HeaderRequestId = "X-Request-Id"
HeaderHost = gohttp.HeaderHost HeaderHost = "X-Host"
HeaderScheme = gohttp.HeaderScheme HeaderScheme = "X-Scheme"
HeaderUserAgent = gohttp.HeaderUserAgent HeaderUserAgent = "User-Agent"
) )
var RelayHeaders = gohttp.RelayHeaders var RelayHeaders = []string{
HeaderClientIp,
HeaderForwardedFor,
HeaderUserId,
HeaderDeviceId,
HeaderClientAppName,
HeaderClientAppVersion,
HeaderSessionId,
HeaderRequestId,
HeaderHost,
HeaderScheme,
HeaderUserAgent,
}
const DefaultRegistry = "127.0.0.1:6379::15" const DefaultRegistry = "127.0.0.1:6379::15"
const EnvRegistry = "DISCOVER_REGISTRY" const EnvRegistry = "DISCOVER_REGISTRY"

View File

@ -47,28 +47,21 @@ type callInfoType struct {
SSL bool SSL bool
} }
// IsServer 返回当前节点是否作为服务端运行
func IsServer() bool { return isServer } func IsServer() bool { return isServer }
// IsClient 返回当前节点是否作为客户端运行
func IsClient() bool { return isClient } func IsClient() bool { return isClient }
// logError 记录 Discover 内部错误 func logError(error string, extra ...any) {
func logError(msg string, extra ...any) { _logger.Error("Discover: "+error, append(extra, "app", Config.App, "addr", myAddr)...)
_logger.Error("Discover: "+msg, append(extra, "app", Config.App, "addr", myAddr)...)
} }
// logInfo 记录 Discover 内部信息 func logInfo(info string, extra ...any) {
func logInfo(msg string, extra ...any) { _logger.Info("Discover: "+info, append(extra, "app", Config.App, "addr", myAddr)...)
_logger.Info("Discover: "+msg, append(extra, "app", Config.App, "addr", myAddr)...)
} }
// SetLogger 设置 Discover 使用的全局 Logger
func SetLogger(logger *log.Logger) { func SetLogger(logger *log.Logger) {
_logger = logger _logger = logger
} }
// Init 初始化 Discover 配置,通常由 Start 自动调用
func Init() { func Init() {
appLock.Lock() appLock.Lock()
defer appLock.Unlock() defer appLock.Unlock()
@ -91,7 +84,6 @@ func Init() {
_logger = log.New(id.MakeID(12)) _logger = log.New(id.MakeID(12))
} }
// Start 启动服务发现,指定当前节点的外部访问地址
func Start(addr string) bool { func Start(addr string) bool {
Init() Init()
myAddr = addr myAddr = addr
@ -130,8 +122,7 @@ func Start(addr string) bool {
func daemon() { func daemon() {
logInfo("daemon thread started") logInfo("daemon thread started")
// 每 5 秒心跳一次,降低 Redis 压力TTL 保持 10 秒 ticker := time.NewTicker(time.Second)
ticker := time.NewTicker(5 * time.Second)
defer ticker.Stop() defer ticker.Stop()
for daemonRunning { for daemonRunning {
@ -200,7 +191,6 @@ func subscribeAppUnderLock(app string) {
}) })
} }
// Stop 停止 Discover 并从注册中心注销当前节点
func Stop() { func Stop() {
appLock.Lock() appLock.Lock()
if isClient && pubsubRedisPool != nil { if isClient && pubsubRedisPool != nil {
@ -220,7 +210,6 @@ func Stop() {
appLock.Unlock() appLock.Unlock()
} }
// Wait 等待守护进程退出
func Wait() { func Wait() {
if daemonStopChan != nil { if daemonStopChan != nil {
<-daemonStopChan <-daemonStopChan
@ -228,8 +217,6 @@ func Wait() {
} }
} }
// EasyStart 自动根据环境变量和本地网卡信息启动 Discover
// 返回监听的 IP 和 端口
func EasyStart() (string, int) { func EasyStart() (string, int) {
Init() Init()
port := 0 port := 0
@ -285,7 +272,6 @@ func EasyStart() (string, int) {
return ip.String(), port return ip.String(), port
} }
// AddExternalApp 动态添加需要发现的外部应用
func AddExternalApp(app, callConf string) bool { func AddExternalApp(app, callConf string) bool {
if addApp(app, callConf, true) { if addApp(app, callConf, true) {
if !isClient { if !isClient {
@ -300,7 +286,6 @@ func AddExternalApp(app, callConf string) bool {
return false return false
} }
// SetNode 手动设置某个服务的节点信息(不通过注册中心)
func SetNode(app, addr string, weight int) { func SetNode(app, addr string, weight int) {
pushNode(app, addr, weight) pushNode(app, addr, weight)
} }
@ -422,7 +407,6 @@ func getCalls() map[string]string {
return calls return calls
} }
// GetAppNodes 获取某个应用的所有节点列表
func GetAppNodes(app string) map[string]*NodeInfo { func GetAppNodes(app string) map[string]*NodeInfo {
return getAppNodes(app) return getAppNodes(app)
} }
@ -444,9 +428,7 @@ func pushNode(app, addr string, weight int) {
if node, ok := _appNodes[app][addr]; ok { if node, ok := _appNodes[app][addr]; ok {
if node.Weight != weight { if node.Weight != weight {
// 调整 UsedTimes 保持相对均衡,使用 Load() 和 Store() node.UsedTimes = uint64(float64(node.UsedTimes) / float64(node.Weight) * float64(weight))
used := node.UsedTimes.Load()
node.UsedTimes.Store(uint64(float64(used) / float64(node.Weight) * float64(weight)))
node.Weight = weight node.Weight = weight
} }
} else { } else {
@ -454,15 +436,14 @@ func pushNode(app, addr string, weight int) {
if len(_appNodes[app]) > 0 { if len(_appNodes[app]) > 0 {
var totalScore float64 var totalScore float64
for _, n := range _appNodes[app] { for _, n := range _appNodes[app] {
totalScore += float64(n.UsedTimes.Load()) / float64(n.Weight) totalScore += float64(n.UsedTimes) / float64(n.Weight)
} }
avgUsed = uint64(totalScore / float64(len(_appNodes[app])) * float64(weight)) avgUsed = uint64(totalScore / float64(len(_appNodes[app])) * float64(weight))
} }
node := &NodeInfo{ _appNodes[app][addr] = &NodeInfo{
Addr: addr, Addr: addr,
Weight: weight, Weight: weight,
} UsedTimes: avgUsed,
node.UsedTimes.Store(avgUsed) }
_appNodes[app][addr] = node
} }
} }

View File

@ -138,24 +138,3 @@ func TestEasyStart(t *testing.T) {
fmt.Printf("EasyStart: %s:%d\n", ip, port) fmt.Printf("EasyStart: %s:%d\n", ip, port)
discover.Stop() discover.Stop()
} }
func BenchmarkDiscover(b *testing.B) {
discover.Config.App = "bench-app"
discover.SetNode("bench-app", "127.0.0.1:8080", 100)
discover.SetNode("bench-app", "127.0.0.1:8081", 100)
b.ResetTimer()
for i := 0; i < b.N; i++ {
// 模拟一个不需要实际网络请求的调用过程,只测试 Discover 内部逻辑(负载均衡、节点选择等)
// 我们通过 Mock 或直接调用内部方法来实现
appClient := discover.AppClient{
App: "bench-app",
Method: "GET",
Path: "/",
}
node := appClient.Next("bench-app", nil)
if node == nil {
b.Fatal("no node")
}
}
}

View File

@ -22,17 +22,20 @@ type LoadBalancer interface {
// DefaultLoadBalancer 默认负载均衡器(简单权重轮询/得分最小者优先) // DefaultLoadBalancer 默认负载均衡器(简单权重轮询/得分最小者优先)
type DefaultLoadBalancer struct{} type DefaultLoadBalancer struct{}
// Response 在默认负载均衡器中不再执行写操作,减少锁竞争
func (lb *DefaultLoadBalancer) Response(appClient *AppClient, node *NodeInfo, err error, response *http.Response, responseTime time.Duration) { func (lb *DefaultLoadBalancer) Response(appClient *AppClient, node *NodeInfo, err error, response *http.Response, responseTime time.Duration) {
node.Data.Store("score", float64(node.UsedTimes)/float64(node.Weight))
} }
// Next 根据得分UsedTimes / Weight选择得分最小的节点
func (lb *DefaultLoadBalancer) Next(appClient *AppClient, nodes []*NodeInfo, request *http.Request) *NodeInfo { func (lb *DefaultLoadBalancer) Next(appClient *AppClient, nodes []*NodeInfo, request *http.Request) *NodeInfo {
var minScore float64 = -1 var minScore float64 = -1
var minNode *NodeInfo var minNode *NodeInfo
for _, node := range nodes { for _, node := range nodes {
// 动态计算得分,避免使用 sync.Map 存储,减少内存分配和锁竞争 scoreValue, ok := node.Data.Load("score")
score := float64(node.UsedTimes.Load()) / float64(node.Weight) if !ok {
scoreValue = float64(node.UsedTimes) / float64(node.Weight)
node.Data.Store("score", scoreValue)
}
score := scoreValue.(float64)
if minNode == nil || score < minScore { if minNode == nil || score < minScore {
minScore = score minScore = score
minNode = node minNode = node

40
Log.go
View File

@ -1,40 +0,0 @@
package discover
import (
"apigo.cc/go/log"
)
const LogTypeDiscover = "discover"
type DiscoverLog struct {
log.BaseLog
App string
Method string
Path string
Node string
Attempts int
UsedTime float32
Error string
}
func (ac *AppClient) Log(node string, usedTime float32, err error) {
if ac.Logger == nil {
ac.Logger = log.DefaultLogger
}
if !ac.Logger.CheckLevel(log.INFO) {
return
}
entry := log.GetEntry[DiscoverLog]()
// 框架会自动调用 FillBase不再手动调用
entry.App = ac.App
entry.Method = ac.Method
entry.Path = ac.Path
entry.Node = node
entry.Attempts = ac.attempts
entry.UsedTime = usedTime
if err != nil {
entry.Error = err.Error()
}
ac.Logger.Log(entry)
}

View File

@ -2,14 +2,13 @@ package discover
import ( import (
"sync" "sync"
"sync/atomic"
) )
// NodeInfo 存储服务节点信息 // NodeInfo 存储服务节点信息
type NodeInfo struct { type NodeInfo struct {
Addr string // 节点地址 Addr string // 节点地址
Weight int // 节点权重 Weight int // 节点权重
UsedTimes atomic.Uint64 // 已使用次数 UsedTimes uint64 // 已使用次数
FailedTimes atomic.Int32 // 失败次数 FailedTimes int // 失败次数
Data sync.Map // 运行时自定义数据 Data sync.Map // 运行时自定义数据
} }

View File

@ -5,7 +5,6 @@
2. **实时同步**: 验证通过 Redis PUBLISH 更新节点信息后,客户端能实时感知并更新本地节点列表。 2. **实时同步**: 验证通过 Redis PUBLISH 更新节点信息后,客户端能实时感知并更新本地节点列表。
3. **故障剔除**: 验证当节点调用持续失败时,能自动从本地列表中剔除。 3. **故障剔除**: 验证当节点调用持续失败时,能自动从本地列表中剔除。
4. **环境变量配置**: 验证 `EasyStart` 结合环境变量的启动流程。 4. **环境变量配置**: 验证 `EasyStart` 结合环境变量的启动流程。
5. **高效日志记录**: 验证 `DiscoverLog` 通过对象池和 `FillBase` 机制实现的高性能异步日志。
## 测试结果 ## 测试结果
- **Unit Tests**: `go test -v ./...` - **Unit Tests**: `go test -v ./...`
@ -13,5 +12,4 @@
- `TestEasyStart`: PASS - `TestEasyStart`: PASS
## Benchmark ## Benchmark
- `BenchmarkDiscover`: ~560 ns/op (Intel(R) Core(TM) i9-9980HK CPU @ 2.40GHz) - 待补充Discover 主要性能开销在负载均衡算法选择,单次选择耗时极低)。
- 负载均衡选择节点耗时极低,适合高并发场景。

2
go.mod
View File

@ -7,7 +7,7 @@ require (
apigo.cc/go/config v1.0.4 apigo.cc/go/config v1.0.4
apigo.cc/go/http v1.0.3 apigo.cc/go/http v1.0.3
apigo.cc/go/id v1.0.4 apigo.cc/go/id v1.0.4
apigo.cc/go/log v1.1.0 apigo.cc/go/log v1.0.2
apigo.cc/go/redis v1.0.2 apigo.cc/go/redis v1.0.2
github.com/gorilla/websocket v1.5.3 github.com/gorilla/websocket v1.5.3
) )