redis数据类型

Redis有以下这五种基本类型:

  • String(字符串)
    • 字符串是最redis最基本的数据结构,二进制安全,可以存放任何数据
    • 底层采用的是SDS简单动态字符串,包括了存放数据的字节数组,以及数组容量和数组长度,有点类似与java的ArrayList,需要比实际内容多分配一些冗余空间,因为redis的字符串支持append操作,如果没有冗余空间,就需要频繁的扩容,内存的开销会很大
    • 当字符串长度小于1MB时,扩容都是现有空间的二倍,超过1MB时,扩容一次只会多扩1MB,最大可以存储512m的数据
  • Hash(哈希)
    • hash是一个键值对集合,是一个String类型的字段和值的映射表,很适合存储对象,每个hash最多能存储int最大值个对象
    • 底层是两个hashTable,一个用于存放数据,一个用于扩容和缩容。扩容时,负载因子和扩容比都是1,而当hashtable 中元素主键减少时,会进行缩容,缩容的条件是元素个数小于数组长度的10%
  • List(列表)
    • 是一个双链表结构,保存了字符串的插入序,越靠近链表的两端,效率越高,适合做消息排行榜和消息队列
  • Set(集合)
    • 是String类型的无序集合,没有重复元素
    • 可以用来计算交、并、差操作
    • 底层是intset或者hashtable,
    • 只有集合中保存的元素都是整数类型,而且保存的元素个数不超过512个,才会使用intset,可以看作是一个数组
    • hashtable就是key为set的值,value为null的哈希表
  • zset(有序集合)
    • 和集合一样都是Sting类型的集合,不允许重复元素
    • 不同的是每个元素都有一个Double类型的分数,用来排序,分数是可以重复的,
    • 底层是ziplist或者skiplist来实现的
    • 只有元素个数小于128个,并且所有元素长度小于64字节,才会使用ziplist,ziplist会使用两个紧挨的节点来保存元素,第一个节点保存元素本身,第二个节点保存分值
    • skiplist是一种特殊类型的有序链表,是一种随机结构,会再一条有序链表上选取一半的节点来建立索引,然后再在这层索引中在选取一半的节点再建立索引,直到最上层的节点只剩下两个节点时,就没必要建立索引了,查找时相当于在做二分查找,效率很高,而且实现简单
    • 能用于排行榜或者代权重的消息队列

list、set、Hash、zset都是容器型数据结构,如果容器不存在,那就创建一个,如果容器中没有元素,那么立即删除容器,释放内存,例如list弹出最后一个元素后,list本身就消失了

redis字符串的存储方式:

  • redis字符串有两种存储方式,长度很短时使用embstr形式存储,长度超过44字节时使用raw形式存储
  • 一个redisObject对象的头结构需要16字节,SDS对象头需要在最少占用3字节
  • embstr使用一次malloc分配,将redisObject对象头结构和SDS对象连续存在一起,raw需要两次malloc分配,两个对象头在地址上一般是不连续的
  • 而内存分配器分配内存的单位是2/4/8/16/32/64字节,为了容纳一个完整的embstr最少需要32字节,如果字符串在稍长一些就需要64字节了,如果超过64字节,redis就认为是一个大字符串了,需要使用raw形式
  • 之所以是44字节,是因为SDS结构中content中的字符串是以字节null结尾的,所以要多出一个字节,在加上两个对象头的19个字节,就只剩下44字节了

字典结构:

  • 字典是redis最常见的复合型数据结构,除了hash结构外,整个redis数据库的所有key和value也组成了一个全局字典,还有带过期时间的key的集合也是一个字典,包括zset中存储value和score值的映射关系也是通过字典结构实现的
  • 字典结构内部包含两个hashtable,通常情况下只有一个hashtable有值,在字典进行扩容缩容时,需要分配新的hashtable,然后进行渐进式搬迁,搬迁结束后,旧的hashtable会被删除,被新的hashtable取代
  • hashtable结构和java中的hashMap几乎一样,第一维是数组,第二维是链表,如果hash函数存在偏向性就有可能被黑客利用对服务器进行攻击,有可能会导致第二维链表长度的极不均匀,造成性能的严重下降,也就是所谓的hash攻击
  • redis使用的hash函数是siphash,能够产生随机性很好的hash值,并且效率很高
  • 扩容:
    • 正常情况下,当hash表中元素个数等于数组长度时,就会开始扩容,扩容的新数组是原数组大小的二倍
    • 但是如果正在做bgsave,为了减少内存页的过多分离,redis就会尽量不去扩容,但是如果hash表非常满了,元素个数达到了数组长度的5倍们就会强制扩容
  • 缩容:
    • 当hash表元素逐渐被删除变得越来越稀疏时,redis会对hash表进行缩容来减少空间占用
    • 缩容的条件是元素个数低于数组长度的10%,缩容时不会考虑redis是否在做bgsave

