这篇Redis内存模型也太细了吧

3,934 阅读19分钟

干货有质量,水文有情怀,微信搜索【程序控】,关注这个有趣的灵魂

在这里插入图片描述

前言

     上一篇文章中,我们介绍了Redis的5种基本对象类型(字符串、哈希、列表、集合、有序集合)和一些高级数据结构HyperLogLog、Geo、Bit。丰富的类型是Redis相对于Memcached等的一大优势。在了解Redis的对象类型的用法和特点的基础上,进一步了解Redis的内存模型,对Redis的使用有很大帮助,例如估算Redis内存使用量,优化内存使用,分析Redis的阻塞、内存占用等问题。

     这篇文章主要介绍Redis的内存模型(以4.0为例),包括Redis占用内存的情况及如何查询、不同的对象类型在内存中的编码方式、内存分配器(jemalloc)、简单动态字符串(SDS)、RedisObject等,然后在此基础上介绍几个Redis内存模型的应用。

内存使用情况

      在客户端通过redis-cli连接服务器后(后面如无特殊说明,客户端一律使用redis-cli),我们可以通过info memory命令查看内存使用情况(info命令可以显示redis服务器的许多信息,包括服务器基本信息、CPU、内存、持久化、客户端连接信息等等;memory是参数,表示只显示内存相关的信息):

	127.0.0.1:6379> info memory
	# Memory
	used_memory:673056
	used_memory_human:657.28K
	used_memory_rss:2416640
	used_memory_rss_human:2.30M
	used_memory_peak:692400
	used_memory_peak_human:676.17K
	used_memory_peak_perc:97.21%
	used_memory_overhead:659810
	used_memory_startup:609488
	used_memory_dataset:13246
	used_memory_dataset_perc:20.84%
	total_system_memory:820248576
	total_system_memory_human:782.25M
	used_memory_lua:25600
	used_memory_lua_human:25.00K
	maxmemory:3221225472
	maxmemory_human:3.00G
	maxmemory_policy:noeviction
	mem_fragmentation_ratio:3.59
	mem_allocator:jemalloc-4.0.3
	active_defrag_running:0
	lazyfree_pending_objects:0

重点介绍:

  • used_memory:Redis分配器分配的内存总量(单位是KB),包括虚拟内存(即swap),used_memory_human是让结果显示的更友好;

  • used_memory_rss:Redis进程占据操作系统的内存(单位是KB),和top及ps命令看到的值是一致的,除了分配器分配的内存之外,还包括内存碎片和进程本身的内存等,但是不包括虚拟内存。

    used_memory和used_memory_rss,前者是从Redis角度得到的量,后者是从操作系统角度得到的量。
二者之所以有所不同,一方面是因为内存碎片和Redis进程运行需要占用内存,使得前者可能比后者小,
另一方面虚拟内存的存在,使得前者可能比后者大。
  • mem_fragmentation_ratio : 内存碎片比率,该值是used_memory_rss / used_memory的比值。由于在实际应用中,Redis的数据量会比较大,此时进程运行占用的内存与Redis数据量和内存碎片相比,都会小得多;因此used_memory_rss和used_memory的比例,便成了衡量Redis内存碎片率的参数; 由于在实际应用中,Redis的数据量会比较大,此时进程运行占用的内存与Redis数据量和内存碎片相比,都会小得多;因此used_memory_rss和used_memory的比例,便成了衡量Redis内存碎片率的参数;这个参数就是mem_fragmentation_ratio。        mem_fragmentation_ratio > 1 碎片率过大,导致内存资源浪费,值越大,说明碎片越严重。mem_fragmentation_ratio < 1: 一般出现在操作系统把Redis内存交换到硬盘导致,redis已使用swap分区。由于虚拟内存的媒介是磁盘,比内存速度要慢很多,当这种情况出现时,应该及时排查,如果内存不足应该及时处理,如增加Redis节点、增加Redis服务器的内存、优化应用等。        参数在1.03左右是比较健康的状态(对于jemalloc来说),上面例子中的mem_fragmentation_ratio值很大,是因为还没有向Redis中存入数据,Redis进程本身运行的内存使得used_memory_rss 比used_memory大得多。

  • mem_allocator:Redis使用的内存分配器,在编译时指定,可以是 libc 、jemalloc或者tcmalloc,默认是jemalloc;上面例子中使用的便是默认的jemalloc-4.0.3。

