diff --git a/middleware/circuit_breaker.go b/middleware/circuit_breaker.go index 3520277..c39b452 100644 --- a/middleware/circuit_breaker.go +++ b/middleware/circuit_breaker.go @@ -252,10 +252,10 @@ func updateResponseTimeStats(cbInfo *CircuitBreakerInfo, duration time.Duration, // formatUnixTime 格式化Unix时间戳 func formatUnixTime(timestamp int64) string { - if timestamp > 0 { - return time.Unix(timestamp, 0).Format("2006-01-02 15:04:05") + if timestamp <= 0 { + return "" } - return "" + return time.Unix(timestamp, 0).Format("2006-01-02 15:04:05") } // InitCircuitBreaker 初始化Sentinel熔断器 @@ -445,20 +445,6 @@ func getAllowedIPsAndCIDRs() (map[string]bool, []*net.IPNet) { return allowedAdminIPsMap, allowedAdminCIDRs } -// getAllowedIPs 获取允许的IP列表(带锁保护,兼容旧代码) -func getAllowedIPs() map[string]bool { - allowedAdminIPsMutex.RLock() - defer allowedAdminIPsMutex.RUnlock() - return allowedAdminIPsMap -} - -// getAllowedCIDRs 获取允许的CIDR列表(带锁保护,兼容旧代码) -func getAllowedCIDRs() []*net.IPNet { - allowedAdminCIDRsMutex.RLock() - defer allowedAdminCIDRsMutex.RUnlock() - return allowedAdminCIDRs -} - // reset 重置所有指标到初始状态 func (m *CircuitBreakerMetrics) reset() { m.TotalRequests.Store(0) @@ -533,9 +519,9 @@ func (cb *CircuitBreakerInfo) updateWindowStats(isSuccess bool, ctx context.Cont // 计算当前窗口内的成功率 total := cb.Metrics.WindowRequests.Load() failures := cb.Metrics.WindowFailures.Load() - if total > 0 { + if total >= 10 { // 有足够样本时才记录 successRate := float64(total-failures) / float64(total) - if successRate < 0.5 && total >= 10 { // 如果成功率低于50%且有足够样本 + if successRate < 0.5 { // 如果成功率低于50% g.Log().Warningf(ctx, "熔断器 %s 窗口内成功率较低: %.2f%%, total=%d, failures=%d", cb.ResourceName, successRate*100, total, failures) } @@ -614,28 +600,25 @@ func initServiceCircuitBreaker(serviceName string, config *CircuitBreakerConfig) } var rule []*circuitbreaker.Rule - if config.EnableSlidingWindow { - rule = []*circuitbreaker.Rule{{ - Resource: resourceName, - Strategy: circuitbreaker.SlowRequestRatio, - RetryTimeoutMs: uint32(config.TimeoutParsed.Milliseconds()), - MinRequestAmount: uint64(config.MinRequestAmount), - StatIntervalMs: uint32(config.StatIntervalMs), - StatSlidingWindowBucketCount: 10, - MaxAllowedRtMs: uint64(config.SlowRequestThresholdParsed.Milliseconds()), - Threshold: threshold, - }} - } else { - rule = []*circuitbreaker.Rule{{ - Resource: resourceName, - Strategy: circuitbreaker.ErrorCount, - RetryTimeoutMs: uint32(config.TimeoutParsed.Milliseconds()), - MinRequestAmount: uint64(config.MinRequestAmount), - StatIntervalMs: uint32(config.StatIntervalMs), - Threshold: float64(config.MaxFailures), - }} + baseRule := &circuitbreaker.Rule{ + Resource: resourceName, + RetryTimeoutMs: uint32(config.TimeoutParsed.Milliseconds()), + MinRequestAmount: uint64(config.MinRequestAmount), + StatIntervalMs: uint32(config.StatIntervalMs), } + if config.EnableSlidingWindow { + baseRule.Strategy = circuitbreaker.SlowRequestRatio + baseRule.StatSlidingWindowBucketCount = 10 + baseRule.MaxAllowedRtMs = uint64(config.SlowRequestThresholdParsed.Milliseconds()) + baseRule.Threshold = threshold + } else { + baseRule.Strategy = circuitbreaker.ErrorCount + baseRule.Threshold = float64(config.MaxFailures) + } + + rule = []*circuitbreaker.Rule{baseRule} + if _, err := circuitbreaker.LoadRulesOfResource(resourceName, []*circuitbreaker.Rule{}); err != nil { return fmt.Errorf("清空熔断规则失败: %v", err) } @@ -849,14 +832,14 @@ func sendFallbackResponse(r *ghttp.Request, serviceName string, config *CircuitB return } + msg := fmt.Sprintf("服务 '%s' 暂时不可用,请稍后再试", serviceName) switch reason { case "blocked": - r.Response.WriteStatusExit(503, fmt.Sprintf("服务 '%s' 熔断保护中,请稍后再试", serviceName)) + msg = fmt.Sprintf("服务 '%s' 熔断保护中,请稍后再试", serviceName) case "distributed": - r.Response.WriteStatusExit(503, fmt.Sprintf("服务 '%s' 分布式熔断中", serviceName)) - default: - r.Response.WriteStatusExit(503, fmt.Sprintf("服务 '%s' 暂时不可用,请稍后再试", serviceName)) + msg = fmt.Sprintf("服务 '%s' 分布式熔断中", serviceName) } + r.Response.WriteStatusExit(503, msg) } // isSuccessStatusCode 判断HTTP状态码是否成功 @@ -877,16 +860,16 @@ func extractServiceName(path string) string { if path == "" { return "" } - parts := strings.Split(path, "/") - if len(parts) == 0 { - return "" - } - serviceName := parts[0] + // 获取第一个路径段 + if idx := strings.Index(path, "/"); idx > 0 { + path = path[:idx] + } + + // 解码URL编码(简化版) + serviceName := path if strings.Contains(serviceName, "%") { - if decoded, err := pathUnescape(serviceName); err == nil { - serviceName = decoded - } + serviceName = urlDecode(serviceName) } if _, ok := circuitBreakerConfigs.Load(serviceName); ok { @@ -895,31 +878,24 @@ func extractServiceName(path string) string { return "" } -// pathUnescape 路径片段的URL解码 -func pathUnescape(s string) (string, error) { - var builder strings.Builder - builder.Grow(len(s)) +// urlDecode 简单的URL解码 +func urlDecode(s string) string { + result := make([]byte, 0, len(s)) for i := 0; i < len(s); i++ { - switch s[i] { - case '%': - if i+2 >= len(s) { - builder.WriteByte(s[i]) - continue + if s[i] == '%' && i+2 < len(s) { + if high := hexDigit(s[i+1]); high != 0xFF { + if low := hexDigit(s[i+2]); low != 0xFF { + result = append(result, (high<<4)|low) + i += 2 + continue + } } - high := hexDigit(s[i+1]) - low := hexDigit(s[i+2]) - if high == 0xFF || low == 0xFF { - builder.WriteByte(s[i]) - } else { - builder.WriteByte((high << 4) | low) - i += 2 - } - default: - builder.WriteByte(s[i]) } + result = append(result, s[i]) } - return builder.String(), nil + + return string(result) } func hexDigit(c byte) byte { @@ -978,11 +954,11 @@ func filterServiceNames(services map[string]interface{}) []string { // isCircuitBreakerOpenInDistributed 检查分布式熔断状态 func isCircuitBreakerOpenInDistributed(ctx context.Context, resourceName string) bool { key := "circuit_breaker:" + resourceName + ":state" - redis := g.Redis() - if redis == nil { + redisClient := g.Redis() + if redisClient == nil { return false } - value, err := redis.Get(ctx, key) + value, err := redisClient.Get(ctx, key) if err != nil || value.IsNil() { return false } @@ -1219,9 +1195,9 @@ func resetSingleService(r *ghttp.Request, serviceName string) error { if configVal, ok := circuitBreakerConfigs.Load(serviceName); ok { config, ok := configVal.(*CircuitBreakerConfig) if ok && config.DistributedTTL > 0 { - redis := g.Redis() - if redis != nil { - if _, err := redis.Del(r.GetCtx(), "circuit_breaker:"+resourceName+":state"); err != nil { + redisClient := g.Redis() + if redisClient != nil { + if _, err := redisClient.Del(r.GetCtx(), "circuit_breaker:"+resourceName+":state"); err != nil { g.Log().Warningf(r.GetCtx(), "清除分布式熔断状态失败: %s, error: %v", resourceName, err) } }