Redis`数据结构`与`对象`概述

news2024/11/26 2:48:10

文章目录

  • Redis`数据结构`与`对象`概述
    • 一、数据结构
      • 1、简单动态字符串(SDS)
        • SDS结构体定义
        • SDS结构示意图
        • 使用SDS的五个优点
      • 2、双端链表(list)
        • 链表结构体定义
        • list结构示意图
      • 3、字典(dict)
        • 字典结构体定义
        • dict结构示意图
        • hash算法
        • 渐进式rehash
      • 4、跳跃表(skiplist)
        • 跳跃表结构体定义
        • 层与跨度
        • skiplist结构示意图
        • 跳跃表特点
      • 5、整数集合(intset)
        • 整数集合结构体定义
        • intset结构示意图
        • 升级
      • 6、压缩列表(ziplist)
        • ziplist结构示意
        • 压缩表特点
        • previous_entry_length
        • 连锁更新
      • 7、快速列表(quicklist)
        • 快速列表结构体定义
        • quicklist结构示意图
    • 二、RedisObject
      • 1、字符串对象
        • int
        • raw
        • emstr
        • 编码转换
      • 2、列表对象
        • ziplist
        • linkedlist
        • 编码转换
      • 3、哈希对象
        • ziplist
        • hashtable
        • 编码转换
      • 4、集合对象
        • intset
        • hashtable
        • 编码转换
      • 5、有序集合对象
        • ziplist
        • skiplist
        • 编码转换

Redis数据结构对象概述


本篇文章通过剖析Redis五种不同类型的对象所使用的底层数据结构,让我们更好的了解这些数据结构是如何深刻地影响对象的功能和性能的

Redis 数据库里面的每个键值对(key-value) 都是由对象(object)组成的:
数据库的键总是一个字符串对象(sds);
数据库的值则可以是字符串对象(string)、列表对象(list)、哈希对象(hash)、集合对象(set)、有序集合(sort set)对象这五种对象中的其中一种。

先用一张图概括:
在这里插入图片描述

Redis用到的所有主要数据结构包括:简单动态字符串、双端链表、字典、跳跃表、整数集合、压缩列表。

Redis的对象系统包括:字符串对象、列表对象、哈希对象、集合对象、有序集合对象。

首先弄清楚Redis数据结构与对象之间的关系:对象是对底层数据结构的二次封装或者抽象,通过编码(每种类型的对象都至少使用了两种不同的编码),使得每种类型的对象都用到了至少一种数据结构。

一、数据结构

Redis用到的所有主要数据结构包括:简单动态字符串、双端链表、字典、跳跃表、整数集合、压缩列表。

1、简单动态字符串(SDS)

Redis 没有直接使用C语言传统的字符串表示,而是自己构建了一种名为简单动态字符串(simple dynamic string SDS)的抽象类型,并将SDS用作Redis 的默认字符串表示。除了用来保存字符串以外,SDS还被用作缓冲区(buffer)AOF模块中的AOF缓冲区。

SDS结构体定义

typedef struct sdshdr {
    int len;      // 记录已经使用的空间长度
    int free;     // 记录未使用字节的数量
    char buf[];   // 字节数组,用于保存字符串
} listNode;

SDS结构示意图

在这里插入图片描述

使用SDS的五个优点

  • 1、获取一个SDS长度的复杂度为O(1);

    C 语言里获取一个长度为字符串的长度,必须遍历整个字符串。
    SDS 的数据结构中可以通过获取len 属性的值,直接知道字符串长度。

  • 2、杜绝缓冲区溢出;

    SDS空间分配策略完全杜绝了发生缓冲区溢出的可能性:
    当我们需要对一个SDS 进行修改的时候,redis 会在执行拼接操作之前,预先检查给定SDS 空间是否足够,如果不够,会先拓展SDS 的空间,然后再执行拼接操作。

  • 3、减少修改字符串带来的内存重分配次数;

    SDS采用了空间预分配策略惰性空间释放策略来避免内存分配问题。

    空间预分配策略是指,每次 SDS 进行空间扩展时,程序不但为其分配所需的空间,还会为其分配额外的未使用空间,以减少内存再分配次数。额外分配的未使用空间大小取决于空间扩展后SDS 的 len 属性值。

    • 如果 len 属性值小于 1M,那么分配的未使用空间 free 的大小与 len 属性值相同。
    • 如果 len 属性值大于等于 1M ,那么分配的未使用空间 free 的大小固定是 1M。

    惰性空间释放策略是指,SDS 字符串长度如果缩短,那么多出的未使用空间将暂时不释放,而是增加到 free 中。以使后期扩展 SDS 时减少内存 再分配次数。如果要释放 SDS 的未使用空间,则可通过 sdsRemoveFreeSpace()函数来释放。

  • 4、二进制安全;

    不像C语言, 除了能保存文本数据,也能保存像图片,音频,视频,压缩文件这样的二进制数据。

  • 5、兼容部分C函数。

    可以使用一部分<string.h>库中的函数。如果是对比C语言,这并不优点,只能算特点。

