Redis原理(一) 内存

    技术2022-07-10  156

    1.Redis数据存储模型

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

    set hello world

    对应的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存储数据时,会选择大小最合适的内存块进行存储。 例如,如果需要存储大小为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

    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。

    Processed: 0.010, SQL: 9