如何合理的使用Redis内存

Posted 实验室清洁工

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了如何合理的使用Redis内存相关的知识,希望对你有一定的参考价值。

简述

众所周知,Redis 的性能之所以高,原因之一就是它的数据都存储在内存中,硬件服务器的内存资源相比于磁盘来说,还是比较昂贵,在成本与性能中我们该如何去权衡,那在使用 Redis 时,如何做到保证良好的性能并更节省内存呢?

内存模型

模型简介

Redis的数据存储,主要涉及到内存分配器(默认jemalloc),简单动态字符串(SDS),5种数据对象类型(字符串、哈希,列表,集合,有序集合),内部编码,redisobject,下面以set hello word操作,写入一条数据为例,做个简单的分析:

如何合理的使用Redis内存_redis

  • dictEntry:每个键值对都会有一个dictEntry结构体,里面存储了指向Key和Value的指针;next指向下一个dictEntry,与本Key-Value无关。
  • Key:Key ”hello” 并不是直接以字符串存储,而是存储在SDS结构中。
  • redisObject:Value 值“world”不是直接以字符串存储,是存储在redisObject中。我们常说的物种数据类型也都是都是通过redisObject来存储的;而redisObject中的type字段指明了Value对象的类型,ptr字段则指向对象所在的地址。不过可以看出,字符串对象虽然经过了redisObject的包装,但仍然需要通过SDS存储。
  • SDS:Redis没有直接使用C字符串(即以空字符’\\0’结尾的字符数组)作为默认的字符串表示,而是使用了SDS。SDS是简单动态字符串(Simple Dynamic String)的缩写。
  • Jemalloc:无论是DictEntry对象,还是redisObject、SDS对象,都需要内存分配器(如jemalloc)分配内存进行存储。以DictEntry对象为例,有3个指针组成,在64位机器下占24个字节,jemalloc会为它分配32字节大小的内存单元。

数据占用

jemalloc内存分配情况如下,在分配内存块时会分配大于实际值的2次n的值

数据类型

占用量

dictEntry

24字节,jemalloc会分配32字节的内存块

redisObject

16字节

key

key的长度+9,jemalloc分配>=该值的2n的值

value

value的长度+9,jemalloc分配>=该值的2n的值

key的个数

所有key的个数

bucket个数

大于key的个数的2n次方,例如key个数是2000,那么bucket=2048

指针大小

8byte

String

一个set命令会产生一下几个内存消耗的结构:

  • 1个dictEntry结构,24字节,负责保存当前的哈希对象;
  • 1个SDS结构,(key长度 + 9)字节,用作key字符串;
  • 1个SDS结构,(val长度 + 9)字节,用作key的values;
  • 1个redisObject结构,16字节,指向当前key下属的dict结构;
Hash

Hash结构在使用HTable数据类型时,value并不是指向一个SDS,是有一个Dict结构。Dict结构保存了Hash对象的键值对。一个hmset命令最终会产生以下几个消耗内存的结构:

如何合理的使用Redis内存_内存_02

  • 1个dictEntry结构,24字节,负责保存当前的哈希对象;
  • 1个SDS结构,(key长度 + 9)字节,用作key字符串;
  • 1个redisObject结构,16字节,指向当前key下属的dict结构;
  • 1个dict结构,88字节,负责保存哈希对象的键值对;
  • n个dictEntry结构,24×n字节,负责保存具体的field和value,n等于field个数;
  • n个redisObject结构,16×n字节,用作field对象;
  • n个redisObject结构,16×n字节,用作value对象;
  • n个SDS结构,(field长度 + 9)× n字节,用作field字符串;
  • n个SDS结构,(value长度 + 9)× n字节,用作value字符串;
SortedSet

一个zadd命令最终会产生以下几个消耗内存的结构:

如何合理的使用Redis内存_redis_03

  • 1个dictEntry结构,24字节,负责保存当前的有序集合对象;
  • 1个SDS结构,(key长度 + 9)字节,用作key字符串;
  • 1个redisObject结构,16字节,指向当前key下属的zset结构;
  • 1个zset结构,16字节,负责保存下属的dict和zskiplist结构;
  • 1个dict结构,88字节,负责保存集合元素中成员到分值的映射;
  • n个dictEntry结构,24×n字节,负责保存具体的成员和分值,n等于集合成员个数;
  • 1个zskiplist结构,32字节,负责保存跳跃表的相关信息;
  • 1个32层的zskiplistNode结构,24+16×32=536字节,用作跳跃表头结点;
  • n个zskiplistNode结构,(24+16×m)×n字节,用作跳跃表节点,m等于节点层数;
  • n个redisObject结构,16×n字节,用作集合中的成员对象;
  • n个SDS结构,(value长度 + 9)×n字节,用作成员字符串;
