15.2 太牛了!任务超时控制和重试策略竟然还能这样实现?

1 阅读8分钟

15.2 太牛了!任务超时控制和重试策略竟然还能这样实现?

在分布式任务调度系统中,任务超时控制和重试策略是确保系统稳定性和任务可靠执行的重要机制。今天我们将深入探讨如何实现这些关键功能。

任务超时控制机制

任务超时控制是防止任务执行时间过长而阻塞系统资源的重要手段。我们需要实现精确的超时检测和优雅的超时中断机制。

package timeout

import (
    "context"
    "fmt"
    "sync"
    "time"
)

// TimeoutManager 超时管理器
type TimeoutManager struct {
    tasks map[string]*TaskTimeoutInfo
    mu    sync.RWMutex
    stopCh chan struct{}
}

// TaskTimeoutInfo 任务超时信息
type TaskTimeoutInfo struct {
    TaskID      string
    Deadline    time.Time
    Timeout     time.Duration
    CancelFunc  context.CancelFunc
    Callback    TimeoutCallback
    Status      TimeoutStatus
}

// TimeoutStatus 超时状态
type TimeoutStatus int

const (
    TimeoutStatusPending TimeoutStatus = iota
    TimeoutStatusRunning
    TimeoutStatusTimeout
    TimeoutStatusCompleted
)

// TimeoutCallback 超时回调函数
type TimeoutCallback func(taskID string) error

// NewTimeoutManager 创建超时管理器
func NewTimeoutManager() *TimeoutManager {
    tm := &TimeoutManager{
        tasks:  make(map[string]*TaskTimeoutInfo),
        stopCh: make(chan struct{}),
    }
    
    // 启动超时检查协程
    go tm.run()
    
    return tm
}

// AddTask 添加任务超时监控
func (tm *TimeoutManager) AddTask(taskID string, timeout time.Duration, callback TimeoutCallback) error {
    tm.mu.Lock()
    defer tm.mu.Unlock()
    
    // 检查任务是否已存在
    if _, exists := tm.tasks[taskID]; exists {
        return fmt.Errorf("task %s already exists", taskID)
    }
    
    // 创建上下文和取消函数
    ctx, cancel := context.WithTimeout(context.Background(), timeout)
    
    // 创建任务超时信息
    taskInfo := &TaskTimeoutInfo{
        TaskID:     taskID,
        Deadline:   time.Now().Add(timeout),
        Timeout:    timeout,
        CancelFunc: cancel,
        Callback:   callback,
        Status:     TimeoutStatusPending,
    }
    
    tm.tasks[taskID] = taskInfo
    
    return nil
}

// StartTask 开始任务超时监控
func (tm *TimeoutManager) StartTask(taskID string) error {
    tm.mu.Lock()
    defer tm.mu.Unlock()
    
    taskInfo, exists := tm.tasks[taskID]
    if !exists {
        return fmt.Errorf("task %s not found", taskID)
    }
    
    taskInfo.Status = TimeoutStatusRunning
    taskInfo.Deadline = time.Now().Add(taskInfo.Timeout)
    
    return nil
}

// CompleteTask 完成任务
func (tm *TimeoutManager) CompleteTask(taskID string) error {
    tm.mu.Lock()
    defer tm.mu.Unlock()
    
    taskInfo, exists := tm.tasks[taskID]
    if !exists {
        return fmt.Errorf("task %s not found", taskID)
    }
    
    // 取消上下文
    if taskInfo.CancelFunc != nil {
        taskInfo.CancelFunc()
    }
    
    taskInfo.Status = TimeoutStatusCompleted
    
    // 从监控列表中移除
    delete(tm.tasks, taskID)
    
    return nil
}

// run 运行超时检查
func (tm *TimeoutManager) run() {
    ticker := time.NewTicker(100 * time.Millisecond)
    defer ticker.Stop()
    
    for {
        select {
        case <-ticker.C:
            tm.checkTimeouts()
        case <-tm.stopCh:
            return
        }
    }
}