C字符串SDS
获取字符串长度的复杂度为O(N)获取字符串长度的复杂度为O(1)
API 是不安全的,可能会造成缓冲区溢出API 是安全的,不会造成缓冲区溢出
修改字符串长度N次必然需要执行N次内存重分配修改字符串长度N次最多执行N次内存重分配
只能保存文本数据可以保存二进制数据和文本文数据
可以使用所有<String.h>库中的函数可以使用一部分<string.h>库中的函数

2、双端链表(list)

链表提供了高效的节点重排能力,以及顺序性的节点访问方式,并且可以通过增删节点来灵活地调整链表的长度。

链表在Redis 中的应用非常广泛,比如列表键的底层实现之一就是链表。当一个列表键包含了数量较多的元素,又或者列表中包含的元素都是比较长的字符串时,Redis 就会使用链表作为列表键的底层实现。

链表结构体定义

// 链表节点
typedef struct listNode {
    struct listNode *prev;
    struct listNode *next;
    void *value;
} listNode;

// 链表【通过链表持有链表节点,操作起来更加方便】
typedef struct list {
    listNode *head;  //表头节点
    listNode *tail;  //表尾节点
    void *(*dup)(void *ptr);  //节点值复制函数
    void (*free)(void *ptr);  //节点值释放函数
    int (*match)(void *ptr, void *key);  //节点值对比函数
    unsigned long len;    //链表长度
} list;

// 由链表结构体可以看出链表特性: 双端(有前后指针)、无环、表头和表尾、长度计数器、多态(通过dup、free、match三个属性设置类型特定函数)。

list结构示意图

在这里插入图片描述

3、字典(dict)

字典是一种用于保存键值对的抽象数据结构。

在字典中,一个键(key)可以和一个值(value)进行关联,字典中的每个键都是独一无二的。在C语言中,并没有这种数据结构,但是Redis 中构建了自己的字典实现。

字典结构体定义

// 字典
struct dict {
    dictType *type;  // 类型指定的哈希函数,用于计算哈希值
    dictht *ht[2];   // 哈希表
    long rehashidx;  // rehash索引,用于判定当前是否在进行rehash
    void *metadata[];           
};

// 哈希表
typedef struct dictht {
   dictEntry **table;      //哈希表数组
   unsigned long size;     //哈希表大小
   unsigned long sizemask; //哈希表大小掩码,用于计算索引值
   unsigned long used;     //该哈希表已有节点的数量
}

// 哈希表节点
struct dictEntry {
    void *key;     // 键
    union {        // 值
        void *val;
        uint64_t u64;
        int64_t s64;
        double d;
    } v;
    struct dictEntry *next;     // 链地址法解决hash冲突 
    void *metadata[];          
};

dict结构示意图

在这里插入图片描述

hash算法

当要将一个新的键值对添加到字典里时,程序需要先根据键值对的键计算出哈希值和索引值,然后再根据索引值,将包含新键值对的哈希表节点放到哈希表数组的指定索引上面。

第一步:先计算出哈希值;
hash=dict→type→hashFunction(key);

第二步:根据计算出来的哈希值 和 哈希表结构的sizemask属性,计算出索引值。
index=hash & dict→ht[x].sizemask;

如何计算哈希值?
当字典被用作数据库的底层实现,或者哈希建的底层实现时,redis使用MurmurHash2算法来计算键的哈希值【MurmurHash2算法不仅计算速度快,而且即使键是有规律的,计算出来的哈希值也是随机的】

渐进式rehash

随着对哈希表的不断操作,哈希表保存的键值用作对会逐渐的发生改变,为了让哈希表的负载因子维持在一个合理的范围之内,我们需要对哈希表的大小进行相应的扩展或者压缩,这时候,我们可以通过 rehash(重新散列)操作来完成。这个rehash 操作并不是一次性、集中式完成的,而是分多次、渐进式地完成的。

