redis模型(2):redis对象

一、对象

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

Redis中的每个对象都由一个redisObject结构表示。

typedef struct redisObject {
    // 对象类型
    unsigned type:4;
    // 编码 对象使用的底层数据结构
    unsigned encoding:4;
    // 指向对象的底层实现数据结构
    void *ptr;

    // 对象最后一次被访问的时间
    unsigned lru:REDIS_LRU_BITS; /* lru time (relative to server.lruclock) */
    // 引用计数--->用于内存回收
    int refcount;
} robj;

通过encoding属性来设定对象所使用的编码,使得Redis可以根据不同的使用场景来为一个对象设置不同的编码,从而优化对象在某一场景下的效率,极大地提升了redis的灵活性和效率。

1、字符串对象

字符串对象的编码可以是int、embstr、raw

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

(2)embstr:如果字符串对象保存的是一个长度<=39字节字符串值。

(3)raw:如果字符串对象保存的是一个长度>39字节字符串值,字符串对象将使用一个简单动态字符串SDS来保存这个字符串值,并将对象的编码设置为raw。

* embstr与raw区别:
embstr编码是专门用于保存短字符串的一种优化编码方式,这种编码和raw编码一样都使用redisObject结构和sdshdr结构来表示字符串对象,但是raw编码会调用两次内存分配函数来分别创建redisObject结构和sdshdr结构,而embstr编码则通过调用一次内存分配函数来分配一块连续的空间,空间中依次包含redisObject和sdshdr两个结构。

embstr编码的字符串对象在执行命令时产生的效果和raw编码相同,使用embstr编码的字符串对象来保存短字符串有以下好处:

  • embstr编码将创建字符串对象所需的内存分配次数从raw编码的两次降低为一次。
  • 释放embstr编码的字符串对象只需要调用一次内存释放函数,而释放raw编码的字符串对象需要调用两次内存释放函数。
  • 因为embstr编码的字符串对象的所有数据都保存在一块连续的内存里面,所以这种编码的字符串对象比起raw编码的字符串对象能够更好地利用缓存带来的优势。

* embstr编码的字符串对象实际上是只读的,当我们对 embstr 编码的字符串对象执行任何修改命令时, 程序会先将对象的编码从 embstr 转换成 raw , 然后再执行修改命令。因为这个原因, embstr 编码的字符串对象在执行修改命令之后, 总会变成一个 raw 编码的字符串对象。

* 为什么是39字节

2、列表对象

列表对象的编码可以是ziplist或者linkedlist

(1)ziplist

使用压缩列表作为底层实现,每个压缩列表节点保存了一个列表元素,例如执行rpush命令插入三个元素

rpush numbers 1 "three" 5

(2)linkedlist

使用双端链表作为底层实现,每个双端链表节点都保存了一个字符串对象,每个字符串对象都保存了一个列表元素。

linkedlist编码的列表对象在底层的双端链表结构中包含了多个字符串对象,字符串对象是redis五种类型的对象中唯一一种会被其他四中类型对象嵌套对象。

上图StringObject字样的格子表示一个字符串对象,完整字符串对象表示如下

(3)编码转换

当列表对象可以同时满足以下两个条件时,列表对象使用 ziplist 编码,否则使用linkedlist编码:

  • 列表对象保存的所有字符串元素的长度都小于 64 字节;
  • 列表对象保存的元素数量小于 512 个;
  • list-max-ziplist-value、list-max-ziplist-entries

* 区别:

  • 列表对象包含的元素比较少时,Redis使用压缩列表作为底层实现,因为压缩列表比双端链表更节约内存,并且在元素数量较少时,在内存中以连续块方式保存的压缩列表比起双端链表可以更快被载入到缓存中。
  • 随着列表对象包含的元素越来越多,使用压缩列表来保存元素的优势逐渐消失时,对象就会将底层实现从压缩列表转向功能更强、更适合保存大量元素的双端链表。

3、哈希对象

哈希对象的编码可以是ziplist或者hashtable。

(1)ziplist

每当有新的键值对要加入到哈希对象时,程序会先将保存了键的压缩列表节点推入到压缩列表表尾,然后再将保存了值的压缩列表节点推入到压缩列表表尾。因此:

  • 保存了同一键值对的两个节点总是紧挨在一起,保存键的节点在前,保存值的节点在后
  • 先添加到哈希对象中的键值对会被放在压缩列表的表头方向,而后来添加到哈希对象中的键值对会被放在压缩列表的表尾方向。

