线上救急-AWS限频

时间:2025-04-21 15:42:35

线上救急-AWS限频

问题

在一个天气炎热的下午,我正喝着可口可乐,悠闲地看着Cursor生成代码,忽然各大群聊中出现了加急➕@全体的消息,当时就心里一咯噔,点开一看,果然,线上服务出问题,多个能源统计接口报错未返回数据。

排查

首先排查线上ES日志,查询能源统计接口的日志中存在大量报错,报错提示如下:

GetEnergyAnalysisRpc err:error: code = 1083 reason = erdr: code = 10083 reason = error: code = 10003 reason = ThrottlingException: Request rate limit exceeded

日志显示得很清楚了,报错原因是AWS Timestream限频了。我们能源相关的数据都是时序的,技术选型的时候时序数据库采用了Timestream,Timestream的查询吞吐量是有限制的,会根据购买的TCU(查询容量单位)来计费和分配,每个TCU会提供一定量的查询资源(包括CPU、内存、存储和网络带宽)。

而当应用发起的查询请求频率或复杂度超过当前购买的TCU配额时,Timestream就会通过返回TooManyRequestsThrottlingException 等错误来限制后续请求。一般在短时间内大量并发查询、复杂查询(涉及大范围时间跨度、高基数的聚合操作或为未命中索引的查询),可能会导致TCU配额被快速耗尽。

解决

紧急措施

首先必须保证线上服务正常运行,因此决定对Timestream进行扩容,购买的一个TCU对应的资源是4个CPU和16GB的memory,这次紧急扩容了64个TCU,提供了更多的计算资源配额,支持更多并发、复杂以及高频次的查询,暂时解决了这个问题,能源统计数据得以正常显示。

追根溯源

解决了线上问题,但是不能直接结束,按照之前预测应该不会出现限频的问题,数据查询方面应该存在一定的问题,需要进一步排查,首先看一下主流的优化方案。

主流优化方案

  1. SQL执行日志记录与AI分析
  • 开启SQL日志记录:在AWS Timestream配置中启用详细日志,记录SQL语句、执行时间等,并将日志存储至Amazon CloudWatch Logs中。
  • AI统计分析:通过AWS Glue或Amazon SageMaker构建模型,对高频查询、执行时长等进行分析,识别低效SQL并分类优化。

在TimeStream对控制台上有一个新的功能,叫queryInsight,可以辅助查询优化调优。

  1. 高频接口缓存优化

ElastiCache应用:针对高频查询接口,采用Amazon ElastiCache(Redis或Memcached)缓存数据,降低数据库的直接查询压力。

缓存策略设计:写操作后立即更新缓存,读操作设置动态过期时间(如变化频繁数据设置过期时间,低频数据设置长过期时间)

  1. 数据分片与读写优化
  • 分片策略:按时间、地理位置或用户ID分片,分散读写负载,例如时间序列数据按天/周分片存储
  • 分区键优化:在user_id等具有唯一性的字段设置分区键(决定数据如何分布到不同分片),提升查询效率并减少TCU消耗
  1. 资源扩容与配置调整
  • 扩展TCU:根据监控临时提升集群的Query Limit或升级TCU配置以应对高并发
  • 调整热数据存储时间:延长Memory Retention时间(如12小时延长到30小时),扩大热数据缓存范围,优化查询性能
  1. SQL优化
  • 避免全表扫描:精确指定时间范围,减少扫描数据量
  • 合并宽表存储:将多条数据合并为宽表,降低I/O压力和存储成本

最终实行方案