哈希表空间分配规则:
如果执行的是拓展操作,那么ht[1] 的大小为第一个大于等于ht[0] 的2的n次幂。
如果执行的是收缩操作,那么ht[1] 的大小为第一个大于等于ht[0] 的2的n次幂。

渐进式rehash的详细步骤:
1、为ht[1] 分配空间,让字典同时持有ht[0]和ht[1]两个哈希表。
2、在几点钟维持一个索引计数器变量rehashidx,并将它的值设置为0,表示rehash 开始。
3、在rehash 进行期间,每次对字典执行CRUD操作时,程序除了执行指定的操作以外,还会将ht[0]中的数据rehash 到ht[1]表中,并且将rehashidx加一。【rehash过程中,新增节点操作始终在ht[1]中进行】
4、当ht[0]中所有数据转移到ht[1]中时,将rehashidx 设置成-1,表示rehash 结束。

采用渐进式rehash 的好处在于它采取分而治之的方式,避免了集中式rehash带来的庞大计算量。

4、跳跃表(skiplist)

跳跃表(skiplist)是一种有序数据结构,它通过在每个节点中维持多个指向其他节点的指针,从而达到快速访问节点的目的。跳跃表是一种随机化的数据,跳跃表以有序的方式在层次化的链表中保存元素,效率可以和平衡树媲美【查找、删除、添加等操作都可以在对数期望时间下完成,并且比起平衡树来说,跳跃表的实现要简单直观得多】

Redis 只在两个地方用到了跳跃表,一个是实现有序集合键,另外一个是在集群节点中用作内部数据结构【slots-to-keys】。

跳跃表结构体定义

/* ZSETs use a specialized version of Skiplists */
typedef struct zskiplistNode {
    sds ele;         //成员对象
    double score;    //分值
    struct zskiplistNode *backward;  //后退指针
    struct zskiplistLevel {          //层
        struct zskiplistNode *forward;  //前进指针
        unsigned long span;          //跨度,用于记录两个节点之间的距离
    } level[];
} zskiplistNode;


typedef struct zskiplist {
    struct zskiplistNode *header, *tail;   //表头节点和表尾节点
    unsigned long length;        //表中节点数量
    int level;          //表中层数最大的节点的层数
} zskiplist;

层与跨度

跳跃表在每次创建一个新跳跃表节点的时候,程序都根据幂次定律(power law:越大的数出现的概率越小)随机生成一个介于1到32之间的值作为level数组的大小,这个大小就是层的高度。

跨度,则是用于记录两个节点之间的距离。

skiplist结构示意图

在这里插入图片描述

跳跃表特点

跳跃表是有序集合的底层实现之一。
主要有zskiplist 和zskiplistNode两个结构组成。
每个跳跃表节点的层高都是1至32之间的随机数。
在同一个跳跃表中,多个节点可以包含相同的分值,但每个节点的对象必须是唯一的。
节点按照分值的大小从大到小排序,如果分值相同,则按成员对象大小排序。

5、整数集合(intset)

整数集合是集合键Set的底层实现之一,当一个集合中只包含整数,且这个集合中的元素数量不多时,redis就会使用整数集合intset作为集合的底层实现。

整数集合结构体定义

typedef struct intset {
    uint32_t encoding;  // 编码方式——int16/int32/int64等
    uint32_t length;    // 长度
    int8_t contents[];  // 用于保存元素的数组
} intset;

intset结构示意图

在这里插入图片描述

升级

要将一个新元素添加到整数集合里,并且新元素的类型比整数集合 现有元素的类型都要长时,整数集合需要先进行升级(upgrade),然后才能将新元素添加到整数集合里。

升级步骤:1、分配空间;2、转换类型,排序不变;3、添加新元素。

没有降级操作:一旦整数集合进行了升级,编码类型就会保持升级后的状态。

6、压缩列表(ziplist)

压缩列表是列表键和哈希键的底层实现之一。

压缩列表ziplist结构本身就是一个连续的内存块:由表头、若干个entry节点和压缩列表尾部标识符zlend组成,通过一系列编码规则,提高内存的利用率,使用于存储整数和短字符串

压缩列表ziplist结构的缺点是:每次插入或删除一个元素时,都需要进行频繁的调用realloc()函数进行内存的扩展或减小,然后进行数据”搬移”,甚至可能引发连锁更新,造成严重效率的损失。

ziplist结构示意

在这里插入图片描述

