头图

对象

Redis基于前面的那些数据结构,创建了一个对象系统用来实现键值对数据库。

那么就形成了五大基础对象

  1. 字符串对象 String
  2. 列表对象 List
  3. 哈希对象 Hash
  4. 集合对象 Set
  5. 有序集合对象 ZSet

因为引入了对象,所有Redis实现了基于引用计数技术的内存回收机制,当程序不在使用某个对象的时候,对象所占用的内存就会被自动释放,另外还实现了对象共享机制,在某些条件下多个数据库可以共享一个对象来节约内存。

类型与编码

Redis使用对象表示数据库中的键值,那么我们创建一个键值对,我们至少会创建两个对象

/*
 * Redis 对象
 */
#define REDIS_LRU_BITS 24
#define REDIS_LRU_CLOCK_MAX ((1<<REDIS_LRU_BITS)-1) /* Max value of obj->lru */
#define REDIS_LRU_CLOCK_RESOLUTION 1000 /* LRU clock resolution in ms */
typedef struct redisObject {

    // 类型
    unsigned type:4;

    // 编码
    unsigned encoding:4;

    // 对象最后一次被访问的时间
    unsigned lru:REDIS_LRU_BITS; /* lru time (relative to server.lruclock) */

    // 引用计数
    int refcount;

    // 指向实际值的指针
    void *ptr;

} robj;

对象的ptr指针指向对象的底层实现数据结构,它是由encoding决定的

TYPE 键名 可以查看键对应的值对象类型
set msg "hello"
type msg    string

rpush numbers 1 3 5
type numbers   list

字符串对象

字符串对象的编码可以是 int,raw,embstr

如果一个字符串对象保存的是整数值没并且这个整数值可以用long表示,那么字符串对象会将整数值保存在字符串对象结构的ptr里(将void* 转换为long)并且将字符串编码设置为int

如果字符串对象保存一个字符串值,并且字符串长度大于32字节,那么使用的SDS,编码设置为raw

如果字符串对象保存一个字符串值,并且字符串长度小于32字节,编码设置为embstr

embstr是专门保存短字符串的一种优化编码,它和raw一样都是使用redisObject结构和sdshdr结构来表示字符串对象,当时raw会调用两次内存分配函数来分别创建redisObject和sdshdr,而embstr会通过一次内存分配来创建一块连续的空间,里面放redisObject和sdshdr 效果时一样的只是过程简单了。

那么embstr的优势是

  1. 创建对象只需要分配一次内存
  2. 释放对象也只需要调用一次释放函数
  3. embstr分配的内存是连续的

如果你要存一个浮点数

那么程序会先将浮点数转换为字符串,然后保存的是字符串的值

编码转换

int和embstr对象在某些条件下会被转换为raw

对应int,如果我们向对象执行了一些命令,使得之歌对象保存的不再是整数,而是一个字符串,那么int会变为raw

比如向一个整数追加一个字符串

set number 10086
append number "hello"
object encoding number
raw

redis并没有定义对embstr对象的API,所有其实上embstr字符串是只读的,当我们实际对embstr字符串操作时,就会自动变raw

列表对象

编码可以时ziplist或者linkedlist

编码转换

什么时候使用ziplist 同时满足

  1. 列表对象保存的所有字符串元素长度都小于64字节
  2. 列表对象保存的元素数量小于512,

不满足这两个就用linkedlist

哈希对象

编码可以是ziplist或者hashtable

1、如果是ziplist会先push入键再push入值 都是在表尾 他们总是挨在一起的

2、如果是hashtable,那么hash对象中的每个键值对都是一个字典键值对保存的,

字典中键和值都是一个字符串对象

什么时候使用ziplist 同时满足才行

  1. 哈希对象保存的所有键和值的字符串元素长度都小于64字节
  2. 哈希对象保存的元素数量小于512,

不满足这两个就用hashtable

集合对象

集合对象可以是intset或者hashtable

如果集合里的值都是整数就用intset

什么时候使用intset 同时满足才行

  1. 集合对象保存的所有元素都是整数
  2. 集合对象保存的元素数量小于512,

有序集合

ziplist或者skiplist

1、ziplist,每个集合元素使用两个紧挨着的压缩列表节点保存,第一节点保存成员,第二个元素保存分值

2、skiplist使用的是一个字典和跳跃表,跳跃表按分值从小到大保存所有集合元素,每个跳跃表节点保存一个集合元素,dict字典为有序集合创建了一个从成员到分值的映射,字典键保存元素成员,字典值保存元素分值。

/*
 * 有序集合
 */
typedef struct zset {

    // 字典,键为成员,值为分值
    // 用于支持 O(1) 复杂度的按成员取分值操作
    dict *dict;

    // 跳跃表,按分值排序成员
    // 用于支持平均复杂度为 O(log N) 的按分值定位成员操作
    // 以及范围操作
    zskiplist *zsl;

} zset;

什么时候用ziplist 同时满足

  1. 有序集合保存的元素数量小于128个
  2. 有序集合保存的所有元素成员的长度都小于64个字节

类型检查和命令多态

Redis中用于操作键的命令可以分为两种

1、可以对任意类型的键执行,DEL,TYPE ,OBJECT等

2、对象的专用命令,set(字符串),hset(哈希),rpush(列表),sadd(集合),zadd(有序集合)。

那么问题就来了,在执行一个类型特定命令之前,redis必须要先检查类型,然后再决定是否执行

多态命令的实现

redis还会根据值对象的编码没选择正确的命令去执行。

现在,考虑这样一个情况,如果我们对一个键执行LLEN命令,那么服务器除了要确保执行命令的是列表键之外,还需要根据键的值对象所使用的编码来选择正确的LLEN命令实现:

如果列表对象的编码为ziplist,那么说明列表对象的实现为压缩列表,程序将使
用ziplistLen函数来返回列表的长度;

如果列表对象的编码为linkedlist,那么说明列表对象的实现为双端链表,程序将使用listLength函数来返回双端链表的长度;

内存回收

总所周知C不没有垃圾回收器的,redis用一个引用技术技术实现的内存回收,程序跟踪对象的引用技术信息,再合适的时候自动释放内存

对象的引用计数信息会随着对象的使用状态而不断变化:

  • 在创建一个新对象时,引用计数的值会被初始化为1;
  • 当对象被一个新程序使用时,它的引用计数值会被增一;
  • 当对象不再被一个程序使用时,它的引用计数值会被减一;
  • 当对象的引用计数值变为0时,对象所占用的内存会被释放。

对象共享

引用计数属性还带来了对象共享的作用,因为当对象被一个新程序使用时,它的引用计数值会被增一。

redis让多个键共享一个值对象

  1. 将数据库键的值指针指向一个现有的值对象
  2. 将被共享的值对象的引用计数加一

注意

Rdis再初始化服务器时,创建了一万个字符串对象,这些对象包含了0到9999的所有整数值,当需要用到时服务器就会使用共享对象,而不是新创建对象。

理论上所有对象都时可以共享的,当时再共享之前需要确认两个需要共享的元素是不是类型相同,时很好cup时间的,所有redis只包含了整数和字符串对象的共享。

对象的空转时长

// 对象最后一次被访问的时间
    unsigned lru:REDIS_LRU_BITS; /* lru time (relative to server.lruclock) */
object tdletime 键 可以打印lru

如果服务器开启了maxmemory选项,并且内存回收算法是 volatile-iru或者 allkeys-lru,那么服务器内存占用超过maxmemory时,空转时间长的会先被释放回收。


Shuaikb
1 声望0 粉丝

« 上一篇
unsigned and 01