字典遍历

  • redis对象的主干是一个字典,如果对象很多,这个字典也会很大,在遍历的过程中,如果找到了key,还需要判断key是否过期,过期了就要把key删除
  • redis为字典的遍历提供了两种迭代器,一种是安全迭代器,另一种是不安全迭代器
    • 安全迭代器是指在遍历过程中可以对字典进行查找和修改,会触发过期判断,会删除内部元素,能够保证迭代过程中不会出现重复元素,keys、bgsave使用的就是安全迭代器,因为结果不允许重复
    • 不安全指的是在遍历过程中,字典是只读的,不能修改,不能调用任何可能触发过期判断的函数,这样不会影响rehash,但是会有重复元素

压缩列表

  • redis为了节约空间,zset和hash容器对象在元素个数较少的时候,采用压缩列表ziplist进行存储
  • ziplist是一块连续的内存空间,元素之间紧挨着存储,没有任何冗余空隙
  • 压缩链表记录了整个列表的占用字节数、元素个数、元素内容列表、标志压缩列表的常量值,以及最后一个元素距离压缩链表起始位置的偏移量,这个偏移量可以快速定位最后一个节点
  • 压缩列表内部元素记录了元素类型编码、元素内容和前一个元素的字节长度,想要倒着遍历的时候,就需要通过最后一个节点和前一个元素的字节长度来定位到遍历的下个元素
  • redis为了节约存储空间,对元素类型编码做了非常复杂的设计,ziplist也会根据元素类型编码(encoding)决定元素内容的形式
  • 因为ziplist是紧凑存储,没有冗余空间, 插入一个新元素就需要扩容,如果ziplist占用内存很大,重新分配内存和拷贝内存就会有很大的消耗,所以ziplist不适合存储大型字符串,元素也不能太多

list

  • redis早期版本的list列表,使用的是压缩链表(ziplist)和普通的双向链表(linkedlist),数据量少的时候使用ziplist,多的时候就使用linkedlist
  • 然而链表的附加空间成本相对较高,只是两个前后指针在64位操作系统就要花费16字节,而且每个元素都是单独分配内存,会加剧内存的碎片化,影响内存管理效率
  • 所以redis使用quicklist代替了ziplist和linkedlist,quicklist是ziplist和linkedlist的混合体,quicklist将linkedlist分段,每一段都使用ziplist存储,多个ziplist之间使用双向指针连接
  • quicklist内部默认的ziplist长度为8KB,超过这个字节数,就会另起一个ziplist

ZSet 有两种不同的实现,分别是 ziplist 和 skiplist。具体使用哪种结构进行存储,规则如下:

  • ziplist:满足以下两个条件

    • [value,score] 键值对数量少于 128 个
    • 每个元素的长度小于 64 字节
  • skiplist:不满足以上两个条件时使用跳表、组合了 hash 和 skiplist

    • hash 用来存储 value 到 score 的映射,这样就可以在 O(1) 时间内找到 value 对应的分数
    • skiplist 按照从小到大的顺序存储分数
    • skiplist 每个元素的值都是 [value,score] 对

skiplist

  • redis的zset是一个复合结构,同时需要一个hash结构存储value和score的对应关系(不允许重复元素,所以value可以作为hash的key),还需要按照score的范围来获取value列表的功能,所以zset的内部是一个hash字典和一个跳跃列表skiplist
  • 跳表可以保证增、删、查等操作时的时间复杂度为O(logN),这个性能可以与AVL平衡二叉树相媲美,但实现方式上却更加简单,唯一美中不足的就是跳表占用的空间比较大,其实就是一种空间换时间的思想
  • redis的跳表共有64层,最多容纳2^64个元素,每个节点都是一个skiplistNode,每个跳表的节点也都会维护着一个score值,这个值在跳表中是按照从小到大的顺序排列好的。

redis消息队列:

  • redis可以使用 list 作为异步消息队列使用,用rpush、lpush入队列,lpop、rpop出队列
  • 客户端通过pop操作来获取消息,然后进行处理,处理完了在接着获取消息,如此循环,
  • 直到队列空了,此时为了防止客户端不断的pop,可以让线程睡眠一段时间,redis提供了阻塞读的功能,也就是blpop、brpop(前缀b为blocking)阻塞读在队列里没有消息时,会进入休眠状态,一旦数据到来,立马醒过来
  • 但是如果线程一直阻塞,服务器一般会断开连接,减少闲置资源的占用,这个时候blpop、brpop就会抛异常,所以如果捕获到异常,需要重试
  • 但是它不是专门的消息队列,没有ACK保证,如果对可靠性要求很高,那它就不适合

