redis 系列6 数据结构之字典(下)

本文涉及的产品
云数据库 Tair(兼容Redis),内存型 2GB
Redis 开源版,标准版 2GB
推荐场景:
搭建游戏排行榜
简介: 原文:redis 系列6 数据结构之字典(下)一.概述   接着上篇继续,这篇把数据结构之字典学习完, 这篇知识点包括:哈希算法,解决键冲突, rehash , 渐进式rehash,字典API。   1.1 哈希算法     当一个新的键值对 需要添加到字典里面时,程序需要先根据“键值对”的键计算出哈希值和索引值,再根据索引值,将包含新“键值对”的哈希表节点放到哈希表数组的指定索引上面。
原文: redis 系列6 数据结构之字典(下)

一.概述

  接着上篇继续,这篇把数据结构之字典学习完, 这篇知识点包括:哈希算法,解决键冲突, rehash , 渐进式rehash,字典API。

  1.1 哈希算法

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

#使用字典设置的哈希函数,计算键key的哈希值
hash = dict -> type->hashFunction(key);
# 使用哈希表的sizemask属性和哈希值,计算出索引值, ht[x] 可以是ht[0] 或者ht[1]
index = hash & dict->ht[x].sizemask;

    例1: 将一个“键值对”K0和V0 添加到字典里面,那么程序会使用如下语句,假设hash变量的哈希值为8, sizemask为3, &是指"按位与"运算符。公式如下:

hash = dict -> type->hashFunction(K0);
index= hash $ dict ->ht[0]. sizemask=8 &3 = 0;

    通过公式计算出健K0的索引值为0,这个新的“键值对”节点应该放置到哈希表数组的索引0位置,对于哈希算法的底层实现,redis使用MurmurHash2算法来计算键的哈希值。Murmur哈希是一种非加密散列函数,目前有三个版本(MurmurHash1、MurmurHash2、MurmurHash3),这里不在深入,例1如下图所示:

  1.2 解决键冲突

    当有两个或以上数量的键被分配到了哈希表数组的同一个索引上面时,称为键冲突。Redis 的哈希表使用“链地址”来解决键冲突,每个哈希表节点都有一个next指针,多个哈希表节点可以用next指针构成一个单向链表,被分配到哈希表数组同一个索引上,用单向链表把多个节点连接一起,解决了键冲突问题。

    例2: 程序要将新"键值对"k2和v2 添加到哈希表中,计算的k2 索引值为2, 但该哈希表数组索引值2上已有"键值对"k1和v1。 解决键索引冲突的办法就是使用next指针将键k2和键k1的节点连接起来,如下图所示:

   1.3 rehash 重新散列

     随着对哈希表数组的不断操作, 哈希表数组保存的"键值对"节点会增多或减少,为了让哈希表的负载因子维持在一个合理的范围之内,当哈希表保存的“键值对”数量太多或者太少时,程序需要对哈希表的大小进行相应的扩展或收缩。扩展或收缩哈希表的工作是通过执行rehash 操作来完成的,Redis字典的哈希表执行reash的步骤如下:

    (1) 为字典的ht[1] 哈希表分配空间。这个空间大小取决于要执行的操作,以及ht[0]当前包含的“键值对”数量(ht[0].used的值)

    (2) 将保存在ht[0]中的所有“键值对”rehash到ht[1]上面, rehash指的是重新计算键的哈希值和索引值,然后“键值对”放置到ht[1]哈希表的指定位置上。

    (3) 当ht[0]包含的所有“键值对”都迁移到了ht[1]之后,释放ht[0], 将ht[1]设置为ht[0],并在ht[1]新创建一个空白的哈希表,为下一次rehash准备。

    例3: 下面是对ht[0]进行扩展操作,在没有rehash之前,字典如下所示

    ht[0].used当前的值为4(扩展公式为ht[0].used *2,  2^3次方), 程序会将ht[1]哈希表的大小设置为8,ht[1]在分配空间之后,字典如下所示:

    将ht[0]包含的四个“键值对”都rehash到ht[1],此时释放了ht[0]的哈希表,如下图所示:

    最后将ht[1] 设置为ht[0], 然后为ht[1]分配一个空白哈希表,至此,对哈希表的扩展操作执行完毕,程序成功将哈希表的大小从原来的4改为了现在的8,如下图所示:

    总结:哈希表的扩展与收缩。当以下条件中的任意一个被满足时,程序会自动开始对哈希表执行扩展操作:

      (1) 服务器目前没有在执行bgsave命令或者bgrewriteaof命令,并且哈希表的负载因子大于等于1。

      (2) 服务器目前正在执行bgsave命令或者bgrewriteaof命令,并且哈希表的负载因子大于等于5。

