Files
go-admin/config/extend.go
T
zhangwenjian cd8edfa5d4 fix🐛: reject rate-limited requests with 429 and make the threshold configurable
A rejected request answered 200 with the failure only in the body, so every
layer that reads the status line counted it as served: load balancers, metrics,
client-side retry. A load test against this reported the limiter's own
rejections as successful traffic and overstated throughput more than tenfold.

The threshold was a constant in the middleware, which made 200 QPS the ceiling
of every deployment with nothing in the configuration to reveal it. It now
reads extend.rateLimit.inboundQPS; an absent value keeps 200, so an upgrade
changes nothing, and zero disables the limiter for a deployment behind its own
gateway.

Also drops Strategy: system.BBR. Reading sentinel's source, the adaptive
strategy is consulted only for Load and CpuUsage - for InboundQPS the trigger
count is compared directly - so it read as if the limit adapted to the machine
when it never did.
2026-08-28 19:42:21 +08:00

75 lines
2.2 KiB
Go

package config
var ExtConfig Extend
// Extend 扩展配置
//
// extend:
// demo:
// name: demo-name
//
// 使用方法: config.ExtConfig......即可!!
type Extend struct {
AMap AMap // 这里配置对应配置文件的结构即可
FileStore FileStore
RateLimit RateLimit
}
// DefaultInboundQPS is the limit applied when nothing is configured. It is the
// value that used to be hard-coded in the middleware, so an existing deployment
// that adds nothing to settings.yml keeps the behaviour it already had.
const DefaultInboundQPS = 200
// RateLimit 全局入站限流。
//
// extend:
// ratelimit:
// inboundqps: 200 # 每秒入站请求上限;填 0 关闭限流
//
// The threshold used to live in common/middleware/sentinel.go as a constant,
// which made 200 QPS the ceiling of every deployment with nothing in the
// configuration to reveal it.
type RateLimit struct {
// InboundQPS caps inbound requests per second across the process.
//
// Absent means DefaultInboundQPS, zero disables the limiter, and a positive
// value is the threshold. The pointer is what separates "not configured"
// from "configured to zero" - the two need different answers and a plain
// float64 cannot tell them apart.
InboundQPS *float64
}
// Threshold reports the limit to apply. Zero means no limiting.
func (r RateLimit) Threshold() float64 {
if r.InboundQPS == nil {
return DefaultInboundQPS
}
return *r.InboundQPS
}
type AMap struct {
Key string
}
// FileStore 对象存储。上传接口的 source 参数决定走哪一家:2 是阿里云,3 是七牛。
// 没有填的那一家在被请求时返回明确错误,而不是上传到别处或者崩溃。
//
// common/file_store 里还实现了华为云 OBS,但上传接口没有对应的 source 取值,
// 所以这里也不为它提供配置。
type FileStore struct {
AliYun ObjectStore
QiNiu ObjectStore
}
type ObjectStore struct {
Endpoint string
AccessKeyID string
AccessKeySecret string
BucketName string
}
// Configured reports whether enough was filled in to attempt a connection.
func (o ObjectStore) Configured() bool {
return o.Endpoint != "" && o.AccessKeyID != "" && o.AccessKeySecret != "" && o.BucketName != ""
}