我用十条优化措施,将Redis费用降本46万/月
陈凯玲(“KL博主”):TapTap infra 工程师。
开源项目 kkFileView 等 kk 系列项目作者,Apollo 配置中心项目 PMC 。
优化成果
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% 左右。
降配时,需要注意如下:
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 比例全部捞出来从大到小依次过一遍。
以上都可以通过设置合理的 TTL,来优化内存使用。实施时需要注意如下:
1. 实施前需要采集 Redis Metrics 数据,筛出内存规格大,未设置 TTL key 占比高的实例。
2. 大部分场景其实业务也不知道合理的 TTL 是多少。所以需要准备好 Key 的访问时间(最后一次访问)分布情况,这个需要 scan 分析,后文会提到这个工具。提供给业务做 TTL 决策。
3. 有了预期的 TTL 后,需要将所有的数据都刷一遍。所以需要一个刷 TTL 的运维工具脚本,同时可以指定清理最后一次访问距今时间的数据。
5、清理历史数据
部分业务把 Redis 当持久化存储,但是中间数据结构发生过变化,导致历史无用数据占用内存空间。
这个场景和和合理设置 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 工具集已开源:
https://github.com/taptap/redis-tools
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】等项目,主要实现了如下功能:
支持扫描所有的 key ,打印 idelTime , 通过统计分析得到 key 的访问时间分布
Redis 磁盘型实例指标采集
通过阿里云的 OpenAPI ,采集获取到 Redis ESSD(Tair) 实例列表,然后通过 info 指令拿到实例运行时关键信息,通过解析 info 拿到关键指标,实现了:
1. 指令级的 QPS 、RT 、带宽等数据观测
2. RocksDB 底层的 Compactions、Flush 等事件数据观测
优化总结
工欲善其事必先利其器,在这次 Redis 优化项目中,我们通过前期对优化工具的研发和准备,以及系统化的优化措施的实施,成功降低了 Redis 的运行成本,提高了资源利用效率,并且保证了线上系统的稳定性。
整个过程中,我们实现了在不中断服务的情况下进行优化,达到了零故障的目标。
Reference
活动推荐
END
热门文章