| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121 |
- package service
- import (
- "context"
- "errors"
- "fmt"
- "strconv"
- "strings"
- "time"
- )
- const (
- SchedulerModeSingle = "single"
- SchedulerModeMixed = "mixed"
- SchedulerModeForced = "forced"
- )
- var (
- ErrSchedulerBucketRetired = errors.New("scheduler bucket retired")
- ErrSchedulerBucketWriteFenced = errors.New("scheduler bucket write fenced")
- ErrSchedulerGroupLifecycleLeaseInvalid = errors.New("scheduler group lifecycle lease invalid")
- ErrSchedulerGroupLifecycleLeaseLost = errors.New("scheduler group lifecycle lease lost")
- )
- // SchedulerBucketWriteToken fences a snapshot writer to one bucket epoch.
- // Tokens must be captured before any database load or queued rebuild work.
- type SchedulerBucketWriteToken struct {
- Bucket SchedulerBucket
- Epoch int64
- }
- func (t SchedulerBucketWriteToken) ValidFor(bucket SchedulerBucket) bool {
- return t.Epoch > 0 && t.Bucket == bucket
- }
- // SchedulerGroupLifecycleLease identifies one owner of a group's short-lived
- // retirement/reopen critical section.
- type SchedulerGroupLifecycleLease struct {
- GroupID int64
- OwnerToken string
- }
- func (l SchedulerGroupLifecycleLease) ValidFor(groupID int64) bool {
- return groupID > 0 && l.GroupID == groupID && l.OwnerToken != ""
- }
- type SchedulerBucket struct {
- GroupID int64
- Platform string
- Mode string
- }
- func (b SchedulerBucket) String() string {
- return fmt.Sprintf("%d:%s:%s", b.GroupID, b.Platform, b.Mode)
- }
- func ParseSchedulerBucket(raw string) (SchedulerBucket, bool) {
- parts := strings.Split(raw, ":")
- if len(parts) != 3 {
- return SchedulerBucket{}, false
- }
- groupID, err := strconv.ParseInt(parts[0], 10, 64)
- if err != nil {
- return SchedulerBucket{}, false
- }
- if parts[1] == "" || parts[2] == "" {
- return SchedulerBucket{}, false
- }
- return SchedulerBucket{
- GroupID: groupID,
- Platform: parts[1],
- Mode: parts[2],
- }, true
- }
- // SchedulerCache 负责调度快照与账号快照的缓存读写。
- type SchedulerCache interface {
- // GetSnapshot 读取快照并返回命中与否(ready + active + 数据完整)。
- GetSnapshot(ctx context.Context, bucket SchedulerBucket) ([]*Account, bool, error)
- // CaptureBucketWriteToken captures the current open epoch without changing
- // retirement state. A tombstoned bucket returns ErrSchedulerBucketRetired.
- CaptureBucketWriteToken(ctx context.Context, bucket SchedulerBucket) (SchedulerBucketWriteToken, error)
- // SetSnapshot 写入快照并切换激活版本。token 必须在 DB load/任务排队前取得。
- SetSnapshot(ctx context.Context, bucket SchedulerBucket, token SchedulerBucketWriteToken, accounts []Account) error
- // RetireBucket persistently tombstones a bucket and fences every older writer.
- // Readers that captured the active version before retirement may finish; new
- // readers observe ready/active as absent.
- RetireBucket(ctx context.Context, bucket SchedulerBucket) error
- // ReopenBucket is the only operation allowed to clear a tombstone. It returns
- // the retirement generation established by RetireBucket; repeated calls for
- // the same generation are idempotent. Callers must serialize a fresh authority
- // check through ReopenBucket with RetireBucket under the same group lifecycle
- // lease; ordinary rebuild paths never call ReopenBucket.
- ReopenBucket(ctx context.Context, bucket SchedulerBucket) (SchedulerBucketWriteToken, error)
- // TryAcquireGroupLifecycleLease serializes authoritative retirement/reopen
- // decisions for one non-zero group across instances.
- TryAcquireGroupLifecycleLease(ctx context.Context, groupID int64, ttl time.Duration) (SchedulerGroupLifecycleLease, bool, error)
- // ReleaseGroupLifecycleLease releases the lease only if its owner token still
- // matches, so an expired holder cannot delete a successor's lease. Missing,
- // expired, mismatched, and already released leases return
- // ErrSchedulerGroupLifecycleLeaseLost.
- ReleaseGroupLifecycleLease(ctx context.Context, lease SchedulerGroupLifecycleLease) error
- // GetAccount 获取单账号快照。
- GetAccount(ctx context.Context, accountID int64) (*Account, error)
- // SetAccount 写入单账号快照(包含不可调度状态)。
- SetAccount(ctx context.Context, account *Account) error
- // DeleteAccount 删除单账号快照。
- DeleteAccount(ctx context.Context, accountID int64) error
- // UpdateLastUsed 批量更新账号的最后使用时间。
- UpdateLastUsed(ctx context.Context, updates map[int64]time.Time) error
- // TryLockBucket 尝试获取分桶重建锁。
- TryLockBucket(ctx context.Context, bucket SchedulerBucket, ttl time.Duration) (bool, error)
- // UnlockBucket 释放分桶重建锁。
- UnlockBucket(ctx context.Context, bucket SchedulerBucket) error
- // ListBuckets 返回已注册的分桶集合。
- ListBuckets(ctx context.Context) ([]SchedulerBucket, error)
- // GetOutboxWatermark 读取 outbox 水位。
- GetOutboxWatermark(ctx context.Context) (int64, error)
- // SetOutboxWatermark 保存 outbox 水位。
- SetOutboxWatermark(ctx context.Context, id int64) error
- }
|