前文 Redis 设计与实现 2:Redis 对象 说到,五大数据类型都会封装成 RedisObject

typedef struct redisObject {
unsigned type:4; // 类型
unsigned encoding:4; // 编码
// ...
void *ptr; // 指向具体底层数据的指针
} robj;

不同数据类型的主要区别就是 typeencoding 属性的差异,同一种数据类型,有不同的编码。

一、编码类型

字符串的编码有rawembstrint三种。

  • raw 用于长字符串。
  • embstr 用于短字符串。
  • int 用于整数类型。

定义在 server.h 中,这里只列出 string 类型的编码

#define OBJ_ENCODING_RAW 0
#define OBJ_ENCODING_INT 1
#define OBJ_ENCODING_EMBSTR 8

编码 1:raw

raw 编码主要用来保存长度超过 44 的字符串。其真实数据,由 sdshdr 结构来表示存储,外层还是由 redisObject 包装。

sdshdr 的结构在前文 Redis 设计与实现 3:字符串 SDS 中有讲到。

sdshdr 结构大致如下:



redisObject 中的 ptr 指针,就是指向 sds

编码 2:embstr

embstr 编码是专门用于保存短字符串的一种优化编码方式。当字符串的长度小于等于 44 的时候,将采用 embstr 编码。

创建字符串对象的代码如下(object.c):

#define OBJ_ENCODING_EMBSTR_SIZE_LIMIT 44
robj *createStringObject(const char *ptr, size_t len) {
if (len <= OBJ_ENCODING_EMBSTR_SIZE_LIMIT)
return createEmbeddedStringObject(ptr,len);
else
return createRawStringObject(ptr,len);
}

embstr 有个显著的特点,就是 redisObjectsds 的内存是挨在一起的。挨在一起的好处:

  • 分配内存的时候,只需要分配一次。而 raw 编码的sdsredisObject分离,就要分配两次内存。
  • 同样,释放内存也只需要释放一次。
  • 连续内存能更好利用内存带来的优势。

embstr 问题一:那么为什么 embstr 跟 raw 的界限是 44 呢?

  • embstrsds使用了 sdshdr8sdshdr8 头占用了 3 个字节:
struct __attribute__ ((__packed__)) sdshdr8 {
uint8_t len; /* 1 字节 */
uint8_t alloc; /* 1 字节 */
unsigned char flags; /* 1 字节 */
char buf[];
};
  • 另外还有 redisObject 占用 16 个字节 (4 + 4 + 24 + 32 + 64 = 128 位):
typedef struct redisObject {
unsigned type:4;
unsigned encoding:4;
unsigned lru:LRU_BITS; // #define LRU_BITS 24
int refcount; // 32 位
void *ptr; // 64 位
} robj;

redisObject + sdshdr8 至少需要 3 + 16 = 19 字节。

redis 认为如果超过 64 字节就是大字符串,所以在 redisObject+ sdshdr8 的总长度是 64 字节的情况下,留给 buf 的长度就只剩下 45 字节,由于字符串结尾需要一个 \0 占用一个字节,所以留个字符串的长度就只有 44 字节了。

公式:64 - 3(sdshdr8 ) - 16(redisObject) - 1(\0) = 44

embstr 问题二:为什么网上有的博文说 embstr 跟 raw 的界限是 39

在 redis 3.2 版本之前,这个界限的确是 39,为什么后面改成 44 了呢?

那是因为 sdshdr 的结构在 3.2 版本的时候修改了。3.2 之前的 sdshdr 结构是:

struct sdshdr {
unsigned int len; // 4 字节
unsigned int free; // 4 字节
char buf[];
};

旧版本的 sdshdr 的头占用了 8 个字节,比新版本的多了 5 个字节,所以界限就是 44 - 5 = 39 啦!

编码 3:int

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

相对于用 raw 编码,int 编码既节省了指针占用的内存,也节省了sds结构的内存。

redis> SET int_key 12345
OK
redis> OBJECT ENCODING int_key
"int"

下图为存着 12345string 示例结构:

二、编码的转换

1. int 转 raw

  • 当字符串传的不是整数的时候,int 就会转成 raw 编码。
  • 如果执行了一些修改的命令,如 append 等( set 不算),都会转成 raw 编码。因为这些操作只有字符串才支持。
  • 一旦编码变为 raw 之后,将不会再转成 embstr
127.0.0.1:6379> SET num 1
OK
127.0.0.1:6379> OBJECT ENCODING num
"int"
127.0.0.1:6379> APPEND num 2
(integer) 2
127.0.0.1:6379> OBJECT ENCODING num
"raw"
127.0.0.1:6379> SET num 12
OK
127.0.0.1:6379> OBJECT ENCODING num
"int"

2. embstr 转 raw

  • 如果执行了一些修改的命令,如append等,都会转成 raw 编码,不管修改后字符串的长度。因为没有给 embstr 编码实现修改接口,所以实际上 embsr 是只读的。
  • 一旦编码变为 raw 之后,将不会再转成 embstr

三、重点回顾

  • 字符串对象有三种编码,rawembstrint
  • raw 负责保存长字符串; embstr 负责保存短字符串; int 负责保存整数。
  • intembstr 在修改的时候,会转成 raw 编码,并且不再转回

本文的分析没有特殊说明都是基于 Redis 6.0 版本源码