zlbytes:用于记录整个压缩列表占用的内存字节数。
zltail:记录要列表尾节点距离压缩列表的起始地址有多少字节。用于快速定位尾节点。
zllen:记录了压缩列表包含的节点数量。
entryX:压缩列表包含的各个节点。
zlend:用于标记压缩列表的末端。
value:也叫content,可以用来保存一个字节数组或者整数。

压缩表特点

压缩列表是一种为了节约内存而开发的顺序型数据结构。
压缩列表被用作列表键和哈希键的底层实现之一。
压缩列表可以包含多个节点,每个节点可以保存一个字节数组或者整数值。
添加新节点到压缩列表,可能会引发连锁更新操作。连锁更新操作出现的几率不高。

previous_entry_length

previous_entry_length用于记录压缩列表前一个节点的长度。

如果前一节点长度<254字节,那么previous_entry_length属性的长度为1字节;如果前一节点长度>=254字节,那么previous_entry_length属性的长度为5字节。

previous_entry_length的作用:从表尾向表头遍历[回溯]。通过指针计算,根据当前节点的起始地址来计算出前一个节点的起始地址。

连锁更新

Redis在某些特殊情况下会产生连续多次的空间扩展操作,这称之为连锁更新。

比如:大多数entry节点的长度都是250~253字节的,如果新增了一个长度超过254字节的节点,则新增节点的后一个节点previous_entry_length要扩展为5字节的,这会导致连锁更新。同理删除也可能会触发连锁更新。

连锁更新的时间复杂度为O(N^2),这会比较占用CPU性能。然而我们不需要过分担心,因为连锁更新真正造成性能问题的几率非常小:连锁更新的前提条件是——恰好有多个连续的介于250~253字节之间的节点。

7、快速列表(quicklist)

quicklist结构是在redis 3.2版本中新加的数据结构,用在列表的底层实现。

quicklistziplist的关系:quicklist是由ziplist组成的双向链表,链表中的每一个节点都以压缩列表ziplist的结构保存着数据,而ziplist有多个entry节点,保存着数据。相当与一个quicklist节点保存的是一片数据,而不再是一个数据

  • quicklist宏观上是一个双向链表,因此,它具有一个双向链表的有点,进行插入或删除操作时非常方便,虽然复杂度为O(n),但是不需要内存的复制,提高了效率,而且访问两端元素复杂度为O(1)。
  • quicklist微观上是一片片entry节点,每一片entry节点内存连续且顺序存储,可以通过二分查找log2(n)log2(n) 的复杂度进行定位

快速列表结构体定义

// 快表
typedef struct quicklist {
    quicklistNode *head;     //指向头部(最左边)quicklist节点的指针
    quicklistNode *tail;     //指向尾部(最右边)quicklist节点的指针
    unsigned long count;     //ziplist中的entry节点计数器
    unsigned long len;       //quicklistNode节点计数器
    // ...    // 一些压缩参数
} quicklist;

// 快表节点
typedef struct quicklistNode {
    struct quicklistNode *prev;  //前驱节点指针
    struct quicklistNode *next;  //后驱节点指针
    unsigned char *entry;        //压缩列表ziplist的总长度
    size_t sz;             /* entry size in bytes */
    // ...                 // 一些压缩参数
} quicklistNode;

// 压缩过的ziplist结构
typedef struct quicklistLZF {
    size_t sz; /* LZF size in bytes*/ //表示被LZF算法压缩后的ziplist的大小
    char compressed[]; //一个柔性数组,保存压缩后的ziplist的数组
} quicklistLZF;

// 管理quicklist中quicklistNode节点中ziplist信息的结构
typedef struct quicklistEntry {
    const quicklist *quicklist;  //指向所属的quicklist的指针
    quicklistNode *node;         //指向所属的quicklistNode节点的指针
    unsigned char *zi;           //指向当前ziplist结构的指针
    unsigned char *value;        //指向当前ziplist结构的字符串vlaue成员
    long long longval;           //指向当前ziplist结构的整数
    size_t sz;                   //保存当前ziplist结构的字节数大小
    int offset;                  //保存相对ziplist的偏移量
} quicklistEntry;

quicklist结构示意图

在这里插入图片描述

二、RedisObject

在前面的文章中,我们介绍了 Redis 用到的主要数据结构,比如简单动态字符串、双端链表、字典、压缩列表、整数集合。
然而 Redis 并没有直接使用这些数据结构来实现键值对的数据库,而是在这些数据结构之上又包装了一层 RedisObject(对象),RedisObject 有五种对象:字符串对象、列表对象、哈希对象、集合对象和有序集合对象。

