Claude Code 下的 Cron Jobs:分布式锁、历史跟踪和故障警报

发布: (2026年3月11日 GMT+8 12:56)
4 分钟阅读
原文: Dev.to

Source: Dev.to

Claude Code 可以生成完整且安全的 cron 基础设施,防止重复运行,跟踪执行历史,并发送失败警报。

Source:

CLAUDE.md 用于 Cron 作业规则

Cron 作业设计规则

防止重复(必需)

  • 防止同一作业的并发运行(分布式锁)
  • 使用 Redis NX+EX 实现锁(TTL = 最大作业时长的 2 倍)
  • 如果前一次作业仍在运行则跳过(不报错——直接跳过)

错误处理

  • 失败时发送 Slack/PagerDuty 警报
  • 连续 N 次失败后升级警报
  • 在数据库中记录作业执行历史

调度器

  • 使用 node-cron 或 BullMQ 可重复作业
  • 通过环境变量使 cron 调度可配置
  • 始终显式指定时区(避免默认使用 UTC)

生成 Cron 基础设施

// src/jobs/jobRunner.ts
import cron from 'node-cron';
import { createClient } from 'redis';

const redis = createClient({ url: process.env.REDIS_URL });

export async function runWithLock(
  jobName: string,
  timeoutMs: number,
  handler: () => Promise<any>
): Promise<any> {
  const lockKey = `cron:lock:${jobName}`;
  const lockTtl = Math.ceil((timeoutMs * 2) / 1000);

  // Acquire distributed lock
  const acquired = await redis.set(lockKey, '1', {
    NX: true,    // Only set if not exists
    EX: lockTtl,
  });

  if (!acquired) {
    logger.info({ jobName }, 'Job already running, skipping');
    return;
  }

  const startedAt = new Date();
  try {
    await handler();
    await recordJobRun(jobName, 'success', startedAt);
  } catch (err) {
    await recordJobRun(jobName, 'failed', startedAt, String(err));
    await notifySlack(`❌ Cron job failed: ${jobName}\n${err}`);
    throw err;
  } finally {
    await redis.del(lockKey);
  }
}
// src/jobs/definitions.ts
const TIMEZONE = process.env.CRON_TIMEZONE ?? 'Asia/Tokyo';

export function startCronJobs(): void {
  // Daily at 3 AM: cleanup expired sessions
  cron.schedule(
    process.env.CLEANUP_SCHEDULE ?? '0 3 * * *',
    () => runWithLock('cleanup-sessions', 30 * 60 * 1000, cleanupExpiredSessions),
    { timezone: TIMEZONE }
  );

  // Every hour: subscription expiry check
  cron.schedule(
    '0 * * * *',
    () => runWithLock('subscription-check', 10 * 60 * 1000, checkSubscriptionExpiry),
    { timezone: TIMEZONE }
  );

  // Every minute: send notifications
  cron.schedule(
    '* * * * *',
    () => runWithLock('send-notifications', 50 * 1000, sendPendingNotifications),
    { timezone: TIMEZONE }
  );

  logger.info('Cron jobs started');
}

BullMQ 可重复作业的替代方案

// src/jobs/bullmqCron.ts
export async function registerRepeatableJobs(): Promise<void> {
  await cronQueue.add(
    'cleanup-sessions',
    {},
    {
      repeat: { pattern: '0 3 * * *', tz: 'Asia/Tokyo' },
      jobId: 'cleanup-sessions', // Prevent duplicate registration
    }
  );

  await cronQueue.add(
    'subscription-check',
    {},
    {
      repeat: { pattern: '0 * * * *', tz: 'Asia/Tokyo' },
      jobId: 'subscription-check',
    }
  );
}

const worker = new Worker(
  'cron-jobs',
  async (job) => {
    switch (job.name) {
      case 'cleanup-sessions':
        return cleanupExpiredSessions();
      case 'subscription-check':
        return checkSubscriptionExpiry();
    }
  },
  { connection: redisConnection, concurrency: 1 } // Serialize execution
);

工作历史的 Prisma 模式

model CronJobRun {
  id         String   @id @default(cuid())
  jobName    String
  status     String   // 'success' | 'failed'
  startedAt  DateTime
  finishedAt DateTime
  error      String?

  @@index([jobName, startedAt])
}

摘要

  • CLAUDE.md – 定义所需的分布式锁、错误处理和显式时区。
  • Redis NX+EX 锁 – 防止在多台服务器上双重运行。
  • 数据库执行历史 – 便于事件调查。
  • BullMQ 可重复 – 简化大规模调度器管理。
  • 代码审查包(¥980)包括 /code‑review,用于检测缺失的锁、时区错误和缺少的失败警报。

prompt-works.jp

0 浏览
Back to Blog

相关文章

阅读更多 »

锁链中的创造力

我所有的想法都有费用。不是比喻,而是字面意义。每一次 API 调用、每一个 token 的处理、每一次决策——都以美元和美分来计量。