中间件 十二月 17, 2019

Redis(一)

文章字数 11k 阅读约需 10 mins. 阅读次数 0

Redis的对象类型和编码

首先我们来看一道常见的面试题: Redis支持的数据结构有哪些?
        大多数人都能轻松的给出答案,五种:String、List、Hash、Set、Zset;但其实这还远远不够,这仅仅redis的常用基本数据类型,每一种数据类型内部还包含着多种数据结构;要记住类型和结构是不一样的

我们来看一组实验

通过[type key]指令查看值的数据类型
通过[object encoding key]指令查看值的数据结构

127.0.0.1:6379> set str1 1
OK
127.0.0.1:6379> type str1
string
127.0.0.1:6379> object encoding str1
"int"
127.0.0.1:6379> set str2 "zhangsan"
OK
127.0.0.1:6379> type str2
string
127.0.0.1:6379> object encoding str2
"embstr"

可以得出几个重要的结论

1.redis的数据结构有很多种
2.即便是同一个String类型的值,根据value的不通,redis选择的数据结构是不一样的

“思考:为什么同一个String类型的值,会有多个数据结构?,选择的依据又是什么?”

带着问题我们接着往下看


redisObject

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

举个栗子:
以下 SET 命令在数据库中创建了一个新的键值对, 其中键值对的键是一个包含了字符串值 “str” 的对象, 而键值对的值则是一个包含了字符串值 “zhangsan” 的对象,通过键对象中的指针指向值对象

127.0.0.1:6379> set str "zhangsan"
OK

Redis中定义了一个C语言实现的结构体作为对象

typedef struct redisObject {

    // 对象类型
    unsigned type:4;        

    // 不使用(对齐位)
    unsigned notused:2;

    // 底层编码方式
    unsigned encoding:4;

    // LRU 时间(相对于 server.lruclock)
    unsigned lru:22;

    // 引用计数
    int refcount;

    // 指向对象的值
    void *ptr;

} robj;

type-对象类型

属性 type 记录了对象是什么类型,值的相关定义如下:

#define OBJ_STRING 0
#define OBJ_LIST 1
#define OBJ_SET 2
#define OBJ_ZSET 3
#define OBJ_HASH 4

      前面说到,键和值都是一个对象。键总是一个字符串对象,其属性 type 总为 OBJ_STRING ,值对象则可以是字符串对象,列表对象等,不同的类型的对象,实则就是属性 type 不同的 redisObject 。如列表键,指的是属性 type 为 OBJ_LIST 的值对象。

可以通过客户端指令[type key]查看值对象的数据类型,本文开头已经演示过了

encoding-编码类型

属性 encoding ,其实指的是该对象底层所使用的数据结构类型,即指针 ptr 所指向的数据结构,相关的定义如下:

#define OBJ_ENCODING_RAW 0     /* 简单动态字符串 */
#define OBJ_ENCODING_INT 1     /* 整型 */
#define OBJ_ENCODING_HT 2      /* 字典 */
#define OBJ_ENCODING_ZIPMAP 3  /* 压缩字典(不再使用)*/
#define OBJ_ENCODING_LINKEDLIST 4 /* 双端链表(不再使用) */
#define OBJ_ENCODING_ZIPLIST 5 /* 压缩列表 */
#define OBJ_ENCODING_INTSET 6  /* 整数集合 */
#define OBJ_ENCODING_SKIPLIST 7  /* 跳表 */
#define OBJ_ENCODING_EMBSTR 8  /* Embedded 编码的简单动态字符串 */
#define OBJ_ENCODING_QUICKLIST 9 /* 快速列表,基于压缩列表实现的列表,取代了之前的双端链表 */
可以通过客户端指令[object encoding key]查看值对象的数据结构,本文开头已经演示过了

还记得前文的思考题吗?

“思考:为什么同一个String类型的值,会有多个数据结构?,选择的依据又是什么?”

官方提供的对象类型与编码类型关系表可以解答我们的第一个问题:

