浅谈redis整数集为什么不能降级

目录
  • 前言
  • 基本结构
  • 何时使用intset
  • intset
  • 添加元素
  • 类型变动
  • 升级
  • 加入65535
  • 旧数据移位
  • 降级
  • 为什么不实现降级
  • 小结

前言

整数集合相信有的同学没有听说过,因为redis对外提供的只有封装的五大对象!而我们本系列主旨是学习redis内部结构。内部结构是redis五大结构重要支撑!

前面我们分别从redis内部结构分析了redis的List、Hash、Zset三种数据结构了。今天我们再来分析set数据结构内部是如何存储的

基本结构

在src/t_set.c中我们发现这样一段代码

由此我们可知在set中是由两种数据结构构成的: hashtable+intset 。关于redis内部其他的结构我专门在【redis专栏中有介绍】。hashtable不是我们今天的主角,我们今天先分析intset俗称整数集合。

从上图中我们可以看出,我构造了两个set集合分别为【commonset】、【cs】。两个集合前者存储字符串、后者专门存储数字。

我们在通过object encoding key 来查看下两个集合的底层数据结构,发现一个是hashtable 一个是intset 。这也验证了我们上面对set基本结构的描述。

在redis中对外提供五大类型实际上都是redis的一个抽象对象叫做redisobject。在内部映射了我们redis内部的数据结构

针对commonset和cs两个集合在内部数据结构大概可以这么理解

何时使用intset

你可以单纯的认为只要是数字就会使用intset结构来存储,我恐怕要给你当头一棒了。实际上并不是这样

需要同时满足以下两个条件:

intset

图中表示的很清楚了,在intset中的encoding有三种取值分别代表contents保存数据类型。这里有人可能会有疑问了contents的类型不就是int8_t吗?为什么还需要encoding呢?这里通过源码跟踪内部的确跟int8_t没啥关系。而且数据的默认类型就是int16_t 。关于length这里无需太多解释,记住一点表示contents元素的个数并非表示contents数组的长度!

了解intset的同学都知道在encoding三种取值范围中涉及了升级的操作!在讲升级之前我们先来了解下C、C++中int的取值范围是如何定义的

int8_t的取值范围是【-128,127】 。 类似于java中byte占1个字节也就是8位。他的取值范围是

添加元素

sadd juejin -123
sadd juejin -6
sadd juejin 12
sadd juejin 56
sadd juejin 321	

juejin这个key内部就是intset 。

上面我们添加了5个元素且这五个元素的长度都在16之内!所以当前的intset的encoding=INTSET_ENC_INT16。-123在contents中占前16位。

所以当前五个元素占contents的长度是16*5=80 ;

注意set在存储int类型数据时,内部是按照从小到大的顺序存储的。

类型变动

上面的问题不知道你有没有考虑过,或者说有没有遇到过!intset默认是int16位,正如我们上面添加的五个元素。加入此时我们添加第6个元素是65535(32位)。那么此时16位的长度就不够存储了这个时候intset会怎么做!

另外当我们添加第6个元素后又将65535删除了之后,结构和添加之前是否一样!下面我们带着这两个问题来一探究竟!!!

升级

首先我们针对第一问题来看看。原来五个元素都是16位就可以满足了,这个时候添加的65535是32位长度的。那么是不是可以直接追加32位分配给65535呢?

答案是肯定不行,首先直接追加无法保证数组元素的大小顺序!其次如果前五个分别是16位,第6个是32位那么在intset结构中没有多余的字段来进行标记。也就是说在解析的时候就无法判断应该解析16位还是32位了.

redis为了方便解析所以在有高长度加入时会将整个contents进行升级。意思就是将整个contents先进行扩容,然后在重新填充数据

加入65535

首先根据length可以确定扩容后元素个数为6 , 每个占位32,所以contents长度为32*6=192 。 此时前80位内容保持不变

旧数据移位

开辟了足够的空间后,我们就可以对旧数据进行移位了这里我们从原数组的末尾开始移动,在移动之前需要明确在新数组中的排序位置。此时我们首先将321进行比对确定在新数组中他的排名是第五名,那么他将占用新contents中128~159区间。

最终前5 个元素就会被移动好 。

最后将新加入的元素填充进去。当发生升级时肯定是因为新元素的长度大于原有长度了。那么他的值一定会是在新数组的两端。负数在最左侧,正数在最右侧

降级

接下来就是第二个问题当新加入的65535又被删除了redis该怎么办,这个时候元素长度实际16位就可以满足了,但是此时encoding却是32位的。按照我的看法应该在实现降级!

但是遗憾的是redis并没有,那么请思考为什么没有?如果让你实现你将如何实现

为什么不实现降级

当加入元素超过当前长度我们很容易就知道此时需要进行升级操作,但是当我们删除一个数据时我们如何判断是否需要降级却很困难,我们需要重新遍历一遍剩下的元素是否小于当前长度,实现复杂度O(N) 。这就是为什么不进行降级原因之一

