1.Redis数据存储模型

当我们执行redis以下命令时:

set hello world

对应的redis内存存储模型图

redis key是如何生成的 redis key原理_Redis

  • dictEntry:每个键值对都会包装成dictEntry对象,存储了指向Key和Value的指针;next指向下一个dictEntry。
  • Key:Key(”hello”)并不是直接以字符串存储,而是存储在SDS结构中。
  • redisObject:值的存储,包装成了redisObject对象,里面的type是表示redis值的类型(string,set,list等),ptr
    是存储的具体值(也是sds存储)。
sds解释:

sds (Simple Dynamic String,简单动态字符串)是 Redis 底层所使用的字符串表示, 几乎所有的 Redis 模块中都用了 sds。

为什么redis不用简单的char * 而是用sds?
sds存储结构:

typedef char *sds;
struct sdshdr {
    // buf 已占用长度
    int len;
    // buf 剩余可用长度
    int free;
    // 实际保存字符串数据的地方
    char buf[];
};

当redis存储hello world 字符串时,sds是这样的:

struct sdshdr {
    len = 11;
    free = 0;
    buf = "hello world\0";  // buf 的实际长度为 len + 1
};

通过 len 属性, sdshdr 可以实现复杂度为 θ(1) 的长度计算操作。
当我们使用append命令追加字符串 abcdef时:

struct sdshdr {
    len = 18;
    free = 18;
    buf = "hello world abcdef\0                  ";     // 空白的地方为预分配空间,共 18 + 18 + 1 个字节
}

注意, 当调用 SET 命令创建 sdshdr 时, sdshdr 的 free 属性为 0 , Redis 也没有为 buf 创建额外的空间 —— 而在执行 APPEND 之后, Redis 为 buf 创建了多于所需空间一倍的大小。这样以后在append 当小于18时,就不会在额外分配空间。
sds总结:

  • 计算长度len,复杂度是θ(1)。
  • 高效的追加,通过预分配,降低内存分配。

2.jemalloc内存分配器

Redis在编译时便会指定内存分配器;内存分配器可以是 libc 、jemalloc或者tcmalloc,默认是jemalloc。

jemalloc作为Redis的默认内存分配器,在减小内存碎片方面做的相对比较好。jemalloc在64位系统中,将内存空间划分为小、大、巨大三个范围;每个范围内又划分了许多小的内存块单位;当Redis存储数据时,会选择大小最合适的内存块进行存储。

redis key是如何生成的 redis key原理_redis_02


例如,如果需要存储大小为130字节的对象,jemalloc会将其放入160字节的内存单元中。

3.redisObject

redis值的存储对象。以下是存储结构:

typedef struct redisObject {
  unsigned type:4;
  unsigned encoding:4;
  unsigned lru:REDIS_LRU_BITS; /* lru time (relative to server.lruclock) */
  int refcount;
  void *ptr;
} robj;

(1)type
表示对象类型,占4个bit。
目前包括REDIS_STRING(字符串)、REDIS_LIST (列表)、REDIS_HASH(哈希)、REDIS_SET(集合)、REDIS_ZSET(有序集合)。
当我们执行type命令时,就是读取的这个字段。

(2)encoding
表示对象的内部编码,占4个bit比特。
每种redis类型,都支持多种编码。以列表对象为例,有压缩列表双端链表两种编码方式;如果列表中的元素较少,Redis倾向于使用压缩列表进行存储,因为压缩列表占用内存更少,而且比双端链表可以更快载入;当列表对象元素较多时,压缩列表就会转化为更适合存储大量元素的双端链表。

(3)lru
lru记录的是对象最后一次被命令程序访问的时间,占据的比特数不同的版本有所不同(如4.0版本占24比特,2.6版本占22比特)。
通过lru时间与当前时间可以算出对象空转时间(多久没被操作过了);
object idletime key命令可以显示空转时间,并且不改变lru时间。

10.0.47.76:14159> OBJECT idletime  limit_activity.2765980
19
10.0.47.76:14159> OBJECT idletime  limit_activity.2765980
23

(4)refcount
refcount记录的是该对象被引用的次数。当初始化时为1,对有对象也是指向这个对象时,refcount就加1。
redis中被多次使用的对象叫做共享对象。

就目前的实现来说,Redis服务器在初始化时,会创建10000个字符串对象,值分别是0-9999的整数值;当Redis需要使用值为0-9999的字符串对象时,可以直接使用这些共享对象。
共享对象的引用次数可以通过object refcount命令查看

10.0.47.76:14159> OBJECT refcount limit_activity.2765980
1

(5)ptr
指向的具体数据。

4.redis内存管理

10.0.47.76:14159> INFO memory
# Memory
used_memory:117565080
used_memory_human:112.12M
used_memory_rss:139546624
used_memory_peak:128693416
used_memory_peak_human:122.73M
used_memory_lua:36864
mem_fragmentation_ratio:1.19
mem_allocator:jemalloc-3.6.0

redis key是如何生成的 redis key原理_sed_03

  • used_memory: redis分配器分配的内存总量,也就是内部数据占用总量。
  • used_memory_rss: 从操作系统角度看redis进程占用的内存量。
    注意:
    used_memory_rss不包括虚拟内存used_memory包括虚拟内存used_memory>used_memory_rss代表使用了过多的虚拟内存。
  • mem_fragmentation_ratio: used_memory_rss/used_memory的值,可以代表碎片化。Redis 正常碎片率一般在 1.03 左右。
    mem_fragmentation_ratio一般大于1,且该值越大,内存碎片比例越大。mem_fragmentation_ratio<1,说明Redis使用了虚拟内存,由于虚拟内存的媒介是磁盘,比内存速度要慢很多,当这种情况出现时,应该及时排查,如果内存不足应该及时处理,如增加Redis节点、增加Redis服务器的内存、优化应用等。

(1)maxmemory
Redis使用 maxmemory 参数限制最大可用内存,默认值为0,表示无限制。

maxmemory 限制的是 Redis 实际使用的内存量,也就是 used_memory 统计项对应的内存。实际消耗的内存可能会比 maxmemory 设置的大,要小心因为这部内存导致 OOM。所以,如果你有 10GB 的内存,最好将 maxmemory 设置为 8 或者 9G。