降低成本
‣
陈凯玲(“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% 左右。
降配时,需要注意如下:
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 当持久化存储,但是中间数据结构发生过变化,导致历史无用数据占用内存空间。
这个场景和和
合理设置 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
https://my.oschina.net/klblog/blog/11572308
Loading...