内存划分情况

     Redis作为内存数据库,在内存中存储的内容主要是数据(键值对);除了数据以外,Redis的其他部分也会占用内存。Redis内存主要划分以下几部分(自身数据内存、自身进程内存、缓冲内存、内存碎片):

  • 自身数据内存:作为数据库,数据肯定是最主要的部分,这部分内存也在used_memory中。Redis所有数据都是Key-Value型,每次创建Key-Value都是创建2个对象,即Key对象和Value对象。Key对象都是字符串,使用过程中尽量避免使用过长的Key。Value对象则包括5种类型(String,List,Hash,Set,Zset),每种类型占用内存不同,在Redis内部,每种类型可能有2种或更多的内部编码实现。         Redis在存储数据的时候会对数据进行包装,如redisObject、SDS等,在下面会讲解每种类型的详细内部编码。

  • 自身进程内存:Redis进程自身也会消耗内存(如代码、常量池),不过很少,大约几兆,在大多数生产环境中与Redis数据占用的内存相比可以忽略。这部分内存不是由jemalloc分配,因此不会统计在used_memory中。         当然除了这些,Redis创建的子进程也会运行占用内存,如Redis执行AOF或者RDB重写时创建的子进程,这部分内存不属于Redis进程,也不会统计在used_memory和used_memory_rss中。

  • 缓冲内存:缓冲内存包括客户端缓冲区、复制积压缓冲区和AOF缓冲区,这部分内存由jemalloc分配,因此会统计在used_memory中。        客户端缓冲区指的是所有连接到Redis服务器的连接的输入输出缓冲区,输入缓冲无法控制,最大空间为1G,输出缓冲区可通过client-output-buffer-limit控制。客户端缓冲区又分为普通客户端、从客户端和订阅客户端。        (1)普通客户端(client-output-buffer-limit normal 0 0 0)默认并没有对输出缓冲区做限制,但是如果当有大量的慢连接客户端接入时,这部分消耗就不能忽略了,因为消费的很慢,在成输出缓冲区数据积压,所以可以设置maxclients做限制。         (2)从客户端(client-output-buffer-limit slave 256mb 64mb 60):主节点会为每一个从节点单独建立一个连接用于做命令复制,当主节点网络延迟较高或主节点挂载大量的从节点时,这部分内存消耗将占用很大一部分,建议主节点挂载从节点最好不要超过2个。        (3)订阅客户端(client-output-buffer-limit pubsub 32mb 8mb 60):当生产消息的速度快于消费的速度时,输出缓冲区容易积压消息;        复制积压缓冲区用于部分复制,部分复制我们会在持久化机制文章中详细介绍,根据repl-backlog-size参数控制,默认1MB。 对于复制积压缓区,主节点有一个,所有从节点会共享这个缓冲区,根据实际情况设置相应的大小 可以有效的避免全量复制。        AOF缓冲区用于在进行AOF重写时,保存最近的写入命令,等待被刷到磁盘。命令会先写入到缓冲区,然后根据响应的策略向磁盘进行同步,消耗的内存取决于写入的命令量和重写时间,通常很小。

  • 内存碎片:Redis默认的内存分配器是jemalloc,内存分配器的作用就是为了更好的管理和重复利用内存。内存碎片是Redis在分配、回收物理内存过程中产生的。例如,如果对数据的更改频繁,而且数据之间的大小相差很大,可能导致redis释放的空间在物理内存中并没有释放,但redis又无法有效利用,这就形成了内存碎片。内存碎片不会统计在used_memory中。          内存碎片的产生与对数据进行的操作、数据的特点以及使用的内存分配器等都有关;如果Redis服务器中的内存碎片已经很大,可以通过安全重启的方式减小内存碎片:因为重启之后,Redis重新从备份文件中读取数据,在内存中进行重排,为每个数据重新选择合适的内存单元,减小内存碎片。