#负载因子= 哈希表已保存节点数量 / 哈希表大小
load_factor=ht[0].used /ht[0].size

      例如:对于一个大小为4,包含4个“键值对”的哈希表来说,这个哈希表的负载因子为: load_factor= 4/4=1。 又例如,对于一个大小为512,包含256个“键值对“的哈希表来说,这个哈希表的负载因子为: load_factor=256/512=0.5(这个要不需要扩展)。另外当哈希表的负载因子小于0.1时,程序自动开始对哈希表执行收缩操作。

     

   1.4 渐进式 rehash

    上面rehash扩展或收缩哈希表需要将ht[0] 里面的所有”键值对“ rehash到ht[1]里面, 这个rehash动作并不是一次性,集中式完成的,而是分多次,渐进式完成的。这样做原因在于考虑到有大量”键值对“,如果一次性将大量”键值对“全部rehash到ht[1]的话,庞大的计算量可能会导致服务器在一段时间内停止服务。因此是渐进式的将ht[0]里面的键值对慢慢地rehash到ht[1]。

    例4 以下是哈希表渐进式rehash的详细步骤:
      (1) 为ht[1] 分配空间,让字典同时持有ht[0]和ht[1]两个哈希表。此时rehashidx值为-1。

    (2) 在字典中维持一个索引计数器变量rehashidx,并将它的值设置为0,表示rehash工作正式开始(rehash了一个键值对)。此时rehashidx值为0。

    (3) 在每次rehash进行期间,除了对字典执行操作,程序还会将ht[0]的rehashidx索引值增一,下图是全部rehash完成,此时rehashidx值为3。

    (4) 最后当ht[0]的所有键值对rehash到ht[1],此时程序将rehashidx属性的值为-1, 表示rehash操作全部完成。将ht[1]设置为ht[0],并在ht[1]新创建一个空白的哈希表,为下一次rehash准备

    总结: 在渐进式 rehash执行期间,新添加到字典的键值对 一律会被保存到ht[1]里面,而ht[0]则不再进行任何添加操作,这一措施保证了ht[0]包含的键值对数量只减不增,并随着rehash操作的执行最终变成空表。

 

  1.5 字典API (主要操作API)

函数

作用

dictCreate

创建一个新的字典

dictAdd

将给定的“键值对”添加到字典里面

dictReplace

将给定的“键值对”添加到字典里面,如果键已经存在于字典,那么用新值取代原有的值

dictFetechValue

返回给定的键的值

dictGetRandomkey

从字典中随机返回一个“键值对”

dictDelete

从字典中删除给定键所对应的“键值对”

dictRelease

释放给定字典,以及字典中包含的所有“键值对”

  最后字典总结:

    (1) 字典被广泛用于实现Redis的各种功能,其中包括数据库和哈希键。

    (2) Redis中的字典使用哈希表作为底层实现,每个字典带有两个哈希表,一个平时使用,另一个在rehash时使用。

    (3) 当字典被用作数据库的底层实现,或者哈希键的底层实现时,Redis使用MurmurHash2算法来计算键的哈希值。

    (4) 哈希表使用链地址法来解决键冲突,被分配到同一个索引上的多个“键值对”会连接成一个单向链表。

    (5) 在对哈希表扩展或收缩操作时,程序需要将现有哈希表包含的所有“键值对”rehash到新哈希表里面,并且这个rehash过程并不是一次性完成的,而是渐近式完成。