List

一个rpush或者lpush命令最终会产生以下几个消耗内存的结构:

如何合理的使用Redis内存_合理_04

  • 1个dictEntry结构,24字节,负责保存当前的列表对象;
  • 1个SDS结构,(key长度 + 9)字节,用作key字符串;
  • 1个redisObject结构,16字节,指向当前key下属的list结构;
  • 1个list结构,48字节,负责管理链表节点;
  • n个listNode结构,24×n字节,n等于value个数;
  • n个redisObject结构,16×n字节,用作链表中的值对象;
  • n个SDS结构,(value长度 + 9)×n字节,用作值对象指向的字符串;

优化建议

合理的控制 key 的长度

在研发业务时,需要保证 key 在简单、清晰的前提下,尽可能把 key 定义得简短一些。随着key的数量级越大,Redis 就可以节省的内存越多

利用jemalloc特性进行优化

jemalloc分配内存时数值是不连续的,因此key/value字符串变化一个字节,可能会引起占用内存很大的变动;在设计时可以利用这一点。如果key的长度如果是8个字节,则SDS为17字节,jemalloc分配32字节;此时将key长度缩减为7个字节,则SDS为16字节,jemalloc分配16字节;则每个key所占用的空间都可以缩小一半。

选择合适的数据类型

String、Set 在存储 int 数据时,会采用整数编码存储。Hash、ZSet 在元素数量比较少时(可配置),会采用压缩列表(ziplist)存储,在存储比较多的数据时,才会转换为哈希表和跳表。可以节省更多空间。因此在可以使用长整型/整型代替字符串的场景下,尽量使用长整型/整型。

避免存储 bigkey

bigkey不仅会占用过多的内存,还会客户端在读书数据时,容易导致阻塞,造成性能瓶颈,对于bigkey,建议进行拆分,建议如下:

  • String:大小控制在 10KB 以下
  • List/Hash/Set/ZSet:元素数量控制在 1 万以下

把 Redis 当作缓存使用

你在使用 Redis 时,

  • 尽可能要把它当做缓存来使用,而不是持久化的数据,不然也会存在数据风险,在写入数据时,尽可能的设置过期时间,业务应用在redis查不多数据时,应从后端数据库查询并加载到redis中。
  • 尽可能的存放一些热点数据,对于不常用的数据,可以查询其他路径获取,如从对象型数据库中获取。

设置最大内存和合理的淘汰策略

Redis key 设置合理的过期时间,如果你的业务应用写入量很大,有了合理的过期时间,会将对应的数据过期掉,也可以抑制内存的快读增长

数据压缩后写入 Redis

开发在研发应用时,可以先将数据进行压缩,压缩后再写入redis,当然这会增加应用开发的复杂度和消耗过多的客户端cpu资源,需要结合业务特性进行权限 务应用中先将数据压缩,再写入到 Redis 中(例如采用 snappy、gzip 等压缩算法)。

监控内存碎片率

内存碎片率是一个重要的参数mem_fragmentation_ratio=used_memory_rss/used_memory

  • 如果内存碎片率过高(jemalloc 在 1.03 左右比较正常),说明内存碎片多,内存浪费严重。并可考虑堆Redis数据库进行切换,重启,在内存中对数据进行重排,减少内存碎片。
  • 如果内存碎片率小于 1,说明 Redis 内存不足,部分数据使用了虚拟内存(即 swap)。由于虚拟内存的存取速度比物理内存差很多(2-3 个数量级),此时 Redis 的访问速度可能会变得很慢。

以上是关于如何合理的使用Redis内存的主要内容,如果未能解决你的问题,请参考以下文章

Redis 使用总结

如何找到分配给redis实例的内存?

合理配置SQLSERVER内存

为什么我的Redis这么“慢”?

如何将Redis内存使用量降低一半?

redis一些问题