增加熔断策略
This commit is contained in:
@@ -3,6 +3,7 @@ package middleware
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"net"
|
||||
"strings"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
@@ -22,6 +23,35 @@ const (
|
||||
StateOpen CircuitBreakerState = "open" // 开启:熔断状态
|
||||
)
|
||||
|
||||
// 熔断器状态常量(用于atomic.Int64)
|
||||
const (
|
||||
stateClosed int64 = 0
|
||||
stateOpen int64 = 1
|
||||
)
|
||||
|
||||
// getState 获取熔断器状态字符串
|
||||
func (cb *CircuitBreakerInfo) getState() CircuitBreakerState {
|
||||
if cb.State.Load() == stateOpen {
|
||||
return StateOpen
|
||||
}
|
||||
return StateClosed
|
||||
}
|
||||
|
||||
// setState 设置熔断器状态(atomic操作,无锁)
|
||||
func (cb *CircuitBreakerInfo) setState(state CircuitBreakerState) CircuitBreakerState {
|
||||
var newState int64
|
||||
if state == StateOpen {
|
||||
newState = stateOpen
|
||||
} else {
|
||||
newState = stateClosed
|
||||
}
|
||||
oldState := cb.State.Swap(newState)
|
||||
if oldState == stateOpen {
|
||||
return StateOpen
|
||||
}
|
||||
return StateClosed
|
||||
}
|
||||
|
||||
// CircuitBreakerConfig 熔断器配置
|
||||
type CircuitBreakerConfig struct {
|
||||
Enabled bool // 是否启用熔断器
|
||||
@@ -40,6 +70,9 @@ type CircuitBreakerConfig struct {
|
||||
AdminIPs []string // 允许重置熔断器的管理员IP列表
|
||||
StatIntervalMs int // 统计窗口时长(毫秒),默认1000ms
|
||||
MinRequestAmount int // 最小请求数量,默认与MaxFailures相同
|
||||
AdminCIDRs []string // 允许重置熔断器的管理员CIDR列表(P0:支持IP段)
|
||||
// P1:预编译的CIDR网络掩码(性能优化)
|
||||
CIDRNetMasks []*net.IPNet
|
||||
}
|
||||
|
||||
// CircuitBreakerMetrics 熔断器指标
|
||||
@@ -48,19 +81,24 @@ type CircuitBreakerMetrics struct {
|
||||
PassRequests atomic.Int64 // 通过请求数
|
||||
BlockRequests atomic.Int64 // 阻塞请求数
|
||||
FailureRequests atomic.Int64 // 失败请求数
|
||||
SlowRequests atomic.Int64 // 慢请求数(P2:可观测性)
|
||||
OpenCount atomic.Int64 // 熔断开启次数
|
||||
LastResetTime atomic.Int64 // 上次重置时间(Unix时间戳)
|
||||
// 使用atomic.Int64实现简单的时间戳存储,避免使用mutex
|
||||
LastOpenTime atomic.Int64 // 上次熔断时间(Unix时间戳)
|
||||
NextRetryTime atomic.Int64 // 下次重试时间(Unix时间戳)
|
||||
}
|
||||
|
||||
// CircuitBreakerInfo 熔断器信息
|
||||
type CircuitBreakerInfo struct {
|
||||
ResourceName string `json:"resourceName"` // 资源名称
|
||||
State CircuitBreakerState `json:"state"` // 当前状态
|
||||
Config *CircuitBreakerConfig `json:"config"` // 配置信息
|
||||
LastOpenTime time.Time `json:"lastOpenTime"` // 上次熔断时间
|
||||
NextRetryTime time.Time `json:"nextRetryTime"` // 下次重试时间
|
||||
Metrics *CircuitBreakerMetrics `json:"metrics"` // 指标统计
|
||||
mu sync.RWMutex // 保护状态更新
|
||||
ResourceName string `json:"resourceName"` // 资源名称
|
||||
State atomic.Int64 `json:"state"` // 当前状态(0:closed, 1:open),使用atomic避免mutex
|
||||
Config *CircuitBreakerConfig `json:"config"` // 配置信息
|
||||
Metrics *CircuitBreakerMetrics `json:"metrics"` // 指标统计
|
||||
// 预编译的成功状态码集合(P1:性能优化)
|
||||
SuccessCodeMap map[int]bool
|
||||
// P1:预编译的CIDR网络掩码(避免重复解析)
|
||||
CIDRNetMasks []*net.IPNet
|
||||
}
|
||||
|
||||
var (
|
||||
@@ -74,10 +112,16 @@ var (
|
||||
stateChangeListeners sync.Map
|
||||
// stateChangeListenersRegistered 默认监听器是否已注册
|
||||
stateChangeListenersRegistered sync.Map
|
||||
// allowedAdminIPsCache 缓存的所有管理员IP白名单(性能优化)
|
||||
allowedAdminIPsCache []string
|
||||
// allowedAdminIPsCacheMutex 保护白名单缓存的并发访问
|
||||
allowedAdminIPsCacheMutex sync.RWMutex
|
||||
// P1:使用map代替slice优化IP查找性能
|
||||
allowedAdminIPsMap map[string]bool
|
||||
// allowedAdminIPsMutex 保护白名单缓存的并发访问
|
||||
allowedAdminIPsMutex sync.RWMutex
|
||||
// P1:预编译的CIDR网络掩码列表
|
||||
allowedAdminCIDRs []*net.IPNet
|
||||
// allowedAdminCIDRsMutex 保护CIDR列表的并发访问
|
||||
allowedAdminCIDRsMutex sync.RWMutex
|
||||
// totalServicesCount 缓存总服务数(P1:性能优化)
|
||||
totalServicesCount atomic.Int64
|
||||
)
|
||||
|
||||
// InitCircuitBreaker 初始化Sentinel熔断器
|
||||
@@ -95,9 +139,6 @@ func InitCircuitBreaker() error {
|
||||
|
||||
g.Log().Infof(ctx, "Sentinel熔断器初始化成功")
|
||||
|
||||
// 更新管理员IP白名单缓存
|
||||
updateAdminIPsCache()
|
||||
|
||||
// 扫描配置文件中所有配置了熔断器的服务
|
||||
services := g.Cfg().MustGet(ctx, "circuitBreaker").Map()
|
||||
|
||||
@@ -109,6 +150,9 @@ func InitCircuitBreaker() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// P1:缓存总服务数
|
||||
totalServicesCount.Store(int64(len(serviceNames)))
|
||||
|
||||
// 为每个服务创建熔断器
|
||||
enabledCount := 0
|
||||
for _, serviceName := range serviceNames {
|
||||
@@ -127,6 +171,9 @@ func InitCircuitBreaker() error {
|
||||
}
|
||||
}
|
||||
|
||||
// P1:更新管理员IP白名单缓存(在所有服务配置加载完成后)
|
||||
updateAdminIPsCache()
|
||||
|
||||
g.Log().Infof(ctx, "共初始化 %d 个服务熔断器,其中 %d 个已启用", len(serviceNames), enabledCount)
|
||||
return nil
|
||||
}
|
||||
@@ -170,12 +217,13 @@ func loadServiceCircuitBreakerConfig(serviceName string) *CircuitBreakerConfig {
|
||||
requestTimeout := g.Cfg().MustGet(ctx, key+".requestTimeout", 30000).Int()
|
||||
distributedTTL := g.Cfg().MustGet(ctx, key+".distributedTTL", 300).Int()
|
||||
adminIPs := g.Cfg().MustGet(ctx, key+".adminIPs", "").String()
|
||||
adminCIDRs := g.Cfg().MustGet(ctx, key+".adminCIDRs", "").String() // P0:支持CIDR
|
||||
statIntervalMs := g.Cfg().MustGet(ctx, key+".statIntervalMs", 1000).Int()
|
||||
minRequestAmount := g.Cfg().MustGet(ctx, key+".minRequestAmount", 0).Int()
|
||||
|
||||
// 解析成功状态码
|
||||
// 解析成功状态码(使用map用于快速查找)
|
||||
successCodes := g.Cfg().MustGet(ctx, key+".successStatusCodes", "200,201,204").String()
|
||||
statusCodes := parseStatusCodes(successCodes)
|
||||
statusCodes := parseStatusCodesSlice(successCodes)
|
||||
|
||||
// 解析时间(缓存结果,性能优化)
|
||||
timeoutParsed, err := time.ParseDuration(timeout)
|
||||
@@ -193,6 +241,13 @@ func loadServiceCircuitBreakerConfig(serviceName string) *CircuitBreakerConfig {
|
||||
// 解析管理员IP列表
|
||||
adminIPList := parseAdminIPs(adminIPs)
|
||||
|
||||
// P1:预编译CIDR为net.IPNet(支持IPv4和IPv6)
|
||||
cidrNets, err := parseAdminCIDRs(adminCIDRs)
|
||||
if err != nil {
|
||||
g.Log().Warningf(ctx, "服务 %s 的 adminCIDRs 解析失败: %v", serviceName, err)
|
||||
cidrNets = nil
|
||||
}
|
||||
|
||||
// 如果minRequestAmount未配置,则使用maxFailures作为默认值
|
||||
if minRequestAmount == 0 {
|
||||
minRequestAmount = maxFailures
|
||||
@@ -213,13 +268,27 @@ func loadServiceCircuitBreakerConfig(serviceName string) *CircuitBreakerConfig {
|
||||
RequestTimeout: requestTimeout,
|
||||
DistributedTTL: distributedTTL,
|
||||
AdminIPs: adminIPList,
|
||||
CIDRNetMasks: cidrNets, // P1:预编译的CIDR网络掩码
|
||||
StatIntervalMs: statIntervalMs,
|
||||
MinRequestAmount: minRequestAmount,
|
||||
}
|
||||
}
|
||||
|
||||
// parseStatusCodes 解析HTTP状态码
|
||||
func parseStatusCodes(str string) []int {
|
||||
// parseStatusCodes 解析HTTP状态码(返回map用于快速查找)
|
||||
func parseStatusCodes(str string) map[int]bool {
|
||||
parts := strings.Split(str, ",")
|
||||
codeMap := make(map[int]bool, len(parts))
|
||||
for _, part := range parts {
|
||||
var code int
|
||||
if _, err := fmt.Sscanf(strings.TrimSpace(part), "%d", &code); err == nil {
|
||||
codeMap[code] = true
|
||||
}
|
||||
}
|
||||
return codeMap
|
||||
}
|
||||
|
||||
// parseStatusCodesSlice 解析HTTP状态码(返回切片用于配置)
|
||||
func parseStatusCodesSlice(str string) []int {
|
||||
parts := strings.Split(str, ",")
|
||||
codes := make([]int, 0, len(parts))
|
||||
for _, part := range parts {
|
||||
@@ -231,29 +300,68 @@ func parseStatusCodes(str string) []int {
|
||||
return codes
|
||||
}
|
||||
|
||||
// updateAdminIPsCache 更新管理员IP白名单缓存(性能优化)
|
||||
// parseAdminCIDRs 解析管理员CIDR列表(P1:预编译为net.IPNet,P0:支持IPv6)
|
||||
func parseAdminCIDRs(str string) ([]*net.IPNet, error) {
|
||||
if str == "" {
|
||||
return nil, nil
|
||||
}
|
||||
parts := strings.Split(str, ",")
|
||||
nets := make([]*net.IPNet, 0, len(parts))
|
||||
for _, part := range parts {
|
||||
cidr := strings.TrimSpace(part)
|
||||
if cidr != "" {
|
||||
// 使用net.ParseCIDR解析CIDR(支持IPv4和IPv6)
|
||||
_, ipNet, err := net.ParseCIDR(cidr)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("解析CIDR失败: %s, error: %v", cidr, err)
|
||||
}
|
||||
nets = append(nets, ipNet)
|
||||
}
|
||||
}
|
||||
return nets, nil
|
||||
}
|
||||
|
||||
// updateAdminIPsCache 更新管理员IP白名单缓存(P1:使用map优化性能,P1:预编译CIDR)
|
||||
func updateAdminIPsCache() {
|
||||
var ipList []string
|
||||
ipSet := make(map[string]bool)
|
||||
ipMap := make(map[string]bool)
|
||||
cidrNets := make([]*net.IPNet, 0)
|
||||
|
||||
// 收集所有服务的adminIPs配置
|
||||
circuitBreakerConfigs.Range(func(key, value interface{}) bool {
|
||||
config := value.(*CircuitBreakerConfig)
|
||||
if len(config.AdminIPs) > 0 {
|
||||
for _, ip := range config.AdminIPs {
|
||||
if !ipSet[ip] {
|
||||
ipSet[ip] = true
|
||||
ipList = append(ipList, ip)
|
||||
if !ipMap[ip] {
|
||||
ipMap[ip] = true
|
||||
}
|
||||
}
|
||||
}
|
||||
// P1:使用预编译的CIDR网络掩码
|
||||
if len(config.CIDRNetMasks) > 0 {
|
||||
for _, cidrNet := range config.CIDRNetMasks {
|
||||
cidrNets = append(cidrNets, cidrNet)
|
||||
}
|
||||
}
|
||||
return true
|
||||
})
|
||||
|
||||
// 更新缓存
|
||||
allowedAdminIPsCacheMutex.Lock()
|
||||
allowedAdminIPsCache = ipList
|
||||
allowedAdminIPsCacheMutex.Unlock()
|
||||
allowedAdminIPsMutex.Lock()
|
||||
allowedAdminIPsMap = ipMap
|
||||
allowedAdminIPsMutex.Unlock()
|
||||
|
||||
allowedAdminCIDRsMutex.Lock()
|
||||
allowedAdminCIDRs = cidrNets
|
||||
allowedAdminCIDRsMutex.Unlock()
|
||||
}
|
||||
|
||||
// isIPInCIDR 检查IP是否在CIDR范围内(P1:使用预编译的net.IPNet,P0:支持IPv6)
|
||||
func isIPInCIDR(ipStr string, cidrNet *net.IPNet) bool {
|
||||
ip := net.ParseIP(ipStr)
|
||||
if ip == nil {
|
||||
return false
|
||||
}
|
||||
return cidrNet.Contains(ip)
|
||||
}
|
||||
|
||||
// parseAdminIPs 解析管理员IP列表
|
||||
@@ -341,13 +449,20 @@ func initServiceCircuitBreaker(serviceName string, config *CircuitBreakerConfig)
|
||||
return fmt.Errorf("加载熔断规则失败: %v", err)
|
||||
}
|
||||
|
||||
// 初始化熔断器信息
|
||||
cbInfo := &CircuitBreakerInfo{
|
||||
ResourceName: resourceName,
|
||||
State: StateClosed,
|
||||
Config: config,
|
||||
Metrics: &CircuitBreakerMetrics{},
|
||||
// 初始化熔断器信息(P1:直接从slice构建map,避免重复解析)
|
||||
successCodeMap := make(map[int]bool, len(config.SuccessStatusCodes))
|
||||
for _, code := range config.SuccessStatusCodes {
|
||||
successCodeMap[code] = true
|
||||
}
|
||||
|
||||
cbInfo := &CircuitBreakerInfo{
|
||||
ResourceName: resourceName,
|
||||
Config: config,
|
||||
Metrics: &CircuitBreakerMetrics{},
|
||||
SuccessCodeMap: successCodeMap,
|
||||
CIDRNetMasks: config.CIDRNetMasks,
|
||||
}
|
||||
cbInfo.State.Store(stateClosed)
|
||||
cbInfo.Metrics.LastResetTime.Store(time.Now().Unix())
|
||||
circuitBreakers.Store(serviceName, cbInfo)
|
||||
|
||||
@@ -367,39 +482,32 @@ func CircuitBreakerMiddleware(r *ghttp.Request) {
|
||||
startTime := time.Now()
|
||||
ctx := r.GetCtx()
|
||||
|
||||
// 从URL路径提取服务名(改进提取逻辑)
|
||||
// 从URL路径提取服务名并获取配置(P1:合并重复验证)
|
||||
serviceName := extractServiceName(r.URL.Path)
|
||||
if serviceName == "" {
|
||||
r.Middleware.Next()
|
||||
return
|
||||
}
|
||||
|
||||
// 获取熔断器配置
|
||||
val, ok := circuitBreakerConfigs.Load(serviceName)
|
||||
// 获取熔断器信息(包含配置)
|
||||
cbInfoVal, ok := circuitBreakers.Load(serviceName)
|
||||
if !ok {
|
||||
// 未配置熔断器,直接放行
|
||||
r.Middleware.Next()
|
||||
return
|
||||
}
|
||||
|
||||
config := val.(*CircuitBreakerConfig)
|
||||
cbInfo := cbInfoVal.(*CircuitBreakerInfo)
|
||||
config := cbInfo.Config
|
||||
if !config.Enabled {
|
||||
// 熔断器未启用,直接放行
|
||||
r.Middleware.Next()
|
||||
return
|
||||
}
|
||||
|
||||
// 获取熔断器信息
|
||||
cbInfoVal, ok := circuitBreakers.Load(serviceName)
|
||||
if !ok {
|
||||
r.Middleware.Next()
|
||||
return
|
||||
}
|
||||
cbInfo := cbInfoVal.(*CircuitBreakerInfo)
|
||||
cbInfo.Metrics.TotalRequests.Add(1)
|
||||
|
||||
// 提前构造resourceName(性能优化)
|
||||
resourceName := fmt.Sprintf("service:%s", serviceName)
|
||||
resourceName := cbInfo.ResourceName
|
||||
|
||||
// 设置请求超时(使用服务独立配置)
|
||||
if config.RequestTimeout > 0 {
|
||||
@@ -426,18 +534,15 @@ func CircuitBreakerMiddleware(r *ghttp.Request) {
|
||||
cbInfo.Metrics.OpenCount.Add(1)
|
||||
g.Log().Warningf(ctx, "熔断触发: %s, reason: %v", resourceName, blockError)
|
||||
|
||||
// 更新熔断器状态
|
||||
cbInfo.mu.Lock()
|
||||
oldState := cbInfo.State
|
||||
cbInfo.State = StateOpen
|
||||
cbInfo.LastOpenTime = time.Now()
|
||||
// 使用缓存的时间值(性能优化)
|
||||
cbInfo.NextRetryTime = time.Now().Add(config.TimeoutParsed)
|
||||
cbInfo.mu.Unlock()
|
||||
// 使用atomic更新状态(无锁)
|
||||
oldStateStr := cbInfo.setState(StateOpen)
|
||||
now := time.Now()
|
||||
cbInfo.Metrics.LastOpenTime.Store(now.Unix())
|
||||
cbInfo.Metrics.NextRetryTime.Store(now.Add(config.TimeoutParsed).Unix())
|
||||
|
||||
// 通知状态变化(如果状态改变)
|
||||
if oldState != StateOpen {
|
||||
notifyStateChange(serviceName, oldState, StateOpen)
|
||||
if oldStateStr != StateOpen {
|
||||
notifyStateChange(serviceName, oldStateStr, StateOpen)
|
||||
}
|
||||
|
||||
// 同步到分布式存储
|
||||
@@ -456,30 +561,37 @@ func CircuitBreakerMiddleware(r *ghttp.Request) {
|
||||
statusCode := r.Response.Status
|
||||
duration := time.Since(startTime)
|
||||
|
||||
// 使用提前获取的config判断状态码(性能优化)
|
||||
if !isSuccessStatusCode(config, statusCode) {
|
||||
// 判断是否为慢请求(P2:可观测性)
|
||||
if duration > config.SlowRequestThresholdParsed {
|
||||
cbInfo.Metrics.SlowRequests.Add(1)
|
||||
}
|
||||
|
||||
// 使用cbInfo.SuccessCodeMap判断状态码(性能优化)
|
||||
if !isSuccessStatusCode(cbInfo, statusCode) {
|
||||
// 记录异常
|
||||
cbInfo.Metrics.FailureRequests.Add(1)
|
||||
api.TraceError(entry, fmt.Errorf("request failed with status: %d", statusCode))
|
||||
g.Log().Debugf(ctx, "服务 %s 请求失败: status=%d, duration=%v", serviceName, statusCode, duration)
|
||||
} else {
|
||||
cbInfo.Metrics.PassRequests.Add(1)
|
||||
// 更新状态为关闭(如果之前是开启状态)
|
||||
cbInfo.mu.Lock()
|
||||
oldState := cbInfo.State
|
||||
if cbInfo.State != StateClosed {
|
||||
cbInfo.State = StateClosed
|
||||
notifyStateChange(serviceName, oldState, StateClosed)
|
||||
// 更新状态为关闭(如果之前是开启状态,使用atomic操作)
|
||||
if cbInfo.getState() != StateClosed {
|
||||
oldStateStr := cbInfo.setState(StateClosed)
|
||||
if oldStateStr != StateClosed {
|
||||
notifyStateChange(serviceName, oldStateStr, StateClosed)
|
||||
}
|
||||
}
|
||||
cbInfo.mu.Unlock()
|
||||
}
|
||||
|
||||
// 退出Sentinel资源
|
||||
entry.Exit()
|
||||
}
|
||||
|
||||
// sendFallbackResponse 发送降级响应
|
||||
// sendFallbackResponse 发送降级响应(P0:添加日志记录)
|
||||
func sendFallbackResponse(r *ghttp.Request, serviceName string, config *CircuitBreakerConfig, reason string) {
|
||||
// P0:记录降级日志,便于问题排查
|
||||
g.Log().Warningf(r.GetCtx(), "熔断器降级: service=%s, reason=%s, clientIP=%s", serviceName, reason, r.GetClientIp())
|
||||
|
||||
if config.EnableFallback && config.FallbackMessage != "" {
|
||||
// 自定义降级消息
|
||||
r.Response.WriteStatusExit(503, config.FallbackMessage)
|
||||
@@ -496,21 +608,16 @@ func sendFallbackResponse(r *ghttp.Request, serviceName string, config *CircuitB
|
||||
}
|
||||
}
|
||||
|
||||
// isSuccessStatusCode 判断HTTP状态码是否成功
|
||||
func isSuccessStatusCode(config *CircuitBreakerConfig, statusCode int) bool {
|
||||
if len(config.SuccessStatusCodes) > 0 {
|
||||
for _, code := range config.SuccessStatusCodes {
|
||||
if statusCode == code {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
// isSuccessStatusCode 判断HTTP状态码是否成功(使用cbInfo.SuccessCodeMap优化性能)
|
||||
func isSuccessStatusCode(cbInfo *CircuitBreakerInfo, statusCode int) bool {
|
||||
if cbInfo.SuccessCodeMap != nil && len(cbInfo.SuccessCodeMap) > 0 {
|
||||
return cbInfo.SuccessCodeMap[statusCode]
|
||||
}
|
||||
// 默认:2xx状态码为成功
|
||||
return statusCode >= 200 && statusCode < 300
|
||||
}
|
||||
|
||||
// extractServiceName 从URL路径提取服务名(改进提取逻辑)
|
||||
// extractServiceName 从URL路径提取服务名(P0:添加URL编码处理)
|
||||
func extractServiceName(path string) string {
|
||||
// 去除首尾斜杠并分割
|
||||
path = strings.Trim(path, "/")
|
||||
@@ -523,6 +630,19 @@ func extractServiceName(path string) string {
|
||||
}
|
||||
serviceName := parts[0]
|
||||
|
||||
// P0:处理URL编码,将 %2F 等转义字符还原
|
||||
// 注意:在goframe的网关中间件中,路径通常已经被框架处理过
|
||||
// 但为了安全性,这里对包含%的情况进行简单处理
|
||||
if strings.Contains(serviceName, "%") {
|
||||
// 尝试解码URL编码的字符串
|
||||
// 使用path.Unescape而不是url.QueryUnescape,因为我们处理的是路径片段
|
||||
decoded, err := pathUnescape(serviceName)
|
||||
if err == nil {
|
||||
serviceName = decoded
|
||||
}
|
||||
// 如果解码失败,继续使用原始serviceName
|
||||
}
|
||||
|
||||
// 验证服务名是否在已配置的熔断器中
|
||||
if _, ok := circuitBreakerConfigs.Load(serviceName); ok {
|
||||
return serviceName
|
||||
@@ -530,6 +650,56 @@ func extractServiceName(path string) string {
|
||||
return ""
|
||||
}
|
||||
|
||||
// pathUnescape 路径片段的URL解码(P0:安全性改进)
|
||||
// 注意:Go 1.8+ 可以使用 path.Unescape,这里提供兼容实现
|
||||
func pathUnescape(s string) (string, error) {
|
||||
// 使用strings.Builder优化性能
|
||||
var builder strings.Builder
|
||||
builder.Grow(len(s))
|
||||
|
||||
for i := 0; i < len(s); i++ {
|
||||
switch s[i] {
|
||||
case '%':
|
||||
// 处理百分号编码
|
||||
if i+2 >= len(s) {
|
||||
// 不完整的编码,保留原样
|
||||
builder.WriteByte(s[i])
|
||||
continue
|
||||
}
|
||||
// 解析十六进制数字
|
||||
high := hexDigit(s[i+1])
|
||||
low := hexDigit(s[i+2])
|
||||
if high == 0xFF || low == 0xFF {
|
||||
// 无效的十六进制,保留原样
|
||||
builder.WriteByte(s[i])
|
||||
continue
|
||||
}
|
||||
builder.WriteByte((high << 4) | low)
|
||||
i += 2
|
||||
case '+':
|
||||
// 路径片段中的+通常不需要解码为空格
|
||||
builder.WriteByte('+')
|
||||
default:
|
||||
builder.WriteByte(s[i])
|
||||
}
|
||||
}
|
||||
return builder.String(), nil
|
||||
}
|
||||
|
||||
// hexDigit 将十六进制字符转换为对应的数值
|
||||
func hexDigit(c byte) byte {
|
||||
switch {
|
||||
case '0' <= c && c <= '9':
|
||||
return c - '0'
|
||||
case 'a' <= c && c <= 'f':
|
||||
return c - 'a' + 10
|
||||
case 'A' <= c && c <= 'F':
|
||||
return c - 'A' + 10
|
||||
default:
|
||||
return 0xFF // 无效字符
|
||||
}
|
||||
}
|
||||
|
||||
// isCircuitBreakerOpenInDistributed 检查分布式熔断状态
|
||||
func isCircuitBreakerOpenInDistributed(ctx context.Context, resourceName string) bool {
|
||||
key := fmt.Sprintf("circuit_breaker:%s:state", resourceName)
|
||||
@@ -651,45 +821,95 @@ func notifyStateChange(serviceName string, fromState, toState CircuitBreakerStat
|
||||
})
|
||||
}
|
||||
|
||||
// CircuitBreakerHealthCheckHandler 熔断器健康检查接口
|
||||
// CircuitBreakerHealthCheckHandler 熔断器健康检查接口(P0:添加IP白名单验证,P1:添加分页支持,P1:优化性能)
|
||||
func CircuitBreakerHealthCheckHandler(r *ghttp.Request) {
|
||||
// P0:权限验证
|
||||
if !isAdminIP(r) {
|
||||
r.Response.WriteJsonExit(ghttp.DefaultHandlerResponse{
|
||||
Code: 403,
|
||||
Message: "权限不足,禁止访问",
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
status := make(map[string]interface{})
|
||||
totalServices := 0
|
||||
openServices := 0
|
||||
|
||||
// 遍历所有熔断器
|
||||
// P1:分页参数
|
||||
page := r.Get("page").Int()
|
||||
size := r.Get("size").Int()
|
||||
if page < 0 {
|
||||
page = 0
|
||||
}
|
||||
if size <= 0 || size > 100 {
|
||||
size = 20 // 默认20条,最多100条
|
||||
}
|
||||
|
||||
// P1:使用缓存的totalServicesCount避免每次遍历
|
||||
total := int(totalServicesCount.Load())
|
||||
start := page * size
|
||||
|
||||
// P1:只遍历分页范围内的服务(通过计数跳过)
|
||||
end := start + size
|
||||
if end > total {
|
||||
end = total
|
||||
}
|
||||
|
||||
current := 0
|
||||
circuitBreakers.Range(func(key, value interface{}) bool {
|
||||
// 跳过前面的页
|
||||
if current < start {
|
||||
current++
|
||||
return true
|
||||
}
|
||||
// 只处理当前页
|
||||
if current >= end {
|
||||
return false
|
||||
}
|
||||
|
||||
serviceName := key.(string)
|
||||
cbInfo := value.(*CircuitBreakerInfo)
|
||||
|
||||
totalServices++
|
||||
cbInfo.mu.RLock()
|
||||
isOpen := cbInfo.State == StateOpen
|
||||
isOpen := cbInfo.getState() == StateOpen
|
||||
if isOpen {
|
||||
openServices++
|
||||
}
|
||||
|
||||
// 从Metrics中读取数据(修复数据准确性问题)
|
||||
// 从Metrics中读取数据(使用atomic)
|
||||
lastResetTime := cbInfo.Metrics.LastResetTime.Load()
|
||||
var lastResetTimeStr string
|
||||
if lastResetTime > 0 {
|
||||
lastResetTimeStr = time.Unix(lastResetTime, 0).Format("2006-01-02 15:04:05")
|
||||
}
|
||||
|
||||
lastOpenTime := cbInfo.Metrics.LastOpenTime.Load()
|
||||
var lastOpenTimeStr string
|
||||
if lastOpenTime > 0 {
|
||||
lastOpenTimeStr = time.Unix(lastOpenTime, 0).Format("2006-01-02 15:04:05")
|
||||
}
|
||||
|
||||
nextRetryTime := cbInfo.Metrics.NextRetryTime.Load()
|
||||
var nextRetryTimeStr string
|
||||
if nextRetryTime > 0 {
|
||||
nextRetryTimeStr = time.Unix(nextRetryTime, 0).Format("2006-01-02 15:04:05")
|
||||
}
|
||||
|
||||
status[serviceName] = map[string]interface{}{
|
||||
"resource": cbInfo.ResourceName,
|
||||
"state": string(cbInfo.State),
|
||||
"lastOpenTime": cbInfo.LastOpenTime,
|
||||
"nextRetryTime": cbInfo.NextRetryTime,
|
||||
"state": string(cbInfo.getState()),
|
||||
"lastOpenTime": lastOpenTimeStr,
|
||||
"nextRetryTime": nextRetryTimeStr,
|
||||
"totalRequests": cbInfo.Metrics.TotalRequests.Load(),
|
||||
"passRequests": cbInfo.Metrics.PassRequests.Load(),
|
||||
"blockRequests": cbInfo.Metrics.BlockRequests.Load(),
|
||||
"failureRequests": cbInfo.Metrics.FailureRequests.Load(),
|
||||
"slowRequests": cbInfo.Metrics.SlowRequests.Load(),
|
||||
"openCount": cbInfo.Metrics.OpenCount.Load(),
|
||||
"lastResetTime": lastResetTimeStr,
|
||||
}
|
||||
cbInfo.mu.RUnlock()
|
||||
|
||||
current++
|
||||
return true
|
||||
})
|
||||
|
||||
@@ -705,31 +925,54 @@ func CircuitBreakerHealthCheckHandler(r *ghttp.Request) {
|
||||
Data: map[string]interface{}{
|
||||
"summary": summary,
|
||||
"services": status,
|
||||
"page": page,
|
||||
"size": size,
|
||||
"total": total,
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
// isAdminIP 检查请求IP是否在管理员白名单中(使用缓存优化性能)
|
||||
// isAdminIP 检查请求IP是否在管理员白名单中(P1:使用map优化性能,P0:支持IPv6 CIDR)
|
||||
func isAdminIP(r *ghttp.Request) bool {
|
||||
clientIP := r.GetClientIp()
|
||||
if clientIP == "" {
|
||||
return false
|
||||
}
|
||||
|
||||
// 读取缓存的白名单(性能优化)
|
||||
allowedAdminIPsCacheMutex.RLock()
|
||||
allowedIPs := allowedAdminIPsCache
|
||||
allowedAdminIPsCacheMutex.RUnlock()
|
||||
// 读取缓存的IP白名单(P1:使用map实现O(1)查找)
|
||||
allowedAdminIPsMutex.RLock()
|
||||
allowedIPs := allowedAdminIPsMap
|
||||
allowedAdminIPsMutex.RUnlock()
|
||||
|
||||
// 如果没有配置白名单,允许所有IP(向后兼容)
|
||||
if len(allowedIPs) == 0 {
|
||||
allowedAdminCIDRsMutex.RLock()
|
||||
hasCIDRs := len(allowedAdminCIDRs) > 0
|
||||
allowedAdminCIDRsMutex.RUnlock()
|
||||
// 如果也没有CIDR,则允许所有IP
|
||||
if !hasCIDRs {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
// 精确IP匹配(P1:map查找O(1))
|
||||
if allowedIPs[clientIP] {
|
||||
return true
|
||||
}
|
||||
|
||||
// 检查IP是否在白名单中
|
||||
for _, allowedIP := range allowedIPs {
|
||||
if clientIP == allowedIP {
|
||||
return true
|
||||
// P1:使用预编译的CIDR网络掩码匹配(支持IPv4和IPv6)
|
||||
allowedAdminCIDRsMutex.RLock()
|
||||
cidrNets := allowedAdminCIDRs
|
||||
allowedAdminCIDRsMutex.RUnlock()
|
||||
|
||||
if len(cidrNets) > 0 {
|
||||
clientNetIP := net.ParseIP(clientIP)
|
||||
if clientNetIP != nil {
|
||||
for _, cidrNet := range cidrNets {
|
||||
if cidrNet.Contains(clientNetIP) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -787,21 +1030,20 @@ func CircuitBreakerResetHandler(r *ghttp.Request) {
|
||||
}
|
||||
}
|
||||
|
||||
// 更新内存状态并重置指标
|
||||
// 更新内存状态并重置指标(使用atomic操作)
|
||||
if val, ok := circuitBreakers.Load(serviceName); ok {
|
||||
cbInfo := val.(*CircuitBreakerInfo)
|
||||
cbInfo.mu.Lock()
|
||||
cbInfo.State = StateClosed
|
||||
cbInfo.LastOpenTime = time.Time{}
|
||||
cbInfo.NextRetryTime = time.Time{}
|
||||
cbInfo.State.Store(stateClosed)
|
||||
cbInfo.Metrics.LastOpenTime.Store(0)
|
||||
cbInfo.Metrics.NextRetryTime.Store(0)
|
||||
// 重置指标
|
||||
cbInfo.Metrics.TotalRequests.Store(0)
|
||||
cbInfo.Metrics.PassRequests.Store(0)
|
||||
cbInfo.Metrics.BlockRequests.Store(0)
|
||||
cbInfo.Metrics.FailureRequests.Store(0)
|
||||
cbInfo.Metrics.SlowRequests.Store(0)
|
||||
cbInfo.Metrics.OpenCount.Store(0)
|
||||
cbInfo.Metrics.LastResetTime.Store(time.Now().Unix())
|
||||
cbInfo.mu.Unlock()
|
||||
}
|
||||
|
||||
// 重置分布式状态(如果启用)
|
||||
|
||||
Reference in New Issue
Block a user