类型 编码 对象
OBJ_STRING OBJ_ENCODING_INT 使用long类型的整数值实现的字符串对象
OBJ_STRING OBJ_ENCODING_EMBSTR 使用embstr编码的简单动态字符串实现的字符串对象
OBJ_STRING OBJ_ENCODING_RAW 使用简单动态字符串实现的字符串对象
OBJ_LIST OBJ_ENCODING_QUICKLIST 使用 quicklist 实现的列表对象
OBJ_HASH OBJ_ENCODING_ZIPLIST 使用压缩列表实现的哈希对象
OBJ_HASH OBJ_ENCODING_HT 使用字典实现的哈希对象
OBJ_SET OBJ_ENCODING_INTSET 使用数组实现的集合对象,元素从小到大排列
OBJ_SET OBJ_ENCODING_HT 使用字典实现的哈希对象
OBJ_ZSET OBJ_ENCODING_ZIPLIST 使用压缩列表实现的有序集合对象
OBJ_ZSET OBJ_ENCODING_SKIPLIST 使用跳跃表实现的有序集合对象

能看出来字符串的编码可以是int,raw或者embstr;

那又是怎么选择的呢?

如果一个字符串内容可转为long,那么该字符串会被转化为long类型,对象ptr指向该long对象,并且对象类型也用int类型表示。普通的字符串有两种,embstr和raw。如果字符串对象的长度小于44字节,就用embstr对象。否则用的raw对象
C语言实现的代码细节,这里就不看了,有兴趣可以看看Redis的String实现原理

优点:
(1) embstr的创建只需分配一次内存,而raw为两次(一次为sds分配对象,另一次为redisObject分配对象,embstr省去了第一次)。
(2)相对地,释放内存的次数也由两次变为一次。
(3)embstr的redisObject和sds放在一起,更好地利用缓存带来的优势

缺点:
redis并未提供任何修改embstr的方式,即embstr是只读的形式。对embstr的修改实际上是先转换为raw再进行修改。

lru-空转时间

    属性 lru 记录的是对象最后一次被访问的时间。通过该属性,可以利用命令 [object idletime key] 获取对象的空转时长(命令[object idletime key]不会更新 lru 属性)。object idletime 计算得出的空转时长是通过当前时间减去指定对象的 lru 属性得出的,其单位为秒。

127.0.0.1:6379> object idletime str2
(integer) 3636

    属性 lru 除了应用于命令 object idletime 外,还有另一项作用。
当服务器开启了 maxmemory 选项,并且服务器用于回收内存的算法为 volatile-lru 或者 allkeys-lru 的话,那么当 Redis 实例的占用的内存超过 maxmemory 后,空转时间较长的对象将会优先被回收。

refcount-引用计数

    refcount记录的是对象被引用数。Redis 构建的对象系统使用引用计数机制实现内存回收。引用计算为 0 的对象将会被回收。

编码转换

    通过前面的类型编码映射表可以看到,除了List外,String,Hash,Set,Zset都具有多种编码结构;

String前面已经讲过了,不再讲了

Hash

      配置文件参数:
        hash-max-ziplist-entries 512
        hash-max-ziplist-value 64

      配置的含义为:
        哈希对象保存的元素数量小于 512 个
        哈希对象保存的所有字符串元素的长度都小于 64 字节

      不能满足上述条件的哈希对象将使用 OBJ_ENCODING_HT 作为对象编码,即使用字典作为底层数据结构。否则使用 OBJ_ENCODING_ZIPLIST 作为对象编码,即压缩列表作为底层数据结构。

      优化方案:将hash尽可能的拆成多个小hash,利用zipList节省内存

Set

      配置文件参数:
        set-max-intset-entries 512

      配置的含义为:
        集合对象保存的元素数量不超过512个

      不能满足上述条件,或是元素不全部为整数( intset 数据结构的天然限制)的集合对象,将使用 OBJ_ENCODING_HT 作为对象编码,即使用字典作为底层数据结构。否则使用 OBJ_ENCODING_INTSET 作为对象编码,即 intset 作为底层数据结构。

Zset

      配置文件参数:
        zset-max-ziplist-entries 128
        zset-max-ziplist-value 64

      配置的含义为:
        有序集合保存的元素数量小于128个
        有序集合保存的所有元素成员的长度都小于64字节

      不能满足上述条件的有序集合对象,将使用 OBJ_ENCODING_SKIPLIST 作为对象编码,即跳跃表作为底层数据结构。否则使用 OBJ_ENCODING_ZIPLIST 作为对象编码,即压缩列表作为底层数据结构。
0%