定时任务基础:CloudWatch Events / EventBridge 定时触发,Cron 表达式

做 Lambda 定时巡检,说白了就是让函数自己按时干活。不用人盯着,到点就跑。这背后的机制,就是 EventBridge(老版本叫 CloudWatch Events)。

我记得刚接手第一个日志巡检项目时,领导说要「每天凌晨 2 点自动跑一遍」。我当时拍拍胸脯说简单——结果写了一个 Cron 表达式,第二天发现函数根本没执行。嗯,时区搞错了。从那以后,每次配定时任务我都会多看一眼时区设置。

CloudWatch Events 还是 EventBridge?

你打开 AWS 控制台搜索「规则」,能看到两个服务。其实 EventBridge 就是 CloudWatch Events 的升级版。2019 年之后 AWS 主推 EventBridge,功能更强,支持的源也更多。但底层原理一样,写法也兼容。

我的建议:新项目一律用 EventBridge。老旧系统里可能还有 CloudWatch Events 规则,不影响使用。迁移也简单,控制台点几下就能导出导入。

两者区别其实不大,核心就两点:

对比项 CloudWatch Events EventBridge
推出时间 2016 年 2019 年
事件源范围 仅 AWS 服务 AWS 服务 + SaaS + 自定义
Schema Registry 无 有,可发现事件结构
目标数 最多 5 个 最多 5 个(可用多个规则破解)
Cron 表达式 完全一致 完全一致

Cron 表达式——你得懂这几个字段

定时任务的核心就是这个字符串。AWS 的 Cron 表达式有 6 个字段:

分钟 小时 日期 月份 星期 年份(可选)

我平时写表达式,最怕记混顺序。尤其是星期(第5位)和年份(第6位)。举个例子:

0 2 * * ? *

这代表「每天凌晨 2:00 整执行」。注意第六位的 * 表示任意年份,可以省略。第五位的 ? 表示不指定星期,与日期互斥——Cron 里 ? 和 * 不一样,千万别写错。

坑啊,我以前吃过亏: 写「每周一凌晨 3 点」时,我把日期字段写了 *,结果变成了「每个周一且任意日期都能触发匹配」。实际上应该用 ? 占掉日期字段:0 3 ? * MON *。记住,日期和星期不能同时指定非通配,否则规则会失效。

常用表达式示例:

含义 Cron 表达式
每 5 分钟执行一次 */5 * * * ? *
每小时整点执行 0 * * * ? *
每天 8:30 执行 30 8 * * ? *
每周一零点执行 0 0 ? * MON *
每月 1 号凌晨 1 点 0 1 1 * ? *
工作日(周一到周五)早 9 点 0 9 ? * MON-FRI *

另外,AWS 的 时区默认是 UTC。我刚开始做国内项目时,想每天北京时间 8:00 跑一次,写了 0 0 * * ? *,结果等了一上午函数没动静。后来才发现需要把小时往前调 8 变成 0 16 * * ? *,或者直接在控制台选择时区为 Asia/Shanghai。嗯,这个容易忽略。

创建定时事件的两种方式

控制台创建最简单:进 EventBridge → 创建规则 → 选「计划」→ 写 Cron 表达式 → 选目标 Lambda → 保存。但项目多了我更喜欢用 CloudFormation 或 Terraform,模板化管理免得出错。

下面是一个 CloudFormation 片段,创建每 5 分钟触发 Lambda 的规则:

Resources:
  CheckLogSchedule:
    Type: AWS::Events::Rule
    Properties:
      Name: "log-check-every-5min"
      ScheduleExpression: "cron(*/5 * * * ? *)"
      State: "ENABLED"
      Targets:
        - Arn: !GetAtt LogCheckFunction.Arn
          Id: "LogCheckTarget"

  InvokeLambdaPermission:
    Type: AWS::Lambda::Permission
    Properties:
      FunctionName: !Ref LogCheckFunction
      Action: "lambda:InvokeFunction"
      Principal: "events.amazonaws.com"
      SourceArn: !GetAtt CheckLogSchedule.Arn

注意那个 Lambda::Permission——很多新人忘了写这个,结果规则创建成功但 Lambda 从不执行。EventBridge 调用 Lambda 需要显式授权。

核心逻辑: 定时规则的本质是一个持续运行的事件路由器。每秒钟检查当前时间是否符合 Cron 表达式,如果匹配就封装一个事件并推送给目标。你可以在 CloudWatch Logs 里看到每次触发记录,排查问题很方便。

流程图放下面,一目了然:

EventBridge 定时触发 Lambda 流程图 系统时钟(每分钟滴答) Cron 表达式匹配 (例如 */5 * * * ? *) 构造 Event 并推送给目标 目标 Lambda 函数 (日志巡检逻辑) 匹配时机:每分钟 匹配窗口:15 秒内 事件大小上限:256KB 时区:默认 UTC 频率精度:1 分钟

从图里能看出,EventBridge 每分钟检查一次 Cron 条件。匹配后立即生成事件,调用 Lambda。我遇到过一个问题:同一分钟内的两次执行。比如 Cron 写的是 0/1 * * * ? *(每整分钟执行),结果是会精确到秒级吗?其实不会,AWS 的调度器不是独占的,可能有几秒的延迟,但不会少执行。这个精度对巡检来说完全够用。

几个必须记住的避坑点

  • Cron 不支持秒级——最小粒度为 1 分钟。如果你需要每 30 秒触发一次,就得用两个规则错开 30 秒,或者用 Step Functions 做循环。
  • 夏令时 / 时区切换——如果选择固定时区,每年会多/少一次触发(不过大多巡检场景不敏感)。我个人的做法是用 UTC 配时间,用代码里做时区转换。
  • 规则状态——创建时默认启用,但如果你后来修改了表达式,需要手动保存并重新启用一次(某些情况下会变成 DISABLED)。
  • 重试机制——如果 Lambda 执行失败,EventBridge 不会自动重试。你需要在 Lambda 侧做错误处理或者用死信队列(DLQ)。我见过一个线上故障:规则触发了,但函数超时,日志没收集到。从那以后我都在 Lambda 里加了异步重试逻辑。

注意: 定时规则的成本很低(每 100 万次调用约 $1),但如果你创建了 10000 条规则每分钟都触发,那费用也会高得吓人。我们团队之前有个同事用脚本批量创建了几百条无用的规则,月底运维看到账单差点心脏病发作……所以规则数量要控制,用完记得禁用或删除。

好了,总结一下本节核心:

  • 定时任务靠 EventBridge 规则,Cron 表达式搞定频率。
  • 6 字段:分 时 日 月 星期 年(年份可省)。
  • 日期和星期用 ? 互斥,别用 * 一起上。
  • 时区默认 UTC,国内项目记得 +8 或指定 Asia/Shanghai。
  • 创建规则后必须配 Lambda 权限,否则触发无响应。