说明:本学习笔记为学习https://www.cnblogs.com/ysocean/的Redis详解系列教程后,总结的一些自认为重点的知识点。
PS:最近公司想招JAVA工程师,被抓去面试别人,搜了一些面试题,发现自己很多知识点也掌握的不牢固,所以准备补补课。目前还没学完,后续会断断续续更新。
1、Redis五大数据类型
(1)string
string 类型是二进制安全的,可以包含任何数据,比如图片或者序列化的对象。
一个 redis 中字符串 value 最多可以是 512M。
(2)hash
hash 是一个键值对集合,是一个 string 类型的 key和 value 的映射表。
value是一个键值对(key-value)
(3)list
list 列表,它是简单的字符串列表,底层实际上是个链表。
特点是:有序、可以重复。
(4)set
set 是 string 类型的无序集合。
特点是:无序、不可以重复。
(5)zset
zset是string 类型的有序集合。
特点是:有序、不可以重复。
2、Redis六大底层数据结构
(1)简单动态字符串(SDS)
(2)链表
(3)字典
(4)跳跃表
(5)整数集合
(6)压缩列表
3、Redis的五大数据类型实现原理
(1)对象的类型与编码
Redis使用前面说的五大数据类型来表示键和值,每次在Redis数据库中创建一个键值对时,至少会创建两个对象,一个是键对象,一个是值对象,而Redis中的每个对象都是由 redisObject 结构来表示:
typedef struct redisObject{
//类型
unsigned type:4;
//编码
unsigned encoding:4;
//指向底层数据结构的指针
void *ptr;
//引用计数
int refcount;
//记录最后一次被程序访问的时间
unsigned lru:22;
}
① type属性
对象的type属性记录了对象的类型,这个类型就是前面讲的五大数据类型:
② *prt 指针
对象的*prt 指针指向对象底层的数据结构。
③ encoding 属性
数据结构由 encoding 属性来决定,每种类型的对象都至少使用了两种不同的编码:
(2)string
字符串对象的编码可以是int,raw或者embstr。
1、int 编码:保存的是可以用 long 类型表示的整数值。
2、raw 编码:保存长度大于44字节的字符串(redis3.2版本之前是39字节,之后是44字节)。
3、embstr 编码:保存长度小于44字节的字符串(redis3.2版本之前是39字节,之后是44字节)。
embstr与raw都使用redisObject和sds保存数据,区别在于,embstr的使用只分配一次内存空间(因此redisObject和sds是连续的),而raw需要分配两次内存空间(分别为redisObject和sds分配空间)。因此与raw相比,embstr的好处在于创建时少分配一次空间,删除时少释放一次空间,以及对象的所有数据连在一起,寻找方便。而embstr的坏处也很明显,如果字符串的长度增加需要重新分配内存时,整个redisObject和sds都需要重新分配空间,因此redis中的embstr实现为只读。
ps:Redis中对于浮点数类型也是作为字符串保存的,在需要的时候再将其转换成浮点数类型。
(3)list
列表对象的编码可以是 ziplist(压缩列表) 和 linkedlist(双端链表)。
当同时满足下面两个条件时,使用ziplist(压缩列表)编码:
1、列表保存元素个数小于512个
2、每个元素长度小于64字节
不能满足这两个条件的时候使用 linkedlist 编码。
ps:上面两个条件可以在redis.conf 配置文件中的 list-max-ziplist-value选项和 list-max-ziplist-entries 选项进行配置。
(4)hash
哈希对象的编码可以是ziplist(压缩列表)或者hashtable(哈希表)。
和上面列表对象使用 ziplist 编码一样,当同时满足下面两个条件时,使用ziplist(压缩列表)编码:
1、列表保存元素个数小于512个
2、每个元素长度小于64字节
不能满足这两个条件的时候使用 hashtable 编码。
ps:第一个条件可以通过配置文件中的 set-max-intset-entries 进行修改。
(5)set
集合对象的编码可以是 intset 或者 hashtable。
intset 编码的集合对象使用整数集合作为底层实现,集合对象包含的所有元素都被保存在整数集合中。
hashtable 编码的集合对象使用字典作为底层实现,字典的每个键都是一个字符串对象,这里的每个字符串对象就是一个集合中的元素,而字典的值则全部设置为null。
当集合同时满足以下两个条件时,使用 intset 编码:
1、集合对象中所有元素都是整数
2、集合对象所有元素数量不超过512
不能满足这两个条件的就使用 hashtable 编码。
ps:第二个条件可以通过配置文件的 set-max-intset-entries 进行配置。
(6)zset
有序集合的编码可以是 ziplist 或者 skiplist,为每个元素设置一个分数(score)作为排序依据。
ziplist编码的有序集合对象使用压缩列表作为底层实现,每个集合元素使用两个紧挨在一起的压缩列表节点来保存,第一个节点保存元素的成员,第二个节点保存元素的分值。并且压缩列表内的集合元素按分值从小到大的顺序进行排列。
skiplist 编码的有序集合对象使用 zet 结构作为底层实现,一个 zset 结构同时包含一个字典和一个跳跃表:
typedef struct zset{
//跳跃表
zskiplist *zsl;
//字典
dict *dice;
} zset;
这两种数据结构会通过指针来共享相同元素的成员和分值,所以不会产生重复成员和分值,造成内存的浪费。
当有序集合对象同时满足以下两个条件时,对象使用 ziplist 编码:
1、保存的元素数量小于128;
2、保存的所有元素长度都小于64字节。
不能满足上面两个条件的使用 skiplist 编码。
ps:以上两个条件也可以通过Redis配置文件zset-max-ziplist-entries 选项和 zset-max-ziplist-value 进行修改。
4、内存回收和内存共享
(1)内存回收
Redis的内存回收基于redisObject 结构中的 refcount 属性实现,具体变化如下:
1、创建一个新对象,属性 refcount 初始化为1
2、对象被一个新程序使用,属性 refcount 加 1
3、对象不再被一个程序使用,属性 refcount 减 1
4、当对象的引用计数值变为 0 时,对象所占用的内存就会被释放。
在redis.conf 配置文件中,在 MEMORY MANAGEMENT下有个 maxmemory-policy 配置,当内存使用达到最大值时,redis使用的清除策略。有以下几种可以选择:
1)volatile-lru 利用LRU算法移除设置过过期时间的key (LRU:最近使用 Least Recently Used) 。
2)allkeys-lru 利用LRU算法移除任何key 。
3)volatile-random 移除设置过过期时间的随机key 。
4)allkeys-random 移除随机key。
5)volatile-ttl 移除即将过期的key(minor TTL) 。
6)noeviction noeviction 不移除任何key,只是返回一个写错误 ,默认选项。
通过这种配置,也可以对内存进行回收。
(2)内存共享
refcount 属性除了能实现内存回收以外,还能用于内存共享。
如通过如下命令set k1 100,创建一个键为k1,值为100的字符串对象,接着通过如下命令set k2 100 ,创建一个键为k2,值为100 的字符串对象,Redis会将数据库键的值指针指向一个现有值的对象,将被共享的值对象引用refcount加1。
ps:Redis的共享对象目前只支持整数值的字符串对象。之所以如此,实际上是对内存和CPU(时间)的平衡:共享对象虽然会降低内存消耗,但是判断两个对象是否相等却需要消耗额外的时间。对于整数值,判断操作复杂度为O(1);对于普通字符串,判断复杂度为O(n);而对于哈希、列表、集合和有序集合,判断的复杂度为O(n^2)。
虽然共享对象只能是整数值的字符串对象,但是5种类型都可能使用共享对象(如哈希、列表等的元素可以使用)。