# Redis 对象
[TOC]
Redis 使用对象来表示数据库中的键和值,每次数据库中新创建一个键值对时,至少会创建两个对象,一个用作键,一个用作值。Redis 对象定义于 [src/redis.h](https://github.com/huangz1990/redis-3.0-annotated/blob/unstable/src/redis.h)。
```c
typedef struct redisObject {
// 类型
unsigned type:4;
// 编码
unsigned encoding:4;
// 对象最后一次被访问的时间
unsigned lru:REDIS_LRU_BITS;
// 引用计数
int refcount;
// 指向实际值的指针
void *ptr;
} robj;
```
在 Redis 数据库中,键总是一个字符串对象,而值则可以是字符串对象、列表对象、哈希对象、集合对象或者有序集合对象的其中一种,`type` 字段表示一个 Redis 对象的类型。
| 类型常量 | 对象的名称 |
| --- | --- |
| REDIS\_STRING | 字符串对象 |
| REDIS\_LIST | 列表对象 |
| REDIS\_HASH | 哈希对象 |
| REDIS\_SET | 集合对象 |
| REDIS\_ZSET | 有序集合对象 |
> Redis 的 `TPYE` 命令返回的是值对象的类型。
对象的 `ptr` 指针指向对象的底层实现数据结构,而这些数据结构由对象的 `encoding` 属性决定。
| 编码常量 | 编码的名称 |
| --- | --- |
| REDIS\_ENCODING\_INT | long类型的对象 |
| REDIS\_ENCODING\_EMBSTR | embstr编码的简单动态字符串 |
| REDIS\_ENCODING\_RAW | 简单动态字符串|
| RREDIS\_ENCODING\_HT | 字典 |
| REDIS\_ENCODING\_LINKEDLIST | 双端链表 |
| REDIS\_ENCODING\_ZIPLIST | 压缩列表 |
| RREDIS\_ENCODING\_INTSET | 整数集合 |
| REDIS\_ENCODING\_SKIPLIST | 跳跃表和字典 |
> 使用 `OBJECT ENCODING` 命令可以查看一个数据库键的值对象编码。
*****
## Redis 的五种对象实现
### 字符串对象
字符串对象的编码可以是int、raw、或者embstr。
- 如果字符串对象保存的是整数值,且该整数值可以用 long 类型来表示:使用 int 方式编码,底层 为long;
- 如果字符串对象保存的是字符串值,且长度大于39字节:使用 raw 方式编码,底层为 SDS;
- 如果字符串对象保存的是字符串值,切长度小于等于39字节:使用 embstr 方式编码,底层为 SDS;
**embstr 编码**是专门用于保存短字符串的一种优化编码方式,它和 raw 编码一样都是使用 RedisObject 结构和 sdshdr 结构来表示字符串对象,但 raw 编码会调用两次内存分配来分别创建 RedisObject 和 sdshdr,而 embstr 编码则通过调用一次内存分配函数来获取一块连续空间,空间中依次包含 RedisObject 和 sdshdr。
**embstr 的优势**:
- 内存分配次数由两次降为一次;
- 内存释放次数由两次降为一次;
- embstr 方式中 RedisObject 和 sdshdr 空间连续,能够更好地利用缓存;
> Redis 没有为 embstr 编码的字符串提供任何相应的修改程序,所以当我们对 embstr 编码的字符串执行任何修改命令时,程序会将 embstr 编码的字符串转换成 raw 编码的方式,再执行修改命令。
### 列表对象
列表对象的编码可以是 ziplist 或者 linkedlist。当列表对象可以同时满足以下两个条件时,列表对象使用 ziplist 编码:
- 列表对象保存的所有字符串元素的长度都小于 64 字节;
- 列表对象保存的元素数量小于 512 个;
> 以上两个条件的上限值是可以修改的,具体为配置文件中的 `list-max-ziplist-value` 和 `list-max-ziplist-entries` 两个配置项。
### 哈希对象
哈希对象的编码可以是 ziplist 或者 hashtable。
- `ziplist` 编码的哈希对象使用压缩列表作为底层实现,保存键和值的两个节点在压缩列表中是连续的;
- `hashtable` 编码的哈希对象使用字典作为底层实现,哈希对象中的每个键值对都是用一个字典键值对来保存;
当哈希对象可以同时满足以下两个条件时,哈希对象使用 `ziplist` 编码:
- 哈希对象保存的所有键值对的键和值的字符串的长度都小于 64 字节;
- 哈希对象保存的键值对数量小于 512 个;
> 以上两个条件的上限值是可以修改的,具体为配置文件中的 `hash-max-ziplist-value` 和 `hash-max-ziplist-entries` 两个配置项。
### 集合对象
集合对象的编码可以是 intset 或者 hashtable。intset 编码的集合对象使用证书集合作为底层实现,集合对象包含的所有元素都被保存在整数集合里面。
当集合对象可以同时满足以下两个条件时,对象使用 intset 编码:
- 集合对象保存的所有元素都是整数值;
- 集合对象保存的元素数量不超过 512 个;
> 以上第二个条件的上限值是可以修改的,具体为配置文件中的 `set-max-ziplist-entries` 配置项。
### 有序集合对象
有序集合的编码可以是 ziplist 或者 skiplist。
ziplist 编码的有序集合对象使用压缩列表作为底层实现,每个集合元素使用两个挨在一起的压缩列表节点来分别保存元素成员和该成员的分值。
skiplist 编码的有序集合对象使用 zset 结构作为底层实现,一个 zset 结构同时包含一个字典和一个跳跃表:
``` c
typedef struct zset {
zskiplist *zsl;
dict* dict;
} zset;
```
`zsl` 跳跃表按照分支从小到大保存了所有集合元素,每个跳跃表节点都保存了一个集合元素:跳跃表节点的 `object` 属性保存了元素的成员,而 `score` 属性则保存了元素的分值。`dict` 字典为有序集合创建了一个从成员到分值的映射,字典中的每个键值对都保存了一个集合元素:字典的键保存了元素的成员,而字典的值则保存了元素的分值。
当有序结合对象可以同时满足以下两个条件时,对象使用 ziplist 编码:
- 有序集合保存的元素数量小于 128 个;
- 有序集合保存的所有元素成员的长度都小于 64 字节;
> 以上两个条件的上限值是可以修改的,具体为配置文件中的 `zset-max-ziplist-value` 和 `zset-max-ziplist-entries` 两个配置项。
*****
## Redis对象特性
### 类型检查与命令多态
Redis 中用于操作键的命令基本上可以分为两种类型:
- 可以对任何类型的键执行,比如:`DEL`、`EXPIRE`、`RENAME`、`TYEP`、`OBJECT`;
- 只能对特定类型的键执行,比如:
- `SET`、`GET`、`APPEND`、`STRLEN` 等命令只能对字符串键执行;
- `HDEL`、`HSET`、`HGET`、`HLEN` 等命令只能对哈希键执行;
- `RPUSH`、`LPOP`、`LINSERT`、`LLEN` 等命令只能对列表键执行;
- `SADD`、`SPOP`、`SINTER`、`SCARD` 等命令只能对集合键执行;
- `ZADD`、`ZCARD`、`ZRANK`、`ZSCORE` 等命令只能对有序集合键执行;
类型特定命令所进行的类型检查时通过 redisObject 结构的 `type` 属性来实现的,在执行一个类型特定命令之前,服务器会先检查输入数据库键的值对象是否为执行命令所需的类型。
Redis 除了会根据值对象的类型来判断是否能够执行指定指令之外,还会根据值对象的编码方式,选择正确的命令实现代码来执行命令。
### 内存回收 & 对象共享
Redis 在自己的对象系统中构建了一个引用计数(reference counting)计数实现的内存回收机制。
对象的引用计数属性还带有对象共享的作用。目前来说,Redis 会在初始化服务器时,创建一万个字符串对象,这些对象包含了从 0 到 9999 的所有整数值,当服务器需要用到值为 0 到 9999 点字符串对象时,服务器就会使用这些共享对象,而不是新创建对象。
> 创建共享字符串对象的数量可以通过修改 redis.h/REDIS_SHARED_INTEGERS 常量来修改。
### 对象的空转时长
redisObject 结构中的 `lru` 属性记录了对象最后一次被命令程序访问的时间。`OBJECT IDLETIME` 命令可以打印出给定键的空转时长。
> `OBJECT IDLETIME` 命令在访问对象时不会修改对象的`lru` 属性。
键的空转时长还有另一个作用:如果服务器打开了 `maxmemory` 选项,并且服务器用于回收内存的算法为 volatile-lru 或者 allkeys-lru,那么当服务器占用的内存数超过上限时,空转时长较高的那部分键会优先被服务器释放,从而回收内存。
- 目录
- 基础知识
- 1、变量和基础类型
- 1.1、内置类型
- 1.2、变量
- 1.3、复合类型
- 1.4、类型修饰符
- 1.5、类型处理
- 1.6、自定义结构
- 1.7、数组
- 2、表达式和语句
- 2.1、运算符
- 2.2、语句
- 3、函数
- 1、语法相关
- 2、资源管理
- 3、面向对象
- 4、模板与泛型编程
- Problem01:判断类中是否包含函数
- Problem02:解析函数的参数类型
- 5、系统库
- Problem01:多线程维护最大值
- Problem02:介绍一下strcpy、strncpy、memcpy、memmove
- Problem03:介绍一下网络编程
- Problem04:select、poll、epoll的区别
- 未整理
- Problem11:实现在main函数前、后执行的函数
- Problem12:可变参函数的实现
- Problem13:全局变量初始化顺序问题
- Problem14:介绍一下隐式转换
- Problem07:实现一个不能被拷贝的类
- Problem08:实现一个只能通过动态、静态分配的类
- 开源项目
- redis
- 第一部分 数据结构与对象
- redis 底层数据结构
- redis 对象
- taskflow
- 数据结构
- Executor