延时队列:

  • redis延时队列可以 通过zset来实现,将消息作为value,消息到期的处理时间为分数,然后使用多个线程轮询到期的任务进行处理

三种特殊的数据结构类型

  • Geospatial :Redis3.2推出的,地理位置定位,用于存储地理位置信息,并对存储的信息进行操作。
    • 地图元素的位置数据使用二维的经纬度表示,经度范围[-180,180],维度范围[-90,90],经度以本初子午线为界东正西负,维度以赤道为界北正南负
  • Hyperloglog :用来做基数统计算法的数据结构,如统计网站的UV。
    • 提供不精确的去重统计方案,标准误差是0.81%
    • Hyperloglog提供了pfadd和pfcount,一个用于增加计数,一个用于获取计数
    • 一个Hyperloglog需要占据12kb的存储空间,所以并不适合统计单个用户的相关数据,redis也对它做了优化,在数据很小的时候,采用稀疏矩阵存储,空间占用很小,只有稀疏矩阵占用空间超过阈值时,才会一次性变为稠密矩阵,才会占用12Kb
  • Bitmap (位图)
    • 用一个比特位来映射某个元素的状态,在Redis中,它的底层是基于字符串类型实现的,可以把bitmaps成作一个以比特位为单位的数组
    • 位图最小的单位是bit,每个bit取值只能是0或者1,用于记录类似签到记录这种数据可以极大的减少开销
    • 位图不是特殊的数据结构,它的内容其实就是普通的字符串,也就是byte数组,可以使用get/set获取、设置整个位图的内容,也可以用 getbit/setbit 将byte数组看为位数组来处理
    • redis的位数组是自动扩展的,位数组的顺序和字符的位顺序是相反的
    • redis提供了bitcount来统计指定范围内1的个数,bitpos来查找指定范围内出现的第一个0或者1,但是start和end参数是字节索引,也就是必须是8的倍数,所以无法直接计算一个月内用户签到了多少次

布隆过滤器

  • 可以把布隆过滤器理解为一个不精确的set结构,官方的布隆过滤器在4.0之后才正式登场,有bf.add和bf.exists两个基本指令,bf.add 用来添加元素(bf.madd批量添加),bf.exists用于判断元素是否存在(bf.mexists批量判断)
  • 布隆过滤器有两个重要参数;
    • 错误率(error_rate),默认为0.01,错误率越小,需要的空间就越大
    • 预计放入的元素数量(initial_size),默认为100,当实际数量超过这个数量,误判率就会上升,所以如果这个值过大就会浪费存储空间,过小又会影响准确率,使用之前要尽可能的估算元素数量
  • 布隆过滤器底层是一个大的位数组和几个不一样的无偏hash函数,无偏指的是能把hash值算的比较均匀
    • 向布隆过滤器添加元素的时候,会使用hash函数对key进行hash,取得一个整数索引值,然后根据索引值对位数组长度进行取模得到一个位置,每个hash函数都会得到一个位置,把这些位置都置为1
    • 向布隆过滤器询问key是否存在时,和添加一样,也会把hash的几个位置都算出来,如果位数组中这几个位置有一个不为1,那么key一定不存在,如果都为1 ,并不能说明key一定存在,只是存在的概率很高
    • 所以布隆过滤器判断对象是否存在时可能会误判:如果判断为不存在那么一定不存在,如果判断为存在有可能不存在
    • 如果实际元素数量远大于初始化量,错误率的上升会很快,此时就需要重建布隆过滤器,重新分配一个更大的容量,然后把历史数据添加进去
  • 布隆过滤器用来做新闻推送时,能够精确的过滤用户已近看过的内容,对于一些没有看过的新内容也会过滤极小的一部分,这样可以保证用户不会看到重复内容
  • 还可以通过布隆过滤器降低数据库的IO请求,执行查询时,先访问布隆过滤器,过滤掉不存在的数据请求,然后再去查询数据库

redis简单的限流:

  • 限流就是当系统的处理能力有限时,需要阻止处理能力以外的请求继续对系统施压,除了要限定某行为在规定时间内被允许的访问次数,当超过次数之后,业务上还需要做出惩罚
  • 限流存在一个滑动的时间窗口,可以通过zset结构的分数值来实现
    • 用一个zset结构存储用户的行为历史,每一个行为都作为zset中的key保存下来。
  • 这种方案需要记录时间窗口内的所有行为,如果数据量很大,就会很消耗内存,就不应该选用这种方式了

Stream

  • 是redis5.0新增的数据结构,是一个支持消息多播的可持久化消息队列,其设计借鉴了kafka
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值