feat: serialize keyed tasks across queues with durable fencing tokens
This commit is contained in:
@@ -9,6 +9,8 @@ import (
|
||||
// Attempt 表示一次任务执行尝试,保存租约、授权代次与独立结果。
|
||||
// 对应表 pulse_attempts;这是服务端持久化模型,不直接作为 API 响应。
|
||||
type Attempt struct {
|
||||
// ConcurrencyToken 资源键的单调许可序号;零表示未启用,重连保留,新尝试递增。
|
||||
ConcurrencyToken uint64 `db:"concurrency_token"`
|
||||
// Record 包含命名空间、记录标识、并发版本和 UTC 时间。
|
||||
core.Record
|
||||
// TaskID 关联的 Pulse 任务标识。
|
||||
|
||||
@@ -35,6 +35,10 @@ type Task struct {
|
||||
RecoverySafety RecoverySafety `db:"recovery_safety"`
|
||||
// BusinessKey 业务侧关联键,用于检索追踪,不自动提供业务幂等;可为空,表示尚未设置该关联或值。
|
||||
BusinessKey *string `db:"business_key"`
|
||||
// ConcurrencyKey 显式互斥资源键,命名空间内跨队列共享;空表示不启用。
|
||||
ConcurrencyKey *string `db:"concurrency_key"`
|
||||
// ConcurrencyKeyDigest 资源键精确 UTF-8 字节的域分隔摘要,与键同时为空或非空。
|
||||
ConcurrencyKeyDigest *core.Digest `db:"concurrency_key_digest"`
|
||||
// OwnerKind 来源资源类型,例如流程激活;为空表示无来源关联。
|
||||
OwnerKind *string `db:"owner_kind"`
|
||||
// OwnerID 来源资源标识,必须与来源类型同时有值或同时为空。
|
||||
|
||||
@@ -9,6 +9,8 @@ import (
|
||||
// WorkerSession 表示一次 Worker 连接的持久化身份与能力快照。
|
||||
// 对应表 pulse_worker_sessions;这是服务端持久化模型,不直接作为 API 响应。
|
||||
type WorkerSession struct {
|
||||
// ConcurrencyKeys 是否明确支持互斥资源键与序号交付,旧 Worker 默认为假。
|
||||
ConcurrencyKeys bool `db:"concurrency_keys"`
|
||||
// Record 包含命名空间、记录标识、并发版本和 UTC 时间。
|
||||
core.Record
|
||||
// WorkerInstanceID Worker 进程实例标识,重连保留,进程重启应更换。
|
||||
|
||||
@@ -19,18 +19,19 @@ type HistoryPageInput struct {
|
||||
|
||||
// AttemptSummary 是执行尝试的查询快照,不提供当前执行授权。
|
||||
type AttemptSummary struct {
|
||||
ID core.ID `json:"id"` // ID 尝试标识,不是执行秘密。
|
||||
TaskID core.ID `json:"task_id"` // TaskID 所属任务;查询始终限定在该任务下。
|
||||
Epoch uint64 `json:"epoch,string"` // Epoch 单任务执行代次,JSON 为十进制字符串。
|
||||
State pulse.AttemptState `json:"state"` // State 尝试状态,与整个任务状态分开。
|
||||
Revision uint64 `json:"revision,string"` // Revision 当前记录版本,可因续租/恢复变化。
|
||||
LeaseUntil time.Time `json:"lease_until"` // LeaseUntil 数据库记录的租约期限;查询不能授权业务执行。
|
||||
AuthorizedAt *time.Time `json:"authorized_at,omitempty"` // AuthorizedAt 开始批准时间;空表示未批准,不证明副作用已经发生。
|
||||
EndedAt *time.Time `json:"ended_at,omitempty"` // EndedAt 尝试结束时间;仍在执行时为空。
|
||||
Retryable bool `json:"retryable,omitempty"` // Retryable 该明确失败报告是否声明可安全重试;不代表平台必定安排重试。
|
||||
ErrorCode string `json:"error_code,omitempty"` // ErrorCode 稳定错误分类;无错误时为空。
|
||||
CreatedAt time.Time `json:"created_at"` // CreatedAt 创建时间,UTC 微秒精度。
|
||||
UpdatedAt time.Time `json:"updated_at"` // UpdatedAt 最新变更时间,不能代替版本校验。
|
||||
ConcurrencyToken uint64 `json:"concurrency_token,omitempty,string"` // ConcurrencyToken 资源键的单调许可序号;无资源键时为零,不能代替当前授权。
|
||||
ID core.ID `json:"id"` // ID 尝试标识,不是执行秘密。
|
||||
TaskID core.ID `json:"task_id"` // TaskID 所属任务;查询始终限定在该任务下。
|
||||
Epoch uint64 `json:"epoch,string"` // Epoch 单任务执行代次,JSON 为十进制字符串。
|
||||
State pulse.AttemptState `json:"state"` // State 尝试状态,与整个任务状态分开。
|
||||
Revision uint64 `json:"revision,string"` // Revision 当前记录版本,可因续租/恢复变化。
|
||||
LeaseUntil time.Time `json:"lease_until"` // LeaseUntil 数据库记录的租约期限;查询不能授权业务执行。
|
||||
AuthorizedAt *time.Time `json:"authorized_at,omitempty"` // AuthorizedAt 开始批准时间;空表示未批准,不证明副作用已经发生。
|
||||
EndedAt *time.Time `json:"ended_at,omitempty"` // EndedAt 尝试结束时间;仍在执行时为空。
|
||||
Retryable bool `json:"retryable,omitempty"` // Retryable 该明确失败报告是否声明可安全重试;不代表平台必定安排重试。
|
||||
ErrorCode string `json:"error_code,omitempty"` // ErrorCode 稳定错误分类;无错误时为空。
|
||||
CreatedAt time.Time `json:"created_at"` // CreatedAt 创建时间,UTC 微秒精度。
|
||||
UpdatedAt time.Time `json:"updated_at"` // UpdatedAt 最新变更时间,不能代替版本校验。
|
||||
}
|
||||
|
||||
// AttemptView 增加当前归属与已保存结果,不返回令牌、摘要或原始请求秘密。
|
||||
|
||||
@@ -6,6 +6,7 @@ import (
|
||||
"regexp"
|
||||
"strings"
|
||||
"time"
|
||||
"unicode"
|
||||
"unicode/utf8"
|
||||
|
||||
"git.yuntuo.hk/k2safe/orbit/internal/fault"
|
||||
@@ -51,6 +52,9 @@ func NormalizeSubmit(in SubmitInput) (SubmitInput, error) {
|
||||
if !utf8.ValidString(in.BusinessKey) || utf8.RuneCountInString(in.BusinessKey) > 191 || strings.ContainsAny(in.BusinessKey, "\x00\r\n") {
|
||||
return in, fault.Invalid("business_key must be at most 191 characters without control newlines")
|
||||
}
|
||||
if err := ValidateConcurrencyKey(in.ConcurrencyKey); err != nil {
|
||||
return in, err
|
||||
}
|
||||
if in.TaskTimeoutMS == 0 {
|
||||
in.TaskTimeoutMS = 24 * 60 * 60 * 1000
|
||||
}
|
||||
@@ -170,3 +174,19 @@ func (s *Service) ConfigureQueue(ctx context.Context, token auth.Token, id core.
|
||||
}
|
||||
return s.store.ConfigureQueue(ctx, token, id, key, in)
|
||||
}
|
||||
|
||||
// ValidateConcurrencyKey 不归一化资源键,字节精确相等才共享许可。
|
||||
func ValidateConcurrencyKey(key string) error {
|
||||
if key == "" {
|
||||
return nil
|
||||
}
|
||||
if !utf8.ValidString(key) || utf8.RuneCountInString(key) > 191 || strings.TrimSpace(key) != key {
|
||||
return fault.Invalid("invalid concurrency_key")
|
||||
}
|
||||
for _, c := range key {
|
||||
if unicode.IsControl(c) {
|
||||
return fault.Invalid("invalid concurrency_key")
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -25,6 +25,8 @@ type SubmitInput struct {
|
||||
Payload json.RawMessage `json:"payload"`
|
||||
// BusinessKey 可选关联键,不提供业务效果幂等保证。
|
||||
BusinessKey string `json:"business_key,omitempty"`
|
||||
// ConcurrencyKey 显式互斥资源键,同命名空间跨队列最多一个有效尝试;不代替业务幂等。
|
||||
ConcurrencyKey string `json:"concurrency_key,omitempty"`
|
||||
// AvailableAt 最早执行时间;为空表示受理后即可进入派发范围。
|
||||
AvailableAt *time.Time `json:"available_at,omitempty"`
|
||||
// StartDeadlineAt 最晚开始时间;为空表示使用任务总截止时间。
|
||||
@@ -63,6 +65,8 @@ type TaskView struct {
|
||||
EffectUncertain bool `json:"effect_uncertain,omitempty"`
|
||||
// BusinessKey 可选业务关联键,不自动提供业务效果幂等。
|
||||
BusinessKey string `json:"business_key,omitempty"`
|
||||
// ConcurrencyKey 显式互斥资源键,同命名空间跨队列最多一个有效尝试;不代替业务幂等。
|
||||
ConcurrencyKey string `json:"concurrency_key,omitempty"`
|
||||
// AvailableAt 最早可执行时间,UTC。
|
||||
AvailableAt time.Time `json:"available_at"`
|
||||
// StartDeadlineAt 最晚开始时间;为空时使用任务总截止时间。
|
||||
@@ -131,6 +135,8 @@ type TaskSummary struct {
|
||||
EffectUncertain bool `json:"effect_uncertain,omitempty"`
|
||||
// BusinessKey 可选业务关联键,不自动提供业务效果幂等。
|
||||
BusinessKey string `json:"business_key,omitempty"`
|
||||
// ConcurrencyKey 显式互斥资源键,同命名空间跨队列最多一个有效尝试;不代替业务幂等。
|
||||
ConcurrencyKey string `json:"concurrency_key,omitempty"`
|
||||
// AvailableAt 最早可执行时间,UTC。
|
||||
AvailableAt time.Time `json:"available_at"`
|
||||
// DeadlineAt 任务总截止时间,UTC。
|
||||
|
||||
@@ -21,6 +21,7 @@ type Capability struct {
|
||||
|
||||
// ConnectInput 在每次连接时提交;同一进程恢复时保留实例和秘密。
|
||||
type ConnectInput struct {
|
||||
ConcurrencyKeys bool `json:"concurrency_keys,omitempty"` // ConcurrencyKeys 显式支持互斥资源键和单调序号交付,重连不可变。
|
||||
CancellationNotifications bool `json:"cancellation_notifications,omitempty"` // CancellationNotifications 显式接受 task.cancel 提示;事实仍从持久化租约核对。
|
||||
InstanceID core.ID `json:"instance_id"` // InstanceID 进程标识,重启换新值。
|
||||
InstanceSecret string `json:"instance_secret"` // InstanceSecret 32 字节随机秘密,只保存摘要。
|
||||
@@ -56,12 +57,13 @@ type CapacityInput struct {
|
||||
|
||||
// Assignment 仅通过已认证执行连接交付,包含敏感尝试秘密。
|
||||
type Assignment struct {
|
||||
Task service.TaskView `json:"task"` // Task 受理快照及业务参数。
|
||||
AttemptID core.ID `json:"attempt_id"` // AttemptID 本次尝试标识。
|
||||
Epoch uint64 `json:"epoch,string"` // Epoch 任务执行代次,隔离旧尝试。
|
||||
LeaseSecret string `json:"lease_secret"` // LeaseSecret 执行秘密,禁止普通日志及查询返回。
|
||||
LeaseUntil time.Time `json:"lease_until"` // LeaseUntil 尚未开始时的预占有效期。
|
||||
ServerTime time.Time `json:"server_time"` // ServerTime 领取时数据库时间。
|
||||
ConcurrencyToken uint64 `json:"concurrency_token,omitempty,string"` // ConcurrencyToken 资源键的单调许可序号;无资源键时为零,不能代替当前授权。
|
||||
Task service.TaskView `json:"task"` // Task 受理快照及业务参数。
|
||||
AttemptID core.ID `json:"attempt_id"` // AttemptID 本次尝试标识。
|
||||
Epoch uint64 `json:"epoch,string"` // Epoch 任务执行代次,隔离旧尝试。
|
||||
LeaseSecret string `json:"lease_secret"` // LeaseSecret 执行秘密,禁止普通日志及查询返回。
|
||||
LeaseUntil time.Time `json:"lease_until"` // LeaseUntil 尚未开始时的预占有效期。
|
||||
ServerTime time.Time `json:"server_time"` // ServerTime 领取时数据库时间。
|
||||
}
|
||||
|
||||
// AttemptRef 证明对一次尝试的控制权;连接代次仍由 Binding 单独验证。
|
||||
|
||||
Reference in New Issue
Block a user