`Redis`当中每种数据类型都是经过特别设计的,相信大家看完这个系列也会体会到`Redis`设计的精妙之处。字符串在我们眼里是非常简单的一种数据结构了,但是`Redis`却把它优化到了极致,为了节省空间,其通过编码的方式定义了三种不同的存储方式:
| 编码属性 | 描述 | object encoding命令返回值 |
| --- | --- | --- |
| OBJ\_ENCODING\_INT | 使用整数的字符串对象 | int |
| OBJ\_ENCODING\_EMBSTR | 使用`embstr`编码实现的字符串对象 | embstr |
| OBJ\_ENCODING\_RAW | 使用`raw`编码实现的字符串对象 | raw |
* `int`编码:当我们用字符串对象存储的是整型,且能用`8`个字节的`long`类型进行表示(即`2`的`63`次方减`1`),则`Redis`会选择使用`int`编码来存储,此时`redisObject`对象中的`ptr`指针直接替换为`long`类型。我们想想`8`个字节如果用字符串来存储只能存`8`位,也就是千万级别的数字,远远达不到`2`的`63`次方减`1`这个级别,所以如果都是数字,用`long`类型会更节省空间。
* `embstr`编码:当字符串对象中存储的是字符串,且长度小于`44`(`Redis 3.2`版本之前是`39`)时,`Redis`会选择使用`embstr`编码来存储。
* `raw`编码:当字符串对象中存储的是字符串,且长度大于`44`时,`Redis`会选择使用`raw`编码来存储。
讲了半天理论,接下来让我们一起来验证下这些结论。首先启动`Redis`:
~~~bash
sudo redis-server /etc/redis/redis.conf
redis-cli
~~~
连接上`Redis`之后依次输入以下命令:
~~~sql
set name lonely_wolf
type name
object encoding name
~~~
得到如下所示结果:
![](https://img.kancloud.cn/eb/7e/eb7e5fdbec50844b4912ac8b5edb2c6c_698x158.png)
可以发现当前的数据类型就是`string`,普通字符串因为长度小于`44`,所以采用的是`embstr`编码。
再依次输入如下命令:
~~~sql
set num 1111111111
type num
object encoding num
set address aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa //长度 44(注释不要输入)
object encoding address
set address aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa //长度 45(注释不要输入)
object encoding address
~~~
最后得到如下所示效果:
![](https://img.kancloud.cn/d6/1c/d61c77043e29c79f256f6f7a2b8b795d_744x268.png)
可以发现,当输入纯数字的时候,采用的是`int`编码,而字符串小于等于`44`则为`embstr`,大于`44`则为`raw`编码。
字符串对象中除了上面提到的纯整数和字符串,还可以存储浮点型类型,所以字符串对象可以存储以下三种类型:
* 字符串
* 整数
* 浮点数
而当我们的`value`为整数时,还可以使用原子自增命令来实现`value`的自增,这个命令在实际开发过程中非常实用。
* `incr key`:将`key`的值自增`1`。
* `incrby key n`:将`key`的值自增`n`。
![](https://img.kancloud.cn/c4/83/c4830ac41eb3096afbfc582a19d68cb7_672x173.png)
不过这两个命令只能用在`value`为整数的场景,当`value`不是整数时则会报错。
#### embstr 编码为什么从 39 位修改为 44 位
`embstr`编码中,`redisObject`和`sds`是连续的一块内存空间,这块内存空间`Redis`限制为了`64`个字节,而`redisObject`固定占了 16 字节(上面定义中有标注),`Redis 3.2`版本之前的`sds`占了`8`个字节,再加上字符串末尾`\0`占用了`1`个字节,所以:`64-16-8-1=39`字节。
`Redis 3.2`版本之后`sds`做了优化,对于`embstr`编码会采用`sdshdr8`来存储,而`sdshdr8`占用的空间只有`24`位:`3`字节(len + alloc + flag)+`\0`字符(1 字节),所以最后就剩下了:`64-16-3-1=44`字节。
#### embstr 编码和 raw 编码的区别
`embstr`编码是一种优化的存储方式,其在申请空间的时候因为`redisObject`和`sds`两个对象是一个连续空间,所以**只需要申请`1`次空间(同样的,释放内存也只需要`1`次)**,而`raw`编码因为`redisObject`和`sds`两个对象的空间是不连续的,所以使用的时候**需要申请`2`次空间(同样的,释放内存也需要`2`次)**。但是使用`embstr`编码时,假如需要修改字符串,那么因为`redisObject`和`sds`是在一起的,所以两个对象都需要重新申请空间,为了避免这种情况发生,`embstr`编码的字符串是只读的,不允许修改。
依次输入如下命令来验证一下编码转换:
~~~sql
set addr china
object encoding addr
append addr -beijing //在addr所对应的值后面追加“-beijing”(注释不要输入)
get addr
object encoding addr
~~~
得到如下所示结果:
![](https://img.kancloud.cn/ca/0c/ca0c1c0d8481d7187f142f573786d4b9_650x202.png)
上图中的示例我们看到,对一个`embstr`编码的字符串对象进行`append`操作时,长度还没有达到`45`,但是编码已经被修改为`raw`了,这就是因为`embstr`编码是只读的,如果需要对其修改,`Redis`内部会将其修改为`raw`编码之后再操作。同样的,如果是操作`int`编码的字符串之后,导致`long`类型无法存储时(`int`类型不再是整数或者长度超过`2`的`63`次方减`1`时),也会将`int`编码修改为`raw`编码。
PS:**需要注意的是,编码一旦升级(int-->embstr-->raw),即使后期再把字符串修改为符合原编码能存储的格式时,编码也不会回退。**
- Redis 为什么这么快
- 什么是 Redis
- Redis 的安装
- Redis 到底有多快
- Redis 是单线程还是多线程
- Redis 为什么选择使用单线程来执行请求
- 什么是 IO 多路复用机制
- Redis 中 I/O 多路复用的应用
- 一个简单的字符串,为什么 Redis 要设计的如此特别
- Redis 的 9 种数据类型
- 二进制安全字符串
- sds 空间分配策略
- sds 和 C 语言字符串区别
- sds 是如何被存储的
- type 属性
- encoding 属性
- 通过牺牲速度来节省内存,Redis 是觉得自己太快了吗
- 什么是压缩列表
- ziplist 的存储结构
- entry 存储结构
- ziplist 数据示例
- ziplist 连锁更新问题
- 为了加快速度,Redis 都做了哪些“变态”设计
- 列表对象
- linkedlist
- linkedlist 和 ziplist 的选择
- quicklist
- 列表对象常用操作命令
- Redis 中哈希分布不均匀该怎么办
- 哈希对象
- hashtable
- ziplist
- ziplist 和 hashtable 的编码转换
- 哈希对象常用命令
- 同一份数据,Redis 为什么要存”两次”
- 五种基本类型之集合对象
- intset 编码
- 集合对象常用命令
- 五种基本类型之有序集合对象
- skiplist 编码
- ziplist 编码
- ziplist 和 skiplist 编码转换
- 有序集合对象常用命令
- 要想用活 Redis,Lua 脚本是绕不过去的坎
- 发布与订阅
- 基于频道的实现
- 基于模式的实现
- Lua 脚本
- Lua 脚本的调用
- Lua 脚本中执行 Redis 命令
- Lua 脚本摘要
- Lua 脚本文件
- 脚本异常
- 作为一款内存数据库,为什么断电后 Redis 数据不会丢失
- Redis 持久化机制
- RDB 持久化机制
- AOF 持久化机制
- 内存耗尽后 Redis 会发生什么
- 内存回收
- 过期策略
- 8 种淘汰策略
- LRU 算法
- LFU 算法
- 不能回滚的 Redis 事务还能用吗
- Redis 有事务吗
- Redis 事务实现原理
- Redis 事务 ACID 特性
- watch 命令
- watch 命令的作用
- watch 原理分析
- Redis 为什么不直接用 master-slave 集群
- Redis 集群方案
- 主从复制
- 配置一主两从 master-slave 集群
- 主从复制原理分析
- 主从服务的不足之处
- Sentinel(哨兵)机制为什么从神坛滑落
- 哨兵 Sentinel 机制
- Sentinel 原理分析
- 配置 Sentinel 集群
- Sentinel 机制实战
- Sentinel 机制的不足之处
- Redis Cluster 集群凭什么成为了最终的胜利者
- Redis 分布式集群方案
- 客户端实现分片
- 中间代理服务实现分片
- Redis Cluster 方案
- 手动配置一个 Redis Cluster 集群
- Redis Cluster 集群常用命令
- 客户端如何使用 Redis Cluster 集群
- Redis Cluster 的不足
- 如何从 10 亿数据中快速判断是否存在某一个元素
- 缓存雪崩
- 缓存击穿
- 缓存穿透
- 布隆过滤器(Bloom Filter)
- 布隆过滤器的 2 大特点
- 布隆过滤器的实现(Guava)
- 布隆过滤器的如何删除