高级阅读约 3 分钟

DynamoDB Streams

DynamoDB Streams 是一份变更数据捕获日志:表上的每一次插入、更新和删除都会被按顺序捕获, 成为一条你可以据以反应的记录流。这就是你在不轮询表的情况下把一张表变成事件源的方式。

在审计日志的场景中,你希望在敏感事件落地的那一刻就做出反应 —— 当有人导出一张发票或授予一个 管理员角色时触发告警 —— 而不必定时扫描表。Streams 就是这件事的推送侧。

DynamoDB Streams 如何工作?

DynamoDB Streams 将表上的每一次插入、更新和删除捕获为一份按时间排序、去重的记录日志,保留最长 24 小时。你通过 StreamViewType 选择每条记录携带的内容(仅键、新映像、旧映像,或新旧两者),然后借助 Lambda 触发器消费 stream,从而在不轮询的情况下对项变更做出反应。

  • Streams 捕获项级变更,作为一份按时间排序、去重的日志,保留最长 24 小时
  • 你通过 StreamViewType 选择每条记录携带什么:仅键、新映像、旧映像,或新旧两者。
  • 记录在一个分区键内有序,且 stream 的分片方式与表相同。
  • 原生消费者是 Lambda —— 一个对每批新记录运行一次的触发器,而 Kinesis Data Streams 则是 用于更丰富扇出的备选方案。

问题所在:不轮询也能反应

你需要“当一个 role.granted 事件被写入时告警我”。最朴素的做法是一个每分钟扫描新事件的计划 作业 —— 它每次都读取整个近期分区、消耗容量,且总是至少晚一分钟。

你真正想要的是一次推送:DynamoDB 在某个项变更的那一刻就告诉你。而这正是 Streams 所提供 的,把变更记录送到你的代码里,而不是让你去四处搜寻它。

Streams 如何工作

据 AWS 文档所述,DynamoDB Streams 存储“一份去重、按时间排序的变更日志,保留最长 24 小时”, 并原生集成 Lambda (DynamoDB 的变更数据捕获)。 每条记录描述一次项级修改。

当你启用一个 stream 时,你要选一个 StreamViewType,它控制每条记录携带变更后的项有多少 内容:

StreamViewTypeeach record contains
KEYS_ONLYonly the key attributes of the changed item
NEW_IMAGEthe entire item as it looks after the change
OLD_IMAGEthe entire item as it looked before the change
NEW_AND_OLD_IMAGESboth the before and after images

记录在每个分区键内有序,且 stream 沿着与表相同的分区结构分片。保留期为 24 小时 —— Streams 是一个反应缓冲区,而非永久历史。要保存持久的历史,你应存储事件本身(而这恰好正是 我们这张审计日志表的本质)。

原生消费者是一个 Lambda 触发器:DynamoDB 在新的 stream 记录到达时用一批它们来调用你的函数。

LambdaStream"DynamoDB"AppLambdaStream"DynamoDB"App"Put EVENT role.granted""change record (NEW_IMAGE)""batch of records""if action is sensitive →alert"

一个实战示例:对敏感审计事件告警

审计日志表配上一个 NEW_IMAGE 的 stream,于是每条记录都携带完整的新事件。一个 Lambda 消费 这一批,并只转发那些要紧的记录:

stream record (NEW_IMAGE)consumer action
TENANT#acmeEVENT#…#a2action=invoice.exportsend to SIEM
TENANT#globex EVENT#…#b9 action=role.grantedpage on-call
TENANT#acmeEVENT#…#a1action=login.successignore

这个函数从不触碰表 —— 它纯粹对 stream 递给它的内容做出反应。无轮询、无扫描,且告警在写入后 数秒内触发。由于记录按分区键有序,一个租户的所有事件都会按它们被写入的顺序到达。

这也是维护一份下游副本的标准方式:一个 stream 消费者可以把每个事件投影进 OpenSearch 以 做全文审计搜索,或者聚合计数 —— 全都派生自同一份变更日志。

在 DynoTable 中操作

在你接上一个 stream 消费者之前,你需要知道你的 Lambda 将收到的项的确切结构 —— 存在哪些属性、 嵌套的映射和列表长什么样、一条 NEW_IMAGE 记录实际会包含什么。

要把一个样本项在纯 JSON 与 stream 记录所用的属性值结构之间相互转换, DynamoDB JSON 转换器能在你的浏览器中完成。而在 DynoTable 中,你可以查看完整的项 — 包括它的 DynamoDB-JSON 形式 — 从而对照真实数据来建模 NEW_IMAGE 记录,而不是去猜字段结构。

在 DynoTable 中检查一个审计事件项,以便对其 Lambda 消费者将收到的 NEW_IMAGE stream 记录建模。
在 DynoTable 中检查一个审计事件项,以便对其 Lambda 消费者将收到的 NEW_IMAGE stream 记录建模。

如果你在本地测试一个消费者,就让表跑在 DynamoDB Local 上并以同样的方式检查它 —— 参见 连接到 DynamoDB Local

坑与后续步骤

  • 24 小时不是一个积压队列。 如果你的消费者停机一整天,记录会过期并消失。Streams 用于近乎 实时的反应,而非持久的回放 —— 历史请保存事件本身。
  • 挑选你所需的最小 StreamViewType NEW_AND_OLD_IMAGES 会让载荷翻倍;如果你只需要键去 重新读取该项,KEYS_ONLY 更便宜。
  • 顺序是每个分区键内的,而非全局的。 两个不同租户的事件之间没有顺序保证 —— 只在一个租户 的分区之内才有。
  • TTL 删除会作为 stream 记录出现,并带有系统属性标记,这正是你归档过期项的方式 —— 参见 DynamoDB TTL

Streams 把审计日志变成了一个事件源。下一个运维方面的考量是一个项生命的另一端 —— 用 DynamoDB TTL 自动让旧事件过期。

下载 DynoTable,在你写下一行 Lambda 代码之前,检查你的 stream 消费者将收到的确切 项结构。

更新于