kl个人博客 首页>>数据库>>Redis 成本优化指南:45% 的成本节约

Redis 成本优化指南:45% 的成本节约

优化成果

2023 年,通过切换低成本的 Redis ESSD 实例、实施流量压缩方案、清理无效数据、治理实例 TTL、下线无用实例等措施,自研了 Redis 流量复制 & 流量放大、Redis 数据迁移、Redis 数据在线压缩 & 解压缩、Redis 数据定向清理 & 定向指定 TTL、Redis 扫描分析 Key 最后访问时间等工具辅助方案落地。实现 Redis 费用降本 46 万 / 月。

PS:文中所述 Redis , 均为阿里云的 Redis 相关产品。

优化措施

以下优化措施没有优先顺序,在我司的大致的优化比例如下表:

优化措施 优化比例
1、清理未使用的实例 5%
2、实例降配:提高内存使用率 15%
3、使用场景打标,允许部分场景内存用满 1%
4、合理设置 TTL 8%
5、清理历史数据 6%
6、改进 kv 结构 1%
7、定期 scan, 释放已过期的内存 1%
8、降低可用性 3%
9、压缩 value 32%
10、迁移到兼容 Redis 协议的磁盘存储项目 28%

1、清理未使用的实例

有的业务下线了,实例还在,需要清理这类实例。这个是最容易实现的,最快能优化的,通过采集 Redis Metrics 数据,筛出长期 QPS 非常低的实例(集群版本 Redis, 自身也会有 ping 等保活访问),观测连接数据等,和业务确认并释放

2、实例降配:提高内存使用率

各种因素,导致实际使用的内存,和申请的内存差别较大,导致使用率上不去,存在内存浪费。比如:

  • 业务容量预估偏差导致的内存长期使用率偏低
  • 业务进入稳定期,内存不在快速增长,内存使用率较稳定

这些场景需要通过降配,将使用率至少稳定在 70% 左右。降配时,需要注意如下:

  1. 集群降配到主从,注意业务端的链接模式是否兼容
  2. 大集群降配到小集群,注意是否有大 key 限制
  3. 大集群降配到小集群,注意数据倾斜问题导致小集群节点塞满了
  4. 大集群降配到小集群,注意节点数的变化,比如 128 节点降配到 32 节点,性能落差会非常大。
  5. 除了内存使用率,降配时还需要注意带宽的使用情况,
  6. 降配时,尽量在同等容量规格的情况下,选择节点数最多的规格,节点数越多性能越好。比如同样是 256GB 的,优先选择 128 节点 2GB 的集群,不要选择 32 节点 * 8GB 的集群。
  7. 降配时,会存在连接闪断抖动,尽量在业务低峰期进行

3、使用场景打标,允许部分场景内存用满

使用 Redis,在我司可归类为【被动缓存】、【非被动缓存】两类场景。

  • 【被动缓存】:典型的缓存场景,用来挡在 MySQL 前面抗流量(先请求 Redis 拿数据,没拿到则到 MySQL 里取数据,设置到 Redis),Redis 内存用满时也不用扩容,设置好驱逐策略即可。
  • 【非被动缓存】:搜广推场景的特征数据,Redis 里的数据是实时流、离线等业务主动写入的。如果内存到了 90% 告警线,需要及时扩容,避免重要数据被驱逐

通过合理的区分不同 Redis 实例的使用场景,制定不一样的告警策略,允许部分场景 Redis 使用率 100%,可进一步提高整个 Redis 的内存使用率。实施时需要注意如下:

  1. 被标记为【被动缓存】的实例,需要明确所有的 key 都设置了 TTL ,明确允许所有的 key 都允许被驱逐。否则会导致内存用满,然后写入被拒的情况。
  2. 被标记为【被动缓存】的实例,在主从架构中,可关闭主服务的 aof(aof 关闭涉及到 config rewrite 会阻塞主进程 100ms ~ 400ms 左右,会对线上集群 rt 产生影响,应尽量在业务低峰期操作),降低数据驱逐时的性能影响,提高性能

4、合理设置 TTL