内存管理

     Redis通过maxmemory(redis.conf中)参数限制最大可用内存。限制内存目的主要有:用于缓存场景,当超出内存上限maxmemory时候使用LRU等删除策略释放空间,防止所用内存超过服务器物理内存。在生产环境中尽量设置最大内存,因为一旦物理内存用爆了就会大量使用Swap,写RDB文件的时候会很慢。若是启用了Redis快照功能,应该设置最大内存值值为系统可使用内存的45%,因为快照时需要一倍的内存来复制整个数据集,也就是说如果当前已使用45%,在快照期间会变成95%(45%+45%+5%),其中5%是预留给其他的开销。 如果没开启快照功能,maxmemory最高能设置为系统可用内存的95%。我们可以通过命令config set maxmemory NGB来动态调整内存上限;

     Redis中有定期删除和惰性删除两种删除缓存的方式,定期就是每隔一段时间,随机检查一些key然后过期的删除掉,为什么不能是全部删除?因为全部会是灾难,100ms一次,太耗时。定期删除可能会导致一些key未删掉,惰性删除就是当用户用这个key的时候再检查是否过期,如果过期主动删除掉,没过期继续。

    这个时候出现了一个问题,最后如果定期没删,我也没查询,那可咋整?

    我们有内存淘汰机制,当Redis所用内存达到maxmeory上限时,会触发相应的溢出控制策略。即按照配置的Policy进行处理, 默认策略为volatile-lru,对设置了expire time的key进行LRU清除(不是按实际expire time)。

    官网上给到的内存淘汰机制是以下几个:

    noenviction:不清除数据,只是返回错误,这样会导致浪费掉更多的内存,对大多数写命令(DEL 命令和其他的少数命令例外)     allkeys-lru:从所有的数据集(server.db[i].dict)中挑选最近最少使用的数据淘汰,以供新数据使用     volatile-lru:从已设置过期时间的数据集(server.db[i].expires)中挑选最近最少使用的数据淘汰,以供新数据使用     allkeys-random:从所有数据集(server.db[i].dict)中任意选择数据淘汰,以供新数据使用     volatile-random:从已设置过期时间的数据集(server.db[i].expires)中任意选择数据淘汰,以供新数据使用     volatile-ttl:从已设置过期时间的数据集(server.db[i].expires)中挑选将要过期的数据淘汰,以供新数据使用

Redis数据存储细节

     上面介绍了Redis的内存的使用、划分和管理的一些细节问题,接下来要讲的是具体数据类型在存储过程中的细节问题,涉及到内存分配器、多种对象类型以及其使用的内部编码、内部简单动态字符串SDS以及redisObject。

  • dictEntry:Redis中的每个键值对都会对应一个dictEntry,里面存储了Key和Value对应的指针,指针指向相应数据的位置。next指向下一个dictEntry,与本Key-Value无关。
  • Key:图中Key对应的hello并不是直接以字符串存储,而是存储在SDS结构中。
  • redisObject:Value对应的值world既不是直接以字符串存储,也不是存储在SDS中,而是存储在redisObject中。在Redis中的Value的多种类型都是通过redisObject来存储。我们看redisObject中的type字段指向了Value对象的类型身体string,ptr则指向对象的内存地址,该地址仍然是一个SDS结构存储数据。
  • 内存分配器jemalloc:无论是DictEntry对象,还是redisObject、SDS对象,都需要内存分配器(如jemalloc)分配内存进行存储。以dictEntry对象为例,有3个指针组成,在64位机器下占24个字节,jemalloc会为它分配32字节大小的内存单元。

接下来详细介绍各个部分:

