简介

Redis 使用对象存储数据库中的键和值,每当在 Redis 中创建一个新的键值对时,都会创建两个对象:一个是键对象,另一个是值对象。

其中,Redis 的每种对象都由对象结构和对应编码的数据结构组合而成,而每种对象类型对应若干编码方式,不同编码方式对应的底层数据结构也会有所不同。

数据库结构

Redis 服务器的数据库都保存在 redisServerdb 数组中,数组中的每个项都是 redisDb 结构,每个 redisDb 结构代表一个数据库。

下面是部分 redisServer 结构:

struct redisServer {
redisDb *db; // 保存数据库的数组
int dbnum; // 服务器的数据库数量
// ...
};

其中,初始化服务器时,会根据 dbnum 的值决定创建多少个数据库。默认情况下,dbnum 的值是 16。

切换数据库

默认情况下,Redis 客户端的目标数据库是 0 号数据库,但是客户端可以使用 SELECT 命令切换目标数据库。

需要注意的是,Redis 现在没有向客户端返回目标数据库的命令,对数据库进行误操作极易出现不符合预期的情况,尤其是像 FLUSHDB 这样的命令。

比较好的做法是尽量少地在代码中切换数据库,即使是在命令行操作,也尽量显式地切换到指定的数据库,然后再执行命令。

数据库键空间

每一个数据库中都存储了一个字典,这个字典存储了数据库中的所有键值对,这个字典又被称为键空间。

所有对数据库中键值对的增删查改操作,实际上都是在操作键空间字典。

只是,由于数据库可以存储多种不同的数据结构类型,这些增删查改操作,都会使用对应数据结构提供的函数执行。

读写键空间的维护操作

当使用 Redis 命令对键空间字典进行读写操作时,服务器不仅会执行这些读写操作,还会做一些维护性的操作,提高 Redis 的可用性,其中包括:

  • 读取一个键时,服务器会根据键是否存在来更新键空间命中次数和不命中次数
  • 读取到一个键之后,服务器会更新这个键的 lru 属性
  • 如果服务器读取到键之后,发现这个键已经过期,会先删除这个键,再执行后续的操作
  • 如果有客户端使用 WATCH 命令监视这个键,服务器修改这个键之后,会将这个键标记为 dirty 状态
  • 服务器每次修改一个键之后,都会对脏计数器的值增 1,这个计数器会触发服务器的持久化或复制操作
  • 如果服务器开启了通知功能,那么对这个键做修改操作之后,服务器将按配置发送对应的数据库通知

类型与编码

Redis 中的每个对象都是由一个 redisObject 结构表示,其结构如下:

typedef struct redisObject {
unsigned type:4; // 类型
unsigned encoding:4; // 编码
unsigned lru:LRU_BITS; // 记录最后访问的时间
int refcount; // 引用计数
void *ptr; // 指向底层实现数据结构的指针
} robj;

其中 typeencodingptr 是最重要的三个属性。

数据类型

对象的 type 属性记录了数据结构的类型,它总是以下枚举值之一:

  • REDIS_STRING
  • REDIS_LIST
  • REDIS_HASH
  • REDIS_SET
  • REDIS_ZSET

对象编码

对象的 encoding 属性记录了 ptr 指针指向对象的编码方式,它总是以下枚举值之一:

  • OBJ_ENCODING_RAW
  • OBJ_ENCODING_INT
  • OBJ_ENCODING_HT
  • OBJ_ENCODING_ZIPMAP
  • OBJ_ENCODING_LINKEDLIST
  • OBJ_ENCODING_ZIPLIST
  • OBJ_ENCODING_INTSET
  • OBJ_ENCODING_SKIPLIST
  • OBJ_ENCODING_EMBSTR
  • OBJ_ENCODING_QUICKLIST
  • OBJ_ENCODING_STREAM

通过使用 encoding 属性设定对象的编码方式,而不是使用固定编码,这样极大地提高了 Redis 的灵活性和效率,也方便 Redis 针对不同的场景选择不同的编码,针对性地做优化。

对象指针

对象的 ptr 属性是一个指针,指向实际保存值的数据结构。

空转时间

对象的 lru 属性记录了对象最后一次被命令程序访问的时间。空转时间指的是当前时间减去 lru 属性得到的时长,即未被访问的时长。

键的空转时间在内存回收算法是 volatile-lruallkeys-lru 时使用到,当服务器占用的内存超过了 maxmemory 之后,空转时长较高的那部分键会优先被服务器释放,从而回收内存。

命令执行流程

Redis 中用于操作键的命令分为两类:任何类型的键都可以执行的命令、针对特定类型的键可执行的命令。例如 DELEXPIRE 等命令属于前者,SETHSET 等命令属于后者。

针对特定类型的键的执行命令,执行前需要检查键的类型,确定当前键是否可执行当前命令。

在 Redis 中,一个数据类型有可能对应多个编码方式,在检查完键的类型之后,还需要根据数据类型的不同编码进行多态处理。

因此,当处理一个特定类型命令的时候,执行的步骤如下:

  • 根据给定的 key 名称,在数据库字典中查找相对应的 Redis 对象,如果没有找到,返回 NULL
  • 检查 Redis 对象中的 type 属性和执行命令所需的类型是否相符,如果不相符,返回类型错误
  • 根据 Redis 对象中的 encoding 属性选择合适的操作函数来处理底层数据结构
  • 将操作函数的返回值作为命令请求的响应返回给客户端

对象共享

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

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

需要注意的是,共享对象只能被字典和双向链表这类能带有指针的数据结构使用。

内存回收

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

