Redis内存优化

 阅读约 8 分钟

一、键值设计

1、key名设计

三大建议

  • 可读性和可管理性

以业务名(或数据库名)为前缀(防止key冲突),用冒号分隔,比如业务名:表名:id

  • 简洁性

保证语义的前提下,控制key的长度,当key较多时,内存占用也不容忽视 (embstr 3-39)

  • 不包含特殊字符

反例:包含换行 等

2、value设计
  • 拒绝bigkey
  • 选择合适的数据结构
  • 过期设计

string类型控制在10kb之内
hash、list、set、zset元素个数不超过5000
反例 一个包含几百万个元素的list、hash等,一个巨大的json字符串
bigkey的危害

  • 网络阻塞
  • 慢查询
  • 节点数据不均衡
  • 反序列消耗
Redis客户端本身不负责序列化
应用频繁序列化和反序列化bigkey:本地缓存或者Redis缓存

bigkey发现

  • 应用异常
  • redis-cli --bigkeys
  • scan + debug object
  • 主动报警
  • 内核热点key问题优化
3、bigkey问题

bigkey的删除

  • 阻塞:注意隐性删除(rename、过期等)
  • lazy delete(unlink命令)

bigkey的预防

  • 优化数据结构:例如二级拆分
  • 物理隔离或者万兆网卡:不是治标方案
  • 命令优化:例如hgetall->hget、hscan
  • 报警和定期优化

键值生命周期

  • 周期数据需要设置过期时间,object idle time可以找垃圾key-value
  • 过期时间不宜集中:缓存穿透和雪崩问题

命令优化

  • O(N)以上的命令关注N的数量
  • 禁用命令 keys flush flushdb等
  • 合理使用select redis的多数据库较弱,使用数字进行区分 很多客户端支持较差
  • Redis事物功能较弱,不建议过多使用
  • Redis集群版本在使用Lua上有特殊要求
  • 必要情况下使用monitor命令时,要注意不要长时间使用

客户端优化
避免多个应用使用一个Redis实例
不相干的业务拆分,公共数据做服务化
使用连接池

最大连接池的设置
maxIdle接近maxTotal即可
业务希望Redis达到的并发量
客户端执行命令时间
Redis资源
资源开销
例子:一次命令时间的平均耗时为1ms,一个连接的QPS大约是1000
业务期望的QPS是50000
理论的maxTotal = 50000/1000 = 50个

二、内存优化

1、内存消耗
  • 内存使用统计
  • 内存消耗划分
  • 子进程内存消耗

内存划分:自身内存(800k左右),对象内存,缓存内存,Lua内存,内存碎片
内存消耗:自身内存,缓冲内存(客户端缓冲器,复制缓冲区,AOF缓冲区),对象内存(key,value)
客户端缓冲区

  • 普通客户端

输入缓冲区,最大1GB,超过后会被强制断开,不可动态设置
输出缓冲区配置 client-output-buffer-limit <class> <hard limiy> <soft limit> <soft seconds>
class客户端类型,分为三种 普通客户端 slave从节点客户端 pubsub发布订阅客户端
hard limit 如果客户端使用的输出缓冲区大于 hard limit 客户端会被立即关闭
soft limit和soft seconds 如果客户端使用的输出缓冲区超过了soft limit并且持续了soft seconds客户端会被立即关闭

默认 client-output-buffer-limit normal 0 0 0
默认 没有限制客户端缓冲
注意 防止大的命令或者monitor
  • slave客户端
默认 client-output-buffer-limit slave 256mb 64mb 60
阻塞 主从延迟较高,或者从节点过多
注意 主从网络,从节点不要超过2个
  • pubsub客户端
默认 client-output-buffer-limit pubsub 32mb 8mb 60
阻塞 生产大于消费
注意 根据实际场景适当调试

缓冲内存
复制缓冲区:此部分内存独享,考虑部分复制,默认是1MB,可以设置更大
AOF缓冲区:AOF重写器键,AOF的缓冲区,没有容量限制