redisObject

     redis源码点击github.com/antirez/red…,拉取代码到本地;      redis的多种对象类型都不是直接存储的,都是通过redisObject对象存储的,redisObject对象包含对象类型、内部编码、内存回收、共享对象等多个部分。redisObject结构如下所示:

	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对象类型:type字段表示对象的数据类型,占据4个bit,当我们执行type object指令时可以查看相应的类型;
	127.0.0.1:6379> set key value
	OK
	127.0.0.1:6379> type key
	string
	127.0.0.1:6379> hset std name xiaoming
	(integer) 1
	127.0.0.1:6379> type std
	hash
	127.0.0.1:6379> sadd lst member1 member2
	(integer) 2
	127.0.0.1:6379> type lst
	set
  1. encoding内部编码类型:encoding表示对象的内部编码,占4个bit。redis支持的各种数据类型,每种至少存在两种内部编码,比如string存在int、embstr、raw三种类型编码,list存在ziplist、linkedlist两种类型编码。每种类型存在多种不同编码的好处在于可以根据不同的使用场景自动切换内部不同的编码来提高效率,大大的提高了灵活性,也做到了解耦用户和底层编码优化。我们可以通过object encoding key命令来查看相应的编码。
	127.0.0.1:6379> object encoding key
	"embstr"
	127.0.0.1:6379> set key 100
	OK
	127.0.0.1:6379> object encoding key
	"int"

     每种类型的具体编码会在下面redis对象类型的内部编码部分讲解,耐心读下去。

  1. LRU计时时钟:lru记录的是对象最后一次被命令访问的时间,占据的比特数不同的版本有所不同(如4.0版本占24比特,2.6版本占22比特)。通过计算lru时间和当前时间差值我们可以得到某个对象的空转时间,object idletime key命令可以显示该空转时间(单位是秒)。object idletime命令的一个特殊之处在于它不改变对象的lru值。
	127.0.0.1:6379> set name xiaoming
	OK
	127.0.0.1:6379> object idletime name
	(integer) 18
	127.0.0.1:6379> object idletime name
	(integer) 23
	127.0.0.1:6379> object idletime name
	(integer) 26
	127.0.0.1:6379> get name
	"xiaoming"
	127.0.0.1:6379> object idletime name
	(integer) 1

     lru值还和内存回收有关系,如果redis打开了maxmemory选项,并且内存回收算法是volatile-lru或者allkeys-lru,当内存占用超过maxmemory指定的值得时候,redis会优先选择空转时间最长的对象进行释放。

  1. refcount引用计数器:refcount记录的是对象被引用的次数,refcount主要用于对象的引用计数和内存回收,是不是想起点什么?对了,就是类似jvm的对象存活判断方法之一引用计数法。当创建新对象时,refcount初始化为1,有程序调用时加一,对象不再被调用时减一,refcount变为0时对象占据的内存则会释放。
  •      这里额外讲解一下共享对象,redis中被多次使用的对象成为共享对象(refcount>1)。redis为了节省内存,当有一些重复对象出现的时候新程序不会创建新的对象,而是使用原来的对象,这个被重复使用的对象叫做共享对象。Redis的共享对象目前只支持整数值的字符串对象。之所以如此,实际上是对内存和CPU(时间)的平衡:共享对象虽然会降低内存消耗,但是判断两个对象是否相等却需要消耗额外的时间。对于整数值,判断操作复杂度为O(1);对于普通字符串,判断复杂度为O(n);而对于哈希、列表、集合和有序集合,判断的复杂度为O(n^2)。虽然共享对象只能是整数值的字符串对象,但是5种类型都可能使用共享对象(如哈希、列表等的元素可以使用)。      就目前的实现来说,Redis服务器在初始化时,会创建10000个字符串对象,值分别是0-9999的整数值;当Redis需要使用值为0~9999的字符串对象时,可以直接使用这些共享对象。10000这个数字可以通过调整参数OBJ_SHARED_INTEGERS的值进行改变。共享对象的引用次数可以通过object refcount命令查看。
  1. *ptr数据指针:ptr指针指向具体的数据。

     redisObject的结构与对象类型、内存编码、内存回收、共享对象都有关系,一个redisObject对象的大小为16字节:4bit+4bit+24bit+4Byte+8Byte=16Byte。

SDS

     Redis使用了SDS作为默认的字符串表示,SDS是简单动态字符串(Simple Dynamic String)的缩写。SDS结构如下所示:

	struct sdshdr {
	    int len;   //buf已使用的长度
	    int free;   //buf未使用的长度
	    char buf[];   //buf表示字节数组,用来存储字符串
	};

在这里插入图片描述      通过SDS的结构可以看出,buf数组的长度=free+len+1(其中1表示字符串结尾的空字符);所以,一个SDS结构占据的空间为:free所占长度+len所占长度+ buf数组的长度=4+4+free+len+1=free+len+9。      那么问题来了,既然redis使用C语言编写的,为什么不直接用C的字符串,换种问法就是SDS的结构优点?

  • 存取二进制:SDS可以存储二进制数据,C字符串不可以。因为C中的字符串是以空字符串结尾,而对于二进制数据内容可能会包含空字符串,因此C字符串无法正确读取。而SDS以字符串长度len来作为字符串结束标识,因此没有这个问题。
  • 内存重分配:使用C字符串时,若要修改字符串需要重新分配内存(先释放再申请),如果没有重新分配,字符串增大时容易造成内存溢出,字符串减小容易造成内存泄漏。而SDS由于可以记录len和free,因此解除了字符串长度和底层数组长度的耦合,可以进行优化。空间预分配策略(即分配内存时比实际需要的多)使得字符串长度增大时重新分配内存的概率大大减小;惰性空间释放策略使得字符串长度减小时重新分配内存的概率大大减小。
  • 缓冲区溢出:由于上一个的内存重分配问题,造成了这里的缓冲区溢出问题。使用C字符串时,如果字符串长度增加时忘记重新分配内存,很容易造成内存溢出的问题。而SDS由于记录了长度,相应的API在可能造成缓冲区溢出时会自动重新分配内存,杜绝了缓冲区溢出。

