对象

Redis并没有直接使用这些数据结构来实现键值对数据库,而是基于这些数据结构创建了一个对象系统,这个系统包含字符串对象、列表对象、哈希对象、集合对象和有序集合对象这五种类型的对象

类型与编码

Redis使用对象来表示数据库中的键和值,每次当我们在Redis的数据库中新创建一个键值对时,我们至少会创建两个对象,一个对象用作键值对的键(键对象),另一个对象用作键值对的值(值对象)。

typedef struct redisObject {
    unsigned type:4;
    unsigned encoding:4;
    unsigned lru:LRU_BITS; /* LRU time (relative to global lru_clock) or
                            * LFU data (least significant 8 bits frequency
                            * and most significant 16 bits access time). */
    int refcount;
    void *ptr;
} robj;

类型

type命令返回结果一致:

#define OBJ_STRING 0        /* String object. */
#define OBJ_LIST 1      /* List object. */
#define OBJ_SET 2       /* Set object. */
#define OBJ_ZSET 3      /* Sorted set object. */
#define OBJ_HASH 4      /* Hash object. */

编码

OBJECT ENCODING命令返回结果一致:

#define OBJ_ENCODING_RAW 0     /* Raw representation */
#define OBJ_ENCODING_INT 1     /* Encoded as integer */
#define OBJ_ENCODING_HT 2      /* Encoded as hash table */
#define OBJ_ENCODING_ZIPMAP 3  /* Encoded as zipmap */
#define OBJ_ENCODING_LINKEDLIST 4 /* No longer used: old list encoding. */
#define OBJ_ENCODING_ZIPLIST 5 /* Encoded as ziplist */
#define OBJ_ENCODING_INTSET 6  /* Encoded as intset */
#define OBJ_ENCODING_SKIPLIST 7  /* Encoded as skiplist */
#define OBJ_ENCODING_EMBSTR 8  /* Embedded sds string encoding */
#define OBJ_ENCODING_QUICKLIST 9 /* Encoded as linked list of ziplists */
#define OBJ_ENCODING_STREAM 10 /* Encoded as a radix tree of listpacks */

通过encoding属性来设定对象所使用的编码,而不是为特定类型的对象关联一种固定的编码,极大地提升了Redis的灵活性和效率,因为Redis可以根据不同的使用场景来为一个对象设置不同的编码,从而优化对象在某一场景下的效率。

字符串对象

字符串对象的编码可以是 int、 raw或者embstr。

OBJ_ENCODING_INT

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

robj *createStringObjectFromLongLong(long long value) 若大于0,且小于10000直接返回共享整数

OBJ_ENCODING_RAW

如果字符串对象保存的是一个字符串值,并且这个字符串值的长度大于32字节,那么字符串对象将使用一个简单动态字符串(SDS)来保存这个字符串值

OBJ_ENCODING_EMBSTR

robj *createStringObject(const char *ptr, size_t len)如果字符串对象保存的是一个字符串值,并且这个字符串值的长度小于等于#define OBJ_ENCODING_EMBSTR_SIZE_LIMIT 44字节,那么字符串对象将使用embstr编码的方式来保存这个字符串值。

embstr编码是专门用于保存短字符串的一种优化编码方式,这种编码和raw编码一样,都使用redisObject结构和sdshdr结构来表示字符串对象,但raw编码会调用两次内存分配函数来分别创建redisObject结构和sdshdr结构,而embstr编码则通过调用一次内存分配函数来分配一块连续的空间

  • embstr编码将创建字符串对象所需的内存分配次数从raw编码的两次降低为一次
  • 释放embstr编码的字符串对象只需要调用一次内存释放函数,而释放raw编码的字符串对象需要调用两次内存释放函数
  • 因为embstr编码的字符串对象的所有数据都保存在一块连续的内存里面,所以这种编码的字符串对象比起raw编码的字符串对象能够更好地利用缓存带来的优势

注:robj *createStringObjectFromLongDouble(long double value, int humanfriendly)浮点数先转为字符串再存储

转换

  • 对于int编码的字符串对象来说,如果我们向对象执行了一些命令,使得这个对象保存的不再是整数值,而是一个字符串值,那么字符串对象的编码将从int变为raw。
  • embstr编码的字符串对象实际上是只读的。当我们对embstr编码的字符串对象执行任何修改命令时,程序会先将对象的编码从embstr转换成raw,再进行修改

列表对象

列表对象的编码可以是ziplist或者linkedlist。新版优化为quicklist: robj *createQuicklistObject(void)

list-max-ziplist-size -2

正值表示ziplist存储的元素数量不超过该数目

