redis内存模型
常用数据类型
string 字符串
hash 哈希
list 列表
set 集合
sorted set 有序集合
redis 存储结构
redisobject
?了解Redis 的内存模型,对 Redis 的使用有很大帮助:
- 估算 Redis 内存使用量,选择合理的机器配置
- 优化内存占用,选择最合适的数据结构和编码
- 快速定位问题,当 Redis 出现阻塞、内存占用等问题时
redis 数据存储细节
?
-
dictEntry。Redis 是 Key-Value 数据库,因此对每个键值对都会有一个 dictEntry,里面存储了指向 Key 和 Value 的指针;next 指向下一个 dictEntry,与本 Key-Value 无关。dictEntry是个链表结构。 -
value。无论value是5中类型中的哪一种,都是存储在 RedisObject 中。而 RedisObject 中的 type 字段指明了 value 对象的类型,ptr 字段则指向对象所在的地址。其中字符串对象虽然经过了 RedisObject 的包装,但仍然需要通过 SDS 存储。
RedisObject
typedef struct redisObject {
? ? unsigned type:4;
? ? unsigned encoding:4;
? ? unsigned lru:LRU_BITS; /* LRU time (relative to global lru_clock) or
? ? ? ? ? ? ? ? ? ? ? ? ? ? * LFU data (least significant 8 bits frequency
? ? ? ? ? ? ? ? ? ? ? ? ? ? * and most significant 16 bits access time). */
? ? int refcount;
? ? void *ptr;
} robj;
- type。表示对象的类型,占 4 个比特;目前包括 REDIS_STRING(字符串)、REDIS_LIST (列表)、REDIS_HASH(哈希)、REDIS_SET(集合)、REDIS_ZSET(有序集合)。
- encoding。对象的内部编码,占 4 个比特。对于 Redis 支持的每种类型,都有至少两种内部编码,例如对于字符串,有 int、embstr、raw 三种编码。
Redis 可以根据不同的使用场景来为对象设置不同的编码,大大提高了 Redis 的灵活性和效率。以列表对象为例,有压缩列表和双端链表两种编码方式;如果列表中的元素较少,Redis 倾向于使用压缩列表进行存储,因为压缩列表占用内存更少,而且比双端链表可以更快载入。当列表对象元素较多时,压缩列表就会转化为更适合存储大量元素的双端链表。 - lru。记录的是对象最后一次被命令程序访问的时间。通过对比 lru 时间与当前时间,可以计算某个对象的空转时间。
如果 Redis 打开了 maxmemory 选项,且内存回收算法选择的是 volatile-lru 或 allkeys—lru,那么当 Redis 内存占用超过 maxmemory 指定的值时,Redis 会优先选择空转时间最长的对象进行释放。 - refcount。记录的是该对象被引用的次数,类型为整型。refcount 的作用,主要在于对象的引用计数和内存回收。
当创建新对象时,refcount 初始化为 1;当有新程序使用该对象时,refcount 加 1;当对象不再被一个新程序使用时,refcount 减 1;当 refcount 变为 0 时,对象占用的内存会被释放。Redis 中被多次使用的对象(refcount>1),称为共享对象。这是为了节约内存。目前仅支持整数值的字符串对象。Redis在初始化时,会创建 10000 个字符串对象,值分别是 0~9999 的整数值;当 Redis 需要使用值为 0~9999 的字符串对象时,可以直接使用这些共享对象。 - ptr。指向具体的数据。
string
- redis是使用C语言开发,但C中并没有字符串类型,只能使用指针或字符数组的形式表示一个字符串,所以redis设计了一种简单动态字符串(SDS[Simple Dynamic String])作为底实现:
定义SDS对象,此对象中包含三个属性:
- len buf中已经占有的长度(表示此字符串的实际长度)
- free buf中未使用的缓冲区长度
- buf[] 实际保存字符串数据的地方
- 空间分配原则:当len小于IMB(1024*1024)时增加字符串分配空间大小为原来的2倍,当len大于等于1M时每次分配 额外多分配1M的空间。由此可以得出以下特性:
- redis为字符分配空间的次数是小于等于字符串的长度N,而原C语言中的分配原则必为N。降低了分配次数提高了追加速度,代价就是多占用一些内存空间,且这些空间不会自动释放。
- 高效的计算字符串长度(时间复杂度为O(1))
- 高效的追加字符串操作。
list
- redis对键表的结构支持使得它在键值存储的世界中独树一帜,一个列表结构可以有序地存储多个字符串,拥有例如:lpush lpop rpush rpop等等操作命令。
- 在3.2版本之前,列表是使用ziplist和linkedlist实现的,在这些老版本中,当列表对象同时满足以下两个条件时,列表对象使用ziplist编码:
- 列表对象保存的所有字符串元素的长度都小于64字节
- 列表对象保存的元素数量小于512个
- 当有任一条件 不满足时将会进行一次转码,使用linkedlist。
- 而在3.2版本之后,重新引入了一个quicklist的数据结构,列表的底层都是由quicklist实现的,它结合了ziplist和linkedlist的优点。
- ziplist
?
- 由表头和N个entry节点和zlend(压缩列表尾部标识符)组成的一个连续的内存块。
- 通过一系列的编码规则,提高内存的利用率,主要用于存储整数和比较短的字符串。
- 在插入和删除元素的时候,都需要对内存进行一次扩展或缩减,还要进行部分数据的移动操作,这样会造成更新效率低下的情况。
-
- zlbytes:表示压缩列表占总内存的字节数
- zltail:表示压缩列表头和尾之间的偏移量,反向遍历ziplist或者pop尾部节点的时候有用
- zllen:表示压缩列表中节点的数量
- 每个节点由三部分组成:prevlength、encoding、data
- prevlengh: 记录上一个节点的长度,为了方便反向遍历ziplist
- encoding: 当前节点的编码规则
- data: 当前节点的值,可以是数字或字符串?
- ziplist是一个经过特殊编码的双向链表,它的设计目标就是为了提高存储效率。ziplist是为节省内存空间而生的。
- ziplist是一个为Redis专门提供的底层数据结构之一,本身可以有序也可以无序。当作为list和hash的底层实现时,节点之间没有顺序;当作为zset的底层实现时,节点之间会按照大小顺序排列。
- linkedlist,一个双向链表,和普通的链表定义相同,每个entry包含向前向后的指针,当插入或删除元素的时候,只需要对此元素前后指针操作即可。所以插入和删除效率很高。但查询的效率却是O(n)[n为元素的个数]。
- quicklist,A doubly linked list of ziplists,意思就是一个由ziplist组成的双向链表。实际上,它整体宏观上就是一个链表结构,只不过每个节点都是以压缩列表ziplist的结构保存着数据,而每个ziplist又可以包含多个entry。也可以说一个quicklist节点保存的是一片数据,而不是一个数据。总结:
- 整体上quicklist就是一个双向链表结构,和普通的链表操作一样,插入删除效率很高,但查询的效率却是O(n)。不过,这样的链表访问两端的元素的时间复杂度却是O(1)。所以,对list的操作多数都是poll和push。
- 每个quicklist节点就是一个ziplist,具备压缩列表的特性。
set
- redis的集合和列表都可以存储多个字符串,它们之间的不同在于,列表可以存储多个相同的字符串,而集合则通过使用散列表(hashtable)来保证自已存储的每个字符串都是各不相同的(这些散列表只有键,但没有与键相关联的值)
- 还可能存在另一种集合,那就是intset,它是用于存储整数的有序集合,里面存放同一类型的整数。
- 共有三种整数:int16_t、int32_t、int64_t。查找的时间复杂度为O(logN),但是插入的时候,有可能会涉及到升级(比如:原来是int16_t的集合,当插入int32_t的整数的时候就会为每个元素升级为int32_t)这时候会对内存重新分配,所以此时的时间复杂度就是O(N)级别的了。注意:intset只支持升级不支持降级操作。
- intset在redis.conf中也有一个配置参数set-max-intset-entries默认值为512。表示如果entry的个数小于此值,则可以编码成REDIS_ENCODING_INTSET类型存储,节约内存。否则采用dict的形式存储。
hash
- hash底层的数据结构实现有两种:
- 一种是ziplist,上面已经提到过。当存储的数据超过配置的阀值时就是转用hashtable的结构。这种转换比较消耗性能,所以应该尽量避免这种转换操作。同时满足以下两个条件时才会使用这种结构:
- 当键的个数小于hash-max-ziplist-entries(默认512)
- 当所有值都小于hash-max-ziplist-value(默认64)
- 另一种就是hashtable。这种结构的时间复杂度为O(1),但是会消耗比较多的内存空间。
zset
- 有序集合和散列一样,都用于存储键值对:有序集合的键被称为成员(member),每个成员都是各不相同的。有序集合的值则被称为分值(score),分值必须为浮点数。有序集合是redis里面唯一一个既可以根据成员访问元素(这一点和散列一样),又可以根据分值以及分值的排列顺序访问元素的结构。它的存储方式也有两种:
- 是ziplist结构。与上面的hash中的ziplist类似,member和score顺序存放并按score的顺序排列。
- 另一种是skiplist与dict的结合。
- skiplist是一种跳跃表结构,用于有序集合中快速查找,大多数情况下它的效率与平衡树差不多,但比平衡树实现简单。
- skiplist:
- 一般跳跃表的实现,主要包含以下几个部分:
表头(head) :指向头节点表尾(tail) :指向尾节点节点(node) :实际保存的元素节点,每个节点可以有多层,层数是在创建此节点的时候随机生成的一个数值,而且每一层都是一个指向后面某个节点的指针。层(level) :目前表内节点的最大层数。长度(length) :节点的数量。- 跳跃表的遍历总是从高层开始,然后随着元素值范围的缩小,慢慢降低到低层。
- 前面也说了,有序列表是使用skiplist和dict结合实现的,skiplist用来保障有序性和访问查找性能,dict就用来存储元素信息,并且dict的访问时间复杂度为O(1)。
?
redis的持久化
rdb持久化
- RDB持久化即通过创建快照(压缩的二进制文件)的方式进行持久化,保存某个时间点的全量数据。RDB持久化是Redis默认的持久化方式。RDB持久化的触发包括手动触发与自动触发两种方式。
- save todo...
- bgsave todo...
- cow todo...
AOF持久化
- AOF(Append-Only-File)持久化即记录所有变更数据库状态的指令,以append的形式追加保存到AOF文件中。在服务器下次启动时,就可以通过载入和执行AOF文件中保存的命令,来还原服务器关闭前的数据库状态。
- 重写aop todo...
redis 内存统计
- used_memory:Redis 分配器分配的内存总量(单位是字节),包括使用的虚拟内存(即 swap)
-
used_memory_rss:Redis 进程占据操作系统的内存(单位是字节)。包括进程运行本身需要的内存、内存碎片等,但不包括虚拟内存。 -
mem_fragmentation_ratio:内存碎片比率,该值是 used_memory_rss / used_memory 的比值。mem_fragmentation_ratio 一般大于 1,且该值越大,内存碎片比例越大;mem_fragmentation_ratio<1,说明 Redis 使用了虚拟内存,由于虚拟内存的媒介是磁盘,比内存速度要慢很多。当小于1时,应该及时排查,如果内存不足应该及时处理,如增加 Redis 节点、增加 Redis 服务器的内存、优化应用等。一般来说,mem_fragmentation_ratio 在 1.03 左右是比较健康的状态。 -
mem_allocator:Redis 使用的内存分配器,在编译时指定;可以是 libc 、jemalloc 或者 tcmalloc,默认是 jemalloc。
登录到redis中,可以使用info memory命令查看。
redis 内存分布
-
数据,作为key-value被存储当数据,这部分占用的内存会统计在 used_memory 中。其中value 包括前文所述的5种类型。Redis 在存储对象时,并不是直接将数据扔进内存,而是会对对象进行各种包装:如 RedisObject、SDS 等。 -
进程本身运行需要的内存。这部分内存大约几兆,在生产环境中可忽略。 -
缓冲内存。缓冲内存包括客户端缓冲区、复制积压缓冲区、AOF 缓冲区等;其中,客户端缓冲区存储客户端连接的输入输出缓冲;复制积压缓冲区用于部分复制功能;AOF 缓冲区用于在进行 AOF 重写时,保存最近的写入命令。 -
内存碎片。内存碎片是 Redis 在分配、回收物理内存过程中产生的。如果对数据的更改频繁,而且数据之间的大小相差很大,可能导致 Redis 释放的空间在物理内存中并没有释放。 但 Redis 又无法有效利用,这就形成了内存碎片,内存碎片不会统计在 used_memory 中。
redis 为什么那么快
- 大部分请求是纯粹的内存操作
- 采用单线程,避免了不必要的上下文切换和竞争条件
- 非阻塞IO - IO多路复用
Redis的线程模型
todo...
redis文件事件处理器
- io多路复用器 todo...
-
消息处理流程 todo... -
文件事件的类型 todo... -
文件事件处理器 todo... -
客户端与服务端连接示例 todo...
redis缓冲区
- 输入输出缓冲区 todo...
- cow复制缓冲区 todo...
|