trpc-naming-polaris/v3 熔断机制分析与 HTTP 5xx 熔断接入

适用版本:trpc-go v0.19.3 · trpc-naming-polaris/v3 v3.0.6 · polaris-go/v2 v2.6.10 适用项目:ad_auth(其他 trpc-go 项目同理,注意核对版本)

TL;DR

  1. 会默认注入熔断:只要 import 了 trpc-naming-polaris/v3 并配置了 plugins.selector.polaris(未设 default: false),北极星熔断器就会被注册为 trpc-go 的全局默认熔断器,且 polaris SDK 熔断默认 enable=true。
  2. 默认只对 3 类框架错误熔断:101 客户端超时、111 连接失败、141 网络错误。业务错误(含 HTTP 4xx/5xx)默认按成功上报,不会触发熔断。
  3. HTTP 503 要熔断:需 ① 让错误上报给 selector(report_any_err_to_selector 或 WithShouldErrReportToSelector);② 若走「按名寻址」还需 ShouldCircuitBreak 判定为失败;③ 下游必须是北极星寻址(ip:// 不生效)。

一、默认是否注入熔断

1.1 线上配置

1
2
3
4
5
6
7
8
plugins:
  selector:
    polaris:
      address_list: ${polaris_address_grpc_list}
      protocol: grpc
      discovery:
        refresh_interval: ${polaris_refresh_interval}
      enable_canary: true

未配置 circuitbreaker、default、report_timeout,全部走默认值。

1.2 插件层:Setup 时无条件注册

naming.go 的 setupComponents 不做任何开关判断,直接调用 circuitbreaker.Setup:

1
2
3
4
5
// trpc-naming-polaris/v3@v3.0.6/naming.go
if err := circuitbreaker.Setup(sdkCtx, &circuitbreaker.Config{
    Name:          conf.Name,
    ReportTimeout: conf.ReportTimeout,
}, setDefault); err != nil { ... }

setDefault 由 default 配置决定,未配置时为 true:

1
2
3
4
5
// trpc-naming-polaris/v3@v3.0.6/circuitbreaker/circuitbreaker.go
circuitbreaker.Register(name, cb)
if setDefault {
    circuitbreaker.SetDefaultCircuitBreaker(cb)
}

trpc-go 原默认值是 NoopCircuitBreaker(不熔断),此处被替换为北极星实现。

1.3 SDK 层:polaris-go 默认参数

插件 setSdkCircuitBreaker 只在 yaml 显式配置时覆盖,否则使用 SDK 默认值:

项 默认值 来源
Enable true config.DefaultCircuitBreakerEnabled
Chain errorCount → errorRate → errorCheck CircuitBreakerConfigImpl.SetDefault
errorCount 连续 10 次失败熔断,统计窗口 1min errorcount/config.go
errorRate 60s 窗口,错误率 ≥50% 且请求数 ≥10 熔断 errorrate/config.go
sleepWindow 30s 后进入半开 DefaultSleepWindow
checkPeriod 2s DefaultCircuitBreakerCheckPeriod
outlierDetection(主动探测) 关闭;开启时插件强制仅用 tcp 探测 DefaultOutlierDetectEnabled / naming.go

1.4 两条调用路径都生效

下游 client 配置方式 Selector 上报逻辑 摘除熔断实例
target: polaris://xxx 插件 selector.Selector circuitbreaker.Report(包级函数) GetOneInstance 过滤
仅 name(默认北极星寻址) trpc-go TrpcSelector DefaultCircuitBreaker.Report(即北极星 CB) 北极星路由/负载均衡过滤
target: ip://... ipSelector noopCircuitBreak 不熔断

1.5 哪些错误会被计为熔断失败

trpc-go/client/client.go 的 selectorFilter:

1
2
3
4
5
6
7
if e := as(err); e != nil {             // 仅框架错误 101/111/141
    opts.Selector.Report(node, cost, e)
} else if opts.shouldErrReportToSelector(err) { // 默认 false
    opts.Selector.Report(node, cost, err)
} else {
    opts.Selector.Report(node, cost, nil)  // 其它错误都按成功上报
}
  • 仅 RetClientConnectFail(111)、RetClientTimeout(101)、RetClientNetErr(141) 的框架错误原样上报。
  • 101 超时若耗时 < report_timeout(默认 1ms)会被忽略。
  • 业务错误、下游返回的错误码 → 按成功上报。

效果:某下游实例在 60s 内超时/连接失败率 >50%(≥10 请求)或连续失败 10 次,被摘除 30s,之后半开探测恢复。


二、HTTP 503 如何支持熔断

2.1 现象

GetAmsAD fail! err=type:business, code:503, msg:http client codec StatusCode: Service Unavailable

2.2 为什么不生效

  1. trpc-go HTTP 客户端对 StatusCode >= 300 生成的是业务错误(type:business, code:503):

    1
    2
    3
    4
    5
    
    // trpc-go@v0.19.3/http/codec.go
    if rsp.StatusCode >= http.StatusMultipleChoices {
        msg.WithClientRspErr(errs.New(rsp.StatusCode,
            fmt.Sprintf("http client codec StatusCode: %s, body: %q", ...)))
    }
    
  2. selectorFilter 中 as(err) 只认框架错误,shouldErrReportToSelector 默认 false → 503 被按成功上报。

  3. 即使上报了,两条路径的判定也不同:

trpc.http.ams.ads 寻址方式 判定逻辑 默认是否把 503 计为失败
target: polaris://xxx 包级 circuitbreaker.Report → canIgnoreError 只忽略 nil 与短耗时 101 是(上报即失败)
仅 name(TrpcSelector) CircuitBreaker.Report → shouldCircuitBreak 默认只认 111/141/101 否
target: ip://...(当前本地 yaml) noopCircuitBreak 不熔断

2.3 改造方案

Step 1(必需):让 503 上报到 selector

方式 A:yaml 配置(零代码)

1
2
3
4
5
client:
  service:
    - name: trpc.http.ams.ads
      target: polaris://<AMS北极星服务名>   # 必须是北极星寻址
      report_any_err_to_selector: true

副作用:4xx(400/404 等)也会上报,在 polaris target 路径下同样计为失败。

方式 B:代码精准控制(推荐,仅 5xx)

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
// deps/ams.go
opts := []client.Option{
    client.WithReqHead(header),
    client.WithShouldErrReportToSelector(func(err error) bool {
        switch errs.Code(err) {
        case http.StatusBadGateway, http.StatusServiceUnavailable, http.StatusGatewayTimeout:
            return true
        }
        return false
    }),
}

Step 2:按寻址方式补齐判定

  • target: polaris://:Step 1 即可,上报的错误直接计为失败。

  • 仅 name(默认北极星寻址):还需在 main.go 中、trpc.NewServer() 之前注册判定函数(插件在 NewServer 时 Setup,之后注册无效):

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    
    import pcb "git.code.oa.com/trpc-go/trpc-naming-polaris/v3/circuitbreaker"
    
    func main() {
        pcb.ShouldCircuitBreak(func(err error) pcb.Should {
            if e, ok := err.(*errs.Error); ok && e.Type == errs.ErrorTypeBusiness {
                switch e.Code {
                case 502, 503, 504:
                    return pcb.True
                }
            }
            return pcb.Unknown // 交回默认逻辑(101/111/141)
        })
        s := trpc.NewServer()
        // ...
    }
    

    ShouldCircuitBreak 只影响 TrpcSelector 路径,对 polaris:// 路径无效。

  • ip://:需改为北极星寻址;或自行 selector.NewIPSelectorWithCircuitBreaker 重新注册 ip selector(不推荐)。

Step 3(可选):调整熔断参数

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
plugins:
  selector:
    polaris:
      report_timeout: 1ms          # 耗时低于该值的 101 超时不计失败
      circuitbreaker:
        # enable: false            # 如需整体关闭
        sleepWindow: 30s
        errorCount:
          continuousErrorThreshold: 10
        errorRate:
          errorRateThreshold: 0.5
          requestVolumeThreshold: 10
          metricStatTimeWindow: 60s

2.4 注意事项

  1. 北极星熔断是实例级的:仅当下游在北极星有多个实例时才有意义。若是 CLB/VIP 单入口,熔断等于全断;而「全死全活」默认开启,全部熔断时会全部放开,等于没熔断。此类场景更适合调用方级别的降级/限流(如 sentinel / hystrix 类 filter)。
  2. 503 常代表下游过载/限流:实例级熔断会把流量集中到剩余实例,可能引发雪崩,阈值需保守。
  3. 业务码不参与熔断:上述方案只对传输层 HTTP 状态码生效;rsp.Ret != 0 等业务码是在 proxy.Post 返回后判断的,不经过 selector。

附:关键源码位置

文件 说明
trpc-naming-polaris/v3@v3.0.6/naming.go setupComponents、setSdkCircuitBreaker、setSdkOutlierDetection
trpc-naming-polaris/v3@v3.0.6/circuitbreaker/circuitbreaker.go Setup、Report、canIgnoreError、ShouldCircuitBreak
trpc-naming-polaris/v3@v3.0.6/selector/selector.go polaris target 路径的 Select / Report
trpc-go@v0.19.3/client/client.go selectorFilter、as()
trpc-go@v0.19.3/client/config.go report_any_err_to_selector
trpc-go@v0.19.3/naming/selector/trpc_selector.go 按名寻址路径的熔断上报
trpc-go@v0.19.3/naming/selector/ip_selector.go ip 直连默认 noopCircuitBreak
trpc-go@v0.19.3/http/codec.go HTTP 状态码转业务错误
polaris-go/v2@v2.6.10/pkg/config/default.go、circuitbreaker.go SDK 熔断默认参数