// checkTimeouts 检查超时任务
func (tm *TimeoutManager) checkTimeouts() {
    tm.mu.Lock()
    defer tm.mu.Unlock()
    
    now := time.Now()
    var timeoutTasks []*TaskTimeoutInfo
    
    // 查找超时任务
    for _, taskInfo := range tm.tasks {
        if taskInfo.Status == TimeoutStatusRunning && now.After(taskInfo.Deadline) {
            timeoutTasks = append(timeoutTasks, taskInfo)
        }
    }
    
    // 处理超时任务
    for _, taskInfo := range timeoutTasks {
        tm.handleTimeout(taskInfo)
    }
}

// handleTimeout 处理超时任务
func (tm *TimeoutManager) handleTimeout(taskInfo *TaskTimeoutInfo) {
    taskInfo.Status = TimeoutStatusTimeout
    
    // 取消上下文
    if taskInfo.CancelFunc != nil {
        taskInfo.CancelFunc()
    }
    
    // 调用回调函数
    if taskInfo.Callback != nil {
        go func() {
            if err := taskInfo.Callback(taskInfo.TaskID); err != nil {
                fmt.Printf("Timeout callback failed for task %s: %v\n", taskInfo.TaskID, err)
            }
        }()
    }
}

// Stop 停止超时管理器
func (tm *TimeoutManager) Stop() {
    close(tm.stopCh)
}

// GetTaskInfo 获取任务超时信息
func (tm *TimeoutManager) GetTaskInfo(taskID string) (*TaskTimeoutInfo, bool) {
    tm.mu.RLock()
    defer tm.mu.RUnlock()
    
    info, exists := tm.tasks[taskID]
    return info, exists
}

// 使用示例
func ExampleTimeoutManager() {
    tm := NewTimeoutManager()
    defer tm.Stop()
    
    // 定义超时回调函数
    callback := func(taskID string) error {
        fmt.Printf("Task %s timeout!\n", taskID)
        // 这里可以执行超时处理逻辑,如任务中断、状态更新等
        return nil
    }
    
    // 添加任务超时监控
    err := tm.AddTask("task_001", 5*time.Second, callback)
    if err != nil {
        panic(err)
    }
    
    // 开始任务超时监控
    err = tm.StartTask("task_001")
    if err != nil {
        panic(err)
    }
    
    // 模拟任务执行
    time.Sleep(3 * time.Second)
    
    // 完成任务
    err = tm.CompleteTask("task_001")
    if err != nil {
        panic(err)
    }
    
    fmt.Println("Task completed successfully")
}

超时任务状态回滚

当任务超时时,我们需要确保系统状态的一致性,可能需要回滚已执行的部分操作。

package rollback

import (
    "fmt"
    "sync"
    "time"
)

// RollbackManager 回滚管理器
type RollbackManager struct {
    tasks map[string]*TaskRollbackInfo
    mu    sync.RWMutex
}

// TaskRollbackInfo 任务回滚信息
type TaskRollbackInfo struct {
    TaskID      string
    Checkpoints []Checkpoint
    Status      RollbackStatus
    CreatedAt   time.Time
}

// Checkpoint 检查点
type Checkpoint struct {
    ID        string
    Timestamp time.Time
    Data      interface{}
    RollbackFunc func(data interface{}) error
}

// RollbackStatus 回滚状态
type RollbackStatus int

const (
    RollbackStatusRunning RollbackStatus = iota
    RollbackStatusCompleted
    RollbackStatusRollbacked
)

// NewRollbackManager 创建回滚管理器
func NewRollbackManager() *RollbackManager {
    return &RollbackManager{
        tasks: make(map[string]*TaskRollbackInfo),
    }
}