原先我们的表没有进行分区,会导致每次数据查询都会扫全表进行查询,从而浪费大量计算资源,因此决定从数据分区进行修改。

  1. 分片策略制定

    根据数据特征选择分片维度:

    • Meature name分片(度量名称): 适用于时间序列数据
    • 自定义Partitionkey(特定业务字段): 使用特定业务场景使用

    缺点:分区键需在建表时定义,无法直接修改现有表结构,因为修改分片键需重新分配所有数据到新分片上,会导致系统长时间不可用或性能严重下降

  2. 分区键优化

    在查询高频字段(如user_id等)上设置分区键,提升查询效率并降低TCU消耗,在经过多字段比较后,统一使用用户id作为分区键,以用户维度作为数据多分区查询字段。

  3. 具体方案

    • 方案一:使用双写方式,新数据同时写入新旧数据表中,数据查询到时候根据数据分布情况查不同表,如果数据范围仅在旧表或仅在新表中,则分别查询对应的旧表或者新表,如果数据同时存在新表和旧表中,则使用union语句跨两张表进行查询。
    • 方案二:使用双写方式,新数据同时写入新旧数据表中,同时作数据迁移。

    方案对比:

    方案 改动代码 数据迁移 对线上服务影响 综合对比 建议
    双写+Union兼容 union查询跨新旧表时有影响,
    但新表有分区键,影响是可控的
    1. 不需要迁移数据
    2. 不需要确认数据保留时长
    3. 对线上数据影响很小
    4. 符合用户数据使用场景,一般用户只会查询当天最新数据,这些数据都会在新表中。
    采纳
    双写+数据迁移 迁移数据时会影响查询 1. 需要迁移数据
    2. 需要确认保留时间
    3. 会影响线上服务查询
    4. 迁移后数据都查询新表
    需要迁移数据,还需要确认数据保留时间,比较麻烦

    最后综合来看,选择了方案一来进一步解决这个问题。

展望

一期方案通过分区键来进行解决,最终经测试验证,用户48小时内的最新数据查询效率提升48%,效果显著。不过为了类似问题不再发生,我们制定了二期优化方案-AWS Timestream Redis缓存。

高频接口筛选的AI分析方法

  • 数据来源:从 Timestream 的 SQL 执行日志中提取查询接口的执行频次、响应时间、资源消耗等指标。

  • AI分析逻辑:

    1. 特征提取:通过日志分析工具(如 AWS CloudWatch Logs、Amazon Kinesis Data Analytics)提取查询接口的类型(如 SELECT、INSERT)、时间分布、参数模式等特征。

    2. 模式识别:利用机器学习模型(如 AWS SageMaker 的分类算法或序列模型)识别高频接口的模式,

      • 每秒查询率(QPS)超过阈值(如 100 QPS)的接口。

      • 响应延迟超过业务容忍值(如 200ms)的接口。

      • 频繁访问相同或相似数据的接口(如固定时间窗口的查询)。

    3. 优先级排序:根据接口的 QPS、延迟敏感度和资源消耗,生成需要优先缓存的接口列表。

  • 工具支持:AWS 提供的 CloudWatch Logs、Amazon Athena(日志分析)或第三方日志分析工具(如 Splunk)可用于数据提取和初步分析。

缓存引擎

缓存选型

AWS ElastiCache 提供两种引擎:RedisMemcached。选型需基于业务场景需求:

引擎 适用场景 优势 局限性
Redis 需要支持复杂数据结构(如哈希表、列表、集合等)或需要高读写性能的场景。例如: 需要存储结构化数据(如用户会话、设备状态) ,需要原子操作(如计数器、队列管理) 支持丰富数据类型和持久化;高并发读写性能(百万级 QPS)。 内存占用较高;配置复杂度略高于 Memcached。
Memcached 仅需简单键值对存储且并发访问量极高(如每秒数万次请求)的场景。例如: 高频访问的静态数据(如配置信息、简单元数据) ,需要极简配置的分布式缓存 内存效率高;极简设计支持极高并发性能。 仅支持简单键值存储;无数据结构扩展;默认不支持持久化。

综合以上对比,Redis明显更加适合复杂业务的使用,因此计划选择Redis作为缓存引擎。

缓存策略设计:写后更新机制

核心目标:确保缓存数据与 Timestream 中的数据强一致性。

实现机制

  • 同步更新流程:

    1. 应用层执行 写操作(如 INSERT、UPDATE)到 Timestream。
    2. 写操作成功后,触发 缓存更新事件(可通过 AWS Lambda 或应用层代码实现)。
    3. 立即更新 Redis/Memcached 中的对应缓存键值,覆盖旧数据。
  • 一致性保证:

    • 避免缓存与数据库数据不一致导致的“脏读”问题。

    • 需确保缓存更新操作与 Timestream 写操作在事务层面的顺序性(如通过分布式锁或队列保证)。