redisObject结构体定义如下:

struct redisObject {
    unsigned type:4;       // 对象类型(5种)
    unsigned encoding:4;   // 编码(8种),记录对象所使用的编码,也就是底层的数据结构
    unsigned lru:LRU_BITS; // 访问时间(用于记录空转时长、内存回收)
    int refcount;          // 引用计数(用于对象共享、垃圾回收)
    void *ptr;             // 指向底层实现结构的指针,由encoding决定
};

从结构体定义就可以归纳出使用redisObject的优点:

  • 校验:通过不同类型的对象,Redis 可以在执行命令之前,根据对象的类型来判断一个对象是否可以执行给定的命令。
  • 实现灵活:我们可以针对不同的使用场景,为对象设置不同的实现,从而优化内存或查询速度。
  • 引用计数:当一个对象的引用计数为0时,则表示该对象已经不被任何对象引用,则可以进行垃圾回收。另外,Redis还基于引用计数实现对象共享机制。
  • 空转时间:记录访问时间,用于服务器开启maxmemory功能下的key删除[优先删除空转时间较大的]。

在这里插入图片描述

代码示例:

# 字符串对象
127.0.0.1:6379> set msg "hello world!"
OK
127.0.0.1:6379> get msg
"hello world!"
127.0.0.1:6379> type msg
string
# 列表对象
127.0.0.1:6379> rpush numbers 1 3 5
(integer) 3
127.0.0.1:6379> type numbers
list
# 哈希对象
127.0.0.1:6379> hset profile name tom age 28 career programmer
(integer) 3
127.0.0.1:6379> type profile
hash
# 集合对象
127.0.0.1:6379> sadd fruits apple banana cherry
(integer) 3
127.0.0.1:6379> type fruits
set
# 有序集合对象
127.0.0.1:6379> zadd price 8.5 apple 5.0 banana 6.8 cherry
(integer) 3
127.0.0.1:6379> type price
zset

对象与数据结构的关系: 每种类型的对象都至少使用了两种不同的编码,也即每种类型的对象都用到了至少一种数据结构。如下表:

type类型encoding编码对象Object
redis_stringredis_encoding_int使用整数值实现的字符串对象
redis_stringredis_encoding_embstr使用embstr编码的sds实现的字符串对象
redis_stringredis_encoding_raw使用sds实现的字符串对象
redis_listredis_encoding_ziplist使用压缩列表实现的列表对象
redis_listredis_encoding_linkedlist使用双端链表实现的列表对象
redis_hashredis_encoding_ziplist使用字典实现的哈希对象
redis_hashredis_encoding_ht使用整数值实现的哈希对象
redis_setredis_encoding_intset使用整数集合实现的集合对象
redis_setredis_encoding_ht使用字典实现的集合对象
redis_zsetredis_encoding_ziplist使用压缩列表实现的有序集合对象
redis_zsetredis_encoding_skiplist使用跳跃表和字典实现的有序集合对象

1、字符串对象

int

如果一个字符串对象保存的是整数值,并且这个整数值可以用 long 类型标识,那么字符串对象会讲整数值保存在 ptr 属性中,并将 encoding 设置为 int。

在这里插入图片描述

raw

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

在这里插入图片描述

emstr

如果字符串对象保存的是一个字符串值,并且这个字符串的长度<= 39 字节,那么字符串对象将使用 embstr 编码的方式来保存这个字符串。

在这里插入图片描述

使用 embstr 的编码方式有一些优点,如下:

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

编码转换

  • bint转raw

    127.0.0.1:6379> set num 10086
    OK
    127.0.0.1:6379> object encoding num
    "int"
    127.0.0.1:6379> append num " is a good number!"
    (integer) 23
    127.0.0.1:6379> get num
    "10086 is a good number!"
    127.0.0.1:6379> object encoding num
    "raw"
    
    
  • embstr转raw

    127.0.0.1:6379> set msg "hello world!"
    OK
    127.0.0.1:6379> object encoding msg
    "embstr"
    127.0.0.1:6379> append msg "Im redis!"
    (integer) 22
    127.0.0.1:6379> get msg
    " hello world!Im redis!"
    127.0.0.1:6379> object encoding msg
    "raw"
    
    

2、列表对象

ziplist

