Bootstrap

15分钟学 Go 实战项目四 :任务调度器(万字详解,推荐收藏慢慢看)

Go语言实战项目:任务调度器

一、项目概述

1. 项目目标

学习目标掌握要点应用场景
并发控制goroutine与通道的使用多任务并行处理
定时任务时间调度与cron表达式定期执行的任务管理
任务管理任务状态与生命周期批处理作业系统
错误处理panic恢复与日志记录提高系统稳定性
性能优化资源池与限流大规模任务调度

让我们开始构建这个任务调度器。首先创建基础结构:

package scheduler

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

// JobStatus 定义任务状态
type JobStatus string

const (
    StatusPending   JobStatus = "pending"
    StatusRunning   JobStatus = "running"
    StatusCompleted JobStatus = "completed"
    StatusFailed    JobStatus = "failed"
    StatusCanceled  JobStatus = "canceled"
)

// Job 定义任务接口
type Job interface {
    ID() string
    Execute(ctx context.Context) error
    Status() JobStatus
    SetStatus(status JobStatus)
}

// BaseJob 提供基础任务实现
type BaseJob struct {
    id     string
    status JobStatus
    mu     sync.RWMutex
}

func NewBaseJob(id string) *BaseJob {
    return &BaseJob{
        id:     id,
        status: StatusPending,
    }
}

func (j *BaseJob) ID() string {
    return j.id
}

func (j *BaseJob) Status() JobStatus {
    j.mu.RLock()
    defer j.mu.RUnlock()
    return j.status
}

func (j *BaseJob) SetStatus(status JobStatus) {
    j.mu.Lock()
    defer j.mu.Unlock()
    j.status = status
}

// Schedule 定义调度信息
type Schedule struct {
    Cron     string        // cron表达式
    Interval time.Duration // 间隔时间
    Retry    int           // 重试次数
    Timeout  time.Duration // 超时时间
}

// JobResult 定义任务执行结果
type JobResult struct {
    JobID     string
    Status    JobStatus
    Error     error
    StartTime time.Time
    EndTime   time.Time
}

// Scheduler 定义调度器接口
type Scheduler interface {
    Submit(job Job, schedule Schedule) error
    Cancel(jobID string) error
    Status(jobID string) (JobStatus, error)
    Start(ctx context.Context) error
    Stop() error
}

现在,让我们实现调度器的核心逻辑:

package scheduler

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

var (
    ErrJobNotFound      = errors.New("job not found")
    ErrJobAlreadyExists = errors.New("job already exists")
    ErrSchedulerStopped = errors.New("scheduler is stopped")
)

// JobScheduler 实现调度器
type JobScheduler struct {
    jobs      map[string]*jobInfo
    jobChan   chan Job
    results   chan JobResult
    mu        sync.RWMutex
    workers   int
    running   bool
    workersWg sync.WaitGroup
}

type jobInfo struct {
    job      Job
    schedule Schedule
    cancel   context.CancelFunc
}

// NewScheduler 创建新的调度器
func NewScheduler(workers int) *JobScheduler {
    if workers <= 0 {
        workers = 1
    }
    return &JobScheduler{
        jobs:    make(map[string]*jobInfo),
        jobChan: make(chan Job, workers*2),
        results: make(chan JobResult, workers*2),
        workers: workers,
    }
}

// Submit 提交任务
func (s *JobScheduler) Submit(job Job, schedule Schedule) error {
    s.mu.Lock()
    defer s.mu.Unlock()

    if !s.running {
        return ErrSchedulerStopped
    }

    if _, exists := s.jobs[job.ID()]; exists {
        return ErrJobAlreadyExists
    }

    s.jobs[job.ID()] = &jobInfo{
        job:      job,
        schedule: schedule,
    }

    // 启动任务调度
    go s.scheduleJob(job.ID())

    return nil
}

// Cancel 取消任务
func (s *JobScheduler) Cancel(jobID string) error {
    s.mu.Lock()
    defer s.mu.Unlock()

    info, exists := s.jobs[jobID]
    if !exists {
        return ErrJobNotFound
    }

    if info.cancel != nil {
        info.cancel()
    }
    info.job.SetStatus(StatusCanceled)
    delete(s.jobs, jobID)

    return nil
}

