rodert单排学习redis进阶【青铜】2

本文涉及的产品
云数据库 Redis 版,社区版 2GB
推荐场景:
搭建游戏排行榜
简介: rodert单排学习redis进阶【青铜】

字典

Redis 中的字典由 dict.h/dict 结构表示:

typedef struct dict {

    // 类型特定函数
    dictType *type;

    // 私有数据
    void *privdata;

    // 哈希表
    dictht ht[2];

    // rehash 索引
    // 当 rehash 不在进行时,值为 -1
    int rehashidx; /* rehashing not in progress if rehashidx == -1 */

} dict;

------------------分割线---------------------------

typedef struct dictType {

    // 计算哈希值的函数
    unsigned int (*hashFunction)(const void *key);

    // 复制键的函数
    void *(*keyDup)(void *privdata, const void *key);

    // 复制值的函数
    void *(*valDup)(void *privdata, const void *obj);

    // 对比键的函数
    int (*keyCompare)(void *privdata, const void *key1, const void *key2);

    // 销毁键的函数
    void (*keyDestructor)(void *privdata, void *key);

    // 销毁值的函数
    void (*valDestructor)(void *privdata, void *obj);

} dictType;

ht 属性是一个包含两个项的数组, 数组中的每个项都是一个 dictht 哈希表, 一般情况下, 字典只使用 ht[0] 哈希表, ht[1] 哈希表只会在对 ht[0] 哈希表进行 rehash 时使用。

4.3.2.Redis rehash(重新散列)

随着操作的不断执行, 哈希表保存的键值对会逐渐地增多或者减少, 为了让哈希表的**负载因子**(load factor)维持在一个合理的范围之内, 当哈希表保存的键值对数量太多或者太少时, 程序需要对哈希表的大小进行相应的扩展或者收缩。

在对哈希表进行扩展或者收缩操作时,reash 过程并不是一次性地完成的,而是**渐进式**地完成的。

以下是哈希表渐进式 rehash 的详细步骤:

为 ht[1] 分配空间, 让字典同时持有 ht[0] 和 ht[1] 两个哈希表。
在字典中维持一个索引计数器变量 rehashidx , 并将它的值设置为 0 , 表示 rehash 工作正式开始。
在 rehash 进行期间, 每次对字典执行添加、删除、查找或者更新操作时, 程序除了执行指定的操作以外, 还会顺带将 ht[0] 哈希表在 rehashidx 索引上的所有键值对 rehash 到 ht[1] , 当 rehash 工作完成之后, 程序将 rehashidx 属性的值增一。
随着字典操作的不断执行, 最终在某个时间点上, ht[0] 的所有键值对都会被 rehash 至 ht[1] , 这时程序将 rehashidx 属性的值设为 -1 , 表示 rehash 操作已完成。

4.3.3.重点

  • 字典被广泛用于实现 Redis 的各种功能, 其中包括数据库和哈希键。
  • Redis 中的字典使用哈希表作为底层实现, 每个字典带有两个哈希表, 一个用于平时使用, 另一个仅在进行 rehash 时使用。
  • 当字典被用作数据库的底层实现, 或者哈希键的底层实现时, Redis 使用 MurmurHash2 算法来计算键的哈希值。
  • 哈希表使用链地址法来解决键冲突, 被分配到同一个索引上的多个键值对会连接成一个单向链表。
  • 在对哈希表进行扩展或者收缩操作时, 程序需要将现有哈希表包含的所有键值对 rehash 到新哈希表里面, 并且这个 rehash 过程并不是一次性地完成的, 而是渐进式地完成的。

4.4.跳跃表

4.4.1.跳跃表

Redis 的跳跃表由 redis.h/zskiplistNode 和 redis.h/zskiplist 两个结构定义, 其中 zskiplistNode 结构用于表示跳跃表节点, 而 zskiplist 结构则用于保存跳跃表节点的相关信息, 比如节点的数量, 以及指向**表头节点和表尾节点**的指针, 等等。

跳跃表节点

typedef struct zskiplistNode {

    // 后退指针
    struct zskiplistNode *backward;

    // 分值
    double score;

    // 成员对象
    robj *obj;

    // 层
    struct zskiplistLevel {

        // 前进指针
        struct zskiplistNode *forward;

        // 跨度
        unsigned int span;

    } level[];

} zskiplistNode;
  • zskiplistNode 不同层高节点

跳跃表节点的 level 数组可以包含多个元素, 每个元素都包含一个指向其他节点的指针, 程序可以通过这些层来加快访问其他节点的速度, 一般来说, 层的数量越多, 访问其他节点的**速度就越快**。

