All checks were successful
构建并部署到测试环境(无 SSH) / build-and-deploy (push) Successful in 9m33s
- 新增网关限流:按本系统运营商账户(卡 carrier_id)→ 限流组 → 操作限制每秒请求数,每次 HTTP 尝试计量,默认限流、仅后台人工点击/通道阈值/自动续费/手动刷新豁免;支持只统计与拦截两种模式,默认只统计 - Asynq 遇限流不计失败、固定短延迟重试;轮询任务与停复机业务重试遇限流短延迟重排 - 停机、复机不再使用网关内部重试,只发一次请求 - 网关返回结构化错误;新增上游错误分类规则表与测试接口 - 新增按月分区的停复机记录表:系统自动停复机只写记录,不再写统一审计与集成日志;普通轮询查询不再写集成日志 - 新增限流组、错误分类、停复机记录、按账户调用统计与枚举后台接口 - 迁移 000243~000246;同步 OpenSpec 提案(含后续 6 个 change)与技术方案、工程约束
137 lines
6.0 KiB
Go
137 lines
6.0 KiB
Go
// Package gatewaylimit 实现按运营商账户(本系统 tb_carrier)的卡网关调用限流、错误分类与调用统计。
|
||
//
|
||
// 账户口径:限流单位是本系统的运营商账户(卡上的 carrier_id),与网关客户端自身的 appId 无关。
|
||
// 调用方已知卡所属账户时用 gateway.WithCarrier 传入;否则守卫按 ICCID 查本系统卡表解析。
|
||
package gatewaylimit
|
||
|
||
import (
|
||
"context"
|
||
stderrors "errors"
|
||
"strconv"
|
||
"sync"
|
||
"time"
|
||
|
||
"go.uber.org/zap"
|
||
|
||
"github.com/break/junhong_cmp_fiber/internal/infrastructure/systemconfig"
|
||
"github.com/break/junhong_cmp_fiber/pkg/constants"
|
||
)
|
||
|
||
// RegisterConfigDefinitions 向受控系统配置注册表注册网关限流相关配置;已注册的 Key 跳过。
|
||
func RegisterConfigDefinitions(registry *systemconfig.Registry, logger *zap.Logger) {
|
||
if registry == nil {
|
||
return
|
||
}
|
||
minOne, minZero := int64(1), int64(0)
|
||
maxQPS, maxDelay, maxAlert, maxMonths := int64(10000), int64(3600), int64(86400*7), int64(120)
|
||
definitions := []systemconfig.Definition{
|
||
{Key: constants.SystemConfigPollingRateLimitMode, Module: constants.SystemConfigModulePolling, ValueType: constants.SystemConfigTypeString, DefaultValue: constants.RateLimitModeObserve, Description: "网关限流模式:observe 只统计不拦截,enforce 拦截", EnumValues: []string{constants.RateLimitModeObserve, constants.RateLimitModeEnforce}},
|
||
{Key: constants.SystemConfigPollingGatewayDefaultQPS, Module: constants.SystemConfigModulePolling, ValueType: constants.SystemConfigTypeInt, DefaultValue: "10", Description: "未被启用限流组覆盖的卡网关操作的默认每秒请求上限", Min: &minOne, Max: &maxQPS},
|
||
{Key: constants.SystemConfigPollingRateLimitedRetryDelay, Module: constants.SystemConfigModulePolling, ValueType: constants.SystemConfigTypeInt, DefaultValue: "5", Description: "Asynq 任务遇限流后的固定重试延迟(秒)", Min: &minOne, Max: &maxDelay},
|
||
{Key: constants.SystemConfigPollingRateLimitDeferAlert, Module: constants.SystemConfigModulePolling, ValueType: constants.SystemConfigTypeInt, DefaultValue: "600", Description: "同一任务因限流累计推迟超过该时长(秒)时写告警", Min: &minOne, Max: &maxAlert},
|
||
{Key: constants.SystemConfigPollingStopResumeRecordRetention, Module: constants.SystemConfigModulePolling, ValueType: constants.SystemConfigTypeInt, DefaultValue: "6", Description: "停复机记录保留月数,到期整区删除", Min: &minOne, Max: &maxMonths},
|
||
{Key: constants.SystemConfigPollingStopResumeRecordPremake, Module: constants.SystemConfigModulePolling, ValueType: constants.SystemConfigTypeInt, DefaultValue: "3", Description: "停复机记录提前创建未来分区的月数", Min: &minZero, Max: &maxMonths},
|
||
}
|
||
for _, definition := range definitions {
|
||
if _, exists := registry.Get(definition.Key); exists {
|
||
continue
|
||
}
|
||
if err := registry.Register(definition); err != nil && logger != nil {
|
||
logger.Error("注册网关限流系统配置失败,将使用代码默认值", zap.String("config_key", definition.Key), zap.Error(err))
|
||
}
|
||
}
|
||
}
|
||
|
||
// ConfigReader 读取已注册的受控系统配置。
|
||
type ConfigReader interface {
|
||
Get(ctx context.Context, key string) (string, error)
|
||
}
|
||
|
||
// Settings 是网关限流运行参数快照。
|
||
type Settings struct {
|
||
Mode string
|
||
DefaultQPS int
|
||
RetryDelay time.Duration
|
||
DeferAlert time.Duration
|
||
RecordRetention int
|
||
RecordPremakeMonth int
|
||
}
|
||
|
||
// Enforce 判断当前是否为拦截模式。
|
||
func (s Settings) Enforce() bool {
|
||
return s.Mode == constants.RateLimitModeEnforce
|
||
}
|
||
|
||
// defaultSettings 是配置读取失败时的安全默认值:只统计、不拦截。
|
||
func defaultSettings() Settings {
|
||
return Settings{Mode: constants.RateLimitModeObserve, DefaultQPS: 10, RetryDelay: 5 * time.Second, DeferAlert: 10 * time.Minute, RecordRetention: 6, RecordPremakeMonth: 3}
|
||
}
|
||
|
||
// SettingsProvider 在进程内缓存限流参数,超过 TTL 重新读取,后台修改数秒内生效。
|
||
type SettingsProvider struct {
|
||
reader ConfigReader
|
||
logger *zap.Logger
|
||
mu sync.Mutex
|
||
cached Settings
|
||
loaded time.Time
|
||
}
|
||
|
||
// NewSettingsProvider 创建限流参数提供者;reader 为 nil 时始终返回安全默认值。
|
||
func NewSettingsProvider(reader ConfigReader, logger *zap.Logger) *SettingsProvider {
|
||
if logger == nil {
|
||
logger = zap.NewNop()
|
||
}
|
||
return &SettingsProvider{reader: reader, logger: logger, cached: defaultSettings()}
|
||
}
|
||
|
||
// Current 返回当前限流参数;读取失败时保留上一次成功值。
|
||
func (p *SettingsProvider) Current(ctx context.Context) Settings {
|
||
if p == nil {
|
||
return defaultSettings()
|
||
}
|
||
p.mu.Lock()
|
||
defer p.mu.Unlock()
|
||
if p.reader == nil || time.Since(p.loaded) < constants.GatewayRateLimitConfigTTL {
|
||
return p.cached
|
||
}
|
||
next, err := p.load(ctx)
|
||
p.loaded = time.Now()
|
||
if err != nil {
|
||
p.logger.Warn("读取网关限流配置失败,沿用上一次配置", zap.Error(err))
|
||
return p.cached
|
||
}
|
||
p.cached = next
|
||
return next
|
||
}
|
||
|
||
func (p *SettingsProvider) load(ctx context.Context) (Settings, error) {
|
||
settings := defaultSettings()
|
||
mode, err := p.reader.Get(ctx, constants.SystemConfigPollingRateLimitMode)
|
||
if err != nil {
|
||
return settings, err
|
||
}
|
||
settings.Mode = mode
|
||
ints := []struct {
|
||
key string
|
||
set func(int)
|
||
}{
|
||
{constants.SystemConfigPollingGatewayDefaultQPS, func(v int) { settings.DefaultQPS = v }},
|
||
{constants.SystemConfigPollingRateLimitedRetryDelay, func(v int) { settings.RetryDelay = time.Duration(v) * time.Second }},
|
||
{constants.SystemConfigPollingRateLimitDeferAlert, func(v int) { settings.DeferAlert = time.Duration(v) * time.Second }},
|
||
{constants.SystemConfigPollingStopResumeRecordRetention, func(v int) { settings.RecordRetention = v }},
|
||
{constants.SystemConfigPollingStopResumeRecordPremake, func(v int) { settings.RecordPremakeMonth = v }},
|
||
}
|
||
for _, item := range ints {
|
||
raw, err := p.reader.Get(ctx, item.key)
|
||
if err != nil {
|
||
return settings, err
|
||
}
|
||
value, err := strconv.Atoi(raw)
|
||
if err != nil {
|
||
return settings, stderrors.New("网关限流整数配置非法")
|
||
}
|
||
item.set(value)
|
||
}
|
||
return settings, nil
|
||
}
|