feat: enforce durable queue resource limits and backpressure
This commit is contained in:
@@ -2,6 +2,7 @@ package model
|
||||
|
||||
import (
|
||||
core "git.yuntuo.hk/k2safe/orbit/model"
|
||||
"time"
|
||||
)
|
||||
|
||||
// Queue 表示任务路由与资源控制队列,不要求每个租户单独创建队列。
|
||||
@@ -15,8 +16,16 @@ type Queue struct {
|
||||
Paused bool `db:"paused"`
|
||||
// MaxConcurrency 允许同时持有的有效执行许可上限,必须大于零。
|
||||
MaxConcurrency uint32 `db:"max_concurrency"`
|
||||
// RatePerSecond 每秒新执行的速率配置;实际限流由共享协调逻辑落实。
|
||||
// RatePerSecond 每个数据库一秒窗口最多提交的开始授权数,由所有节点共享。
|
||||
RatePerSecond uint32 `db:"rate_per_second"`
|
||||
// MaxUnfinishedTasks 未终结任务数上限;降额不删除或终止已受理任务。
|
||||
MaxUnfinishedTasks uint32 `db:"max_unfinished_tasks"`
|
||||
// UnfinishedTasks 等待、预占、运行及待核查的任务总数,首次终结时同事务减一。
|
||||
UnfinishedTasks uint64 `db:"unfinished_tasks"`
|
||||
// StartWindowUntil 当前一秒计数窗口结束时刻,空表示尚无开始授权。
|
||||
StartWindowUntil *time.Time `db:"start_window_until"`
|
||||
// StartsInWindow 当前窗口的开始授权次数,重复确认不计数,调整配置不清零。
|
||||
StartsInWindow uint32 `db:"starts_in_window"`
|
||||
// Policy 队列资源与执行配置的 JSON 文档。
|
||||
Policy core.JSON `db:"policy"`
|
||||
}
|
||||
|
||||
@@ -143,7 +143,7 @@ func (s *Service) CreateQueue(ctx context.Context, token auth.Token, key string,
|
||||
if err := ValidateKey(key); err != nil {
|
||||
return QueueResult{}, err
|
||||
}
|
||||
if !ValidName(in.Name, 128) || in.MaxConcurrency == 0 || in.MaxConcurrency > 100000 || in.RatePerSecond == 0 || in.RatePerSecond > 1000000 {
|
||||
if !ValidName(in.Name, 128) || in.MaxConcurrency == 0 || in.MaxConcurrency > 100000 || in.RatePerSecond == 0 || in.RatePerSecond > 1000000 || in.MaxUnfinishedTasks > 1000000 {
|
||||
return QueueResult{}, fault.Invalid("queue name, concurrency or rate is invalid")
|
||||
}
|
||||
return s.store.CreateQueue(ctx, token, key, in)
|
||||
@@ -154,3 +154,19 @@ func (s *Service) GetQueue(ctx context.Context, token auth.Token, id core.ID) (Q
|
||||
}
|
||||
return s.store.GetQueue(ctx, token, id)
|
||||
}
|
||||
|
||||
func (s *Service) ConfigureQueue(ctx context.Context, token auth.Token, id core.ID, key string, in QueueConfigInput) (QueueResult, error) {
|
||||
if id.Validate() != nil {
|
||||
return QueueResult{}, fault.Invalid("queue id is invalid")
|
||||
}
|
||||
if err := ValidateKey(key); err != nil {
|
||||
return QueueResult{}, err
|
||||
}
|
||||
if in.ExpectedRevision == 0 || strings.TrimSpace(in.Reason) == "" || !utf8.ValidString(in.Reason) || utf8.RuneCountInString(in.Reason) > 1024 {
|
||||
return QueueResult{}, fault.Invalid("reason and nonzero expected_revision are required")
|
||||
}
|
||||
if in.MaxConcurrency == 0 || in.MaxConcurrency > 100000 || in.RatePerSecond == 0 || in.RatePerSecond > 1000000 || in.MaxUnfinishedTasks == 0 || in.MaxUnfinishedTasks > 1000000 {
|
||||
return QueueResult{}, fault.Invalid("queue limits are invalid")
|
||||
}
|
||||
return s.store.ConfigureQueue(ctx, token, id, key, in)
|
||||
}
|
||||
|
||||
@@ -149,14 +149,20 @@ type TaskPage struct {
|
||||
NextCursor string `json:"next_cursor,omitempty"`
|
||||
}
|
||||
type QueueInput struct {
|
||||
// MaxUnfinishedTasks 未终结任务上限;省略或零使用 100000,最高 1000000。
|
||||
MaxUnfinishedTasks uint32 `json:"max_unfinished_tasks,omitempty"`
|
||||
// Name 队列名称,同一命名空间内唯一。
|
||||
Name string `json:"name"`
|
||||
// MaxConcurrency 最大同时执行数量,供执行阶段使用。
|
||||
// MaxConcurrency 允许占用的有效执行许可上限。
|
||||
MaxConcurrency uint32 `json:"max_concurrency"`
|
||||
// RatePerSecond 每秒开始数量上限,供执行阶段使用。
|
||||
// RatePerSecond 数据库一秒窗口最多提交的开始授权数。
|
||||
RatePerSecond uint32 `json:"rate_per_second"`
|
||||
}
|
||||
type QueueView struct {
|
||||
// MaxUnfinishedTasks 未终结任务上限,包含待核查;只限制新的受理。
|
||||
MaxUnfinishedTasks uint32 `json:"max_unfinished_tasks"`
|
||||
// UnfinishedTasks 当前未终结任务数,JSON 十进制字符串;运行计数不改变配置版本。
|
||||
UnfinishedTasks uint64 `json:"unfinished_tasks,string"`
|
||||
// ID 资源标识,32 位小写十六进制字符串。
|
||||
ID core.ID `json:"id"`
|
||||
// NamespaceID 命名空间标识,由凭据确定隔离范围。
|
||||
@@ -165,13 +171,30 @@ type QueueView struct {
|
||||
Name string `json:"name"`
|
||||
// Paused 是否停止新执行;不禁止受理新任务。
|
||||
Paused bool `json:"paused"`
|
||||
// MaxConcurrency 最大同时执行数量,供执行阶段使用。
|
||||
// MaxConcurrency 允许占用的有效执行许可上限。
|
||||
MaxConcurrency uint32 `json:"max_concurrency"`
|
||||
// RatePerSecond 每秒开始数量上限,供执行阶段使用。
|
||||
// RatePerSecond 数据库一秒窗口最多提交的开始授权数。
|
||||
RatePerSecond uint32 `json:"rate_per_second"`
|
||||
// Revision 并发版本,JSON 使用十进制字符串避免跨语言精度丢失。
|
||||
Revision uint64 `json:"revision,string"`
|
||||
}
|
||||
|
||||
// QueueConfigInput 完整替换控制配置,必须保留同一操作的原版本及原因。
|
||||
type QueueConfigInput struct {
|
||||
// ExpectedRevision 非零的队列配置版本。
|
||||
ExpectedRevision uint64 `json:"expected_revision,string"`
|
||||
// Reason 管理原因,写入不可变审计。
|
||||
Reason string `json:"reason"`
|
||||
// Paused 停止新的领取和开始;在途续租、结果与受理继续。
|
||||
Paused bool `json:"paused" required:"true"`
|
||||
// MaxConcurrency 有效并发上限,降低时已有许可自然收尾。
|
||||
MaxConcurrency uint32 `json:"max_concurrency"`
|
||||
// RatePerSecond 数据库一秒窗口最多开始授权数,降低时保留原计数。
|
||||
RatePerSecond uint32 `json:"rate_per_second"`
|
||||
// MaxUnfinishedTasks 未终结任务上限,降低时不删除历史或在途任务。
|
||||
MaxUnfinishedTasks uint32 `json:"max_unfinished_tasks"`
|
||||
}
|
||||
|
||||
type QueueResult struct {
|
||||
// Queue 本次操作返回的队列快照。
|
||||
Queue QueueView `json:"queue"`
|
||||
@@ -186,6 +209,7 @@ type Store interface {
|
||||
ListTasks(context.Context, auth.Token, ListInput) (TaskPage, error)
|
||||
CancelTask(context.Context, auth.Token, core.ID, string, CancelInput) (CancelResult, error)
|
||||
CreateQueue(context.Context, auth.Token, string, QueueInput) (QueueResult, error)
|
||||
ConfigureQueue(context.Context, auth.Token, core.ID, string, QueueConfigInput) (QueueResult, error)
|
||||
GetQueue(context.Context, auth.Token, core.ID) (QueueView, error)
|
||||
ListTaskAttempts(context.Context, auth.Token, core.ID, HistoryPageInput) (AttemptPage, error)
|
||||
GetTaskAttempt(context.Context, auth.Token, core.ID, core.ID) (AttemptView, error)
|
||||
|
||||
@@ -18,6 +18,7 @@ import (
|
||||
var StaleSession = fault.New("STALE_SESSION", "connection is expired or superseded")
|
||||
var LeaseLost = fault.New("LEASE_LOST", "attempt no longer grants execution authority")
|
||||
var GateClosed = fault.New("EXECUTION_PAUSED", "queue or execution gate is paused")
|
||||
var RateLimited = fault.New("RATE_LIMITED", "queue start limit reached; retry the original start within its lease")
|
||||
|
||||
type Service struct{ store Store }
|
||||
|
||||
|
||||
Reference in New Issue
Block a user