Redis设计与实现第八章笔记
date
Jul 24, 2021
slug
Redis设计与实现第八章笔记
status
Published
tags
读书笔记
summary
对象
type
Post
8.1 对象的类型与编码
Redis中每个对象都由一个redisObject 结构表示:
8.1.1 类型
type属性记录了对象的类型,这个属性的值可以是下表中的一个
当对一个数据库键执行TYPE命令时,命令返回的结果为数据库键对应的值对象的类型
8.1.2 编码和底层实现
ptr指针指向对象的底层实现数据结构,这些数据结构由对象的encoding属性决定
每种对象可以使用的编码:
使用OBJECT ENCODING命令可以查看一个数据库键的值对象的编码
8.2 字符串对象
字符串对象的编码可以是int、raw或者embstr
- 如果一个字符串对象保存的是整数值,并且这个整数值可以用long类型来表示,那么字符串对象会将整数值保存在字符串对象结构的ptr属性里面(将void*转换成long),并将字符串对象的编码设置为int
- 如果字符串对象保存的是一个字符串值,并且这个字符串值的长度小于等于32字节,那么字符串对象将使用embstr编码来保存
- 如果字符串对象保存的是一个字符串值,并且这个字符串值的长度小于等于32字节,那么字符串对象将使用简单动态字符串(SDS)来保存,并将编码设置为raw‘
embstr编码是专门用于保存短字符串的一种优化编码方式,好处是:
- embstr编码将创建字符串对象所需的内存分配次数从raw的两次变为一次
- 释放embstr需要调用一次内存释放函数,raw两次
- embstr对象的所有数据都保存在一块连续的内存里面,比raw对象能更好的利用缓存带来的优势
8.2.1 编码的转换
int编码和embstr编码的字符串对象在条件满足的情况下,会被转换为raw编码的字符串对象
- 对于int编码来说,如执行一些操作使得该对象保存的不再是整数值,而是字符串,那么字符串对象的编码将从int变为raw
- embstr对象是只读的,当对该种对象执行任何修改命令时,会将embstr转换成raw,再执行命令
8.2.2 字符串命令的实现
8.3 列表对象
列表对象的编码可以是ziplist或者linkedlist
8.3.1 编码转换
当列表对象可以同时满足以下两个条件时,列表对象使用ziplist编码:
- 列表对象保存的所有字符串元素的长度都小于64字节
- 列表对象保存的元素数量小于512个;
以上两个条件可以修改,list-max-ziplist-value和list-max-ziplist-entries
以上任意一个不能满足时,ziplist编码的对象需要转换成linkedlist。
8.3.2 列表命令的实现
8.4 哈希对象
哈希对象的编码可以是ziplist或者hashtable。
8.4.1 编码转换
同时满足以下条件时,哈希对象使用ziplist编码
- 保存的所有键值对的键和值的字符串长度都小于64字节
- 哈希对象保存的键值对数量小于512个。
两个条件的上限值可以修改,hash-max-ziplist-value和hash-max-ziplist-entries
使用ziplist所需的两个条件任一个不满足时,对象的编码就会从ziplist变为hashtable
8.4.2 哈希命令的实现
8.5 集合对象
集合对象的编码可以是intset或者hashtable
8.5.1 编码的转换
同时满足两个条件,使用intset编码:
- 集合对象保存的所有元素都是整数值
- 集合对象保存的元素数量不超过512个
不能同时满足两个条件的,需要转换成hashtable编码
第二个条件可修改:set-max-intset-entries
8.5.3 集合命令的实现
8.6 有序集合对象
有序集合编码可以是ziplist或者skiplist
ziplist编码:
- 每个集合元素使用两个紧邻的压缩列表节点保存。第一个保存元素的成员,第二个保存元素的分值
- 压缩列表内的集合元素按分值从小到大进行排序,分值较小的元素被放置在靠近表头的方向,分值较大的元素则被放置在靠近表尾的方向
为什么有序集合需要同时使用跳跃表和字典来实现?
无论是单独使用字典还是跳表,在性能上比同时使用字典和跳表都会有所降低
- 如果只使用字典,O(1)复杂度查找成员的分值这一特性会被保留,但因为字典以无序的方式来保存集合元素,所以每次在执行范围型操作——ZRANK、ZRANGE时,程序需要对字典保存的所有元素进行排序需要O(logN)时间复杂度,以及额外的O(N)内存空间(存排序后的元素)
- 如果只用跳表,跳表执行范围型操作的优点会保留。但根据成员查分值这一操作的复杂度将从O(1)上升为O(logN).
8.6.1 编码的转换
同时满足两个条件,使用ziplist编码:
- 有序集合保存的元素数量小于128个
- 有序集合保存的所有元素成员的长度都小于64字节
不能同时满足的将使用skiplist编码
以上两个条件可以修改:zset-max-ziplist-entries和zset-max-ziplist-value
8.6.2 有序集合命令的实现
8.7 类型检查与命令多态
Redis中用于操作键的命令可以分为两类:
- 可以对任何类型的键执行,比如DEL、EXPIRE、RENAME、TYPE、OBJECT等
- 只能对特定类型的键执行:
8.7.1 类型检查的实现
在执行一个类型特定的命令之前,Redis会先检查输入键的类型是否正确,然后再决定是否执行给定的命令
类型检查是通过redisObject结构的type属性来实现的:
- 在执行一个类型特定命令之前,服务器会先检查输入数据库键的值对象是否为执行命令所需的类型,如果是,就执行命令
- 否则,拒绝执行,并向客户端返回一个类型错误
8.7.2 多态命令的实现
Redis会根据值对象的编码方式,选择正确的命令实现代码来执行命令
DEL、EXPIRE等命令和LLEN等命令的区别在于,前者是基于类型的多态——一个命令可以同时用于处理多种不同类型的键,后者是基于编码的多态——一个命令可以同时用于处理多种不同编码
8.8 内存回收
Redis构建了一个基于引用计数技术实现的内存回收机制。
对象引用计数信息变化:
- 在创建一个新对象时,引用计数的值会被初始化为1
- 当对象被一个新程序使用时,引用+1
- 不再被一个程序使用时,引用-1
- 对象的引用计数值变为0时,对象所占用的内存会被释放
8.9 对象共享
对象的引用计数属性还带有对象共享的作用
Redis中,让多个键共享同一个值对象需要执行以下两个步骤:
- 将数据库的值指针指向一个现有的值对象
- 将被共享的值对象的引用计数增一
Redis会共享值为0到9999的字符串对象
这些共享对象不单单只有字符串键可以使用,那些在数据结构中嵌套了字符串对象的对象(linkedlist编码的列表对象、hashtable编码的哈希对象、hashtable编码的集合对象、以及zset编码的有序集合对象)都可以使用这些共享对象
为什么Redis不共享包含字符串的对象?
当服务器考虑将一个共享对象设置为键的值对象时,需要检查给定的共享对象和键想创建的目标对象是否完全相同。而一个共享对象保存的值越复杂,验证的复杂度越高,消耗的CPU时间也越多
- 共享对象时保存整数值的字符串对象,验证操作的复杂度为O(1)
- ...是保存字符串值的字符串对象,...为O(N)
- 如果是包含了多个值(对象)对象,比如列表对象或者哈希对象,复杂度为O(N^2)
8.10 对象的空转时长
redisObject结构的lru属性,记录了对象最后一次被命令程序访问的时间。
OBJECT IDLETIME命令可以打印出给定键的空转时长,就是通过将当前时间减去键的值对象的lru时间计算出的。
如果服务器打开了maxmemory选项,并且服务器用于回收内存的算法为volatile-lru或者allkeys-lru,那么当服务器占用的内存数超过了maxmemory选项所设置的上限值时,空转时间较高的那部分键会优先被服务器释放
有关配置:maxmemory、maxmemory-policy