Redis设计与实现第八章笔记

date
Jul 24, 2021
slug
Redis设计与实现第八章笔记
status
Published
tags
读书笔记
summary
对象
type
Post

8.1 对象的类型与编码

Redis中每个对象都由一个redisObject 结构表示:
 
notion image

8.1.1 类型

type属性记录了对象的类型,这个属性的值可以是下表中的一个
 
notion image
当对一个数据库键执行TYPE命令时,命令返回的结果为数据库键对应的值对象的类型
 
notion image

8.1.2 编码和底层实现

ptr指针指向对象的底层实现数据结构,这些数据结构由对象的encoding属性决定
 
notion image
每种对象可以使用的编码:
 
notion image
使用OBJECT ENCODING命令可以查看一个数据库键的值对象的编码
 
notion image
 
notion image

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 字符串命令的实现

 
notion image

8.3 列表对象

列表对象的编码可以是ziplist或者linkedlist
 
notion image
 
notion image
 
notion image

8.3.1 编码转换

当列表对象可以同时满足以下两个条件时,列表对象使用ziplist编码:
  • 列表对象保存的所有字符串元素的长度都小于64字节
  • 列表对象保存的元素数量小于512个;
以上两个条件可以修改,list-max-ziplist-value和list-max-ziplist-entries
以上任意一个不能满足时,ziplist编码的对象需要转换成linkedlist。

8.3.2 列表命令的实现

 
notion image

8.4 哈希对象

哈希对象的编码可以是ziplist或者hashtable。
 
notion image
 
notion image
 
notion image

8.4.1 编码转换

同时满足以下条件时,哈希对象使用ziplist编码
  • 保存的所有键值对的键和值的字符串长度都小于64字节
  • 哈希对象保存的键值对数量小于512个。
两个条件的上限值可以修改,hash-max-ziplist-value和hash-max-ziplist-entries
使用ziplist所需的两个条件任一个不满足时,对象的编码就会从ziplist变为hashtable

8.4.2 哈希命令的实现

 
notion image

8.5 集合对象

集合对象的编码可以是intset或者hashtable
 
notion image
 
notion image

8.5.1 编码的转换

同时满足两个条件,使用intset编码:
  • 集合对象保存的所有元素都是整数值
  • 集合对象保存的元素数量不超过512个
不能同时满足两个条件的,需要转换成hashtable编码
第二个条件可修改:set-max-intset-entries

8.5.3 集合命令的实现

 
notion image
 
notion image

8.6 有序集合对象

有序集合编码可以是ziplist或者skiplist
ziplist编码:
  • 每个集合元素使用两个紧邻的压缩列表节点保存。第一个保存元素的成员,第二个保存元素的分值
  • 压缩列表内的集合元素按分值从小到大进行排序,分值较小的元素被放置在靠近表头的方向,分值较大的元素则被放置在靠近表尾的方向
 
notion image
 
notion image
 
notion image

为什么有序集合需要同时使用跳跃表和字典来实现?

无论是单独使用字典还是跳表,在性能上比同时使用字典和跳表都会有所降低
  • 如果只使用字典,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 有序集合命令的实现

 
notion image

8.7 类型检查与命令多态

Redis中用于操作键的命令可以分为两类:
  • 可以对任何类型的键执行,比如DEL、EXPIRE、RENAME、TYPE、OBJECT等
  • 只能对特定类型的键执行:
 
notion image

8.7.1 类型检查的实现

在执行一个类型特定的命令之前,Redis会先检查输入键的类型是否正确,然后再决定是否执行给定的命令
类型检查是通过redisObject结构的type属性来实现的:
  • 在执行一个类型特定命令之前,服务器会先检查输入数据库键的值对象是否为执行命令所需的类型,如果是,就执行命令
  • 否则,拒绝执行,并向客户端返回一个类型错误

8.7.2 多态命令的实现

Redis会根据值对象的编码方式,选择正确的命令实现代码来执行命令
DEL、EXPIRE等命令和LLEN等命令的区别在于,前者是基于类型的多态——一个命令可以同时用于处理多种不同类型的键,后者是基于编码的多态——一个命令可以同时用于处理多种不同编码
notion image

8.8 内存回收

Redis构建了一个基于引用计数技术实现的内存回收机制。
对象引用计数信息变化:
  • 在创建一个新对象时,引用计数的值会被初始化为1
  • 当对象被一个新程序使用时,引用+1
  • 不再被一个程序使用时,引用-1
  • 对象的引用计数值变为0时,对象所占用的内存会被释放
notion image

8.9 对象共享

对象的引用计数属性还带有对象共享的作用
Redis中,让多个键共享同一个值对象需要执行以下两个步骤:
  • 将数据库的值指针指向一个现有的值对象
  • 将被共享的值对象的引用计数增一
Redis会共享值为0到9999的字符串对象
notion image
这些共享对象不单单只有字符串键可以使用,那些在数据结构中嵌套了字符串对象的对象(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
 

© 李工 2021 - 2025