对象内存
key:不要过长,量大不容忽视(redis3:embstr 39字节)
value:ziplist,intset等优化方式

内存碎片

必然存在 jemalloc
优化方式:
避免频繁更新操作: append setrange等
安全重启 例如redis sentinel和redis-cluster等

子进程内存消耗

必然存在 fork(bgsave和bgrewriteaof)
优化方式:
去掉THP特性
观察写入量

2、内存管理
  • 设置内存上限

定义实例最大内存,便于管理机器内存,一般预留30%

  • 动态调整内存上限

config set maxmemory 6GB
config rewrite

  • 内存回收策略

删除过期键值

惰性删除:访问key->expired dict -> del key
定时删除:每秒运行10次,采样删除

内存溢出
超过maxmemory后触发响应策略,由maxmemory-policy控制

3、内存优化

内存分布
自身内存,缓冲内存,对象内存
合理选择数据结构
独立统计:集合,MitMaps,HyperLogLog
客户端内存优化
客户端缓冲区
输入缓冲区:最大1G
一次内存暴增 可能原因:

  • 批量写入
  • 主从不一致
  • 客户端溢出

处理和预防:
找到对应的业务方直接干掉
运维层面:线上Redis禁用monitor,适度限制缓冲区大小
开发层面:理解monitor的原理,也可以短暂寻找热点key,使用CacheCloud可以直接监控到

4、Redis运维

Linux内核优化
overcommit含义:
0 表示内核将检查是否由足够的可用内存,如果有足够的可用内存,内存申请通过,否则内存申请不通过,并把错误返回给应用进程
1 表示内核运行超量使用内存知道用完为止
2 表示内核坚决不过两的使用内存,即系统整个内存地址空间不超过swap+50%的RAM值,50%是overmit_ratio默认值,此参数同样支持修改
获取
cat /proc/sys/vm/overcommit_memory
设置
echo "vm.overcommit_memory=1" >> /etc/sysctl.conf
sysctl vm.overcommit_memory=1
最佳实践:

  • Redis设置合理的maxmory,保证机器有20%-30%的闲置内存
  • 集中化管理AOF重写的RDB的bgsave

swappiness最佳实践

  • vm.swappiness=1

物理内存充足的时候,使Redis足够快
物理内存不足的时候,避免Redis死掉

  • THP

作用:加速fork
建议:禁用,可能产生更大的内存消耗
设置方法:echo never > /sys/kernel/mm/transparent_hugepage/enabled

  • OOM killer

作用:内存使用超出,操作系统按照规则kill掉某些进程
配置方法:/proc/{progress_id}/oom_adj越小,被杀掉的概率越小
运维经验:不要过度依赖此特性,应该合理管理内存

安全的Redis

被攻击Redis的特征:

  • redis所在的机器有外网IP
  • Redis以默认端口6379为启动端口,并且是对外网开放的
  • Redis是以root用户启动的
  • Redis没有设置密码
  • Redis的bind设置为0.0.0.0或者""

攻击方式:

  • flushall
  • set crakit id_rsa.pub
  • config set dir
  • config set dbfilename
  • save

安全七法则

  • 设置密码:

服务端配置:requirepass和masterauth
客户端连接:auth命令和-a参数
相关建议
密码要足够复杂,防止暴力破解
masterauth不要忘记
auth还是通过铭文传输

  • 伪装危险命令

服务端配置:rename-command为空或者随机字符串
客户端连接:不可用或者使用指定随机字符串
相关建议
不支持config set 动态设置
RDB和AOF如果包含了rename-command之前的命令,将无法使用
config命令本身是再Redis内核会使用到,不建议使用

  • bind

服务端配置:bind限制的是网卡,并不是IP
相关建议
bind不支持 config set
bind 127.0.0.1需要谨慎

  • 防火墙:杀手锏
  • 定期备份
  • 不使用默认端口,防止被若攻击杀掉
  • 使用非root用户启动

热点key

  • 客户端
  • 代理端
  • 服务端
  • 机器搜集
阅读 151发布于 11月1日
推荐阅读
目录