ziplist(压缩列表)主要是为节省内存而设计的内存结构,它的优点就是节省内存,但缺点就是比其他结构要消耗更多的时间,所以 Redis 在列表对象的数据量小的时候使用压缩列表存储。
在这里插入图片描述

linkedlist

当列表的长度小于 512,并且所有元素的长度都小于 64 字节时,使用压缩列表存储;否则使用 linkedlist 存储。

在这里插入图片描述

编码转换

  • ziplist转linkedlist

    127.0.0.1:6379> rpush blabla "hello" "world"
    (integer) 2
    127.0.0.1:6379> llen blabla
    (integer) 2
    127.0.0.1:6379> object encoding blabla
    "ziplist"
    # 新增列表值的字符串长度65
    127.0.0.1:6379> rpush blabla wwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwww
    (integer) 3
    127.0.0.1:6379> object encoding blabla
    "linkedlist"
    
    
  • ziplist转linkedlist

    127.0.0.1:6379> eval "for i=1,512 do redis.call('RPUSH',KEYS[1],i) end" 1 "integers"
    (nil)
    127.0.0.1:6379> llen integers
    (integer) 512
    127.0.0.1:6379> object encoding integers
    "ziplist"
    127.0.0.1:6379> rpush integers 513
    (integer) 513
    127.0.0.1:6379> object encoding integers
    "linkedlist"
    
    

3、哈希对象

ziplist

ziplist(压缩列表)主要是为节省内存而设计的内存结构,它的优点就是节省内存,但缺点就是比其他结构要消耗更多的时间,所以 Redis 在哈希对象的数据量小的时候使用压缩列表存储。

在这里插入图片描述

hashtable

当哈希对象保存的键值对数量小于 512,并且所有键值对的长度都小于 64 字节时,使用压缩列表存储;否则使用 hashtable 存储。

在这里插入图片描述

编码转换

  • ziplist转hashtable

    127.0.0.1:6379> hset anotherbook  hello world
    (integer) 1
    127.0.0.1:6379> object encoding anotherbook
    "ziplist"
    # 新增哈希对象值的字符串长度65
    127.0.0.1:6379> hset book author wwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwww
    (integer) 1
    127.0.0.1:6379> object encoding book
    "hashtable"
    
    
  • ziplist转hashtable

    127.0.0.1:6379> eval "for i=1, 511 do redis.call('hset', KEYS[1], i ,i)end" 1 "anotherbook"
    (nil)
    127.0.0.1:6379> object encoding anotherbook
    "ziplist"
    127.0.0.1:6379> hset anotherbook  hello1 world1
    (integer) 1
    127.0.0.1:6379> object encoding anotherbook
    "hashtable"
    
    

4、集合对象

intset

intset(整数集合)主要是为节省内存而设计的内存结构,它的优点就是节省内存,但缺点就是比其他结构要消耗更多的时间,所以 Redis 在集合对象的数据量小的时候使用整数集合存储。

在这里插入图片描述

hashtable

当集合的长度小于 512,并且所有元素都是整数时,使用整数集合存储;否则使用 hashtable 存储。

在这里插入图片描述

编码转换

  • intset转hashtable

    127.0.0.1:6379> sadd elements 1 3 5
    (integer) 3
    127.0.0.1:6379> object encoding elements
    "intset"
    127.0.0.1:6379> sadd elements "seven"
    (integer) 1
    127.0.0.1:6379> object encoding elements
    "hashtable"
    
    
  • intset转hashtable

    127.0.0.1:6379> eval "for i=1, 511 do redis.call('sadd', KEYS[1], i)end" 1 "anotherelements"
    (nil)
    127.0.0.1:6379> scard anotherelements
    (integer) 511
    127.0.0.1:6379> object encoding anotherelements
    "intset"
    127.0.0.1:6379> sadd anotherelements 512
    (integer) 1
    127.0.0.1:6379> scard anotherelements
    (integer) 512
    127.0.0.1:6379> object encoding anotherelements
    "hashtable"
    
    

5、有序集合对象

ziplist

ziplist(压缩列表)主要是为节省内存而设计的内存结构,它的优点就是节省内存,但缺点就是比其他结构要消耗更多的时间,所以 Redis 在有序集合对象的数据量小的时候使用压缩列表存储。

在这里插入图片描述

skiplist

当有序集合的长度小于 128,并且所有元素的长度都小于 64 字节时,使用压缩列表存储;否则使用 skiplist 存储。
在这里插入图片描述