// RegisterTask 注册任务
func (rm *RollbackManager) RegisterTask(taskID string) error {
    rm.mu.Lock()
    defer rm.mu.Unlock()
    
    if _, exists := rm.tasks[taskID]; exists {
        return fmt.Errorf("task %s already registered", taskID)
    }
    
    rm.tasks[taskID] = &TaskRollbackInfo{
        TaskID:    taskID,
        Status:    RollbackStatusRunning,
        CreatedAt: time.Now(),
    }
    
    return nil
}

// AddCheckpoint 添加检查点
func (rm *RollbackManager) AddCheckpoint(taskID string, checkpoint Checkpoint) error {
    rm.mu.Lock()
    defer rm.mu.Unlock()
    
    taskInfo, exists := rm.tasks[taskID]
    if !exists {
        return fmt.Errorf("task %s not found", taskID)
    }
    
    if taskInfo.Status != RollbackStatusRunning {
        return fmt.Errorf("task %s is not running", taskID)
    }
    
    taskInfo.Checkpoints = append(taskInfo.Checkpoints, checkpoint)
    
    return nil
}

// CompleteTask 完成任务
func (rm *RollbackManager) CompleteTask(taskID string) error {
    rm.mu.Lock()
    defer rm.mu.Unlock()
    
    taskInfo, exists := rm.tasks[taskID]
    if !exists {
        return fmt.Errorf("task %s not found", taskID)
    }
    
    taskInfo.Status = RollbackStatusCompleted
    
    return nil
}

// RollbackTask 回滚任务
func (rm *RollbackManager) RollbackTask(taskID string) error {
    rm.mu.Lock()
    defer rm.mu.Unlock()
    
    taskInfo, exists := rm.tasks[taskID]
    if !exists {
        return fmt.Errorf("task %s not found", taskID)
    }
    
    if taskInfo.Status == RollbackStatusRollbacked {
        return fmt.Errorf("task %s already rollbacked", taskID)
    }
    
    // 逆序执行回滚函数
    for i := len(taskInfo.Checkpoints) - 1; i >= 0; i-- {
        checkpoint := taskInfo.Checkpoints[i]
        if checkpoint.RollbackFunc != nil {
            if err := checkpoint.RollbackFunc(checkpoint.Data); err != nil {
                return fmt.Errorf("rollback checkpoint %s failed: %v", checkpoint.ID, err)
            }
        }
    }
    
    taskInfo.Status = RollbackStatusRollbacked
    
    return nil
}

// GetTaskInfo 获取任务回滚信息
func (rm *RollbackManager) GetTaskInfo(taskID string) (*TaskRollbackInfo, bool) {
    rm.mu.RLock()
    defer rm.mu.RUnlock()
    
    info, exists := rm.tasks[taskID]
    return info, exists
}

// 使用示例
func ExampleRollbackManager() {
    rm := NewRollbackManager()
    
    // 注册任务
    err := rm.RegisterTask("task_001")
    if err != nil {
        panic(err)
    }
    
    // 定义回滚函数
    rollbackFunc1 := func(data interface{}) error {
        fmt.Printf("Rollback operation 1 with data: %v\n", data)
        return nil
    }
    
    rollbackFunc2 := func(data interface{}) error {
        fmt.Printf("Rollback operation 2 with data: %v\n", data)
        return nil
    }
    
    // 添加检查点
    err = rm.AddCheckpoint("task_001", Checkpoint{
        ID:           "checkpoint_1",
        Timestamp:    time.Now(),
        Data:         "data_1",
        RollbackFunc: rollbackFunc1,
    })
    if err != nil {
        panic(err)
    }
    
    err = rm.AddCheckpoint("task_001", Checkpoint{
        ID:           "checkpoint_2",
        Timestamp:    time.Now(),
        Data:         "data_2",
        RollbackFunc: rollbackFunc2,
    })
    if err != nil {
        panic(err)
    }
    
    // 模拟任务执行失败,需要回滚
    err = rm.RollbackTask("task_001")
    if err != nil {
        panic(err)
    }
    
    fmt.Println("Task rollbacked successfully")
}

