修复并发,暂时的,不够完整,后续还是需要重新设计,这个太乱了,傻逼AI
All checks were successful
构建并部署到测试环境(无 SSH) / build-and-deploy (push) Successful in 9m22s

This commit is contained in:
2026-05-11 17:19:40 +08:00
parent 05a5694a5f
commit b7369a9c71
16 changed files with 270 additions and 68 deletions

View File

@@ -9,12 +9,14 @@ import (
"syscall"
"time"
"github.com/bytedance/sonic"
"github.com/hibiken/asynq"
"github.com/redis/go-redis/v9"
"go.uber.org/zap"
"github.com/break/junhong_cmp_fiber/internal/bootstrap"
"github.com/break/junhong_cmp_fiber/internal/gateway"
"github.com/break/junhong_cmp_fiber/internal/model"
"github.com/break/junhong_cmp_fiber/internal/polling"
iot_card_svc "github.com/break/junhong_cmp_fiber/internal/service/iot_card"
"github.com/break/junhong_cmp_fiber/internal/store/postgres"
@@ -34,6 +36,7 @@ const (
workerModulePollingInitializer = "polling_initializer"
workerModulePollingScheduler = "polling_scheduler"
workerModuleAsynqScheduler = "asynq_scheduler"
importRescueLimit = 500 // 启动补偿单次扫描的最大导入任务数
)
// workerModuleStatus 描述当前 Worker 角色下的模块启停计划。
@@ -121,6 +124,7 @@ func runWorker(cfg *config.Config) {
taskHandler := createTaskHandler(runtime, appLogger)
taskHandler.RegisterHandlers()
rescuePendingImportTasks(ctx, runtime, appLogger)
appLogger.Info("Worker 服务器配置完成",
zap.Int("concurrency", cfg.Queue.Concurrency),
@@ -382,18 +386,36 @@ func startAsynqScheduler(cfg *config.Config, redisAddr string, appLogger *zap.Lo
// registerAsynqScheduleTasks 注册 Worker 入口需要的全部定时任务。
func registerAsynqScheduleTasks(asynqScheduler *asynq.Scheduler) error {
if _, err := asynqScheduler.Register("@every 1m", asynq.NewTask(constants.TaskTypeOrderExpire, nil)); err != nil {
if _, err := asynqScheduler.Register("@every 1m", asynq.NewTask(
constants.TaskTypeOrderExpire,
nil,
asynq.Queue(constants.QueueForTaskType(constants.TaskTypeOrderExpire)),
)); err != nil {
return fmt.Errorf("注册订单超时定时任务失败: %w", err)
}
if _, err := asynqScheduler.Register("@every 1m", asynq.NewTask(constants.TaskTypeAlertCheck, nil)); err != nil {
if _, err := asynqScheduler.Register("@every 1m", asynq.NewTask(
constants.TaskTypeAlertCheck,
nil,
asynq.Queue(constants.QueueForTaskType(constants.TaskTypeAlertCheck)),
)); err != nil {
return fmt.Errorf("注册告警检查定时任务失败: %w", err)
}
if _, err := asynqScheduler.Register("0 2 * * *", asynq.NewTask(constants.TaskTypeDataCleanup, nil)); err != nil {
if _, err := asynqScheduler.Register("0 2 * * *", asynq.NewTask(
constants.TaskTypeDataCleanup,
nil,
asynq.Queue(constants.QueueForTaskType(constants.TaskTypeDataCleanup)),
)); err != nil {
return fmt.Errorf("注册数据清理定时任务失败: %w", err)
}
if _, err := asynqScheduler.Register(
"0 2 * * *",
asynq.NewTask(constants.TaskTypeDailyTrafficFlush, nil, asynq.MaxRetry(3), asynq.Timeout(5*time.Minute)),
asynq.NewTask(
constants.TaskTypeDailyTrafficFlush,
nil,
asynq.MaxRetry(3),
asynq.Timeout(5*time.Minute),
asynq.Queue(constants.QueueForTaskType(constants.TaskTypeDailyTrafficFlush)),
),
); err != nil {
return fmt.Errorf("注册每日流量落盘定时任务失败: %w", err)
}
@@ -417,6 +439,80 @@ func createTaskHandler(runtime *workerRuntime, appLogger *zap.Logger) *queue.Han
)
}
// rescuePendingImportTasks 将历史遗留的待处理导入任务重新投递到独立导入队列。
func rescuePendingImportTasks(ctx context.Context, runtime *workerRuntime, appLogger *zap.Logger) {
rescuePendingIotCardImportTasks(ctx, runtime.db, runtime.asynqClient, appLogger)
rescuePendingDeviceImportTasks(ctx, runtime.db, runtime.asynqClient, appLogger)
}
// rescuePendingIotCardImportTasks 补偿仍停留在待处理状态的 IoT 卡导入任务。
func rescuePendingIotCardImportTasks(ctx context.Context, db *gorm.DB, asynqClient *asynq.Client, appLogger *zap.Logger) {
var importTasks []model.IotCardImportTask
if err := db.WithContext(ctx).
Where("status = ?", model.ImportTaskStatusPending).
Limit(importRescueLimit).
Find(&importTasks).Error; err != nil {
appLogger.Warn("扫描待补偿 IoT 卡导入任务失败", zap.Error(err))
return
}
for _, importTask := range importTasks {
payload := task.IotCardImportPayload{TaskID: importTask.ID}
enqueueImportRescueTask(ctx, asynqClient, constants.TaskTypeIotCardImport, payload, importTask.ID, appLogger)
}
}
// rescuePendingDeviceImportTasks 补偿仍停留在待处理状态的设备导入任务。
func rescuePendingDeviceImportTasks(ctx context.Context, db *gorm.DB, asynqClient *asynq.Client, appLogger *zap.Logger) {
var importTasks []model.DeviceImportTask
if err := db.WithContext(ctx).
Where("status = ?", model.ImportTaskStatusPending).
Limit(importRescueLimit).
Find(&importTasks).Error; err != nil {
appLogger.Warn("扫描待补偿设备导入任务失败", zap.Error(err))
return
}
for _, importTask := range importTasks {
payload := task.DeviceImportPayload{TaskID: importTask.ID}
enqueueImportRescueTask(ctx, asynqClient, constants.TaskTypeDeviceImport, payload, importTask.ID, appLogger)
}
}
// enqueueImportRescueTask 将补偿任务提交到任务类型对应的独立队列。
func enqueueImportRescueTask(ctx context.Context, asynqClient *asynq.Client, taskType string, payload any, taskID uint, appLogger *zap.Logger) {
payloadBytes, err := sonic.Marshal(payload)
if err != nil {
appLogger.Warn("序列化导入补偿任务载荷失败",
zap.String("task_type", taskType),
zap.Uint("task_id", taskID),
zap.Error(err))
return
}
queueName := constants.QueueForTaskType(taskType)
taskMessage := asynq.NewTask(
taskType,
payloadBytes,
asynq.Queue(queueName),
asynq.TaskID(fmt.Sprintf("import-rescue:%s:%d", taskType, taskID)),
asynq.Unique(30*time.Minute),
)
if _, err := asynqClient.EnqueueContext(ctx, taskMessage); err != nil {
appLogger.Warn("提交导入补偿任务失败",
zap.String("task_type", taskType),
zap.String("queue", queueName),
zap.Uint("task_id", taskID),
zap.Error(err))
return
}
appLogger.Info("导入补偿任务已提交",
zap.String("task_type", taskType),
zap.String("queue", queueName),
zap.Uint("task_id", taskID))
}
// shutdownWorker 按当前实例实际启动过的模块执行优雅关闭。
func shutdownWorker(
cancel context.CancelFunc,