refactor(circuitBreaker): 重构熔断器初始化逻辑和探活机制 #112
Conversation
将init函数重命名为Init并显式调用,优化探活逻辑为并发执行,提高效率
There was a problem hiding this comment.
Pull request overview
该 PR 旨在重构熔断器(circuitBreaker)的初始化流程,并调整探活(liveness probe)机制,使探活在启动时与定时周期内执行,并尝试并发探测待恢复的 API。
Changes:
- 在
main中启动探活协程,并在探活开始前调用circuitBreaker.Init() - 为
LiveNessProbe增加并发探测逻辑probeTogether(),并在Start()中复用 - 调整
circuitBreaker.Init():初始化时将 API 放入Probe而不是直接加入LB
Reviewed changes
Copilot reviewed 3 out of 3 changed files in this pull request and generated 4 comments.
| File | Description |
|---|---|
| main.go | 启动探活 goroutine,并新增一次 circuitBreaker.Init() 调用 |
| app/utils/circuitBreaker/liveNessProbe.go | 新增并发探活 probeTogether(),ticker 到点后调用 |
| app/utils/circuitBreaker/circuitBreaker.go | Init 时改为把 API 加入 Probe 并初始化快照,不再直接 seed 到 LB |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| Probe.Add(api, funnelApi.ZF) | ||
|
|
||
| snapShot.Store(oauthKey, oauthSnap) | ||
| snapShot.Store(zfKey, zfSnap) |
There was a problem hiding this comment.
Init() no longer adds configured APIs into the load balancer (previously via lb.Add(...)), so CB.LB starts empty and GetApi() will return NoApiAvailable until the liveness probe succeeds and calls CB.LB.Add. If this behavior change is intentional, consider blocking startup (or delaying server listen) until at least one API is marked healthy, or seed LB with all configured APIs and let probing remove unhealthy ones to avoid initial request failures.
| snapShot.Store(zfKey, zfSnap) | |
| snapShot.Store(zfKey, zfSnap) | |
| lb.Add(api, funnelApi.Oauth) | |
| lb.Add(api, funnelApi.ZF) |
| go func() { | ||
| if err := srv.ListenAndServe(); err != nil && !errors.Is(err, http.ErrServerClosed) { | ||
| zap.L().Fatal("Server Error Occurred", zap.Error(err)) | ||
| } | ||
| }() | ||
|
|
||
| wg.Add(1) | ||
| go func() { | ||
| circuitBreaker.Init() | ||
| defer wg.Done() | ||
| circuitBreaker.Probe.Start(ctx) | ||
| }() |
There was a problem hiding this comment.
With the new behavior where CB.LB starts empty until probes add healthy APIs, the server begins accepting requests (ListenAndServe) before Probe.Start(ctx) has had a chance to populate the load balancer. This creates a startup window where requests may consistently fail with NoApiAvailable. Consider starting the probe (and waiting for at least one healthy API or a timeout) before calling ListenAndServe, or keep LB seeded at init time.
| func (l *LiveNessProbe) probeTogether() { | ||
| var wg sync.WaitGroup | ||
| for apiKey, loginType := range l.ApiMap { | ||
| wg.Add(1) | ||
| go func(apikey string, lt funnelApi.LoginType) { | ||
| defer wg.Done() | ||
| api := strings.TrimSuffix(apikey, string(lt)) | ||
| if err := liveNess(l.User, api, lt); err == nil { | ||
| CB.LB.Add(api, lt) | ||
| CB.Success(api, lt) | ||
| l.Remove(apikey) | ||
| } | ||
| }(apiKey, loginType) | ||
| } | ||
| wg.Wait() |
There was a problem hiding this comment.
probeTogether() iterates over l.ApiMap without holding the mutex while goroutines concurrently call l.Remove() (which deletes from the same map). This can trigger fatal error: concurrent map iteration and map write at runtime. Fix by taking the lock and copying keys/values to a slice (or using a sync.Map) before launching goroutines, and only mutate ApiMap under the lock (e.g., remove keys after probing via a channel processed by a single goroutine).
| wg.Add(1) | ||
| go func() { | ||
| circuitBreaker.Init() | ||
| defer wg.Done() | ||
| circuitBreaker.Probe.Start(ctx) | ||
| }() |
There was a problem hiding this comment.
circuitBreaker.Init() is now called twice (once during startup and again inside the probe goroutine). Since Init() reassigns the global CB and Probe, this can reset circuit-breaker state while the server is already running and may race with request handling. Initialize once (ideally before starting any goroutines) and only start Probe.Start(ctx) here.
….go中调用两次circuitBreaker.Init()的问题
No description provided.