看到这里,如果还有疑惑,不理解什么是跳跃表,传送一篇不错的跳跃表介绍文章:https://www.cnblogs.com/hunternet/p/11248192.html

4.4.2.重点

跳跃表是有序集合的底层实现之一, 除此之外它在 Redis 中没有其他应用。

Redis 的跳跃表实现由 zskiplist 和 zskiplistNode 两个结构组成, 其中 zskiplist 用于保存**跳跃表信息(比如表头节点、表尾节点、长度), 而 zskiplistNode 则用于表示跳跃表节点**。

  1. 每个跳跃表节点的层高都是 1 至 32 之间的**随机数**。
  2. 在同一个跳跃表中, 多个节点可以包含**相同的分值, 但每个节点的成员对象必须是唯一**的。
  3. 跳跃表中的节点按照分值大小进行排序, 当分值相同时, 节点按照成员对象的大小进行排序。

4.5.整数集合

  • 整数集合是**集合键(set)**的底层实现之一。
  • 整数集合的底层实现为**数组, 这个数组以有序、无重复的方式保存集合元素,在有需要时, 程序会根据新添加元素**的类型, 改变这个数组的类型
  • 升级操作为整数集合带来了操作上的灵活性, 并且尽可能地节约了内存
  • 整数集合**只支持升级**操作, 不支持降级操作。

整数集合(intset)是 Redis 用于保存整数值的集合抽象数据结构, 它可以保存类型为 int16_tint32_t 或者 int64_t 的整数值, 并且保证集合中不会出现**重复元素**。

数据结构:

typedef struct intset {

    // 编码方式
    uint32_t encoding;

    // 集合包含的元素数量
    uint32_t length;

    // 保存元素的数组
    int8_t contents[];

} intset;


4.4.2.重点

跳跃表是有序集合的底层实现之一, 除此之外它在 Redis 中没有其他应用。

Redis 的跳跃表实现由 zskiplist 和 zskiplistNode 两个结构组成, 其中 zskiplist 用于保存**跳跃表信息(比如表头节点、表尾节点、长度), 而 zskiplistNode 则用于表示跳跃表节点**。

每个跳跃表节点的层高都是 1 至 32 之间的**随机数**。

在同一个跳跃表中, 多个节点可以包含**相同的分值, 但每个节点的成员对象必须是唯一**的。

跳跃表中的节点按照分值大小进行排序, 当分值相同时, 节点按照成员对象的大小进行排序。

4.5.整数集合

整数集合是**集合键(set)**的底层实现之一。

整数集合的底层实现为**数组, 这个数组以有序、无重复的方式保存集合元素,在有需要时, 程序会根据新添加元素**的类型, 改变这个数组的类型。

升级操作为整数集合带来了操作上的灵活性, 并且尽可能地节约了内存。

整数集合**只支持升级**操作, 不支持降级操作。

整数集合(intset)是 Redis 用于保存整数值的集合抽象数据结构, 它可以保存类型为 int16_t 、 int32_t 或者 int64_t 的整数值, 并且保证集合中不会出现**重复元素**。

 我们知道,数组要求每个元素大大小相同,如果要存储长度不同的字符串,那就需要用**最大长度**的字符串大小作为元素的大小。以最大长度为标准,就会浪费一部分存储空间。


数组的优势占用一片**连续的空间**可以很好的利用CPU缓存访问数据。如果我们想要保留这种优势,又想节省存储空间我们可以对数组进行压缩。

那就需要给每个节点增加一个 lenght 的属性。

4.6.2.Redis 压缩列表

压缩列表(zip1ist)是 Redis 列表和 Redis 哈希的底层实现之一。

当一个列表只包含少量列表项,并且每个列表项要么就是小整数值,要么就是长度比较短的字符串,那么Redis就会使用压缩列表来做列表的底层实现。


当一个哈希只包含少量键值对,比且每个键值对的键和值要么就是小整数值,要么就是长度比较短的字符串,那么Redis就会使用压缩列表来做哈希的底层实现。

参考:https://www.cnblogs.com/hunternet/p/11306690.html

  1. 表是Redis为节约内存自己设计的一种顺序型数据结构。
  2. 表被用作列表键和哈希键的底层实现之一。
  3. 压缩列表可以包含多个节点,每个节点可以保存一个字节数组或者整数值。
  1. 添加新节点到压缩列表,或者从压缩列表中删除节点,可能会引发连锁更新操作,但这种操作出现的几率并不高。

4.7.Redis的对象

4.7.1.Redis的对象

Redis 中当我们创建一个键值对时,我们至少会创建俩个对象,一个用作键(键对象),一个用作值(值对象)。

  • Redis 对象结构