使用 Redis 时,99% 的场景 Key 是可过期删除的。只不过,不同的场景对 TTL 的要求是不一样的,这个需要业务自行判断。但是 infra 可以把大实例的 TTL 比例全部捞出来从大到小依次过一遍。

  • 有的实例 512G 没有设置 TTL ,实际活跃实际只有 256G 不到,通过设置合理的 TTL ,可以优化 50% 的内存。
  • 有的实例 TTL 设置了一年,实际活跃数据只有半年,或者一个月

以上都可以通过设置合理的 TTL,来优化内存使用。实施时需要注意如下:

  1. 实施前需要采集 Redis Metrics 数据,筛出内存规格大,未设置 TTL key 占比高的实例。
  2. 大部分场景其实业务也不知道合理的 TTL 是多少。所以需要准备好 Key 的访问时间(最后一次访问)分布情况,这个需要 scan 分析,后文会提到这个工具。提供给业务做 TTL 决策。
  3. 有了预期的 TTL 后,需要将所有的数据都刷一遍。所以需要一个刷 TTL 的运维工具脚本,同时可以指定清理最后一次访问距今时间的数据。

5、清理历史数据

部分业务把 Redis 当持久化存储,但是中间数据结构发生过变化,导致历史无用数据占用内存空间。这个场景和 5、合理设置 TTL 有点类似,同样需要:

  1. 准备好 Key 的访问时间(最后一次访问)分布情况,提供给业务做数据清理决策。
  2. 有了预期的清理逻辑后,需要一个清理 Key 的运维工具脚本,功能包含【指定 key 前缀清理】、【指定清理最后一次访问距今时间的数据 Key】。

6、改进 kv 结构

  1. 部分业务把整个大的 json 对象一股脑的存到 Redis ,其实部分字段用不着,可以裁剪 value 的大小,减少内存使用。
  2. 部分场景,比如判断用户设备是否是新设备,采用 Bloomfilter 相比 String,相同数据规模,可以节省 90% 以上的内存。

7、定期 scan, 释放已过期的内存

因为 Redis 已过期 key 的清理策略,是惰性删除的(已经过期的 key ,只有被访问过或者 hz 扫描到才会被删除),所以通过定期使用 SCAN 命令扫描键空间并手动删除已过期的键,可以更精细地管理 Redis 内存,提高系统的稳定性和资源利用效率。实施时需要注意:

  1. 选择在业务低峰期进行,控制好 SCAN 的速率,尽量减少对线上业务的影响。

8、降低可用性

在 dev、qa 环境中,可通过如下措施来进一步降低成本:

  1. 合并小实例,部分实例已经是最小规格,但是内存使用率只有不到 10% ,这种情况下,可以通过小实例合并来缩减成本
  2. 在 dev、qa 等对可用性要求不高的环境里,可通过对集群、主备版本,降配到单实例规格降低成本。

9、压缩 value

选择合适的压缩算法,对 value 进行压缩后存储。在我司的场景里,压缩率高的可达 50%~80% ,意味着部分实例至少可优化 50% 左右的内存。因为这个方案实施比较复杂,所以尽量挑选 100GB 以上的实例进行优化,性价比较高。适合压缩的特征如下:

  • value 大(100b 以上),字符重复度高

我们准备了 3 个主流的压缩算法,gzip 、zstd、snappy 。测试下来 ,不同的压缩算法特点如下:

  • gzip 、zstd : 这两个压缩和解压缩表现都不错,性能比较均衡。在不同的 value 特征的压缩 & 解压缩性能表现差异不大
  • snappy :压缩比较耗时,但是解压缩非常快。

我本地对 852k 的一个 user_profile 数据做了一个测试。(不同大小的数据性能差异较大,最终应该以线上数据流量的测试结果为准) 测试方法:运行 100 次取均值

snappy

  • 压缩耗时:122722us
  • 压缩率:22.60%
  • 解压耗时:49us

gzip

  • 压缩耗时:39821us
  • 压缩率:15.79%
  • 解压耗时:3521us