编码转换

  • ziplist转skiplist

    127.0.0.1:6379> zadd orderslen 2 www
    (integer) 1
    127.0.0.1:6379> object encoding orderslen
    "ziplist"
    127.0.0.1:6379> zadd orderslen 3 wwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwww
    (integer) 1
    127.0.0.1:6379> object encoding orderslen
    "skiplist"
    
    
  • ziplist转skiplist

    127.0.0.1:6379> eval "for i=1,128 do redis.call('zadd',KEYS[1],i,i)end" 1 "orders"
    (nil)
    127.0.0.1:6379> zcard orders
    (integer) 128
    127.0.0.1:6379> object encoding orders
    "ziplist"
    127.0.0.1:6379> zadd orders 3.14 pai
    (integer) 1
    127.0.0.1:6379> zcard orders
    (integer) 129
    127.0.0.1:6379> object encoding orders
    "skiplist"
    
    

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/480273.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

想要成为 NLP 领域的大牛?从 ChatGPT 的 5 大自然语言模型开始了解吧(LM、Transformer、GPT、RLHF、LLM)——小白也能看得懂

目录 前言ChatGPT基础科普——知其一点所以然1. LM2. Transformer3. GPT4. RLHF5. LLM 参考资料其它资料下载 前言 如果想在自然语言处理&#xff08;Natural Language Processing&#xff0c;NLP&#xff09;领域内脱颖而出&#xff0c;那么你一定不能错过 ChatGPT 的 5 大自然…

软件设计师笔记

软件设计师笔记 计算机组成与体系结构 数据的表示、计算机结构、Flynn分类法、CISC与RISC、流水线技术、存储系统、总线系统、可靠性、校验码 1. 数据的表示 &#xff08;一&#xff09;进制转换 R进制转十进制使用按权展开法&#xff1a; 十进制转R进制使用短除法 二进制…

Python——狂肝两万字带你学会【类与对象】

目录 01-初始对象 生活中的数据组织 程序中的数据组织​ 使用对象组织数据 总结01 02-类的成员方法 类的定义和使用 成员变量和成员方法 成员方法的定义语法 注意事项 成员方法——代码演示 总结02 03-类和对象 现实世界的事物和类 类和对象 使用类和对象描述…

Java 基础进阶篇(三)—— 权限修饰符、final 关键字与枚举

文章目录 一、权限修饰符二、final 关键字2.1 final 作用2.2 final 修饰变量举例2.3 常量 三、枚举3.1 枚举的格式3.2 枚举的特征3.3 枚举的应用 一、权限修饰符 权限修饰符 用于约束成员变量、构造器、方法等的访问范围。 权限修饰符&#xff1a; 有四种作用范围由小到大 (p…

vue+element 多选级联选择器自定义props

前言 我这里分享的是Cascader 级联选择器中的多选、以及如何自定义props的使用详解 1.使用Cascader 级联选择器 效果 代码 <div class"block"><span class"demonstration">默认显示所有Tag</span><el-cascader:options"op…

Vue电商项目--vuex模块开发

vuex状态管理库 vuex是什么&#xff1f; vuex是官方提供的一个插件&#xff0c;状态管理库&#xff0c;集中式管理项目中组件共有的数据。 切记&#xff0c;并不是全部的项目都需要Vuex,如果项目很小&#xff0c;完全不需要vuex,如果项目很大&#xff0c;组件很多&#xff0…

一道Python初学者常犯错误解析

1. 引言 在Python学习中&#xff0c;经常会遇到各种各样的代码错误&#xff0c;尤其对于初学者而言&#xff0c;明明觉得逻辑上是对的&#xff0c;但是代码运行起来&#xff0c;往往不是自己想要的结果。 本文就最近在某平台看到的一个常见错误进行展开&#xff0c;帮助大家更…

06 虚拟化Open vSwitch环境部署

文章目录 06 虚拟化Open vSwitch环境部署6.1 安装Open vSwitch网桥6.1.1 安装Open vSwitch组件6.1.1.1 安装Open vSwitch组件6.1.1.2 启动Open vSwitch服务6.1.1.3 设置Open vSwitch服务随系统自动启动 6.1.2 确认安装是否成功6.1.2.1确认 Open vSwitch组件是否安装成功6.1.2.2…

kill 信号

kill -0 PidNum 参数是0&#xff0c;不会发送任何的信号&#xff0c;不会关闭程序&#xff0c;但会执行错误检查&#xff0c;对程序运行状态进行监控。可以用他来检测某个进程ID或进程组ID是否存在。从理解上看&#xff0c;作用相当于ps -p 。 进程已停止、不存在或其他异…