[!WARNING]

  • 写延迟风险:同步更新机制会延长写操作的总耗时,需在业务允许范围内平衡一致性与性能。
  • 失败处理:若缓存更新失败(如网络中断),需设计补偿机制(如重试、异步队列处理)。
  • 适用范围:仅适用于写操作与读操作强关联的场景(例如需要实时展示最新数据的仪表盘)。

分级过期策略

背景:时间序列数据的访问模式通常存在波动,分级过期策略通过动态管理缓存生命周期,平衡性能与资源占用。

分级过期策略设计

  • 高频变化数据(TTL:5-30分钟):

    • 适用场景:数据频繁更新且业务需要实时性(如传感器实时数据)。
    • 实现:为这类数据设置短过期时间(TTL),确保缓存中的数据不会过时太久。
    • 优势:减少因数据过期引发的缓存缺失(Cache Miss),同时避免陈旧数据被访问。
  • 低频变化数据(TTL:24小时):

    • 适用场景:数据更新周期较长但访问频率高(如用户配置、静态元数据)。

    • 实现:设置较长的 TTL,延长数据在缓存中的存活时间。

    • 优势:提高命中率,降低 Timestream 的查询压力。

主动预热机制

定义:在业务高峰前(如促销活动、每日早高峰),通过分析历史访问模式,将热点数据提前加载到缓存中。

实现步骤:

  • 数据分析:通过 AWS CloudTrail 或应用日志分析历史热点数据(如高频查询的时间范围、设备 ID、用户 ID 等)。
  • 触发条件:利用 AWS Lambda 或计划任务(如 cron job)在高峰前启动预热。
  • 数据加载:执行批量查询(如 SELECT 语句)将数据写入缓存,并设置合适的 TTL。

实际优化中的注意事项

缓存命中率监控

  • 监控指标:在 AWS CloudWatch 中监控以下指标:

    • 命中率(Cache Hit Ratio):衡量缓存是否有效减少 Timestream 查询量。

    • 缓存请求延迟:确保读取缓存的性能满足业务要求。

    • 缓存大小与内存使用率:避免因缓存过大导致内存溢出或交换(Swap)。

  • 告警阈值:

    • 当命中率低于 85% 时触发告警,可能原因包括:

      • TTL 设置不当:热点数据过早失效。

      • 缓存未命中策略:缓存未覆盖高频查询接口。

      • 数据更新频率过高:导致缓存频繁失效。

Redis缓存与Timestream热数据时间调整方案的配合

  • Timestream 的存储分层:

    • Timestream 默认将数据分为 热数据(Hot Storage) 和 冷数据(Cold Storage),其中热数据存储在内存中,查询性能更高。

    • 可通过配置调整热数据的保留时间(默认为 1 天),延长保留时间可减少冷数据访问频率,但会增加热存储成本。

  • 优化组合:

    • Redis 缓存:通过缓存高频查询结果,进一步减少 Timestream 的查询压力。

    • 热数据时间延长:确保低频但需长期保留的热点数据仍保留在热存储层,避免冷数据查询的高延迟。

  • 实施要求:

    • 间隔执行变更:例如:

      1. 先调整 Timestream 的热数据保留时间,观察指标(如查询延迟、成本)变化。

      2. 再实施 Redis 缓存优化,单独评估其性能提升效果。

    • 目的:避免两个策略的效果相互干扰,便于分析优化方案的独立贡献。

最佳实践

  1. 缓存键设计:

    • 使用有意义的键名(如 device:123:status),方便按业务维度管理缓存生命周期。

    • 对于复杂查询,可采用 Query Caching(将 SQL 表达式哈希为键)或 Query Pattern Caching(缓存查询模式而非具体数据)。

  2. 分布式缓存:

    • 数据一致性:Memcached 的分布式缓存基于哈希分片,需确保写操作同步到所有节点(若使用集群模式)。

    • 缓存雪崩/击穿:采用 随机过期时间(如在基础 TTL 上随机加 0-5 分钟)和 互斥锁(如 Redis 的 Redlock 算法)缓解这些问题。

  3. 成本优化:

    • 结合 Timestream 的按需付费模式,通过缓存减少查询量可显著降低 Timestream 费用。

    • Redis 的持久化(如 RDB、AOF)需谨慎配置,避免额外 I/O 开销。