redis 6.0 源码:https://github.com/redis/redis/tree/6.0

Redis 设计与实现 6:五大数据类型之字符串的更多相关文章

  1. redis学习(七)——五大数据类型总结:字符串、散列、列表、集合和有序集合

    目录 字符串类型(String) 散列类型(Hash) 列表类型(List) 集合类型(Set) 有序集合类型(SortedSet) 其它命令 一.字符串类型(String) 1.介绍: 字符串类型是 ...

  2. Redis设计与实现读书笔记——简单动态字符串

    前言 项目里用到了redis数据结构,不想只是简单的调用api,这里对我的读书笔记做一下记录.原文地址: http://www.redisbook.com/en/latest/internal-dat ...

  3. 前端总结·基础篇·JS(一)五大数据类型之字符串(String)

    前端总结系列 前端总结·基础篇·CSS(一)布局 前端总结·基础篇·CSS(二)视觉 前端总结·基础篇·CSS(二)补充 前端总结·基础篇·JS(一)五大数据类型之字符串(String) 目录 这是& ...

  4. 【笔记】《Redis设计与实现》chapter2 简单动态字符串

    ------------恢复内容开始------------ 2.1 SDS的定义 struct sdshdr{ // 记录buf数组中已使用字节的数量 // 等于SDS所保存字符串的长度(不含'\0 ...

  5. Redis详解(五)------ redis的五大数据类型实现原理

    前面两篇博客,第一篇介绍了五大数据类型的基本用法,第二篇介绍了Redis底层的六种数据结构.在Redis中,并没有直接使用这些数据结构来实现键值对数据库,而是基于这些数据结构创建了一个对象系统,这些对 ...

  6. Redis 详解 (五) redis的五大数据类型实现原理

    目录 1.对象的类型与编码 ①.type属性 ②.encoding 属性和 *prt 指针 2.字符串对象 3.列表对象 4.哈希对象 5.集合对象 6.有序集合对象 7.五大数据类型的应用场景 8. ...

  7. redis五大数据类型以及常用操作命令

    Redis的五大数据类型 String(字符串) string是redis最基本的类型,你可以理解成与Memcached一模一样的类型,一个key对应一个value.string类型是二进制安全的.意 ...

  8. redis 五大数据类型使用

    redis 五大数据类型使用 字符串str 单个值 127.0.0.1:6379> set name pp # 设置键值[O(1)] OK 127.0.0.1:6379> setex na ...

  9. redis的五大数据类型实现原理

    1.对象的类型与编码 Redis使用前面说的五大数据类型来表示键和值,每次在Redis数据库中创建一个键值对时,至少会创建两个对象,一个是键对象,一个是值对象,而Redis中的每个对象都是由 redi ...

随机推荐

  1. 朴素的模式匹配算法BF

    1 #include <iostream> 2 using namespace std; 3 int BF(char S[], char T[]); 4 int main() 5 { 6 ...

  2. SpringCloud 源码系列(1)—— 注册中心 Eureka(上)

    Eureka 是 Netflix 公司开源的一个服务注册与发现的组件,和其他 Netflix 公司的服务组件(例如负载均衡.熔断器.网关等)一起,被 Spring Cloud 整合为 Spring C ...

  3. 【SDOI2013】JZOJ8月3日提高组T4 直径

    题目 题目描述 小 Q 最近学习了一些图论知识.根据课本,有如下定义. 树:无回路且连通的无向图,每条边都有正整数的权值来表示其长度.如果一棵树有 N 个节点,可以证明其有且仅有 N-1 条边. 路径 ...

  4. paddleocr安装笔记

    下载解压安装 wget http://npm.taobao.org/mirrors/python/3.7.6/Python-3.7.6.tgztar xvf Python-3.7.6.tgzcd Py ...

  5. day6(celery原理与组件)

    1.Celery介绍 1.1 celery应用举例 Celery 是一个 基于python开发的分布式异步消息任务队列,通过它可以轻松的实现任务的异步处理,如果你的业务场景中需要用到异步任务,就可以考 ...

  6. Java数据结构(十四)—— 平衡二叉树(AVL树)

    平衡二叉树(AVL树) 二叉排序树问题分析 左子树全部为空,从形式上看更像一个单链表 插入速度没有影响 查询速度明显降低 解决方案:平衡二叉树 基本介绍 平衡二叉树也叫二叉搜索树,保证查询效率较高 它 ...

  7. Go语言基础--1.1 变量的声明

    1.标准格式: var name type     (var 关键字 name 变量名 type 类型)   命名规则:建议使用驼峰命名法 例如:var userName string    var ...

  8. DVWA SQL Injection Medium

    Mdeium 基本的步骤及知识点在上节已经提到过这里不再赘述:https://blog.csdn.net/WQ_BCJ/article/details/84554777 1)与low级别不同,本次采用 ...

  9. 3、pytorch实现最基础的MLP网络

    %matplotlib inline import numpy as np import torch from torch import nn import matplotlib.pyplot as ...

  10. Scrum 冲刺 第五篇

    Scrum 冲刺 第五篇 每日会议照片 昨天已完成工作 队员 昨日完成任务 黄梓浩 初步完成app项目架构搭建 黄清山 完成部分个人界面模块数据库的接口 邓富荣 完成后台首页模块数据库的接口 钟俊豪 ...