// Status 获取任务状态
func (s *JobScheduler) Status(jobID string) (JobStatus, error) {
    s.mu.RLock()
    defer s.mu.RUnlock()

    info, exists := s.jobs[jobID]
    if !exists {
        return "", ErrJobNotFound
    }

    return info.job.Status(), nil
}

// Start 启动调度器
func (s *JobScheduler) Start(ctx context.Context) error {
    s.mu.Lock()
    if s.running {
        s.mu.Unlock()
        return nil
    }
    s.running = true
    s.mu.Unlock()

    // 启动工作协程
    for i := 0; i < s.workers; i++ {
        s.workersWg.Add(1)
        go s.worker(ctx)
    }

    // 监控结果
    go s.monitorResults(ctx)

    return nil
}

// Stop 停止调度器
func (s *JobScheduler) Stop() error {
    s.mu.Lock()
    s.running = false
    s.mu.Unlock()

    close(s.jobChan)
    s.workersWg.Wait()
    close(s.results)

    return nil
}

// scheduleJob 调度单个任务
func (s *JobScheduler) scheduleJob(jobID string) {
    s.mu.RLock()
    info, exists := s.jobs[jobID]
    if !exists {
        s.mu.RUnlock()
        return
    }
    s.mu.RUnlock()

    ctx, cancel := context.WithCancel(context.Background())
    info.cancel = cancel

    // 如果设置了间隔时间
    if info.schedule.Interval > 0 {
        ticker := time.NewTicker(info.schedule.Interval)
        defer ticker.Stop()

        for {
            select {
            case <-ctx.Done():
                return
            case <-ticker.C:
                s.executeJob(ctx, info)
            }
        }
    } else {
        // 一次性任务
        s.executeJob(ctx, info)
    }
}

// executeJob 执行任务
func (s *JobScheduler) executeJob(ctx context.Context, info *jobInfo) {
    // 创建带超时的上下文
    jobCtx := ctx
    if info.schedule.Timeout > 0 {
        var cancel context.CancelFunc
        jobCtx, cancel = context.WithTimeout(ctx, info.schedule.Timeout)
        defer cancel()
    }

    // 设置任务状态为运行中
    info.job.SetStatus(StatusRunning)

    // 发送任务到工作池
    select {
    case <-ctx.Done():
        return
    case s.jobChan <- info.job:
    }
}

// worker 工作协程
func (s *JobScheduler) worker(ctx context.Context) {
    defer s.workersWg.Done()

    for job := range s.jobChan {
        startTime := time.Now()
        var err error
        var status JobStatus

        // 执行任务并处理panic
        func() {
            defer func() {
                if r := recover(); r != nil {
                    err = fmt.Errorf("job panic: %v", r)
                    status = StatusFailed
                }
            }()

            err = job.Execute(ctx)
            if err != nil {
                status = StatusFailed
            } else {
                status = StatusCompleted
            }
        }()

        // 发送执行结果
        s.results <- JobResult{
            JobID:     job.ID(),
            Status:    status,
            Error:     err,
            StartTime: startTime,
            EndTime:   time.Now(),
        }
    }
}

// monitorResults 监控任务结果
func (s *JobScheduler) monitorResults(ctx context.Context) {
    for result := range s.results {
        s.mu.RLock()
        info, exists := s.jobs[result.JobID]
        s.mu.RUnlock()

        if !exists {
            continue
        }

        // 更新任务状态
        info.job.SetStatus(result.Status)

        // 处理失败重试
        if result.Status == StatusFailed && info.schedule.Retry > 0 {
            info.schedule.Retry--
            go s.executeJob(ctx, info)
        }
    }
}

让我们创建一个示例任务的实现:

package scheduler

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

// SimpleJob 简单任务实现
type SimpleJob struct {
    *BaseJob
    name     string
    duration time.Duration
}

func NewSimpleJob(id string, name string, duration time.Duration) *SimpleJob {
    return &SimpleJob{
        BaseJob:  NewBaseJob(id),
        name:     name,
        duration: duration,
    }
}

