线上救急-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就会通过返回TooManyRequests
或 ThrottlingException
等错误来限制后续请求。一般在短时间内大量并发查询、复杂查询(涉及大范围时间跨度、高基数的聚合操作或为未命中索引的查询),可能会导致TCU配额被快速耗尽。
解决
紧急措施
首先必须保证线上服务正常运行,因此决定对Timestream进行扩容,购买的一个TCU对应的资源是4个CPU和16GB的memory,这次紧急扩容了64个TCU,提供了更多的计算资源配额,支持更多并发、复杂以及高频次的查询,暂时解决了这个问题,能源统计数据得以正常显示。
追根溯源
解决了线上问题,但是不能直接结束,按照之前预测应该不会出现限频的问题,数据查询方面应该存在一定的问题,需要进一步排查,首先看一下主流的优化方案。
主流优化方案
- SQL执行日志记录与AI分析
- 开启SQL日志记录:在AWS Timestream配置中启用详细日志,记录SQL语句、执行时间等,并将日志存储至Amazon CloudWatch Logs中。
- AI统计分析:通过AWS Glue或Amazon SageMaker构建模型,对高频查询、执行时长等进行分析,识别低效SQL并分类优化。
在TimeStream对控制台上有一个新的功能,叫queryInsight,可以辅助查询优化调优。
- 高频接口缓存优化
ElastiCache应用:针对高频查询接口,采用Amazon ElastiCache(Redis或Memcached)缓存数据,降低数据库的直接查询压力。
缓存策略设计:写操作后立即更新缓存,读操作设置动态过期时间(如变化频繁数据设置过期时间,低频数据设置长过期时间)
- 数据分片与读写优化
- 分片策略:按时间、地理位置或用户ID分片,分散读写负载,例如时间序列数据按天/周分片存储
- 分区键优化:在user_id等具有唯一性的字段设置分区键(决定数据如何分布到不同分片),提升查询效率并减少TCU消耗
- 资源扩容与配置调整
- 扩展TCU:根据监控临时提升集群的Query Limit或升级TCU配置以应对高并发
- 调整热数据存储时间:延长Memory Retention时间(如12小时延长到30小时),扩大热数据缓存范围,优化查询性能
- SQL优化
- 避免全表扫描:精确指定时间范围,减少扫描数据量
- 合并宽表存储:将多条数据合并为宽表,降低I/O压力和存储成本
最终实行方案
原先我们的表没有进行分区,会导致每次数据查询都会扫全表进行查询,从而浪费大量计算资源,因此决定从数据分区进行修改。
-
分片策略制定
根据数据特征选择分片维度:
- Meature name分片(度量名称): 适用于时间序列数据
- 自定义Partitionkey(特定业务字段): 使用特定业务场景使用
缺点:分区键需在建表时定义,无法直接修改现有表结构,因为修改分片键需重新分配所有数据到新分片上,会导致系统长时间不可用或性能严重下降
-
分区键优化
在查询高频字段(如user_id等)上设置分区键,提升查询效率并降低TCU消耗,在经过多字段比较后,统一使用用户id作为分区键,以用户维度作为数据多分区查询字段。
-
具体方案
- 方案一:使用双写方式,新数据同时写入新旧数据表中,数据查询到时候根据数据分布情况查不同表,如果数据范围仅在旧表或仅在新表中,则分别查询对应的旧表或者新表,如果数据同时存在新表和旧表中,则使用union语句跨两张表进行查询。
- 方案二:使用双写方式,新数据同时写入新旧数据表中,同时作数据迁移。
方案对比:
方案 改动代码 数据迁移 对线上服务影响 综合对比 建议 双写+Union兼容 是 否 union查询跨新旧表时有影响,
但新表有分区键,影响是可控的1. 不需要迁移数据
2. 不需要确认数据保留时长
3. 对线上数据影响很小
4. 符合用户数据使用场景,一般用户只会查询当天最新数据,这些数据都会在新表中。采纳 双写+数据迁移 是 是 迁移数据时会影响查询 1. 需要迁移数据
2. 需要确认保留时间
3. 会影响线上服务查询
4. 迁移后数据都查询新表需要迁移数据,还需要确认数据保留时间,比较麻烦 最后综合来看,选择了方案一来进一步解决这个问题。
展望
一期方案通过分区键来进行解决,最终经测试验证,用户48小时内的最新数据查询效率提升48%,效果显著。不过为了类似问题不再发生,我们制定了二期优化方案-AWS Timestream Redis缓存。
高频接口筛选的AI分析方法
-
数据来源:从 Timestream 的 SQL 执行日志中提取查询接口的执行频次、响应时间、资源消耗等指标。
-
AI分析逻辑:
-
特征提取:通过日志分析工具(如 AWS CloudWatch Logs、Amazon Kinesis Data Analytics)提取查询接口的类型(如 SELECT、INSERT)、时间分布、参数模式等特征。
-
模式识别:利用机器学习模型(如 AWS SageMaker 的分类算法或序列模型)识别高频接口的模式,
-
每秒查询率(QPS)超过阈值(如 100 QPS)的接口。
-
响应延迟超过业务容忍值(如 200ms)的接口。
-
频繁访问相同或相似数据的接口(如固定时间窗口的查询)。
-
-
优先级排序:根据接口的 QPS、延迟敏感度和资源消耗,生成需要优先缓存的接口列表。
-
-
工具支持:AWS 提供的 CloudWatch Logs、Amazon Athena(日志分析)或第三方日志分析工具(如 Splunk)可用于数据提取和初步分析。
缓存引擎
缓存选型
AWS ElastiCache 提供两种引擎:Redis 和 Memcached。选型需基于业务场景需求:
引擎 | 适用场景 | 优势 | 局限性 |
---|---|---|---|
Redis | 需要支持复杂数据结构(如哈希表、列表、集合等)或需要高读写性能的场景。例如: 需要存储结构化数据(如用户会话、设备状态) ,需要原子操作(如计数器、队列管理) | 支持丰富数据类型和持久化;高并发读写性能(百万级 QPS)。 | 内存占用较高;配置复杂度略高于 Memcached。 |
Memcached | 仅需简单键值对存储且并发访问量极高(如每秒数万次请求)的场景。例如: 高频访问的静态数据(如配置信息、简单元数据) ,需要极简配置的分布式缓存 | 内存效率高;极简设计支持极高并发性能。 | 仅支持简单键值存储;无数据结构扩展;默认不支持持久化。 |
综合以上对比,Redis明显更加适合复杂业务的使用,因此计划选择Redis作为缓存引擎。
缓存策略设计:写后更新机制
核心目标:确保缓存数据与 Timestream 中的数据强一致性。
实现机制
-
同步更新流程:
- 应用层执行 写操作(如 INSERT、UPDATE)到 Timestream。
- 写操作成功后,触发 缓存更新事件(可通过 AWS Lambda 或应用层代码实现)。
- 立即更新 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 的查询压力。
-
热数据时间延长:确保低频但需长期保留的热点数据仍保留在热存储层,避免冷数据查询的高延迟。
-
-
实施要求:
-
间隔执行变更:例如:
-
先调整 Timestream 的热数据保留时间,观察指标(如查询延迟、成本)变化。
-
再实施 Redis 缓存优化,单独评估其性能提升效果。
-
-
目的:避免两个策略的效果相互干扰,便于分析优化方案的独立贡献。
-
最佳实践
-
缓存键设计:
-
使用有意义的键名(如 device:123:status),方便按业务维度管理缓存生命周期。
-
对于复杂查询,可采用 Query Caching(将 SQL 表达式哈希为键)或 Query Pattern Caching(缓存查询模式而非具体数据)。
-
-
分布式缓存:
-
数据一致性:Memcached 的分布式缓存基于哈希分片,需确保写操作同步到所有节点(若使用集群模式)。
-
缓存雪崩/击穿:采用 随机过期时间(如在基础 TTL 上随机加 0-5 分钟)和 互斥锁(如 Redis 的 Redlock 算法)缓解这些问题。
-
-
成本优化:
-
结合 Timestream 的按需付费模式,通过缓存减少查询量可显著降低 Timestream 费用。
-
Redis 的持久化(如 RDB、AOF)需谨慎配置,避免额外 I/O 开销。
-