增加熔断策略

This commit is contained in:
2026-01-01 14:07:14 +08:00
parent e03a4bfcff
commit 63191c147d

View File

@@ -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)
}
}