(2)hashtable

hashtable编码的哈希对象使用字典作为底层实现,哈希对象中的每个键值对都使用一个字典键值对来保存:

  • 字典的每个键都是一个字符串对象,对象中保存了键值对的键
  • 字典的每个值都是一个字符串对象,对象中保存了键值对的值

(3)编码转换

当哈希对象可以同时满足一下两个条件时,哈希对象使用ziplist编码,不能满足这两个条件的哈希对象需要使用hashtable编码

  • 哈希对象保存的所有键值对的键和值字符串长度都小于64字节。
  • 哈希对象保存的键值对数量小于512个。
  • hash-max-ziplist-value、hash-max-ziplist-entries

4、集合对象

集合对象的编码可以是intset或者hashtable

(1)intset

intset 编码的集合对象使用整数集合作为底层实现,集合对象包含的所有元素都被保存在整数集合里面。

(2)hashtable

hashtable编码的集合对象使用字典作为底层实现,字典的每个键都是一个字符串对象,每个字符串对象包含了一个集合元素,而字典的值则全部被设置为NULL。

(3)编码转换

当集合对象可以同时满足以下两个条件时,对象使用intset编码,否则使用hashtable编码

  • 集合对象保存的所有元素都是整数值。
  • 集合对象保存的元素数量不超过512个。
  • set-max-intset-entries

5、有序集合

有序集合对象的编码可以是ziplist或者skiplist

(1)ziplist

每个集合元素使用两个紧挨在一起的压缩列表节点来保存,第一个节点保存元素的成员(member),第二个元素保存元素的分值(score)。
集合元素按分值从小到大进行排序,分值较小的元素靠近表头,而分值较大的元素靠近表尾。

(2)skiplist

skiplist编码的有序集合对象使用zset结构作为底层实现,一个zset结构同时包含一个字典和一个跳跃表:

typedef struct zset {
    zskiplist *zsl;
    dict *dict;
} zset;
  • zsl 跳跃表按分值从小到大保存了所有集合元素, 每个跳跃表节点都保存了一个集合元素:跳跃表节点的 object 属性保存了元素的成员,score 属性则保存了元素的分值。 通过这个跳跃表,程序可以对有序集合进行范围型操作,比如 ZRANK 、 ZRANGE 等命令。
  • dict 字典为有序集合创建了一个从成员到分值的映射,字典中的每个键值对都保存了一个集合元素:字典的键保存了元素的成员,字典的值则保存了元素的分值。通过这个字典,程序可以用 O(1) 复杂度查找给定成员的分值,如 ZSCORE 命令。
  • 有序集合每个元素的成员都是一个字符串对象, 而每个元素的分值都是一个 double 类型的浮点数。

(3)编码转化

当有序集合对象可以同时满足以下条件时,对象使用ziplist编码,否则使用skiplist编码:

  • 有序集合保存的元素数量小于128个。
  • 有序集合保存的所有元素成员的长度都小于64字节。
  • zset-max-ziplist-entries、zset-max-ziplist-value。

(4)为什么有序集合需要同时使用跳跃表和字典来实现?

  • 如果只使用字典来实现有序集合, 那么虽然以 O(1) 复杂度查找成员的分值这一特性会被保留, 但是,因为字典以无序的方式来保存集合元素,所以每次在执行范围型操作 —— 比如 ZRANK 、 ZRANGE 等命令时, 程序都需要对字典保存的所有元素进行排序,完成这种排序需要至少 O(N log N) 时间复杂度,以及额外的 O(N) 内存空间(因为要创建一个数组来保存排序后的元素)。
  • 如果只使用跳跃表来实现有序集合,那么跳跃表执行范围型操作的所有优点都会被保留,但因为没有了字典,所以根据成员查找分值这一操作的复杂度将从 O(1) 上升为 O(log N) 。
  • 值得一提的是,虽然 zset 结构同时使用跳跃表和字典来保存有序集合元素, 但这两种数据结构都会通过指针来共享相同元素的成员和分值,所以同时使用跳跃表和字典来保存集合元素不会产生任何重复成员或者分值,也不会因此而浪费额外的内存。

二、对象特性

Redis中用于操作键的命令基本上可以分为两种类型,一种命令可以对任何类型的键执行,比如说del命令、expire命令、rename命令、type命令、object命令等,另一种命令只能对特定类型的键执行。