func (j *SimpleJob) Execute(ctx context.Context) error {
    fmt.Printf("Job %s (%s) started\n", j.name, j.ID())
    
    select {
    case <-ctx.Done():
        return ctx.Err()
    case <-time.After(j.duration):
        fmt.Printf("Job %s (%s) completed\n", j.name, j.ID())
        return nil
    }
}

// DataProcessJob 数据处理任务
type DataProcessJob struct {
    *BaseJob
    data []int
}

func NewDataProcessJob(id string, data []int) *DataProcessJob {
    return &DataProcessJob{
        BaseJob: NewBaseJob(id),
        data:    data,
    }
}

func (j *DataProcessJob) Execute(ctx context.Context) error {
    fmt.Printf("Processing data for job %s\n", j.ID())
    
    result := 0
    for _, v := range j.data {
        select {
        case <-ctx.Done():
            return ctx.Err()
        default:
            result += v
            time.Sleep(100 * time.Millisecond) // 模拟处理时间
        }
    }
    
    fmt.Printf("Job %s completed, result: %d\n", j.ID(), result)
    return nil
}

// Main 示例程序
func main() {
    // 创建调度器
    scheduler := NewScheduler(3)
    ctx, cancel := context.WithCancel(context.Background())
    defer cancel()

    // 启动调度器
    err := scheduler.Start(ctx)
    if err != nil {
        panic(err)
    }

    // 创建并提交任务
    job1 := NewSimpleJob("1", "Simple Task 1", 2*time.Second)
    job2 := NewDataProcessJob("2", []int{1, 2, 3, 4, 5})

    // 提交周期性任务
    err = scheduler.Submit(job1, Schedule{
        Interval: 5 * time.Second,
        Timeout:  3 * time.Second,
        Retry:    2,
    })
    if err != nil {
        panic(err)
    }

    // 提交一次性任务
    err = scheduler.Submit(job2, Schedule{
        Timeout: 10 * time.Second,
    })
    if err != nil {
        panic(err)
    }

    // 等待一段时间观察执行情况
    time.Sleep(20 * time.Second)

    // 停止调度器
    scheduler.Stop()
}

让我们创建一个流程图来说明任务调度器的工作流程:
在这里插入图片描述
现在让我们添加监控和指标收集功能:

package scheduler

import (
    "sync"
    "sync/atomic"
    "time"
)

// Metrics 定义监控指标
type Metrics struct {
    TotalJobs         uint64
    CompletedJobs     uint64
    FailedJobs        uint64
    RunningJobs       uint64
    AverageExecuteTime int64 // 纳秒
    MaxExecuteTime     int64 // 纳秒
    TotalRetries      uint64
    LastExecuteTime   time.Time
}

// MetricsCollector 指标收集器
type MetricsCollector struct {
    metrics Metrics
    mu      sync.RWMutex
}

func NewMetricsCollector() *MetricsCollector {
    return &MetricsCollector{}
}

// RecordJobSubmit 记录任务提交
func (mc *MetricsCollector) RecordJobSubmit() {
    atomic.AddUint64(&mc.metrics.TotalJobs, 1)
}

// RecordJobStart 记录任务开始
func (mc *MetricsCollector) RecordJobStart() {
    atomic.AddUint64(&mc.metrics.RunningJobs, 1)
    mc.mu.Lock()
    mc.metrics.LastExecuteTime = time.Now()
    mc.mu.Unlock()
}

// RecordJobComplete 记录任务完成
func (mc *MetricsCollector) RecordJobComplete(duration time.Duration) {
    atomic.AddUint64(&mc.metrics.CompletedJobs, 1)
    atomic.AddUint64(&mc.metrics.RunningJobs, ^uint64(0))
    
    // 更新平均执行时间
    durationNanos := duration.Nanoseconds()
    for {
        old := atomic.LoadInt64(&mc.metrics.AverageExecuteTime)
        completed := atomic.LoadUint64(&mc.metrics.CompletedJobs)
        new := (old*int64(completed-1) + durationNanos) / int64(completed)
        if atomic.CompareAndSwapInt64(&mc.metrics.AverageExecuteTime, old, new) {
            break
        }
    }
    
    // 更新最大执行时间
    for {
        old := atomic.LoadInt64(&mc.metrics.MaxExecuteTime)
        if durationNanos <= old {
            break
        }
        if atomic.CompareAndSwapInt64(&mc.metrics.MaxExecuteTime, old, durationNanos) {
            break
        }
    }
}