-1 到 -5表示存储的字节最大值

  • -5: max size: 64 Kb <– not recommended for normal workloads
  • -4: max size: 32 Kb <– not recommended
  • -3: max size: 16 Kb <– probably not recommended
  • -2: max size: 8 Kb <– good
  • -1: max size: 4 Kb <– good

list-compress-depth

  • 0: 禁用所有的压缩
  • 1: 不压缩开头和结尾的节点,[head]->node->node->...->node->[tail]
  • 2: 不压缩开头和结尾,及连接的1个节点,共4个节点 [head]->[next]->node->node->...->node->[prev]->[tail]
  • 类推

哈希对象

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

编码转换

  • hash-max-ziplist-value 64哈希对象保存的所有键值对的键和值的字符串长度都小于64字节;
  • hash-max-ziplist-entries 512哈希对象保存的键值对数量小于512个;

满足用ziplist编码 robj *createZiplistObject(void),不能满足这两个条件的哈希对象需要使用hashtable编码 robj *createHashObject(void)

集合对象

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

编码转换

整数集合编码条件robj *createIntsetObject(void)

  • 集合对象保存的所有元素都是整数值
  • 集合对象保存的元素数量不超过set-max-intset-entries 512

否则使用hashtable编码robj *createHashObject(void)

有序集合

有序集合的编码可以是ziplist或者skiplist。

编码转换

ziplist编码robj *createZiplistObject(void)条件:

  • 有序集合保存的元素数量小于zset-max-ziplist-entries 128个;
  • 有序集合保存的所有元素成员的长度都小于zset-max-ziplist-value 64字节;

类型检查与命令多态

类型检查

SET只能操作字符串,类型特定命令需要首先对类型进行检查, 具体通过检查redisObject结构的type属性来实现的

多态命令

Redis除了会根据值对象的类型来判断键是否能够执行指定命令之外,还会根据值对象的编码方式,选择正确的命令实现代码来执行命令。

列表对象有ziplist和linkedlist两种编码可用,其中前者使用压缩列表API来实现列表命令,而后者则使用双端链表API来实现列表命令。

内存回收

因为C语言并不具备自动内存回收功能,所以Redis在自己的对象系统中构建了一个引用计数(referencecounting)技术实现的内存回收机制,通过这一机制,程序可以通过跟踪对象的引用计数信息,在适当的时候自动释放对象并进行内存回收。

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

maxmemory-policy

达到最大使用内存大小maxmemory,执行淘汰策略maxmemory-policy

  • volatile-lru:采用最近使用最少的淘汰策略,Redis将回收那些超时的(仅仅是超时的)键值对,也就是它只淘汰那些超时的键值对。
  • allkeys-lru:采用最近最少使用的淘汰策略,Redis将对所有(不仅仅是超时的)的键值对采用最近最少使用的淘汰策略。
  • volatile-lfu:采用最近最不常用的淘汰策略,所谓最近最不常用,也就是一定时期内被访问次数最少的。Redis将回收超时的键值对。
  • allkeys-lfu:采用最近最不常用的淘汰策略,Redis将对所有的键值对采用最近最不常用的淘汰策略。
  • volatile-random:采用随机淘汰策略删除超时的键值对。
  • allkeys-random:采用随机淘汰策略删除所有的键值对,这个策略不常用。
  • volatile-ttl:采用删除存活时间最短的键值对策略。
  • noeviction:不淘汰任何键值对,直接返回OOM(默认)
    {"volatile-lru", MAXMEMORY_VOLATILE_LRU},
    {"volatile-lfu", MAXMEMORY_VOLATILE_LFU},
    {"volatile-random",MAXMEMORY_VOLATILE_RANDOM},
    {"volatile-ttl",MAXMEMORY_VOLATILE_TTL},
    {"allkeys-lru",MAXMEMORY_ALLKEYS_LRU},
    {"allkeys-lfu",MAXMEMORY_ALLKEYS_LFU},
    {"allkeys-random",MAXMEMORY_ALLKEYS_RANDOM},
    {"noeviction",MAXMEMORY_NO_EVICTION},

对象共享

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

尽管共享更复杂的对象可以节约更多的内存,但受到CPU时间的限制,Redis只对包含整数值的字符串对象进行共享

LRU

redisObject结构包含的最后一个属性为lru属性,该属性记录了对象最后一次被命令程序访问的时间

OBJECT IDLETIME可以打印出给定键的空转时长(当前时间-LRU),但不会修改值对象的lru属性

LFU

最近不经常使用,一个数据被访问过,把它的频次+1,发生淘汰的时候,把频次低的淘汰掉。

LFU data (least significant 8 bits frequency and most significant 16 bits access time).