内存分配器jemalloc

     redis在编译的时候可以指定内存分配器:libc、jemalloc、tcmalloc,默认是jemalloc。jemalloc作为Redis的默认内存分配器,在减小内存碎片方面做的相对比较好。jemalloc在64位系统中,将内存空间划分为小、大、巨大三个范围;每个范围内又划分了许多小的内存块单位;当Redis存储数据时,会选择大小最合适的内存块进行存储。jemalloc的内存划分单元如下: 在这里插入图片描述      例如,如果需要存储大小为200字节的对象,jemalloc会将其放入224字节的内存单元中,若是656字节对象,则会放在768字节的内存单元。

Redis对象类型的内部编码

     redis支持的5种数据结构类型,每种都至少支持两种内部编码,这样做的优势是,接口和底层编码实现的解耦合,当需要根据不同场景切换内部编码的时候,用户不受影响。      关于Redis内部编码的转换,都符合以下规律:编码转换在Redis写入数据时完成,且转换过程不可逆,只能从小内存编码向大内存编码转换。

字符串

     字符串是最基础的类型,因为所有的键都是字符串类型,且字符串之外的其他几种复杂类型的元素也是字符串。字符串长度不能超过512MB。

内部编码:

  • int:8个字节的长整型。字符串值是整型时,这个值在C中使用long整型表示。
  • embstr:<=44字节的字符串。(可能会因版本不同而不同)
  • raw:大于44个字节的字符串

     embstr和str都是用RedisObject和SDS保存数据,embstr使用的时候只分配一次内存空间(而redisObject和SDS是连续的),而raw需要分配两次内存空间(分别为RedisObject和SDS分配空间)。      与raw相比,embstr的好处在于创建时少分配一次空间,删除时少释放一次空间。对于embstr来说,如果字符串的长度增加需要重新分配内存时,整个redisObject和sds都需要重新分配空间。

     编码转换测试:

	127.0.0.1:6379> set key1 66
	OK
	127.0.0.1:6379> object encoding key1
	"int"
	127.0.0.1:6379> set key3 qwertyuiolkjhgfdsazxcvbnmlkjhgfdsa
	OK
	127.0.0.1:6379> strlen key3
	(integer) 34
	127.0.0.1:6379> object encoding key3
	"embstr"
	127.0.0.1:6379> set key3 qwertyuioplkjhgfdsazxcvbnm,lkjhgfdsaqwertyuiolkjhgfdsazxcvbnmlkjhgfdsa
	OK
	127.0.0.1:6379> strlen key3
	(integer) 70
	127.0.0.1:6379> object encoding key3
	"raw"

列表

     列表(list)用来存储多个有序的字符串,每个字符串称为元素,一个列表可以存储2^32-1个元素。Redis中的列表支持两端插入和弹出,并可以获得指定位置(或范围)的元素,可以充当数组、队列、栈等。

     3.0的redis列表的内部编码可以是压缩列表(ziplist)或双端链表(linkedlist),而4.0的列表则只有quicklist快链表和双端链表linkedlist这两种结构了。      双端链表:由一个list结构和多个listNode结构组成; 在这里插入图片描述 双端链表保存了头节点head和尾节点tail,并且每一个节点都有指向前和后的指针,还有链表长度len,dup、free和match为节点值设置类型特定函数,所以链表可以用于保存各种不同类型的值。而链表中每个节点指向的是type为字符串的redisObject。

     编码转换测试:

	127.0.0.1:6379> rpush list2 va1
	(integer) 1
	127.0.0.1:6379> object encoding list2
	"quicklist"	