typedef struct redisObject {

    // 类型
    unsigned type:4;

    // 编码
    unsigned encoding:4;

    // 指向底层实现数据结构的指针
    void *ptr;

    // ...

} robj;
  • Redis 内存回收

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

typedef struct redisObject {

    // ...

    // 引用计数
    int refcount;

    // ...

} robj;
  • Redis 对象共享

举个例子, 假设键 A 创建了一个包含整数值 100 的字符串对象作为值对象,如果这时键 B 也要创建一个同样保存了整数值 100 的字符串对象作为值对象。

在 Redis 中, 让多个键共享同一个值对象需要执行以下两个步骤:

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

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

  • Redis 对象的空转时长

除了前面介绍过的 type 、 encoding 、 ptr 和 refcount 四个属性之外, redisObject 结构包含的最后一个属性为 lru 属性, 该属性记录了对象最后一次被命令程序访问的时间:

typedef struct redisObject {

    // ...

    unsigned lru:22;

    // ...

} robj;

4.7.2.重点

内存回收和对象的空转时长涉及到 Redis 配置文件(内存的算法 volatile-lru、allkeys-lru等其他知识点),后面单独一篇详细讲解。

Redis 数据库中的每个键值对的键和值都是一个对象。

Redis 共有字**符串、列表、哈希、集合、有序集合**五种类型的对象, 每种类型的对象至少都有两种或以上的编码方式, 不同的编码可以在不同的使用场景上优化对象的使用效率。

服务器在执行某些命令之前, 会先检查给定键的类型能否执行指定的命令, 而检查一个键的类型就是检查键的值对象的类型。

Redis 的对象系统带有引用计数实现的**内存回收机制**, 当一个对象不再被使用时, 该对象所占用的内存就会被自动释放。

Redis 会共享值为 0 到 9999 的字符串对象。

对象会记录自己的最后一次被访问的时间, 这个时间可以用于计算对象的**空转时间**。


相关实践学习
基于Redis实现在线游戏积分排行榜
本场景将介绍如何基于Redis数据库实现在线游戏中的游戏玩家积分排行榜功能。
云数据库 Redis 版使用教程
云数据库Redis版是兼容Redis协议标准的、提供持久化的内存数据库服务,基于高可靠双机热备架构及可无缝扩展的集群架构,满足高读写性能场景及容量需弹性变配的业务需求。 产品详情:https://www.aliyun.com/product/kvstore     ------------------------------------------------------------------------- 阿里云数据库体验:数据库上云实战 开发者云会免费提供一台带自建MySQL的源数据库 ECS 实例和一台目标数据库 RDS实例。跟着指引,您可以一步步实现将ECS自建数据库迁移到目标数据库RDS。 点击下方链接,领取免费ECS&RDS资源,30分钟完成数据库上云实战!https://developer.aliyun.com/adc/scenario/51eefbd1894e42f6bb9acacadd3f9121?spm=a2c6h.13788135.J_3257954370.9.4ba85f24utseFl
目录
相关文章
|
2天前
|
负载均衡 NoSQL 关系型数据库
Redis分布式锁学习总结
Redis分布式锁学习总结
5 0
|
9天前
|
NoSQL 数据可视化 Java
rodert单排学习redis进阶【白银一】
rodert单排学习redis进阶【白银一】
11 0
|
9天前
|
缓存 NoSQL Java
rodert单排学习redis进阶【青铜】1
rodert单排学习redis进阶【青铜】
18 0
|
存储 NoSQL Redis
Redis学习一(基础入门).
一、前言     Redis是一个开源的使用ANSI C语言编写、遵守BSD协议、支持网络、可基于内存亦可持久化的日志型、key-Value 的数据库、并提供多种语言的API。     通常,Redis 将数据存储于内存中,或被配置为使用虚拟内存。
2493 0
|
1月前
|
NoSQL Linux 网络安全
Linux安装Redis(详细教程)
Linux安装Redis(详细教程)
98 2
|
30天前
|
NoSQL Linux Redis
Redis -- 安装客户端redis-plus-plus
Redis -- 安装客户端redis-plus-plus
55 0
|
4天前
|
NoSQL Redis Windows
win10下Redis安装、启动教程
win10下Redis安装、启动教程
14 2
|
6天前
|
消息中间件 缓存 NoSQL
Redis单实例安装
Redis单实例安装
16 1
|
8天前
|
NoSQL Linux 网络安全
基于 centOS7 的 redis 安装
基于 centOS7 的 redis 安装
39 1
|
19天前
|
NoSQL Ubuntu Java
【Redis】 在 Ubuntu 上安装 Redis
【Redis】 在 Ubuntu 上安装 Redis

热门文章

最新文章