1、类型检查

  • 在执行一个类型特定命令之前,服务器会先检查输入数据库键的值对象是否为执行命令所需的类型。
  • 类型特定命令所进行的类型检查是通过redisObject结构的type属性来实现的,如果类型不正确,服务器将拒绝执行命令,并向客户端返回一个类型错误。

2、命令多态

Redis除了会校验值对象的类型,还会根据值对象的编码方式,选择正确的命令实现代码来执行命令。

  • 例如执行LLEN命令,服务器除了要确保执行命令的是列表键之外,还需要根据键的值对象所使用的编码来选择正确的LLEN命令实现:如果编码为ziplist,程序将使用 ziplistLen 函数来返回列表的长度;如果编码为linkedlist,程序将使用 listLength 函数来返回双端链表的长度。

3、内存回收

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

每个对象的引用计数信息由redisObject结构的refcount属性记录:

typedef struct redisObject {
    // ...
    //引用计数
    int refcount;
    // ...
} robj;

对象的引用计数信息会随着对象的使用状态而不断变化:

  • 在创建一个新对象时,引用计数的值会被初始化为1;
  • 当对象被一个新程序使用时,它的引用计数值会被增一;
  • 当对象不再被一个程序使用时,它的引用计数值会被减一;
  • 当对象的引用计数值变为0时,对象所占用的内存会被释放。

对象的整个生命周期可以划分为创建对象、操作对象、释放对象三个阶段。

4、对象共享

(1)除了用于实现引用计数内存回收机制之外, 对象的引用计数属性还带有对象共享的作用。在 Redis 中, 让多个键共享同一个值对象需要执行以下两个步骤:

  • 将数据库键的值指针指向一个现有的值对象;
  • 将被共享的值对象的引用计数增一。

例如包含整数值100的字符串对象同时被键A和键B共享之后,除了对象的引用计数从之前的1变成了2之外,其他属性都没有变化。
     共享对象机制对于节约内存非常有帮助,数据库中保存的相同值对象越多,对象共享机制就能节约越多的内存。

(2)

  • Redis会在初始化服务器时,创建一万个字符串对象,这些对象包含了从0到9999的所有整数值,当服务器需要用到值为0到9999的字符串对象时,服务器就会使用这些共享对象,而不是新创建对象。
  • 可以使用OBJECT REFCOUNT命令查看键A的值对象的引用计数。
  • 这些共享对象不单单只有字符串键可以使用,那些在数据结构中嵌套了字符串对象的对象(linkedlist编码的列表对象、hashtable编码的哈希对象、hashtable编码的集合对象,以及zset编码的有序集合对象)都可以使用这些共享对象。

(3)为什么Redis不共享包含字符串的对象?

当服务器考虑将一个共享对象设置为键的值对象时,程序需要先检查给定的共享对象和键想创建的目标对象是否完全相同,只有在共享对象和目标对象完全相同的情况下,程序才会将共享对象用作键的值对象,而一个共享对象保存的值越复杂,验证共享对象和目标对象是否相同所需的复杂度就会越高,消耗的CPU时间也会越多:

  • 如果共享对象是保存整数值的字符串对象,那么验证操作的复杂度为O(1);
  • 如果共享对象是保存字符串值的字符串对象,那么验证操作的复杂度为O(N);
  • 如果共享对象是包含了多个值(或者对象的)对象,比如列表对象或者哈希对象,那么验证操作的复杂度将会是O(N ^2)。

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

5、对象的空转时长

  • redisObject 结构的 lru 属性记录了对象最后一次被命令程序访问的时间。
  • OBJECT IDLETIME命令可以打印出键的空转时长。
  • 如果服务器打开了maxmemory选项,并且服务器用于回收内存的算法为volatile-lru或者 allkeys-lru ,那么当服务器占用的内存数超过了 maxmemory 选项所设置的上限值时, 空转时长较高的那部分键会优先被服务器释放, 从而回收内存。

三、常用命令

其中在使用时间复杂度为O(n)的命令时应特别注意

(1)字符串

(2)哈希

(3)列表

(4)集合

(5)有序集合

原文地址:https://www.cnblogs.com/zjxiang/p/12115729.html

时间: 2024-10-31 19:45:03

redis模型(2):redis对象的相关文章

redis模型(3):对象编码