哈希

     哈希也是常用的数据结构之一,是一种key-value形式的数据结构(注意区分哈希数据结构和key-value型数据库redis)。数据结构哈希使用的是内部编码可以是压缩列表(ziplist)和哈希表(hashtable)两种。压缩列表ziplist在上面介绍过,与哈希表相比,压缩列表用于元素个数少、元素长度小的场景;其优势在于集中存储,节省空间。      哈希表hashtable:一个hashtable由1个dict结构、2个dictht结构、1个dictEntry指针数组(称为bucket)和多个dictEntry结构组成。哈希表如下所示,不详解。 在这里插入图片描述      编码转换测试:

     只有同时满足下面两个条件时,才会使用压缩列表:哈希中元素数量小于512个;哈希中所有键值对的键和值字符串长度都小于64字节。如果有一个条件不满足,则使用哈希表;且编码只可能由压缩列表转化为哈希表,反方向则不可能。

	127.0.0.1:6379> hset std name xiaoming
	(integer) 1
	127.0.0.1:6379> object encoding std
	"ziplist"
	127.0.0.1:6379> hset std year 111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111
	(integer) 1
	127.0.0.1:6379> object encoding std
	"hashtable"

集合

     集合和列表类似,但是集合不能存在重复元素,并且是无序的。一个集合中最多可以存储2^32-1个元素;除了支持常规的增删改查,Redis还支持多个集合取交集、并集、差集。      集合的内部编码可以是整数集合(intset)或哈希表(hashtable)。哈希表和前面哈希一样,暂不解释,需要注意的是,集合在使用哈希表时,值全部被置为null。intset结构如下所示:

	typedef struct intset{
	    uint32_t encoding;
	    uint32_t length;
	    int8_t contents[];
	} intset;

     其中,encoding代表contents中存储内容的类型,虽然contents(存储集合中的元素)是int8_t类型,但实际上其存储的值是int16_t、int32_t或int64_t,具体的类型便是由encoding决定的;length表示元素个数。      整数集合适用于集合所有元素都是整数且集合元素数量较小的时候,与哈希表相比,整数集合的优势在于集中存储,节省空间;同时,虽然对于元素的操作复杂度也由O(1)变为了O(n),但由于集合数量较少,因此操作的时间并没有明显劣势。

编码转换测试:

	127.0.0.1:6379> object encoding std
	"hashtable"
	127.0.0.1:6379> sadd set1 name year sex
	(integer) 3
	127.0.0.1:6379> object encoding set1
	"hashtable"
	127.0.0.1:6379> sadd set2 12 13 14
	(integer) 3
	127.0.0.1:6379> object encoding set2
	"intset"
	127.0.0.1:6379> sadd set3 13 15 year
	(integer) 3
	127.0.0.1:6379> object encoding set3
	"hashtable"

有序集合

     有序集合和集合一样是不可重复的元素,但是有序集合中的元素是有顺序的。与列表使用索引下标作为排序依据不同,有序集合为每个元素设置一个分数(score)作为排序依据。 有序集合的内部编码可以是压缩列表(ziplist)或跳跃表(skiplist)。跳跃表是一种有序的数据结构,一种特殊的有序链表,它通过在每个节点中维持多个指向其他节点的指针,从而达到快速访问节点的目的。跳跃表结构如下图所示: 在这里插入图片描述

  • 跳跃表是由多层有序链表组合而成的,最底一层的链表保存了所有的数据,每向上的一层链表依次保存了下一层链表的部分数据。
  • 相邻的两层链表中元素相同的节点之间存在引用关系,一般是上层节点中存在一个指向下层节点的引用。
  • 跳跃表的目的在于提高了查询效率,同时也牺牲了一定的存储空间。

     编码转换测试:

     只有同时满足下面两个条件时,才会使用压缩列表:有序集合中元素数量小于128个;有序集合中所有成员长度都不足64字节。如果有一个条件不满足,则使用跳跃表;且编码只可能由压缩列表转化为跳跃表,反方向则不可能。

	127.0.0.1:6379> zadd zset1 1 zheng 2 zhao 3 zhang 
	(integer) 3
	127.0.0.1:6379> object encoding zset1
	"ziplist"
	127.0.0.1:6379> zadd zset1 4 zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz
	(integer) 1
	127.0.0.1:6379> object encoding zset1
	"skiplist"

     5种类型的内部编码简单总结:

数据结构内部编码
stringint、embstr、raw
listquicklist、linkedlist
hashziplist、hashtable
setintset、hashtable
zsetziplist、skiplist

作者寄语

     这篇文章主要介绍Redis的内存模型(以4.0为例),包括Redis占用内存的情况及如何查询、不同的对象类型在内存中的编码方式、内存分配器(jemalloc)、简单动态字符串(SDS)、RedisObject等。

     你知道的越多,你不知道的也越多。keep hungry keep foolish!

在这里插入图片描述