// RecordJobFail 记录任务失败
func (mc *MetricsCollector) RecordJobFail() {
    atomic.AddUint64(&mc.metrics.FailedJobs, 1)
    atomic.AddUint64(&mc.metrics.RunningJobs, ^uint64(0))
}

// RecordRetry 记录重试
func (mc *MetricsCollector) RecordRetry() {
    atomic.AddUint64(&mc.metrics.TotalRetries, 1)
}

// GetMetrics 获取当前指标
func (mc *MetricsCollector) GetMetrics() Metrics {
    mc.mu.RLock()
    defer mc.mu.RUnlock()
    
    metrics := mc.metrics
    metrics.RunningJobs = atomic.LoadUint64(&mc.metrics.RunningJobs)
    metrics.TotalJobs = atomic.LoadUint64(&mc.metrics.TotalJobs)
    metrics.CompletedJobs = atomic.LoadUint64(&mc.metrics.CompletedJobs)
    metrics.FailedJobs = atomic.LoadUint64(&mc.metrics.FailedJobs)
    metrics.TotalRetries = atomic.LoadUint64(&mc.metrics.TotalRetries)
    metrics.AverageExecuteTime = atomic.LoadInt64(&mc.metrics.AverageExecuteTime)
    metrics.MaxExecuteTime = atomic.LoadInt64(&mc.metrics.MaxExecuteTime)
    
    return metrics
}

现在让我们添加日志记录功能:

package scheduler

import (
    "fmt"
    "log"
    "os"
    "sync"
    "time"
)

// LogLevel 定义日志级别
type LogLevel int

const (
    LogLevelDebug LogLevel = iota
    LogLevelInfo
    LogLevelWarn
    LogLevelError
)

// Logger 日志记录器接口
type Logger interface {
    Debug(format string, v ...interface{})
    Info(format string, v ...interface{})
    Warn(format string, v ...interface{})
    Error(format string, v ...interface{})
}

// SchedulerLogger 调度器日志记录器
type SchedulerLogger struct {
    level    LogLevel
    logger   *log.Logger
    mu       sync.Mutex
    filename string
}

// NewSchedulerLogger 创建日志记录器
func NewSchedulerLogger(level LogLevel, filename string) (*SchedulerLogger, error) {
    file, err := os.OpenFile(filename, os.O_CREATE|os.O_APPEND|os.O_WRONLY, 0644)
    if err != nil {
        return nil, err
    }

    return &SchedulerLogger{
        level:    level,
        logger:   log.New(file, "", log.Ldate|log.Ltime|log.Lmicroseconds),
        filename: filename,
    }, nil
}

func (l *SchedulerLogger) log(level LogLevel, format string, v ...interface{}) {
    if level < l.level {
        return
    }

    l.mu.Lock()
    defer l.mu.Unlock()

    levelStr := ""
    switch level {
    case LogLevelDebug:
        levelStr = "DEBUG"
    case LogLevelInfo:
        levelStr = "INFO"
    case LogLevelWarn:
        levelStr = "WARN"
    case LogLevelError:
        levelStr = "ERROR"
    }

    msg := fmt.Sprintf(format, v...)
    l.logger.Printf("[%s] %s", levelStr, msg)
}

func (l *SchedulerLogger) Debug(format string, v ...interface{}) {
    l.log(LogLevelDebug, format, v...)
}

func (l *SchedulerLogger) Info(format string, v ...interface{}) {
    l.log(LogLevelInfo, format, v...)
}

func (l *SchedulerLogger) Warn(format string, v ...interface{}) {
    l.log(LogLevelWarn, format, v...)
}

func (l *SchedulerLogger) Error(format string, v ...interface{}) {
    l.log(LogLevelError, format, v...)
}

// JobLogger 任务日志记录器
type JobLogger struct {
    *SchedulerLogger
    jobID string
}