zstd

  • 压缩耗时:6953us
  • 压缩率:17.08%
  • 解压耗时:3153us

最后 snappy 就直接否了,因为压缩的资延时不符合预期。然后不同的实例都验证下 gzip、zstd 哪个性能好就选择哪个。具体实施时需要注意如下:

  1. 准备好 Redis 流量复制的工具,复制线上流量进行 zstd、gzip 的压缩、解压缩性能测试,明确压缩率、压缩延时和解压缩延时数据。
  2. 定制 Redis 访问客户端,无缝兼容各种指令的 gzip 、zstd 压缩数据,做到应用端不用改代码,只需要升级客户端版本就可以完成压缩、解压缩兼容。
  3. 研发存量数据全量压缩的运维工具,这个工具支持指定压缩算法、支持指定 Key 前缀压缩、支持解压缩等。
  4. 注意开启压缩后,对应用 CPU 等资源消耗的的影响,读取延时普遍会增加 1~2ms 左右。
  5. 刷线上存量的数据压缩前,确保应用都更新到兼容压缩的 Redis 访问客户端了,然后对部分 key 灰度压测,观测线上情况,没有问题后在全量刷。发现问题后及时解压缩。

10、迁移到兼容 Redis 协议的磁盘存储项目

早期,本着性能优先的原则,所有的 Redis 实例都是【内存型】。随着数据规模增大,内存资源占用会随之递增,Redis 存储成本大幅攀升。 另一方面,随着业务的发展,当业务进入发展后期,数据量已经形成一定规模,而数据的访问频度则慢慢降下来,资源使用率普遍偏低 。针对 这类问题,可重新从性能、和成本综合评估业务更适合【内存型】、【磁盘型】的 Redis 实例。

内存型

  • 优点:性能好(p99 延时很稳定)、扩展性好、支持水平伸缩
  • 缺点:成本高

磁盘型

基于 RocksDB 实现的磁盘存储、兼容 Redis 协议的产品,在阿里云叫 Tair ESSD 型。社区里也有 pika 、kvrocks 这类项目。

  • 优点:成本低,是内存型的 16% 。有容量起点,相比内存型,至少存储 64GB 以上才有性价比。
  • 缺点:性能一般(p99 延时存在波动,突刺效应明显),是内存型的 70% 性能。且只支持垂直扩容,没法水平伸缩。

这个优化措施涉及到数据的迁移,且需要兼顾业务稳定性。所以实施起来比【压缩 Value】还要复杂,主要注意点如下:

  1. 如何筛选迁移到【磁盘型】的 Redis 实例?写请求低于 10k/s ,读请求低于 100k/s, 对 p99 时延要求不敏感,比如可以接受 avg 大于 0.5ms ,p99 5ms 的场景,且存储容量非常大 100GB 以上 ,而且预期存储容量会进一步增长
  2. 采集好【磁盘型】的 Metrics ,做好监控观测。阿里云只给了基础的 Metrics,底层的数据指标,比如 RocksDB 层的、以及指令级的延时(对后面判断指令性能问题非常有帮助)、QPS 等都需要通过 info 指令拿到信息后自己解析采集。
  3. 准备好 Redis 流量复制工具,复制线上流量到【磁盘型】Redis 目标实例,对流量进行等比,加倍回放,对每个迁移的实例针对性的进行性能压测验证。确保覆盖每个指令的性能问题、兼容问题。
  4. 对【磁盘型】实例做好容量规划,QPS 增长评估。
  5. 使用 DTS 进行数据迁移时,确保原实例内存至少有 20% 的冗余,否则需要先扩容原实例在迁移。因为 DTS 是基于【Redis 主从复制这套逻辑】来进行同步的,同步时,缓冲区需要占用内存。
  6. 如果原实例的写入流量较大,DTS 迁移前,最好先调整下【 client-output-buffer-limit replica】、【repl-backlog-size 】、【DTS 收到 Master 的 RDB 后,立马回复 ACK 的开关】这些参数,避免同步反复失败。