前端小白是如何利用chatgt用一周时间从做一款微信小程序的

前端小白是如何利用chatgt用一周时间从0做一款微信小程序的 随着chatgpt的大火&#xff0c;真的是在工作上给各行各业的人带来了极大的便利&#xff0c;本人是一个java程序员&#xff0c;其实我自己是一直想开发一款属于自己的小程序的&#xff0c;但是迫于对前端知识的贫瘠&a…

【五一创作】VimPlug插件配置

目录 Install Question Q1&#xff1a;字体乱码 Q2&#xff1a;插件配置 Q3&#xff1a;安装扩展插件 Q4&#xff1a;查看安装插件状态 Q5&#xff1a;查看默认插件 Q6&#xff1a;卸载插件 Q7&#xff1a;增加用户配置 Install Github地址&#xff1a;GitHub - chxu…

内网渗透之横向移动NTMLRelay(中继)攻击-InveighNTLM-Relay重放

横向移动 NTLM中继攻击 Relay重放(smb) 适用于无法获取hash或密码时使用 NTML Relay重放包括smb to relay ldap ews webserver: 执行下面的命令时会默认以当前用户名和密码去登录目标计算机 dir \\ 192.168.3.32\c$webserver切换到本地的administrator时 dir \\ 192.168.3.3…

权限提升:令牌窃取 || 进程注入.

权限提升&#xff1a;令牌窃取 || 进程注入. 权限提升简称提权&#xff0c;由于操作系统都是多用户操作系统&#xff0c;用户之间都有权限控制&#xff0c;比如通过 Web 漏洞拿到的是 Web 进程的权限&#xff0c;往往 Web 服务都是以一个权限很低的账号启动的&#xff0c;因…

管理系统的实现_03

文章目录 登录界面的开发安装axios用于前后端交互第一步、在项目目录下执行命令第二步、在main.js文件夹添加如下代码第三步、使用this.axios 即可访问到 Login.vue 完整代码如下搭建第一个springboot项目第一步、修改配置文件(application.properties)第二步、创建包目录 用sp…

希尔排序(C++)

希尔排序 是插入排序的一种&#xff0c;也是缩小增量排序。希尔排序是记录按下标的一定增量分组&#xff0c;对每组使用直接插入排序算法排序&#xff1b;随着增量逐渐减少&#xff0c;每组包含的关键词越来越多&#xff0c;当增量减至1时&#xff0c;整个文件恰被分成一组&am…

常用的极限

常用的极限 方法1 利用基本极限求极限 1.常用的基本极限 一个函数极限是非零常数&#xff0c;分母极限为零&#xff0c;分子极限必为零 幂指函数转为指数函数的形式,再等价代换 方法二 分子分母凑成可以使用等价无穷小代换的形式 arcsinx 和 sin x 作为分子是相减的. 1 先使用…

数据结构之带头循环双向链表

目录 1.何为双链表&#xff1f; 2.带头循环双向链表 1.函数接口与结构体 2.初始化链表 3.销毁链表 4.打印链表 5.创建节点 6.尾插 7.尾删 8.头插 9.头删 10 查找节点 11.在pos前插入x 12.删除pos位置的值 在学习了单链表之后&#xff0c;我们发现单链表弥补了了顺…

Spring 管理 Bean-IOC--基于注解配置 bean

目录 Spring 管理 Bean-IOC--基于注解配置 bean ● 基本介绍 ● 组件注解的形式有 代码演示--了解注解 UserDao UserService UserAction MyComponent 配置 beans.xml 注意 测试 注意事项和细节说明 自动装配 基本说明 应用实例需求 UserService UserAction 配置…

MLC LLM:将大模型运行在手机端的部署工具

前言 MLC LLM 是一个通用的解决方案 它允许任何语言模型在不同的硬件后端和本地应用程序集上进行本地部署 并为每个人提供一个高效的框架&#xff0c;以进一步优化模型的性能&#xff0c;满足他们自己的用例 其使命是让每个人都能在自己的设备&#xff08;如手机端&#xf…

WordPress 不使用ftp更新

文章目录 摘要修改 wp-config.php文件处理 413 Request Entity Too Large修改nginx配置&#xff1a;client_max_body_size重启nginx 处理uploaded file exceeds the upload max filesize找到php.ini修改 upload_max_filesize重启php 摘要 每次 WordPress 有插件或主题更新都要…