// NewJobLogger 创建任务日志记录器
func NewJobLogger(logger *SchedulerLogger, jobID string) *JobLogger {
    return &JobLogger{
        SchedulerLogger: logger,
        jobID:          jobID,
    }
}

func (l *JobLogger) log(level LogLevel, format string, v ...interface{}) {
    format = fmt.Sprintf("[Job-%s] %s", l.jobID, format)
    l.SchedulerLogger.log(level, format, v...)
}

// LogJobExecution 记录任务执行日志
func (l *JobLogger) LogJobExecution(start time.Time, status JobStatus, err error) {
    duration := time.Since(start)
    if err != nil {
        l.Error("Job execution failed after %v: %v", duration, err)
    } else {
        l.Info("Job execution completed in %v with status: %s", duration, status)
    }
}

二、系统功能说明

1. 核心功能对比

功能特性实现方式优势
任务调度goroutine池控制并发数量,避免资源耗尽
定时执行time.Ticker精确的时间控制,低资源消耗
失败重试重试计数器提高任务可靠性
监控指标atomic原子操作并发安全的指标收集
日志记录文件日志支持不同级别的日志记录

2. 性能指标

指标项说明监控方式
任务吞吐量单位时间内处理的任务数TotalJobs计数器
平均执行时间任务平均执行时长AverageExecuteTime
失败率任务失败百分比FailedJobs/TotalJobs
重试次数任务重试总次数TotalRetries
并发任务数同时执行的任务数RunningJobs

3. 使用示例

让我们创建一个完整的使用示例:

package main

import (
    "context"
    "fmt"
    "log"
    "time"
)

// EmailJob 邮件发送任务
type EmailJob struct {
    *BaseJob
    to      string
    subject string
    body    string
}

func NewEmailJob(id string, to string, subject string, body string) *EmailJob {
    return &EmailJob{
        BaseJob: NewBaseJob(id),
        to:      to,
        subject: subject,
        body:    body,
    }
}

func (j *EmailJob) Execute(ctx context.Context) error {
    fmt.Printf("Sending email to %s: %s\n", j.to, j.subject)
    // 模拟发送邮件
    time.Sleep(500 * time.Millisecond)
    return nil
}

// BackupJob 备份任务
type BackupJob struct {
    *BaseJob
    source      string
    destination string
    size        int64
}

func NewBackupJob(id string, source string, destination string, size int64) *BackupJob {
    return &BackupJob{
        BaseJob:     NewBaseJob(id),
        source:      source,
        destination: destination,
        size:        size,
    }
}

func (j *BackupJob) Execute(ctx context.Context) error {
    fmt.Printf("Backing up %s to %s (size: %d bytes)\n", j.source, j.destination, j.size)
    
    // 模拟备份过程
    processed := int64(0)
    for processed < j.size {
        select {
        case <-ctx.Done():
            return ctx.Err()
        default:
            // 模拟每次处理1MB
            chunk := int64(1024 * 1024)
            if processed+chunk > j.size {
                chunk = j.size - processed
            }
            processed += chunk
            
            // 模拟处理时间
            time.Sleep(100 * time.Millisecond)
            
            fmt.Printf("Backup progress: %.2f%%\n", float64(processed)/float64(j.size)*100)
        }
    }
    
    return nil
}

