feat(queue): 增加非文本模型分布式准入队列

使用 PostgreSQL 统一同步与异步非文本任务的并发准入、持久化等待和 Worker 容量分配,并将生产 API 与独立 Worker 角色拆分。

补充策略管理、共享契约、OpenAPI、Kubernetes 双节点 Worker 清单及跨节点验收脚本;未默认启用任何生产 queue_size 策略。

已在原基线完成 Go、前端、迁移、Shell、Kustomize 与长任务容量验收;合入最新主干后将重新执行发布门禁。
This commit is contained in:
2026-07-29 16:15:43 +08:00
parent 2e5a90731b
commit 9e4fc7362d
55 changed files with 4565 additions and 237 deletions
+149 -34
View File
@@ -1,13 +1,24 @@
package store
import (
"errors"
"fmt"
"math"
"strings"
)
var ErrInvalidRateLimitPolicy = errors.New("invalid rate limit policy")
func IsInvalidRateLimitPolicy(err error) bool {
return errors.Is(err, ErrInvalidRateLimitPolicy)
}
const (
RateLimitPolicyModeInherit = "inherit"
RateLimitPolicyModeOverride = "override"
DefaultQueueMaxWaitSeconds = 600
MaxQueueSize = 10000
MaxQueueWaitSeconds = 3600
)
type EffectiveRateLimitPolicyInput struct {
@@ -78,46 +89,150 @@ func NormalizeRateLimitPolicy(policy map[string]any) map[string]any {
}
out := clonePolicy(policy)
rules, _ := out["rules"].([]any)
if len(rules) > 0 {
return out
}
legacyScopes := []map[string]any{policy}
for _, key := range []string{"platformLimits", "modelLimits", "platform_limits", "model_limits"} {
if nested, ok := policy[key].(map[string]any); ok {
legacyScopes = append(legacyScopes, nested)
if len(rules) == 0 {
legacyScopes := []map[string]any{policy}
for _, key := range []string{"platformLimits", "modelLimits", "platform_limits", "model_limits"} {
if nested, ok := policy[key].(map[string]any); ok {
legacyScopes = append(legacyScopes, nested)
}
}
if limit, ok := lowestPositiveLegacyLimit(legacyScopes,
"max_concurrent_requests", "maxConcurrentRequests", "concurrent"); ok {
rules = append(rules, map[string]any{
"metric": "concurrent",
"limit": limit,
"leaseTtlSeconds": 120,
})
}
if limit, ok := lowestPositiveLegacyLimit(legacyScopes,
"max_request_per_minute", "maxRequestsPerMinute", "rpm"); ok {
rules = append(rules, map[string]any{
"metric": "rpm",
"limit": limit,
"windowSeconds": 60,
})
}
if limit, ok := lowestPositiveLegacyLimit(legacyScopes,
"max_tokens_per_minute", "maxTokensPerMinute", "tpm_total"); ok {
rules = append(rules, map[string]any{
"metric": "tpm_total",
"limit": limit,
"windowSeconds": 60,
})
}
}
if limit, ok := lowestPositiveLegacyLimit(legacyScopes,
"max_concurrent_requests", "maxConcurrentRequests", "concurrent"); ok {
rules = append(rules, map[string]any{
"metric": "concurrent",
"limit": limit,
"leaseTtlSeconds": 120,
})
}
if limit, ok := lowestPositiveLegacyLimit(legacyScopes,
"max_request_per_minute", "maxRequestsPerMinute", "rpm"); ok {
rules = append(rules, map[string]any{
"metric": "rpm",
"limit": limit,
"windowSeconds": 60,
})
}
if limit, ok := lowestPositiveLegacyLimit(legacyScopes,
"max_tokens_per_minute", "maxTokensPerMinute", "tpm_total"); ok {
rules = append(rules, map[string]any{
"metric": "tpm_total",
"limit": limit,
"windowSeconds": 60,
})
}
if len(rules) > 0 {
out["rules"] = rules
normalizedRules := make([]any, 0, len(rules))
for _, rawRule := range rules {
rule, ok := rawRule.(map[string]any)
if !ok {
normalizedRules = append(normalizedRules, rawRule)
continue
}
normalizedRule := clonePolicy(rule)
if strings.TrimSpace(stringValue(normalizedRule["metric"])) == "queue_size" {
if floatValue(normalizedRule["limit"]) <= 0 {
continue
}
if floatValue(normalizedRule["maxWaitSeconds"]) <= 0 {
normalizedRule["maxWaitSeconds"] = DefaultQueueMaxWaitSeconds
}
} else {
delete(normalizedRule, "maxWaitSeconds")
}
normalizedRules = append(normalizedRules, normalizedRule)
}
out["rules"] = normalizedRules
return out
}
type QueuePolicyRule struct {
Limit int
MaxWaitSeconds int
Policy map[string]any
}
func QueueRuleFromPolicy(policy map[string]any) (QueuePolicyRule, bool) {
normalized := NormalizeRateLimitPolicy(policy)
rules, _ := normalized["rules"].([]any)
for _, rawRule := range rules {
rule, _ := rawRule.(map[string]any)
if strings.TrimSpace(stringValue(rule["metric"])) != "queue_size" {
continue
}
limit := int(math.Floor(floatValue(rule["limit"])))
if limit <= 0 {
return QueuePolicyRule{}, false
}
maxWaitSeconds := int(math.Floor(floatValue(rule["maxWaitSeconds"])))
if maxWaitSeconds <= 0 {
maxWaitSeconds = DefaultQueueMaxWaitSeconds
}
return QueuePolicyRule{
Limit: limit,
MaxWaitSeconds: maxWaitSeconds,
Policy: normalized,
}, true
}
return QueuePolicyRule{}, false
}
// NormalizeAndValidateRateLimitPolicy validates only the canonical queue
// extension. Existing rate-limit metrics intentionally keep their historical
// permissive parsing contract.
func NormalizeAndValidateRateLimitPolicy(policy map[string]any) (map[string]any, error) {
if policy == nil {
return nil, nil
}
rulesValue, rulesPresent := policy["rules"]
if rulesPresent {
if _, ok := rulesValue.([]any); !ok {
return nil, fmt.Errorf("%w: rateLimitPolicy.rules must be an array", ErrInvalidRateLimitPolicy)
}
}
rules, _ := rulesValue.([]any)
for index, rawRule := range rules {
rule, ok := rawRule.(map[string]any)
if !ok {
return nil, fmt.Errorf("%w: rateLimitPolicy.rules[%d] must be an object", ErrInvalidRateLimitPolicy, index)
}
metric := strings.TrimSpace(stringValue(rule["metric"]))
maxWait, hasMaxWait := numericRuleValue(rule, "maxWaitSeconds")
if metric != "queue_size" {
if hasMaxWait {
return nil, fmt.Errorf("%w: rateLimitPolicy.rules[%d].maxWaitSeconds is only valid for queue_size", ErrInvalidRateLimitPolicy, index)
}
continue
}
limit, hasLimit := numericRuleValue(rule, "limit")
if !hasLimit {
return nil, fmt.Errorf("%w: rateLimitPolicy.rules[%d].limit must be an integer", ErrInvalidRateLimitPolicy, index)
}
if limit != math.Trunc(limit) || limit < 0 || limit > MaxQueueSize {
return nil, fmt.Errorf("%w: queue_size limit must be 0 or an integer between 1 and %d", ErrInvalidRateLimitPolicy, MaxQueueSize)
}
if limit == 0 {
continue
}
if hasMaxWait && (maxWait != math.Trunc(maxWait) || maxWait < 1 || maxWait > MaxQueueWaitSeconds) {
return nil, fmt.Errorf("%w: queue_size maxWaitSeconds must be an integer between 1 and %d", ErrInvalidRateLimitPolicy, MaxQueueWaitSeconds)
}
}
return NormalizeRateLimitPolicy(policy), nil
}
func numericRuleValue(rule map[string]any, key string) (float64, bool) {
raw, ok := rule[key]
if !ok || raw == nil {
return 0, false
}
switch raw.(type) {
case float64, float32, int, int8, int16, int32, int64, uint, uint8, uint16, uint32, uint64:
return floatValue(raw), true
default:
return 0, false
}
}
func ConcurrentPolicyCapacity(policy map[string]any) (int, bool) {
limit, ok := RateLimitPolicyMetric(policy, "concurrent")
if !ok || limit <= 0 {