Redis 优化
目录
优雅的 key
删除 Bigkey
恰当的数据类型
批处理优化
Pipeline
集群下的批处理
服务端优化
持久化配置
慢查询
命令以及安全配置
内存安全和配置
内存缓冲区配置
集群最佳实践
集群带宽问题
集群还是主从
优雅的 key
删除 Bigkey
Bigkey 内存占用较多,即便删除这样的 key 也是比较耗时的,这同样也会导致主线程阻塞,引发一系列问题
在 redis 3.0 版本以前,如果是集合类型,都是先遍历 big key 的元素,再逐个删除,最后再删除 key
来到 redis 4.0 及以后官方就提供了异步删除的命令 :unlink
恰当的数据类型
比如存储一个对象类型的数据,我们推荐三种存储方式
-
json 字符串:实现简单粗暴,缺点:数据耦合吗,不够灵活
-
字段打散:可以灵活访问对象中的任意字段,缺点:占用空间大,没办法做到统一控制
-
hash:低层使用 ziplist ,占用空间小,可以灵活的访问对象中的任意字段,缺点:代码相对复杂
这么对比下来,hash 完美胜出,但是这还不是最优解,下面是 hash 可能还存在的问题
hash 的 entry 数量超过 500 时,会使用哈希表而不是 Ziplist ,这样也会导致内存占用过大的问题,但是我们可以通过 hash-max-ziplist-entries 配置 entry 上限,但是 entry 过多的话还是会导致 bigkey 的问题再次出现
那么如何优化呢?
拆分 string 类型,假如有 hash 类型的 key ,其中有 100 万对 field 和 value ,field 是自增 id,这个 key 存在什么问题?如何优化呢?
有一个方案就是将每一个 hash 再次才分成多个小的 hash ,这样外层的一个 key 就可以掌管多个内部的 key ,也就是相当于掌管里面所有的 hash ,我们只需要对内部的 hash 的做优化就可以了,如果内部 hash 过于庞大的话,再考虑对外部的 hash 做优化就可以基本解决这个问题
-
key 的最佳实践
-
固定格式的 key
-
足够精简,不超过 44 个字节
-
不包含特殊字符
-
-
value 的最佳实践
-
合理拆分数据,拒绝 big key
-
选择适合的数据类型
-
hash 结构的 entry 最好不要超过 1000
-
设置合适的超时时间
-
批处理优化
Pipeline
单个命令的执行流程:一次命令的响应时间 = 1 次往返的网络传输耗时 + 1 次 Redis 执行命令耗时
那么 N 次命令的执行流程也是一个逻辑,但是如果是这样的话,网络传输的往返次数就会非常耗时,如果我们将要请求的 N 条数据一次打包好,然后只往返一次,那么就可以大大减少网络传输的耗时,也可以减少网络拥堵,在 Redis 中我们可以使用比如 mset 的命令可以打包一串数据只发送一次请求,但是在这里我们也还是要注意,在打包一次传输的时候,还是不能一次性打包太多数据,不然当带宽占满后还是会造成网络堵塞的情况,反而得不偿失,拿这里的 mset 举例子,利用 mset 批量插入数据时最好不要一次插入 10 万条数据
原生的批处理命令具有局限性,比如:mset 只能处理字符串,hmset 只能处理 hash 而且 key 是不变的,变的仅仅只有 value ,sadd 还是 key 不能变而且只能处理 set 集合,所以这些命令都具备局限性
这个时候我们就可以使用本章要说明的 pipeline ,它和 mset 很像,但是它允许你传入任何命令
注意事项:批处理时不建议一次携带太多数据,其次 pipeline 的多个命令之间不具备原子性
集群下的批处理
如 mset 或者 pipeline 这样的批处理需要在一次请求中携带多条命令,而此时 redis 如果是一个集群,那么批处理的多个 key 必须落在一共插槽中,否则就会导致执行失败
串行命令 | 串行 slot | 并行 slot | hash_tag | |
---|---|---|---|---|
实现思路 | for 循环遍历,依次执行每一条命令 | 在客户端计算每个 key 的 slot ,将 slot 一致分为一组,每组都利用 Pipeline 批处理,串行执行各组命令 | 在客户端计算每个 key 的 slot ,将 slot 一致分为一组,每组都利用 Pipeline 批处理,并行执行各组命令 | 将所有 key 设置相同的 hash_tag,则所有 key 的 slot 一定相同 |
耗时 | N 次网络消耗 + N 次网阔耗时 | m 次网络耗时 + n 次命令耗时,m = key 的 slot 个数 | 1 次网络耗时 + n 次命令耗时 | 1 次网络耗时 + n 次命令耗时 |
优点 | 实现简单 | 耗时较短 | 耗时非常短 | 耗时非常短且实现非常简单 |
缺点 | 耗时非常久 | 实现稍微复杂,slot 越多,耗时越久 | 实现复杂 | 容易出现数据倾斜 |
在这张表上,我们可以看到最后一个 hash_tag 是最好的,但是也存在一定的风险,这相当于把 redis 中的所有的数据都同化了,这就是数据倾斜,所以在实际的开发中我们还是更加推荐并行 slot
服务端优化
持久化配置
Redis 的持久化是用来保护数据的安全性的
-
但是对于一些作为缓存的数据就不需要去做持久化操作了
-
建议关闭 RDB 使用 AOF
-
利用脚本定期为 slave 上做 RDB 数据备份
-
设置合理的 rewrite 阈值,避免频繁的 bgrewrite
-
配置 no-appendfsync-on-rewrite = yes , 禁止在 rewrite 期间做 aof ,避免因 aof 引起的阻塞
部署的相关建议
-
redis 实例的物理机要预留足够的内存空间,应对 fork 和 rewrite
-
单个 redis 实例内存上限不要太大,比如 4G 或者 8G 。可以加快 fork 的速度,减少主从同步,数据迁移的压力
-
不要与 cpu 密集型的服务部署在一起
-
不要与高硬盘负载的服务部署在一起
慢查询
在 redis 中执行耗时达到了某个阈值的命令,称之为慢查询
-
slowlog-log-slower-than 慢查询阈值,单位是微秒。默认是 10000 ,建议是 1000
-
慢查询会被放入慢查询日志当中,日志的长度有上限,可以通过配置指定
-
slowlog-max-len 慢查询日志本质是一个列队默认长度是 128 ,建议 1000
修改这两个配置可以使用 config set 命令,比如 config set slowlog-log-slower-than ,get 是同理,即查看配置
-
slowlog len 查询慢查询日志的长度
-
slowlog get[n] 读取 n 条慢查询日志
-
slowlog reset 清空慢查询列表
命令以及安全配置
Redis 会绑定在 0.0.0.0:6379 ,这样将会使得 redis 服务暴露在公网上,而 redis 如果没有做身份认证,就会出现严重的安全漏洞
漏洞重现方式 Redis未授权访问配合SSH key文件利用分析-腾讯云开发者社区-腾讯云
漏洞出现的几个核心的原因
-
redis 未设置密码
-
利用 redis 的 config set 命令动态修改 redis 配置
-
使用了 root 账号权限启动了 redis
为了避免这种情况的发生,这里给出一些建议:
-
Redis 一定要设置密码
-
禁止线上使用下面命令:key,flushall,flushdb, config set 等命令,可以利用 rename-command 禁用(这条命令悠着点用,典型的弹道偏左)
-
bind 限制网卡,禁止外网网卡访问
-
开启防火墙
-
不要使用 root 账号登录 redis
-
不要使用默认端口
内存安全和配置
当 redis 内存不足时,可能会导致 key 频繁被删除,响应时间变长,QPS 不稳定等问题。当内存使用率达到 90% 以上时我们就需要提高警惕了,并且快速定位到内存占用大的原因
内存占用 | 说明 |
---|---|
数据内存 | 是 redis 最主要的部分,存储 redis 的键值信息,主要问题是 bigkey 问题,内存碎片问题 |
进程内存 | redis 主进程运行本身很定需要占用内存,如代码,常量池等,这部分内存大约几兆,在大多数生产环境中与 redis 数据占用内存相比可以忽略 |
缓冲区内存 | 一般包括客户端缓冲区,AOF 缓冲区,复制缓冲区等,客户端缓冲区又包括输入缓冲区和输出缓冲区。这部分内存占用波动较大,不当使用 bigkey ,可能会导致内存溢出 |
什么是内存碎片:内存碎片是在内存分配的过程中产生的,redis 低层采用的内存分配策略是 LRU ,这种策略它会提前鉴定很多个不同的内存大小,比如说 8 字节,16 字节,32 字节,48 字节 等,不过它是有一套自己的内存分配规则,也不一定是我这里说的 2 的次方,当我们要向 redis 申请一段内存的时候,比如我们这个数据的大小是 10 个字节,如果这个时候这个 10 个字节正好是在 8 - 16 之间的一个阈值,则 redis 就会给这段数据分配 16 个字节的内存,这个时候多出来的内存我们就称之为内存碎片,想要解决内存碎片的问题,我们可以定期的去重启 redis 服务
redis 提供了一些命令,可以查看目前 redis 的内存的一个分配情况分别是 info memory 和 memory xxx
-
memory doctor 内存诊断策略
-
memory purge 清除一些数据
-
memory status 查看内存状态
-
memory usege 查看某个 key 的内存占用情况
内存缓冲区配置
-
复制缓冲区:主从复制的 repl_backlog_buf ,如果太小可能导致频繁的全量复制,影响性能,通过 repl-backlog-size 来设置,默认 1mb
-
AOF 缓冲区:AOF 刷盘之前的缓存区域,AOF 执行 rewrite 的缓冲区。无法设置容量上限
-
客户端缓冲区:分为输入缓冲区和输出缓冲区,输入缓冲区最大 1G 且不能设置(这种概率还是比较小的,只要我们控制好慢查询就可以很好的避免这种情况的发生),输出缓冲区可以设置
默认配置
当前 redis 连接了多少的客户端 info clients , clients list 可以查看当前所有的连接到 redis 的所有的客户端的详细信息
集群最佳实践
在 redis 的默认配置中,如果发现任意一条插槽不可用,则整个集群都会停止服务,为了保证高可用特性,这里建议 cluster-require-full-coverage 设置为 no ,这样做就是会牺牲一定的数据完整性,但是保障了业务的基本运作
集群带宽问题
集群节点之间会不断的 ping 来判断集群之间其他节点的状态,每次 ping 携带的信息至少包括
-
插槽信息
-
集群状态信息
集群中的节点越多,集群的信息就会越庞大,10 个节点的相关信息就可以达到大约 1kb,此时的节点之间的互通带宽的占用率就会提高,为了解决这一问题,我们可以:
-
避免大集群,集群节点数不要太多,最好少于 1000 ,如果业务庞大则建立多个集群
-
避免在单个物理机上运行多个 redis 实例
-
配置合适的 cluster-node-timeout 值
集群还是主从
集群虽然具备高可用,能够实现故障自动修复,但是如果使用不当,一样会出现一些问题
-
集群完整性问题
-
集群带宽问题
-
数据倾斜问题
-
客户端性能问题
-
命令的集群兼容性问题
-
lua 和事务问题
单体 redis 已经能达到万进别的 QPS 了,并且也具备很高的高可用特性,如果主从能够满足业务需求,尽量不要搭建 redis 集群
至此,我总结的 Redis 优化完成,有什么不足还请大佬指点