func main() {
    // 创建日志记录器
    logger, err := NewSchedulerLogger(LogLevelDebug, "scheduler.log")
    if err != nil {
        log.Fatal(err)
    }

    // 创建调度器
    scheduler := NewScheduler(5) // 5个工作协程
    ctx, cancel := context.WithCancel(context.Background())
    defer cancel()

    // 创建指标收集器
    metrics := NewMetricsCollector()

    // 启动调度器
    if err := scheduler.Start(ctx); err != nil {
        logger.Error("Failed to start scheduler: %v", err)
        return
    }

    // 创建定时邮件任务
    dailyReport := NewEmailJob(
        "daily-report",
        "[email protected]",
        "Daily System Report",
        "Daily system status report...",
    )
    
    err = scheduler.Submit(dailyReport, Schedule{
        Interval: 24 * time.Hour,
        Timeout:  5 * time.Minute,
        Retry:    3,
    })
    if err != nil {
        logger.Error("Failed to submit daily report job: %v", err)
    }

    // 创建备份任务
    backup := NewBackupJob(
        "weekly-backup",
        "/source/data",
        "/backup/data",
        1024*1024*1024, // 1GB
    )
    
    err = scheduler.Submit(backup, Schedule{
        Interval: 7 * 24 * time.Hour,
        Timeout:  2 * time.Hour,
        Retry:    2,
    })
    if err != nil {
        logger.Error("Failed to submit backup job: %v", err)
    }

    // 创建测试任务用于演示
    for i := 1; i <= 5; i++ {
        job := NewEmailJob(
            fmt.Sprintf("test-job-%d", i),
            fmt.Sprintf("user%[email protected]", i),
            fmt.Sprintf("Test Email %d", i),
            "This is a test email",
        )
        
        err = scheduler.Submit(job, Schedule{
            Timeout: 1 * time.Minute,
        })
        if err != nil {
            logger.Error("Failed to submit test job %d: %v", i, err)
        }
        
        metrics.RecordJobSubmit()
    }

    // 等待一段时间观察执行情况
    time.Sleep(10 * time.Second)

    // 输出metrics
    m := metrics.GetMetrics()
    fmt.Printf("\nScheduler Metrics:\n")
    fmt.Printf("Total Jobs: %d\n", m.TotalJobs)
    fmt.Printf("Completed Jobs: %d\n", m.CompletedJobs)
    fmt.Printf("Failed Jobs: %d\n", m.FailedJobs)
    fmt.Printf("Running Jobs: %d\n", m.RunningJobs)
    fmt.Printf("Average Execute Time: %v\n", time.Duration(m.AverageExecuteTime))
    fmt.Printf("Max Execute Time: %v\n", time.Duration(m.MaxExecuteTime))
    fmt.Printf("Total Retries: %d\n", m.TotalRetries)

    // 停止调度器
    scheduler.Stop()
}

让我们创建一个完整的使用示例:

三、项目实现要点

1. 任务调度实现

  1. 使用 goroutine 池控制并发数量
  2. 通过 channel 实现任务队列
  3. 支持定时执行和一次性任务
  4. 实现任务取消和超时控制

2. 错误处理策略

错误类型处理方式重试策略
任务超时取消执行按配置重试
任务panic恢复并记录标记为失败
业务错误记录日志可配置重试
系统错误告警通知立即重试

3. 性能优化措施

  1. 内存优化:

    • 使用对象池复用对象
    • 及时释放资源
    • 控制并发数量
  2. CPU优化:

    • 使用worker池
    • 避免频繁创建goroutine
    • 合理设置超时时间
  3. IO优化:

    • 异步日志记录
    • 批量处理任务
    • 使用缓冲区

四、测试策略

1. 单元测试

package scheduler

import (
    "context"
    "testing"
    "time"
)

// TestJobExecution 测试任务执行
func TestJobExecution(t *testing.T) {
    scheduler := NewScheduler(1)
    ctx := context.Background()
    
    err := scheduler.Start(ctx)
    if err != nil {
        t.Fatalf("Failed to start scheduler: %v", err)
    }
    
    // 测试正常执行
    job := NewSimpleJob("test-1", "Test Job", time.Millisecond*100)
    err = scheduler.Submit(job, Schedule{})
    if err != nil {
        t.Fatalf("Failed to submit job: %v", err)
    }
    
    time.Sleep(time.Millisecond * 200)
    status, err := scheduler.Status("test-1")
    if err != nil {
        t.Fatalf("Failed to get job status: %v", err)
    }
    if status != StatusCompleted {
        t.Errorf("Expected status %s, got %s", StatusCompleted, status)
    }
    
    // 测试超时
    job = NewSimpleJob("test-2", "Test Job", time.Second)
    err = scheduler.Submit(job, Schedule{
        Timeout: time.Millisecond * 100,
    })
    if err != nil {
        t.Fatalf("Failed to submit job: %v", err)
    }
    
    time.Sleep(time.Millisecond * 200)
    status, err = scheduler.Status("test-2")
    if err != nil {
        t.Fatalf("Failed to get job status: %v", err)
    }
    if status != StatusFailed {
        t.Errorf("Expected status %s, got %s", StatusFailed, status)
    }
    
    scheduler.Stop()
}