你可能会说重新遍历一遍很快的反正在内存中,那么你有没有想过如果降级之后又遇到升级情况,这样来回的升级降级就降低了我们程序的性能了。我们知道升级是必须的所以这里降级redis采取的是忽略的策略

小结

参考资料:内存升级优化内存降级

到此这篇关于浅谈redis整数集为什么不能降级的文章就介绍到这了,更多相关redis整数集降级内容请搜索我们以前的文章或继续浏览下面的相关文章希望大家以后多多支持我们!

(0)

相关推荐

  • Redis中5种数据结构的使用场景介绍

    一.redis 数据结构使用场景 原来看过 redisbook 这本书,对 redis 的基本功能都已经熟悉了,从上周开始看 redis 的源码.目前目标是吃透 redis 的数据结构.我们都知道,在 redis 中一共有5种数据结构,那每种数据结构的使用场景都是什么呢? String--字符串 Hash--字典 List--列表 Set--集合 Sorted Set--有序集合 下面我们就来简单说明一下它们各自的使用场景: 1. String--字符串 String 数据结构是简单的 key-

  • 通俗易懂的Redis数据结构基础教程(入门)

    Redis有5个基本数据结构,string.list.hash.set和zset.它们是日常开发中使用频率非常高应用最为广泛的数据结构,把这5个数据结构都吃透了,你就掌握了Redis应用知识的一半了. string 首先我们从string谈起.string表示的是一个可变的字节数组,我们初始化字符串的内容.可以拿到字符串的长度,可以获取string的子串,可以覆盖string的子串内容,可以追加子串. Redis的字符串是动态字符串,是可以修改的字符串,内部结构实现上类似于Java的ArrayL

  • 浅谈redis五大数据结构和使用场景

    老规矩,先抛结论后验证 string:有点像java的hashMap,存的时候什么key,取的时候也什么key,常用于做缓存,保存用户信息.查询列表等: hash:这个有点像hashMap的value又套了个hashMap,下文有举例,一看就明白了: list:有序列表,类似Java的linkedList,可以在左边右边插入数据: set:去重集合,类似Java的hashset,可用于求交集,比如共同好友: zset:带权重的set集合,可用于做排行榜: 为了方便理解,我们基于这个dog类来做测

  • 详解redis数据结构之压缩列表

     详解redis数据结构之压缩列表 redis使用压缩列表作为列表键和哈希键的底层实现之一.当一个列表键只包含少量的列表项,并且每个列表项都是由小整数值或者是短字符串组成,那么redis就会使用压缩列表存储列表项:同理,当一个哈希表包含的键值对都是由小整数值或者是短字符串组成,并且存储的键值对数目不多时,redis也会使用压缩列表来存储哈希表.以下是压缩列表存储结构: zlbytes长度为4个字节,记录了整个压缩列表所占用的字节数 zltail长度为4个字节,记录了压缩列表起始位置到压缩列表尾节

  • 详解Redis中的双链表结构

    Redis中双链表实现的基本结构: 1.节点结构 typedef struct listNode { struct listNode *prev; //前向节点 struct listNode *next; //后向节点 void *value; //该节点的值 } listNode; 2.双向链表结构 typedef struct list { listNode *head; //头节点 listNode *tail; //尾节点 void *(*dup)(void *ptr); //复制函数

  • 浅谈redis整数集为什么不能降级

    目录 前言 基本结构 何时使用intset intset 添加元素 类型变动 升级 加入65535 旧数据移位 降级 为什么不实现降级 小结 前言 整数集合相信有的同学没有听说过,因为redis对外提供的只有封装的五大对象!而我们本系列主旨是学习redis内部结构.内部结构是redis五大结构重要支撑! 前面我们分别从redis内部结构分析了redis的List.Hash.Zset三种数据结构了.今天我们再来分析set数据结构内部是如何存储的 基本结构 在src/t_set.c中我们发现这样一段

  • 浅谈Redis 缓存的三大问题及其解决方案

    目录 一.缓存穿透 1. 常见解决方案 2. 布隆过滤器 3. 缓存空数据与布隆过滤器的比较 二.缓存击穿 解决方案 三.缓存雪崩 解决方案 Redis 经常用于系统中的缓存,这样可以解决目前 IO 设备无法满足互联网应用海量的读写请求的问题. 一.缓存穿透 缓存穿透是指缓存和数据库中都没有的数据,而用户不断发起请求,如发起 id 为-1 的数据或者特别大的不存在的数据.有可能是黑客利用漏洞攻击从而去压垮应用的数据库. 1. 常见解决方案 对于缓存穿透问题,常见的解决方案有以下三种: 验证拦截:

  • 浅谈Redis缓存雪崩解决方案

    目录 1.保持缓存层的高可用 2.限流降级组件 3.缓存不过期 4.优化缓存过期时间 5.使用互斥锁重建缓存 6.异步重建缓存 缓存层承载着大量的请求,有效保护了存储层.但是如果由于大量缓存失效或者缓存整体不能提供服务,导致大量的请求到达存储层,会使存储层负载增加(大量的请求查询数据库) .这就是缓存雪崩的场景; 解决缓存雪崩可以从下面的几点着手: 1.保持缓存层的高可用 使用Redis哨兵模式或者Redis集群部署方式,即是个别Redis节点下线,整个缓存层依然可以使用.除此之外还可以在多个机

  • 浅谈Redis缓存击穿、缓存穿透、缓存雪崩的解决方案

    目录 前言 Redis缓存使用场景 Redis缓存穿透 解决方案 1.对空值缓存 2.添加参数校验 3.采用布隆过滤器 Redis缓存雪崩 解决方案 1.大量热点数据同时失效带来的缓存雪崩问题 2. 服务降级 3. Redis 缓存实例发生故障宕机带来的缓存雪崩问题 Redis缓存击穿 解决方案 1. 热key不过期 2. 分布式锁 总结 缓存击穿 缓存穿透 缓存雪崩 前言 在日常的项目中,缓存的使用场景是比较多的.缓存是分布式系统中的重要组件,主要解决在高并发.大数据场景下,热点数据访问的性能

  • 浅谈redis的maxmemory设置以及淘汰策略

    redis的maxmemory参数用于控制redis可使用的最大内存容量.如果超过maxmemory的值,就会动用淘汰策略来处理expaire字典中的键. 关于redis的淘汰策略: Redis提供了下面几种淘汰策略供用户选择,其中默认的策略为noeviction策略: ·   noeviction:当内存使用达到阈值的时候,所有引起申请内存的命令会报错. ·   allkeys-lru:在主键空间中,优先移除最近未使用的key. ·   volatile-lru:在设置了过期时间的键空间中,优

  • 浅谈Redis哨兵模式的使用

    概述 主从切换技术的方法是:当主服务器宕机后,需要手动把一台从服务器切换为主服务器,这就需要人工 干预,费事费力,还会造成一段时间内服务不可用.这不是一种推荐的方式,更多时候,我们优先考虑 哨兵模式.Redis从2.8开始正式提供了Sentinel(哨兵) 架构来解决这个问题. 谋朝篡位的自动版,能够后台监控主机是否故障,如果故障了根据投票数自动将从库转换为主库. 哨兵模式是一种特殊的模式,首先Redis提供了哨兵的命令,哨兵是一个独立的进程,作为进程,它会独 立运行.其原理是哨兵通过发送命令,

  • 浅谈redis缓存在项目中的使用

    背景 Redis 是一个开源的内存数据结构存储系统. 可以作为数据库.缓存和消息中间件使用. 支持多种类型的数据结构. Redis 内置了 复制(replication),LUA脚本(Lua scripting), LRU驱动事件(LRU eviction),事务(transactions) 和不同级别的 磁盘持久化(persistence). 通过 Redis 哨兵(Sentinel)和 Redis 集群(Cluster)的自动分区,提供高可用性(high availability). 基本数

  • 浅谈Redis主从复制以及主从复制原理

    面临问题 1. 机器故障.我们部署到一台 Redis 服务器,当发生机器故障时,需要迁移到另外一台服务器并且要保证数据是同步的.而数据是最重要的,如果你不在乎,基本上也就不会使用 Redis 了. 2. 容量瓶颈.当我们有需求需要扩容 Redis 内存时,从 16G 的内存升到 64G,单机肯定是满足不了.当然,你可以重新买个 128G 的新机器. 解决办法 要实现分布式数据库的更大的存储容量和承受高并发访问量,我们会将原来集中式数据库的数据分别存储到其他多个网络节点上.Redis 为了解决这个

  • 浅谈Redis中的内存淘汰策略和过期键删除策略

    目录 8种淘汰策略 过期键的删除策略 总结 redis是我们现在最常用的一个工具,帮助我们建设系统的高可用,高性能. 而且我们都知道redis是一个完全基于内存的工具,这也是redis速度快的一个原因,当我们往redis中不断缓存数据的时候,其内存总有满的时候(而且内存是很贵的东西,尽量省着点用),所以尽可能把有用的数据,或者使用频繁的数据缓存在redis中,物尽其用. 那么如果正在使用的redis内存用完了,我们应该怎么取舍redis中已存在的数据和即将要存入的数据呢,我们要怎么处理呢? re

  • 浅谈Redis的keys命令到底有多慢

    keys命令的用法: keys pattern 查找符合正则匹配的key的列表.扫描对象是Redis服务中所有的key,想想都很慢对不对? 同时执行keys命令的同时,Redis进程将被阻塞,无法执行其他命令,假如超过了哨兵的down-after-milliseconds配置,还会进行主从切换,切换过程中,如果主节点恢复正常,还可能出现脑裂等一系列问题. 所以,生产环境中,建议直接禁用keys命令. Keys命令的替代方案 1.scan扫描,避免阻塞 2.将需要统计的数据放入一个set中 (但是

随机推荐