一.简单动态字符串 Redis没有直接使用C语言传统的字符串表示,而是自己构建了一种名为简单动态字符串(SDS)的抽象类型作为Redis的默认字符串表示. struct sdshdr { // 记录 buf 数组中已使用字节的数量 // 等于 SDS 所保存字符串的长度 int len; // 记录 buf 数组中未使用字节的数量 int free; // 字节数组,用于保存字符串 char buf[]; }; SDS与C字符串的区别: 1.常数复杂度获取字符串长度 C 字符串并不记录自身的长度

Reactor事件模型在Redis中的应用

1 模型简介 Redis没有使用第三方的libevent等网络库,而是自己开发了一个单线程的Reactor模型的事件处理模型.而Memcached内部使用的libevent库,多线程模型. 综合对比可见:nginx,memcached,redis网络模型总结 Redis在主循环中统一处理文件事件和时间事件,信号事件则由专门的handler来处理. 文件事件,我理解为IO事件,Redis将产生事件套接字放入一个就绪队列中,即redisServer.aeEventLoop.fired数组,然后在ae

Redis的五种对象类型及其底层实现

目录(?)[-] Redis对象类型简介 Redis对象底层数据结构 字符串对象 列表对象 哈希对象 集合对象 有序集合对象 结尾 Redis对象类型简介 Redis是一种key/value型数据库,其中,每个key和value都是使用对象表示的.比如,我们执行以下代码: redis>SET message "hello redis" 其中的key是message,是一个包含了字符串"message"的对象.而value是一个包含了"hello re

Redis 存储字符串和对象

1 测试类 import redis.clients.RedisClinet; import redis.clients.SerializeUtil; import redis.clients.jedis.Jedis; public class Test { /** * Administrator * @param args */ public static void main(String[] args) { // 操作单独的文本串 Jedis redis= new Jedis( "10.2.

redis 系列15 数据对象的(类型检查,内存回收,对象共享)和数据库切换

原文:redis 系列15 数据对象的(类型检查,内存回收,对象共享)和数据库切换 一.  概述 对于前面的五章中,已清楚了数据对象的类型以及命令实现,其实还有一种数据对象为HyperLogLog,以后需要用到再了解.下面再了解类型检查,内存回收,对象共享,对象的空转时长. 1.1   类型检查与命令多态 redis中用于操作键的命令基本上可以分为两种类型,一种是可以对任何的键执行,如:del, expire,rename,type,object 这些命令等,对于这些命令属于多态命令.另一种命令

深入了解Redis【二】对象及数据结构综述

引言 Redis中每个键值对都是由对象组成: 键总是一个字符串对象(string) 值可以是字符串对象(string).列表对象(list).哈希对象(hash).集合对象(set).有序集合对象(zset). 介绍 redis官方网站中对其数据类型的简单介绍: An introduction to Redis data types and abstractions 摘抄一段关于redis key的介绍: Redis keys Redis keys are binary safe, this m

redis模型(4):集群

一.集群节点 1.节点 一个节点就是一个运行在集群模式下的Redis服务器,Redis服务器在启动时会根据cluster-enabled配置选项是否为yes来决定是否开启服务器的集群模式. 运行在集群模式下的 Redis 服务器会继续使用所有在单机模式中使用的服务器组件: 节点会继续使用文件事件处理器来处理命令请求和返回命令回复. 节点会继续使用时间事件处理器来执行serverCron函数,而serverCron函数又会调用集群模式特有的clusterCron函数.clusterCron函数负责

Redis(一)、Redis五种数据结构

Redis五种数据结构如下: 对redis来说,所有的key(键)都是字符串. 1.String 字符串类型 是redis中最基本的数据类型,一个key对应一个value. String类型是二进制安全的,意思是 redis 的 string 可以包含任何数据.如数字,字符串,jpg图片或者序列化的对象. 使用:get . set . del . incr. decr 等 127.0.0.1:6379> set hello world OK 127.0.0.1:6379> get hello

redis演练(7) redis Sentinel实现故障转移

书接上文<redis演练(6) redis主从模式搭建>. <redis演练(6) redis主从模式搭建>中仅仅配置了redis主从环境.分别配置了2个主从结构. 分别是1.有向无环,2星型模型.配置起来非常简单.但是,遗留了一个尾巴,没有阐述.如果master宕掉了怎么办?redis如何实现fail-over故障转移?本文,就重点说一下这块.主要内容 手动实现fail-over效果 sentinel实现自动fail-over效果 手动实现fail-over效果 #有向无环模型(