Redis 内存不足怎么办🤔️

667 阅读4分钟

redis 相关优化

redis 占用的内存空间划分

  1. redis 进程自身占用的内存
  2. 存储用户数据的对象内存
  3. 各种的buffer 缓冲内存
  4. 内存碎片
  • redis 默认无限使用服务器内存,如果不设置 maxmemory 的话,会一直膨胀,直到内存耗尽。
  • 如果设置了 maxmemory的话,在到达使用上限时,会触发溢出控制策略maxmemory-policy,默认是 noeviction拒绝写入,并返回客户端OOM,但是不影响正常的读取。
  • 当然还可以设置其他的 maxmemory-policy,按照规则删除一些键,腾出足够的空间。
  • 当redis可用空间有限,而且业务逻辑允许键被删除的情况下,通过设置 非noeviction的溢出控制策略,肯定是最简单的做法。
  • 下面讨论的都是在 noeviction 情况下的一些优化策略 在这其中 对象内存 肯定是占用最多的,也是最应该优化的。其中 进程自身占用的内存 是优化空间最小的,不做讨论。

对象内存优化

  1. 控制键值对象的长度。关于key 在保证描述性的情况下,尽量往简单里写,能写成 uid 的就不要写 userId。关于value 如果我们存储的是json字符串,可以把字符串压缩一下再存储;如果是需要把对象序列化成二进制数组存储的,可以选择使用效率更高的序列化方式。 当然这种是对代码有侵入性的,而且在压缩、解压json 的过程中,也会对应一定的cpu消耗。
  2. 共享对象池。redis是支持共享对象池的,但是仅限于[0,9999] 之间的数字,所以value能用数字的尽量使用数字。为什么仅限[0,9999] 之间的是共享内存池的呢,首先是整数类型更容易比较,如果是字符串类型的话,每次比较消耗的时间复杂度是 O(n),如果引入字符串的共享对象池,意味着更复杂的处理逻辑和性能消耗,对于单线程模型的redis来说,可以但没必要。
  3. 字符串优化。redis 的字符串是使用SDS实现的,len、buf、free 三个字段,其中free是预分配的长度,可以在append操作时使用。当一次设置字符串的时候,free 默认是0,既没有预分配空间。如果频繁的 append、setrange 的话,会导致不断的扩容、收缩,可能会导致内存浪费和碎片化。
  4. 编码格式优化。其中ziplist是特殊编码的连续内存块,在 list,zset,hash 的使用时都可以使用ziplist 的编码方式,这一点通过修改redis 配置可以轻松实现。这里要注意的是,ziplist相关属性的设置不能无限膨胀,ziplist带来的内存占用小的优点,是通过增加cpu消耗,服务器响应耗时变长为代价的。
  5. 选择合适的数据结构,减少键的数量。redis提供了多种数据结构 list、set、zset、hash、bitmap,合理利用各种数据结构。拿hash来说,hash可以使用 ziplist优化,占用空间肯定比使用多个string更小,set还是可以使用 intset。还是可以考虑使用bitmap,bitmap也是通过SDS实现的。
  6. 设置键的过期时间。

buffer 缓存内存

  1. 客户端缓冲区。这里是指客户端通过tcp连接到redis服务器的输入、输出缓冲。
  2. 复制积压缓冲区。这里是用于部分复制的实现。
  3. aof缓存区。这里是用于实现aof重写的实现。

内存碎片

内存碎片无法避免,但是可以通过一些操作尽量降低碎片率。

  1. 尽量减少频率更新字符串SDS。
  2. 大量的键过期删除,释放的内存空间无法得到合理的利用也会导致高碎片率。
  3. 如果条件允许的话,可以重启一下节点,节点的重启可以做到内存碎片的重新整理。

子进程(bgSave)消耗

当redis 执行bgSave的时候,是通过fork一个子进程实现的,其中牵扯到 copy-on-write 。父子进程共享同一块内存,只在有数据有变动的时候才会对要修改的页复制出一个新的副本。 考虑如果在执行bgSave 的过程中,发生了大量的写请求,这时候就会有大量的复制操作。这个可以通过修改 overcommit_memoryTHP 的配置来优化。

其他

当我们明确redis 的空间有限制时,就可以减少对redis 的使用,结合场景使用 cdn,Guava Cache等。