高级篇(二)Redis 最佳实践
1. Redis键值设计
1.1 优雅的key结构
key 最佳实践约定:
- 遵循基本格式:
[业务名称]:[数据名]:[id]
- 长度不超过44字节
- 不包含特殊字符
key是string类型,底层编码包含int、embstr和raw三种。embstr在小于44字节使用,采用连续内存空间,内存占用更小。当字节数大于44字节时,会转为raw模式存储,在raw模式下,内存空间不是连续的,而是采用一个指针指向了另外一段内存空间,在这段空间里存储SDS内容,这样空间不连续,访问的时候性能也就会收到影响,还有可能产生内存碎片。
1.2 拒绝BigKey
推荐值:
- 单个key的value小于10KB
- 对于集合类型的key,建议元素数量小于1000
1.2.1 BigKey的危害
- 网络阻塞
- 对 BigKey 执行读请求时,少量的 QPS 就可能导致带宽使用率被占满,导致 Redis 实例,乃至所在物理机变慢
- 数据倾斜
- BigKey 所在的 Redis 实例内存使用率远超其他实例,无法使数据分片的内存资源达到均衡
- Redis 阻塞
- 对元素较多的 hash、list、zset 等做运算会耗时较长,使主线程被阻塞
- CPU 压力
- 对 BigKey 的数据序列化和反序列化会导致 CPU 的使用率飙升,影响 Redis 实例和本机其它应用
1.2.2 如何发现BigKey
redis-cli --bigkeys
SCAN
命令- 第三方工具
- 网络监控
1.2.3 如何删除BigKey
BigKey 内存占用较多,即便时删除这样的 key 也需要耗费很长时间,导致 Redis 主线程阻塞,引发一系列问题。
使用unlink
命令异步删除
1.3 恰当的数据类型
- 例1:比如存储一个User对象,我们有三种存储方式:
- json 字符串:实现简单;但数据耦合
- 字段打散:可以访问任意字段;但占用空间大,没法统一控制
- hash(推荐):底层 ziplist,占用空间小,可以访问任意字段;但代码相对复杂
- 例2:假如有hash类型的key,其中有100万对 field 和 value ,field 是自增 id,这个 key 存在什么问题?如何优化?
存在的问题:
- hash 的 entry 数量超过 500 时,会使用哈希表而不是 ZipList,内存占用较多
- 可以通过 hash-max-ziplist-entries 配置 entry 上限。但是如果 entry 过多就会导致 BigKey 问题
解决方案:
- 拆分为小的 hash,将 id / 100 作为 key, 将 id % 100 作为 field,这样每 100 个元素为一个 Hash
2. 批处理优化
2.1 pipeline
redis 处理指令是很快的,主要花费的时候在于网络传输。于是乎很容易想到将多条指令批量的传输给 redis
方案:
mset
、hmset
批量插入命令- pipeline:
redis-py
可指定是否 transaction
2.2 集群下的批处理
redis-py:
cluster.mset({'k1': 'v1', 'k2': 'v2', 'k3': 'v3'})
不能用,必须同一个 slot 才行。因此要先用cluster.keyslot(<key>)
分组。cluster.pipeline().set('k1', 'v1').set('k2', 'v2').set('k3', 'v3').execute()
可以用。
3. 服务端优化
3.1 持久化配置
- 做缓存的 Redis 实例不要做持久化
- 建议关闭 RDB,使用 AOF,RDB 安全性不好,太频繁了性能消耗大
- 利用脚本定期在 slave 做 RDB,实现数据备份
- 设置合理的 rewrite 阈值,避免频繁的 bgrewrite
- 配置
no-appendfsync-on-rewrite yes
,禁止在 rewrite 期间做 AOF, 避免 AOF 引起阻塞(牺牲数据安全性) - 部署相关:
- 要预留足够内存应对 fork 和 rewrite
- 单个实例内存上限不要太大(可单机多实例),以加快 fork 速度、减少主从同步、数据迁移压力
- 不要和 CPU 密集型应用部署在一起
- 不要和高硬盘负载应用部署在一起
3.2 慢查询优化
- 配置慢查询日志
- slowlog-log-slower-than:慢查询阈值,单位是微秒。默认是10000,建议1000
- slowlog-max-len:慢查询日志(本质是一个队列)的长度。默认是128,建议1000
- 查看慢查询
slowlog len
:查询慢查询日志长度slowlog get [n]
:读取n条慢查询日志slowlog reset
:清空慢查询列表
3.3 命令及安全配置
Redis 未授权访问配合 SSH key 文件利用分析漏洞
为了避免这样的漏洞,这里给出一些建议:
- Redis 一定要设置密码
- 禁止线上使用下面命令:keys、flushall、flushdb、config set 等命令。可以利用 rename-command 配置禁用。
- bind 配置:限制网卡,禁止外网网卡访问
- 开启防火墙
- 不要使用 Root 账户启动 Redis
- 尽量不用默认的端口
3.4 内存配置
当 Redis 内存不足时,可能导致 Key 频繁被删除、响应时间变长、QPS 不稳定等问题。当内存使用率达到 90% 以上时就需要我们警惕,并快速定位到内存占用的原因。
内存占用 | 说明 |
---|---|
数据内存 | 是Redis最主要的部分,存储Redis的键值信息。主要问题是BigKey问题、内存碎片问题 |
进程内存 | Redis主进程本身运⾏肯定需要占⽤内存,如代码、常量池等等;这部分内存⼤约⼏兆,在⼤多数⽣产环境中与Redis数据占⽤的内存相⽐可以忽略。 |
缓冲区内存 | 一般包括客户端缓冲区、AOF缓冲区、复制缓冲区等。客户端缓冲区又包括输入缓冲区和输出缓冲区两种。这部分内存占用波动较大,不当使用BigKey,可能导致内存溢出。 |
查看内存占用命令:
- info memory: 查看内存分配的情况
- memory …:查看 key 的内存占用情况
内存缓冲区常见的有三种:
- 复制缓冲区:主从复制的 repl_backlog_buf,如果太小可能导致频繁的全量复制,影响性能。通过 replbacklog-size 来设置,默认1mb
- AOF 缓冲区:AOF 刷盘之前的缓存区域,AOF 执行 rewrite 的缓冲区。无法设置容量上限
- 客户端缓冲区:分为输入缓冲区和输出缓冲区,输入缓冲区最大 1G 且不能设置。输出缓冲区(
client-output-buffer-limit
配置)可以设置
处理大量的 big value,那么会导致我们的输出结果过多,如果输出缓存区过大,内存占满,会导致 redis 直接断开,而默认配置的情况下, 其实他是没有大小的,这就比较坑了,内存可能一下子被占满,会直接导致咱们的 redis 断开,所以解决方案有两个
- 设置一个大小
- 增加我们带宽的大小,避免我们出现大量数据从而直接超过了redis的承受能力
3.5 集群还是主从
集群虽然具备高可用特性,能实现自动故障恢复,但是如果使用不当,也会存在一些问题:
- 集群完整性问题:
cluster-require-full-coverage
配置是否所有 slot 都能用,集群才提供服务 - 集群带宽问题:集群节点之间会不断的互相 Ping 来确定集群中其它节点的状态,节点太多时,会占用很多带宽
- 数据倾斜问题
- 客户端性能问题
- 命令的集群兼容性问题
- lua 和事务问题:lua 和事务都是要保证原子性问题,如果你的 key 不在一个节点,那么是无法保证lua的执行和事务的特性的,所以在集群模式是没有办法执行 lua 和事务的
单体 Redis(主从 Redis)已经能达到万级别的 QPS,并且也具备很强的高可用特性。如果主从能满足业务需求的情况下,所以如果不是在万不得已的情况下,尽量不搭建 Redis 集群。