// TestSchedulerConcurrency 测试并发处理
func TestSchedulerConcurrency(t *testing.T) {
    scheduler := NewScheduler(3)
    ctx := context.Background()
    
    err := scheduler.Start(ctx)
    if err != nil {
        t.Fatalf("Failed to start scheduler: %v", err)
    }
    
    // 提交多个任务
    for i := 0; i < 10; i++ {
        job := NewSimpleJob(fmt.Sprintf("test-%d", i), 
            fmt.Sprintf("Test Job %d", i), 
            time.Millisecond*100)
        
        err = scheduler.Submit(job, Schedule{})
        if err != nil {
            t.Fatalf("Failed to submit job: %v", err)
        }
    }
    
    // 等待所有任务完成
    time.Sleep(time.Second)
    
    // 验证所有任务状态
    for i := 0; i < 10; i++ {
        status, err := scheduler.Status(fmt.Sprintf("test-%d", i))
        if err != nil {
            t.Fatalf("Failed to get job status: %v", err)
        }
        if status != StatusCompleted {
            t.Errorf("Job %d: Expected status %s, got %s", 
                i, StatusCompleted, status)
        }
    }
    
    scheduler.Stop()
}

// TestSchedulerRetry 测试重试机制
func TestSchedulerRetry(t *testing.T) {
    scheduler := NewScheduler(1)
    ctx := context.Background()
    
    err := scheduler.Start(ctx)
    if err != nil {
        t.Fatalf("Failed to start scheduler: %v", err)
    }
    
    // 创建一个总是失败的任务
    failingJob := &SimpleJob{
        BaseJob:  NewBaseJob("test-retry"),
        name:     "Failing Job",
        duration: time.Millisecond * 10,
        fail:     true,
    }
    
    err = scheduler.Submit(failingJob, Schedule{
        Retry: 3,
    })
    if err != nil {
        t.Fatalf("Failed to submit job: %v", err)
    }
    
    // 等待重试完成
    time.Sleep(time.Second)
    
    metrics := scheduler.metrics.GetMetrics()
    if metrics.TotalRetries != 3 {
        t.Errorf("Expected 3 retries, got %d", metrics.TotalRetries)
    }
    
    scheduler.Stop()
}

2. 性能测试指标

测试指标预期目标测试方法
任务吞吐量>1000/s并发提交测试
响应时间<100ms延迟统计
内存占用<100MB压力测试
CPU使用率<50%长期运行测试

五、最佳实践建议

  1. 任务设计原则:

    • 保持任务原子性
    • 实现幂等性
    • 合理设置超时
    • 做好日志记录
  2. 调度器使用建议:

    • 根据负载调整worker数量
    • 合理设置重试策略
    • 监控系统指标
    • 及时处理告警
  3. 性能优化建议:

    • 使用对象池
    • 实现批量处理
    • 优化锁竞争
    • 合理使用缓存

六、扩展功能

  1. 分布式支持:

    • 使用etcd实现任务分发
    • 实现领导者选举
    • 支持任务迁移
    • 集群监控
  2. 持久化支持:

    • 任务持久化存储
    • 状态恢复机制
    • 历史记录查询
    • 任务依赖管理
  3. 监控告警:

    • 任务执行监控
    • 资源使用监控
    • 自定义告警规则
    • 多渠道通知

总结

本项目实现了一个功能完整的任务调度器,主要特点包括:

  1. 支持定时任务和一次性任务
  2. 实现任务重试和超时控制
  3. 提供完整的监控指标
  4. 支持日志记录和追踪
  5. 具备良好的扩展性

建议在使用过程中注意以下几点:

  1. 合理配置并发数量
  2. 及时处理任务失败
  3. 监控系统指标
  4. 定期维护任务

后续可以考虑添加更多高级特性,如:

  1. 任务优先级
  2. 资源配额
  3. 任务编排
  4. 可视化管理

怎么样今天的内容还满意吗?再次感谢观众老爷的观看,关注GZH:凡人的AI工具箱,回复666,送您价值199的AI大礼包。最后,祝您早日实现财务自由,还请给个赞,谢谢!

;