前言:
此刻同学们对“lzf算法”可能比较关怀,姐妹们都需要剖析一些“lzf算法”的相关知识。那么小编同时在网络上汇集了一些有关“lzf算法””的相关内容,希望兄弟们能喜欢,姐妹们一起来了解一下吧!前言
我们知道redis支持五种数据类型,其实这五种类型就是五种数据对象。我们不曾注意到。其实,实际工作中,我们操作的每一个命令,底层都至少会创建两个对象。一个是键对象、一个是值对象。
今天我们就来学习一下,redis中的五种数据对象是如何工作的,它们又有哪些特性呢?
对象的属性
redis 的键和值都是一个对象,每个对象都有以下五个属性:类型、编码、指针、引用计数、空转时长。
typedef struct redisObject { unsigned type: 4; #类型 unsigned encoding:4; #编码 int refcount; #引用计数 unsigned lru:22; #空转时长 void *ptr; #指向底层实现数据结构的指针 ...}
type 属性,可为以下五种的其中一种:字符串、列表、哈希、集合、有序集合
refcount 属性,用于记录该对象被引用的次数,当引用计数为0时,对象会释放
lru 属性,用于记录对象最后一次访问的时间,若访问的时间过久,对象会释放
ptr 属性,用于指向对象的底层实现的数据结构,而数据结构是由encoding决定的
encoding 属性,记录了对象所使用的编码,也就是说,对象底层使用了哪种数据结构作为对象的底层实现。属性值可以是以下表格中的一个。
TYPE 命令,可以查看一个数据库键的值对象的类型
OBJECT ENCODING 命令,可以查看一个数据库键的值对象的编码
127.0.0.1:6379> set msg 'hello word'OK127.0.0.1:6379> get msg"hello word"127.0.0.1:6379> TYPE msgstring127.0.0.1:6379> OBJECT ENCODING msg"embstr"127.0.0.1:6379>
注意:每种类型的对象都至少使用了两种以上不同的编码方式。通过改变 encoding 的方式,来提高 redis 的灵活性和效率,因为 redis 会根据不同的场景,来为对象设置不同的编码,从而优化对象在某一场景下的效率。
一、字符串对象
字符串的编码可以是以下三种:int、raw、embstr;
int 编码,redis存储的是一个整数值,该整数值可以用long类型表示时,使用int编码。
raw 编码,redis存储的是一个字符串,该字符串长度大于39个字节时,使用raw编码。
embstr 编码,redis存储的是一个字符串,且长度大于等于39个字节时,使用embstr编码。
raw 和 embstr 编码的区别:
raw 会调用两次内存分配机制,内存不是连续空间。释放内存时也需要调用两次函数。
embstr 会调用一次内存分配机制,内存是连续的空间。释放内存只需要调用一次函数。
1.1 编码的转换
因为append命令只允许对 raw 编码的字符串对象进行操作。当我们对一个 int 编码的 或 embstr 编码的字符串对象进行一定的操作时,会将编码转换为 raw 编码的字符串对象。
127.0.0.1:6379> set msg 123OK127.0.0.1:6379> OBJECT ENCODING msg"int"127.0.0.1:6379> append msg 222(integer) 6127.0.0.1:6379> OBJECT ENCODING msg"raw"127.0.0.1:6379>
127.0.0.1:6379> set a 'abc'OK127.0.0.1:6379> object encoding a"embstr"127.0.0.1:6379> append a 123(integer) 6127.0.0.1:6379> object encoding a"raw"127.0.0.1:6379>
以上命令,则是实践 int 和 embstr 编码,通过 append 转换为 raw 编码类型的过程。
二、列表对象
早期的列表对象的编码是由 ziplist 、 linkedlist,也就是说当元素少时使用ziplist,当元素多时使用 linkedlist。但是后来,新版本中对列表数据结构进行了改造,使用 quicklist 代替了 ziplist 和 linkedlist;
quicklist 编码,是使用快速列表作为列表对象的底层实现。
ziplist 编码,是使用压缩列表作为列表对象的底层实现。
linkedlist 编码,是使用双端链表作为列表对象的底层实现。
下面命令,体现出新版本中使用“快速列表”作为底层数据结构的实现
127.0.0.1:6379> lpush list 1(integer) 1127.0.0.1:6379> object encoding list"quicklist"127.0.0.1:6379>
quicklist 是 ziplist 和 linkedlist 的混合体,它将 linkedlist 按段切分,每一段使用 ziplist 存储,多个 ziplist 之间使用双向指针串接起来。如下图所示。
quicklist 内部默认单个 ziplist 长度为 8k 字节,超出了这个字节数,就会新起一个 ziplist。ziplist 的长度由配置参数 list-max-ziplist-size 决定。快速列表 默认的压缩深度是0,也就是不压缩。压缩的实际深度可由list-compress-depth 决定。为了支持快速的 push/pop 操作,quicklist 的首尾两个 ziplist 不压缩,此时深度就是 1。如果深度为 2,就表示 quicklist 的首尾第一个 ziplist 以及首尾第二个 ziplist 都不压缩。
2.1 快速列表的数据结构、节点
#快速列表struct quicklist { quicklistNode* head; quicklistNode* tail; long count; #元素总数 int nodes; #ziplist 节点的个数 int compressDepth; # LZF 算法压缩深度}
#快速列表节点struct quicklistNode { quicklistNode* prev; quicklistNode* next; ziplist* zl; #指向压缩列表 int32 size; #ziplist 的字节总数 int16 count; #ziplist 中的元素数量 int2 encoding; #存储形式 2bit,原生字节数组还是 LZF 压缩存储}
2.2 双端链表、压缩列表、快速列表的特点
双端链表,进行push、pop来说速度很快,但是它的内存开销比较大,因为它要额外存储前驱指针和后继指针。链表采用不连续的内存空间,所以它对修改和插入操作来说相当快速,但是容易产生内存碎片。
压缩列表,仅限于存储字节较短的数据,因为它是为节省内存而开发的数据结构,压缩列表使用连续的内存空间,所以它对删除和插入的效率相对较低,但是查询效率很高。压缩列表对于插入很大的数据情况下,会产生扩容和大量拷贝流程,此时效率相对较低。
快速列表是对压缩列表和双端链表的空间和时间效率的折中产物。它结合了二者的优点。
三、哈希对象
哈希对象的编码方式有 ziplist 、hashtable
ziplist 编码,哈希对象底层使用压缩列表作为底层实现。
hashtable 编码,哈希对象底层使用字典作为底层实现。
下面命令,是压缩列表实现的哈希对象存储。
127.0.0.1:6379> hset mytest name "cici"(integer) 1127.0.0.1:6379> hset mytest age 18(integer) 1127.0.0.1:6379> hset mytest sex '女'(integer) 1127.0.0.1:6379> object encoding mytest"ziplist"127.0.0.1:6379>
下面命令,是字典实现的哈希对象存储。
127.0.0.1:6379> hset mytest content '我是中国人,我在测试哈希,你看到了吗?我是中国人,我在测试哈希,你看到了吗?我是中国人,我在测试哈希,你看到了吗?我是中国人,我在测试哈希,你看到了吗?我是中国人,我在测试哈希,你看到了吗?我是中国人,我在测试哈希,你看到了吗?我是中国人,我在测试哈希,你看到了吗?'127.0.0.1:6379> object encoding mytest"hashtable"127.0.0.1:6379>
当 ziplist 作为哈希对象的底层实现时,若再次存储键值,长度大于等于64字节时,哈希对象的底层实现,会进行编码转换,此时的哈希对象的编码方式为 hashtable 。
如上图所示,hashtable 编码的哈希对象,使用字典作为底层实现,哈希对象中的每个键值对都使用一个字典键值对来存储。字典的每个键和值都是一个字符串对象,对象中分别存存了键值对的键和值。
3.1 编码转换
当哈希对象同时满足以下两个条件时,默认情况下哈希对象会使用压缩 ziplist 编码;
哈希对象保存的所有键值对的键和值长度小于64字节哈希对象保存的键值对数量小于 512 个
以上两个条件可以通过配置文件进行修改。若不满足以上两个条件的情况下,将会采用 hashtable 进行编码。
四、集合对象
集合对象的编码有 intset、hashtable
intset 编码,集合对象使用整数集合作为底层实现,集合元素都存储在整数集合里。
hashtable 编码,集合对象使用字典作为底层实现。
下面命令,是整数集合实现的集合对象存储。
127.0.0.1:6379> sadd mynumber 111 222 333(integer) 3127.0.0.1:6379> object encoding mynumber"intset"127.0.0.1:6379>
下面命令,是字典实现的集合对象存储
127.0.0.1:6379> sadd mynumber "hello" "你好"(integer) 2127.0.0.1:6379> object encoding mynumber"hashtable"127.0.0.1:6379>
下面命令,是查看集合中的元素数量 和 元素值,由此可见,集合编码发生了变化,已经从 intset 编码转换成 hashtable 编码。
127.0.0.1:6379> scard mynumber(integer) 5127.0.0.1:6379> smembers mynumber1) "hello"2) "333"3) "111"4) "222"5) "\xe4\xbd\xa0\xe5\xa5\xbd"127.0.0.1:6379>
由上图可见,集合中因为添加了字符串类型值,因此从原本的 intset 编码转为 hashtable 编码。
4.1 编码转换
当集合对象同时满足以下两个条件时,对象会使用 intset 编码
集合对象保存的所有元素都是整数值。集合对象保存的元素数量不超过 512 个。
以上两个条件是可以修改的,通过修改配置文件即可。若不满足以上条件的集合对象将使用 hashtable 编码。
五、有序集合对象
有序集合对象是有 ziplist 、skiplist
ziplist 编码,有序集合对象使用压缩列表作为底层实现。
skiplist 编码,有序集合对象使用 zset 结构作为底层实现,一个zset包含一个字典和一个跳跃表。
下面命令,是压缩列表作为有序集合对象的底层实现。
127.0.0.1:6379> zadd price 5.6 apple 3.4 orange(integer) 2127.0.0.1:6379> object encoding price"ziplist"127.0.0.1:6379>
注意:有序集合在压缩列表中按分值大小进行排序存储。
下面命令,是跳跃表作为有序集合对象的底层实现,它是一个 zset 结构。
127.0.0.1:6379> zadd price 4.4 "买了一件衣服,这个衣服真好看,真的很好看,真的很好看,真的很好看"(integer) 1127.0.0.1:6379> object encoding price"skiplist"127.0.0.1:6379>
下面命令,是查看有序集合的元素个数和排序情况。
127.0.0.1:6379> zcard price(integer) 3127.0.0.1:6379> ZRANGE price 0 -11) "orange"2) "\xe4\xb9\xb0\xe4\xba\x86\xe4\xb8\x80\xe4\xbb\xb6\xe8\xa1\xa3\xe6\x9c\x8d\xef\xbc\x8c\xe8\xbf\x99\xe4\xb8\xaa\xe8\xa1\xa3\xe6\x9c\x8d\xe7\x9c\x9f\xe5\xa5\xbd\xe7\x9c\x8b\xef\xbc\x8c\xe7\x9c\x9f\xe7\x9a\x84\xe5\xbe\x88\xe5\xa5\xbd\xe7\x9c\x8b\xef\xbc\x8c\xe7\x9c\x9f\xe7\x9a\x84\xe5\xbe\x88\xe5\xa5\xbd\xe7\x9c\x8b\xef\xbc\x8c\xe7\x9c\x9f\xe7\x9a\x84\xe5\xbe\x88\xe5\xa5\xbd\xe7\x9c\x8b"3) "apple"127.0.0.1:6379>
由上图可见,当有序集合使用了 skiplist 编码方式,其实底层采用了zset结构来存储了数据内容。
zset 结构分别用了一个字典 和 一个跳跃表来完成底层实现。
字典的优点,在于它以时间复杂度为O(1) 的速度取值。
跳跃表的优点,在于它以分值进行从小到大的排序。结合二者的优点作为 zset 的整体结构来完成了有序集合的底层实现。
5.1 编码的转换
有序集合对象同时满足以下两个条件时,对象使用ziplist 编码
有序集合保存的元素数量小于128个。有序集合保存的所有元素成员长度都小于64字节。
以上两个条件是可以修改的,通过修改配置文件即可。若不满足以上条件的有序集合对象将使用 skiplist 编码。
六、多态命令的实现
前面我们说过,对象包含类型、编码、指针、引用计数、空转时长 五个属性。编码在和指针在前面已经分别阐述了具体的实现方式。那类型主要用于哪方面呢?
其实,类型主要用于判断用户执行一个命令时,检测命令的键是否能够执行该命令。如果可以执行,则服务器就对键执行指定的命令,否则,服务器会拒绝执行命令,并抛出一个异常。
多态主要的体现,是当一个对象的底层实现,不止一种编码方式时,需要根据编码方式进行区分,来选择正确的命令执行方式。
七、引用计数、空转时长
1)Redis在自己的对象系统中,构建了一个对象引用计数,可用于内存回收机制,也可实现对象的共享机制,让多个数据库共享一个对象,用于节约内存的作用。
2)对象带有访问时间记录信息,用于删除空间转时间较大的那个键。
八、重点回顾
本文主要学习到,redis的五大数据对象,我们知道redis中的每个键值对其实都是一个对象,而每个对象的底层实现都至少采用两种以上的编码方式来提高redis的性能和效率。另外,redis还支持引用计数和内存共享机制。用于提高对象的最大化利用和快速释放无用对象。当我们执行每一个redis命令时,redis都会首先检测该命令的键是否支持执行该命令,若不支持,则报类型异常。
九、参考文献
《Redis设计与实现》
《Redis深度历险》
标签: #lzf算法