重试策略实现

重试策略是处理临时性故障的重要手段,我们需要实现灵活的重试机制。

package retry

import (
    "context"
    "fmt"
    "math/rand"
    "time"
)

// RetryPolicy 重试策略
type RetryPolicy struct {
    MaxRetries      int
    InitialInterval time.Duration
    MaxInterval     time.Duration
    Multiplier      float64
    RandomizationFactor float64
    RetryableFunc   func(error) bool
}

// RetryableFunc 默认的可重试错误判断函数
func DefaultRetryableFunc(err error) bool {
    // 默认所有错误都可重试
    // 实际应用中可以根据错误类型判断是否可重试
    return err != nil
}

// NewRetryPolicy 创建重试策略
func NewRetryPolicy(maxRetries int, initialInterval time.Duration) *RetryPolicy {
    return &RetryPolicy{
        MaxRetries:          maxRetries,
        InitialInterval:     initialInterval,
        MaxInterval:         60 * time.Second,
        Multiplier:          2.0,
        RandomizationFactor: 0.5,
        RetryableFunc:       DefaultRetryableFunc,
    }
}

// WithMaxInterval 设置最大间隔
func (rp *RetryPolicy) WithMaxInterval(maxInterval time.Duration) *RetryPolicy {
    rp.MaxInterval = maxInterval
    return rp
}

// WithMultiplier 设置乘数
func (rp *RetryPolicy) WithMultiplier(multiplier float64) *RetryPolicy {
    rp.Multiplier = multiplier
    return rp
}

// WithRandomizationFactor 设置随机因子
func (rp *RetryPolicy) WithRandomizationFactor(factor float64) *RetryPolicy {
    rp.RandomizationFactor = factor
    return rp
}

// WithRetryableFunc 设置可重试错误判断函数
func (rp *RetryPolicy) WithRetryableFunc(f func(error) bool) *RetryPolicy {
    rp.RetryableFunc = f
    return rp
}

// Retry 执行重试操作
func (rp *RetryPolicy) Retry(ctx context.Context, operation func() error) error {
    var lastErr error
    
    interval := rp.InitialInterval
    
    for i := 0; i <= rp.MaxRetries; i++ {
        // 执行操作
        err := operation()
        if err == nil {
            // 操作成功
            return nil
        }
        
        lastErr = err
        
        // 检查是否可重试
        if !rp.RetryableFunc(err) {
            return err
        }
        
        // 如果不是最后一次重试,等待后重试
        if i < rp.MaxRetries {
            // 计算等待时间
            sleepTime := rp.calculateSleepTime(interval)
            
            // 等待或被取消
            select {
            case <-ctx.Done():
                return ctx.Err()
            case <-time.After(sleepTime):
            }
            
            // 更新间隔时间
            interval = time.Duration(float64(interval) * rp.Multiplier)
            if interval > rp.MaxInterval {
                interval = rp.MaxInterval
            }
        }
    }
    
    return fmt.Errorf("operation failed after %d retries: %v", rp.MaxRetries, lastErr)
}

// calculateSleepTime 计算睡眠时间
func (rp *RetryPolicy) calculateSleepTime(interval time.Duration) time.Duration {
    // 添加随机因子避免惊群效应
    randomFactor := 1.0 + (rand.Float64()-0.5)*2*rp.RandomizationFactor
    sleepTime := time.Duration(float64(interval) * randomFactor)
    
    return sleepTime
}

// ExponentialBackoff 指数退避重试策略
func ExponentialBackoff(maxRetries int, initialInterval time.Duration) *RetryPolicy {
    return NewRetryPolicy(maxRetries, initialInterval).
        WithMultiplier(2.0).
        WithRandomizationFactor(0.5)
}