下面记录整个项目使用阿里云 Tair 期间发现的性能问题、Bug,这个项目能够顺利完成,我们几乎是从小白鼠开始,一路升级。

  • hash 结构的 hgetall 性能 Bug, 已修复
  • 全量离线分析不可用 Bug , 已修复
  • Cpu 100% 的监控问题 ,已修复
  • 数据每天的冷备份 backup 操作会对性能有比较大的影响,预计会将 backup 操作迁移到从库执行,已修复
  • zset 的 zrange 性能慢,用 zscan 可以避免这个问题
  • lrem 在 list 当 mq 使用的场景存在严重的性能问题
  • spop 会随着 count 数、set 总数增加线性增加

也给阿里云提了很多需求(下面所列只是冰山一角),这里非常感谢阿里云的支持,协助整个项目落地。

  • Dataworks 支持写入数据到非集群部署模式的 Redis 实例,也就是支持 Redis ESSD 实例
  • Redis ESSD 实例监控指标优化
  • ESSD 云盘 IO 瓶颈导致的性能尖刺问题,会限制批量刷盘时的写入速度(9 月中旬)已上线,需要升级小版本到 2.4.2.2
  • Redis ESSD 目前指令验证这块比较繁琐麻烦,后面能不能在 Redis ESSD 文档里加上每个指令的算法复杂度,或者本身性能很糟糕的指令单独标出来。给选型做参考

优化工具

在 Redis 成本优化项目落地过程中,我们沉淀了一套辅助项目落地的 Redis 成本优化工具,这些工具缺一不可,发挥了重要的作用。

Redis 流量复制 & 流量等比放大

在【迁移到兼容 Redis 协议的磁盘存储项目】、【压缩 value】项目中,都需要用到 Redis 流量复制工具。该工具实现了如下功能:

  1. 支持只复制原实例指令输出到控制台,或复制流量回放到指定目标实例
  2. 支持只复制读流量、只复制写流量、读写流量一起复制
  3. 支持流量等比放大回放
  4. 支持原实例是集群模式时(集群模式 mget 订阅到的是单 key,不符合真实业务 mget),指定 mget keys 大小发送到目标实例,且支持比例设置,比如 10% mget 的 keys=100, 40% mget 的 keys=30

Redis 数据在线压缩 & 解压缩

这个工具主要用在【压缩 value】项目中,用于验证不同的压缩算法的压缩性能、解压缩性能、压缩率等。实现了如下功能:

  1. 支持指定压缩算法 gzip、zstd,进行压缩性能、解压缩性能、压缩率等的计算
  2. 支持复制线上流量进行压缩或解压缩
  3. 支持跑存量的数据进行压缩或解压缩

Redis 数据定向清理 & 定向指定 TTL

这个工具主要用于【清理历史数据】、【合理设置 TTL】等项目,主要实现了如下功能:

  1. 支持指定 key 前缀删除数据
  2. 支持指定 key 前缀设置 TTL
  3. 支持 dryrun 模式,只输出操作的日志,实际上不进行删除或 TTL 设置。
  4. 支持指定 key 的 MaxIdleTime 进行删除或 TTL 设置

Redis 扫描分析 Key 最后访问时间

这个工具主要用于【清理历史数据】、【合理设置 TTL】等项目,主要实现了如下功能:

  1. 支持扫描所有的 key ,打印 idelTime , 通过统计分析得到 key 的访问时间分布

Redis 磁盘型实例指标采集

通过阿里云的 OpenAPI ,采集获取到 Redis ESSD(Tair) 实例列表,然后通过 info 指令拿到实例运行时关键信息,通过解析 info 拿到关键指标,实现了:

  1. 指令级的 QPS 、RT 、带宽等数据观测
  2. RocksDB 底层的 Compactions、Flush 等事件数据观测

优化总结

工欲善其事必先利其器,在这次 Redis 优化项目中,我们通过前期对优化工具的研发和准备,以及系统化的优化措施的实施,成功降低了 Redis 的运行成本,提高了资源利用效率,并且保证了线上系统的稳定性。整个过程中,我们实现了在不中断服务的情况下进行优化,达到了零故障的目标。

kl个人博客