对象的引用计数信息通过 refcount 属性记录,其使用如下:

  • 当创建新对象时,引用计数的值会初始化为 1
  • 当这个对象被共享时,引用计数的值会自增
  • 当使用完一个对象后,或者消除对这个对象的引用之后,引用计数的值会自减
  • 当对象的引用计数值变为 0 时,对象所占用的内存会被释放

Redis - 对象结构的更多相关文章

  1. Redis对象的设计与实现

    一.Redis对象结构Redis中的每个对象都由一个redisObject结构表示: typedef struct redisObject { unsigned type;//类型 unsigned ...

  2. Redis短结构与分片

    本文将介绍两种降低Redis内存占用的方法——使用短结构存储数据和对数据进行分片. 降低Redis内存占用有助于减少创建快照和加载快照所需的时间.提升载入AOF文件和重写AOF文件时的效率.缩短从服务 ...

  3. 【redis源码阅读】redis对象

    结构定义 在redis中,对象的数据结构定义如下: ​typedef struct redisObject { ​unsigned type:4; ​unsgined encoding:4; ​uns ...

  4. Redis对象类型

    Redis对象类型 Redis基于基础的数据结构创建的对象: 字符串对象. 列表对象. 哈希对象. 集合对象 有序集合对象. 对象回收:Redis对象系统实现了基于引用计数技术的内存回收机制,当程序不 ...

  5. Redis底层探秘(五):Redis对象

    前面几篇文章,我们一起学习了redis用到的所有主要数据结构,比如简单动态字符串(sds).双端链表.字典.压缩列表.整数集合等等. redis并没有直接使用这些数据结构来实现键值对数据库,而是基于这 ...

  6. 面试官:你了解过Redis对象底层实现吗

    上一章我们讲了Redis的底层数据结构,不了解的人可能会有疑问:这个和平时用的五大对象有啥关系呢?这一章我们就主要解释他们所建立的联系. 看这个文件之前,如果对ziplist.skiplist.int ...

  7. Redis之对象篇——Redis对象系统简介

    Redis之对象篇--Redis对象系统简介 前言     之前几篇文章,简单介绍 Redis用到的所有主要数据结构,简单动态字符串(SDS).双端链表.字典.压缩列表.整数集合.跳跃表. 图解Red ...

  8. Redis对象——字符串

    文章导航-readme 前言     上一篇文章Redis之对象篇--Redis对象系统简介简单介绍了Redis的对象系统.Redis使用对象来表示数据库中的键和值每个对象都由一个redisObjec ...

  9. Redis底层结构全了解

    第一篇文章,思来想去,写一写Redis吧,最近在深入研究它. 一丶Redis底层结构 1. redis 存储结构 redis的存储结构从外层往内层依次是redisDb.dict.dictht.dict ...

  10. Redis的结构和运作机制

    目录 1.数据库的结构 1.1 字典的底层实现 2.过期键的检查和清除 2.1 定时删除 2.2 惰性删除 2.3 定期删除 2.4 对RDB.AOF和复制的影响 3.持久化机制 3.1 RDB方式 ...

随机推荐

  1. python机器学习——随机森林算法

    背景与原理: 首先我们需要知道集成学习的概念,所谓集成学习,就是使用一系列学习器进行学习,并且通过某种规则把这些学习器的学习结果整合起来从而获得比单个学习器学习效果更好的机器学习方法.这样的方法可以用 ...

  2. limit资源限制ulimit 详解

    系统性能一直是一个受关注的话题,如何通过最简单的设置来实现最有效的性能调优,如何在有限资源的条件下保证程序的运作,ulimit 是我们在处理这些问题时,经常使用的一种简单手段.ulimit 是一种 L ...

  3. PHP Redis - 事务

    Redis 事务可以一次执行多个命令, 并有两个重要的保证: ① 事务是一个单独的隔离操作:事务中的所有命令都会序列化.按顺序地执行.事务在执行的过程中,不会被其他客户端发送来的命令请求所打断. ② ...

  4. Windows下配置Hadoop的Java开发环境

    最近在学习用java来编写MapReduce程序,我是先在windows中开发完成,运行没有问题之后,再打成jar包,放到Linux集群中运行,由于在配置windows的开发环境的时候就花了大半天的时 ...

  5. Jupyter lab 切换kernel

    在使用pytorch的时候需要用到pandas这个包,报错说"no module named pandas", 但是我在终端查找了conda 装了pandas,所以不是安装的问题, ...

  6. 用python提取txt文件中的特定信息并写入Excel

    这个是用 excel里面的 去掉空格最后导出的一个list: 原本是有空格的 后面是抵消了中间的空格. 然后 这里侧重说一下什么是split()函数 语法:str.split(str="&q ...

  7. 更多Linux实用命令

    更多实用命令 进程相关 当程序运行在系统上时,我们称之为进程(process).想监测这些进程,需要熟悉 ps/top 等命令的用法.ps 命令好比工具中的瑞士军刀,它能输出运行在系统上的所有程序的许 ...

  8. Oracle学习-----基本SQL select语句

    一.基本select语句 SELECT 标识  选择那些列 FROM     标识从哪个表中选择 select * 标识 全部选择 select department_id, location_id ...

  9. 简单了解promise

    promise是什么: JavaScript中存在很多异步操作, Promise将异步操作队列化,按照期望的顺序执行,返回 符合预期的结果.可以通过链式调用多个 Promise达到我们的目的. Pro ...

  10. MySQL -my.cnf配置文件优化

    # [mysqld] datadir=/var/lib/mysql #socket=/var/lib/mysql/mysql.sock user=mysql ### 设置主从的时候的唯一ID 每台主机 ...