相关实践学习
基于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
目录
相关文章
|
1月前
|
存储 消息中间件 缓存
Redis 5 种基础数据结构?
Redis的五种基础数据结构——字符串、哈希、列表、集合和有序集合——提供了丰富的功能来满足各种应用需求。理解并灵活运用这些数据结构,可以极大地提高应用程序的性能和可扩展性。
34 2
|
2月前
|
缓存 NoSQL PHP
Redis作为PHP缓存解决方案的优势、实现方式及注意事项。Redis凭借其高性能、丰富的数据结构、数据持久化和分布式支持等特点,在提升应用响应速度和处理能力方面表现突出
本文深入探讨了Redis作为PHP缓存解决方案的优势、实现方式及注意事项。Redis凭借其高性能、丰富的数据结构、数据持久化和分布式支持等特点,在提升应用响应速度和处理能力方面表现突出。文章还介绍了Redis在页面缓存、数据缓存和会话缓存等应用场景中的使用,并强调了缓存数据一致性、过期时间设置、容量控制和安全问题的重要性。
46 5
|
2月前
|
存储 消息中间件 NoSQL
Redis数据结构:List类型全面解析
Redis数据结构——List类型全面解析:存储多个有序的字符串,列表中每个字符串成为元素 Eelement,最多可以存储 2^32-1 个元素。可对列表两端插入(push)和弹出(pop)、获取指定范围的元素列表等,常见命令。 底层数据结构:3.2版本之前,底层采用**压缩链表ZipList**和**双向链表LinkedList**;3.2版本之后,底层数据结构为**快速链表QuickList** 列表是一种比较灵活的数据结构,可以充当栈、队列、阻塞队列,在实际开发中有很多应用场景。
|
2月前
|
存储 NoSQL 关系型数据库
Redis的ZSet底层数据结构,ZSet类型全面解析
Redis的ZSet底层数据结构,ZSet类型全面解析;应用场景、底层结构、常用命令;压缩列表ZipList、跳表SkipList;B+树与跳表对比,MySQL为什么使用B+树;ZSet为什么用跳表,而不是B+树、红黑树、二叉树
|
2月前
|
存储 NoSQL Redis
Redis常见面试题:ZSet底层数据结构,SDS、压缩列表ZipList、跳表SkipList
String类型底层数据结构,List类型全面解析,ZSet底层数据结构;简单动态字符串SDS、压缩列表ZipList、哈希表、跳表SkipList、整数数组IntSet
|
16天前
|
存储 缓存 NoSQL
解决Redis缓存数据类型丢失问题
解决Redis缓存数据类型丢失问题
158 85
|
3月前
|
消息中间件 缓存 NoSQL
Redis 是一个高性能的键值对存储系统,常用于缓存、消息队列和会话管理等场景。
【10月更文挑战第4天】Redis 是一个高性能的键值对存储系统,常用于缓存、消息队列和会话管理等场景。随着数据增长,有时需要将 Redis 数据导出以进行分析、备份或迁移。本文详细介绍几种导出方法:1)使用 Redis 命令与重定向;2)利用 Redis 的 RDB 和 AOF 持久化功能;3)借助第三方工具如 `redis-dump`。每种方法均附有示例代码,帮助你轻松完成数据导出任务。无论数据量大小,总有一款适合你。
85 6
|
13天前
|
缓存 监控 NoSQL
Redis经典问题:缓存穿透
本文详细探讨了分布式系统和缓存应用中的经典问题——缓存穿透。缓存穿透是指用户请求的数据在缓存和数据库中都不存在,导致大量请求直接落到数据库上,可能引发数据库崩溃或性能下降。文章介绍了几种有效的解决方案,包括接口层增加校验、缓存空值、使用布隆过滤器、优化数据库查询以及加强监控报警机制。通过这些方法,可以有效缓解缓存穿透对系统的影响,提升系统的稳定性和性能。
|
2月前
|
缓存 NoSQL 关系型数据库
大厂面试高频:如何解决Redis缓存雪崩、缓存穿透、缓存并发等5大难题
本文详解缓存雪崩、缓存穿透、缓存并发及缓存预热等问题,提供高可用解决方案,帮助你在大厂面试和实际工作中应对这些常见并发场景。关注【mikechen的互联网架构】,10年+BAT架构经验倾囊相授。
大厂面试高频:如何解决Redis缓存雪崩、缓存穿透、缓存并发等5大难题
|
2月前
|
存储 缓存 NoSQL
【赵渝强老师】基于Redis的旁路缓存架构
本文介绍了引入缓存后的系统架构,通过缓存可以提升访问性能、降低网络拥堵、减轻服务负载和增强可扩展性。文中提供了相关图片和视频讲解,并讨论了数据库读写分离、分库分表等方法来减轻数据库压力。同时,文章也指出了缓存可能带来的复杂度增加、成本提高和数据一致性问题。
【赵渝强老师】基于Redis的旁路缓存架构