// FibonacciBackoff 斐波那契退避重试策略
func FibonacciBackoff(maxRetries int, initialInterval time.Duration) *RetryPolicy {
    return NewRetryPolicy(maxRetries, initialInterval).
        WithMultiplier(1.618). // 黄金比例近似值
        WithRandomizationFactor(0.5)
}

// 使用示例
func ExampleRetryPolicy() {
    // 创建指数退避重试策略
    policy := ExponentialBackoff(3, 1*time.Second).
        WithMaxInterval(30*time.Second)
    
    // 定义操作
    operation := func() error {
        // 模拟可能失败的操作
        if rand.Float32() < 0.7 { // 70% 失败率
            return fmt.Errorf("operation failed")
        }
        return nil
    }
    
    // 执行重试操作
    ctx := context.Background()
    err := policy.Retry(ctx, operation)
    if err != nil {
        fmt.Printf("Operation failed after retries: %v\n", err)
    } else {
        fmt.Println("Operation succeeded")
    }
}

任务优雅启停机制

任务的优雅启停是确保系统稳定性的关键,我们需要实现启动时的资源预热和退出时的状态保存。

package graceful

import (
    "context"
    "fmt"
    "sync"
    "time"
)

// GracefulManager 优雅管理器
type GracefulManager struct {
    components map[string]Component
    mu         sync.RWMutex
    shutdownCh chan struct{}
    wg         sync.WaitGroup
}

// Component 组件接口
type Component interface {
    Start(ctx context.Context) error
    Stop(ctx context.Context) error
    Name() string
}

// TaskExecutor 任务执行器组件
type TaskExecutor struct {
    name       string
    isRunning  bool
    tasks      map[string]interface{}
    mu         sync.RWMutex
}

// NewTaskExecutor 创建任务执行器
func NewTaskExecutor(name string) *TaskExecutor {
    return &TaskExecutor{
        name:  name,
        tasks: make(map[string]interface{}),
    }
}

// Name 组件名称
func (te *TaskExecutor) Name() string {
    return te.name
}

// Start 启动组件
func (te *TaskExecutor) Start(ctx context.Context) error {
    te.mu.Lock()
    defer te.mu.Unlock()
    
    fmt.Printf("Starting task executor: %s\n", te.name)
    
    // 模拟资源预热
    fmt.Println("Warming up resources...")
    time.Sleep(2 * time.Second)
    
    te.isRunning = true
    fmt.Printf("Task executor %s started\n", te.name)
    
    return nil
}

// Stop 停止组件
func (te *TaskExecutor) Stop(ctx context.Context) error {
    te.mu.Lock()
    defer te.mu.Unlock()
    
    if !te.isRunning {
        return nil
    }
    
    fmt.Printf("Stopping task executor: %s\n", te.name)
    
    // 保存状态
    fmt.Println("Saving state...")
    time.Sleep(1 * time.Second)
    
    // 清理资源
    fmt.Println("Cleaning up resources...")
    time.Sleep(1 * time.Second)
    
    te.isRunning = false
    fmt.Printf("Task executor %s stopped\n", te.name)
    
    return nil
}

// SaveCheckpoint 保存检查点
func (te *TaskExecutor) SaveCheckpoint(taskID string, data interface{}) error {
    te.mu.Lock()
    defer te.mu.Unlock()
    
    if !te.isRunning {
        return fmt.Errorf("task executor is not running")
    }
    
    te.tasks[taskID] = data
    fmt.Printf("Checkpoint saved for task: %s\n", taskID)
    
    return nil
}

// NewGracefulManager 创建优雅管理器
func NewGracefulManager() *GracefulManager {
    return &GracefulManager{
        components: make(map[string]Component),
        shutdownCh: make(chan struct{}),
    }
}

// RegisterComponent 注册组件
func (gm *GracefulManager) RegisterComponent(component Component) error {
    gm.mu.Lock()
    defer gm.mu.Unlock()
    
    if _, exists := gm.components[component.Name()]; exists {
        return fmt.Errorf("component %s already registered", component.Name())
    }
    
    gm.components[component.Name()] = component
    return nil
}

// Start 启动所有组件
func (gm *GracefulManager) Start(ctx context.Context) error {
    gm.mu.RLock()
    defer gm.mu.RUnlock()
    
    fmt.Println("Starting all components...")
    
    for _, component := range gm.components {
        if err := component.Start(ctx); err != nil {
            return fmt.Errorf("failed to start component %s: %v", component.Name(), err)
        }
    }
    
    fmt.Println("All components started")
    return nil
}

// Stop 停止所有组件
func (gm *GracefulManager) Stop(ctx context.Context) error {
    gm.mu.RLock()
    defer gm.mu.RUnlock()
    
    fmt.Println("Stopping all components...")
    
    // 并行停止所有组件
    var wg sync.WaitGroup
    errCh := make(chan error, len(gm.components))
    
    for _, component := range gm.components {
        wg.Add(1)
        go func(c Component) {
            defer wg.Done()
            if err := c.Stop(ctx); err != nil {
                errCh <- fmt.Errorf("failed to stop component %s: %v", c.Name(), err)
            }
        }(component)
    }
    
    wg.Wait()
    close(errCh)
    
    // 收集错误
    var errs []error
    for err := range errCh {
        errs = append(errs, err)
    }
    
    if len(errs) > 0 {
        return fmt.Errorf("errors occurred while stopping components: %v", errs)
    }
    
    fmt.Println("All components stopped")
    return nil
}

// Shutdown 优雅关闭
func (gm *GracefulManager) Shutdown(ctx context.Context) error {
    close(gm.shutdownCh)
    return gm.Stop(ctx)
}

// WaitForShutdown 等待关闭信号
func (gm *GracefulManager) WaitForShutdown(ctx context.Context) {
    select {
    case <-gm.shutdownCh:
        fmt.Println("Shutdown signal received")
    case <-ctx.Done():
        fmt.Println("Context cancelled")
    }
}

// 使用示例
func ExampleGracefulManager() {
    gm := NewGracefulManager()
    
    // 创建并注册组件
    executor1 := NewTaskExecutor("executor_1")
    executor2 := NewTaskExecutor("executor_2")
    
    gm.RegisterComponent(executor1)
    gm.RegisterComponent(executor2)
    
    // 启动所有组件
    ctx := context.Background()
    if err := gm.Start(ctx); err != nil {
        panic(err)
    }
    
    // 模拟保存检查点
    executor1.SaveCheckpoint("task_001", map[string]interface{}{
        "progress": 50,
        "data":     "sample data",
    })
    
    // 模拟运行一段时间
    time.Sleep(5 * time.Second)
    
    // 优雅关闭
    shutdownCtx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
    defer cancel()
    
    if err := gm.Shutdown(shutdownCtx); err != nil {
        fmt.Printf("Shutdown failed: %v\n", err)
    } else {
        fmt.Println("Shutdown completed successfully")
    }
}

总结

今天我们深入探讨了分布式任务调度系统中的超时控制和重试策略:

  1. 任务超时控制机制

    • 实现了基于context的超时管理
    • 设计了超时检测和回调机制
    • 提供了任务完成和超时处理的完整流程
  2. 超时任务状态回滚

    • 实现了基于检查点的回滚机制
    • 支持逆序执行回滚操作
    • 确保系统状态的一致性
  3. 重试策略

    • 实现了指数退避和斐波那契退避策略
    • 支持自定义重试条件判断
    • 提供了灵活的重试参数配置
  4. 任务优雅启停机制

    • 实现了组件化的启动和停止管理
    • 支持资源预热和状态保存
    • 提供了优雅关闭和错误处理机制

通过这些机制的组合,我们可以构建一个健壮的分布式任务调度系统,能够有效处理各种异常情况,确保任务的可靠执行。在实际应用中,需要根据具体业务场景调整参数和策略,以达到最佳的效果。