当前位置: 首页 > news >正文

Redis的常见数据结构

Redis内部数据结构

  • 1. redis数据结构和内部编码
  • 2. string
    • 2.1 常见命令
    • 2.2 计数命令
    • 2.3 字符串操作命令
    • 2.4 典型使用场景
  • 3. hash
    • 3.1 常见命令
    • 3.2 hash内部编码
    • 3.3 使用场景
  • 4. list
    • 4.1 常见命令
    • 4.2 阻塞版本命令
    • 4.3 使用场景
  • 5. set
    • 5.1 常见命令
    • 5.2 集合间操作
    • 5.3 使用场景
  • 6. zset
    • 6.1 常见命令
    • 6.2 集合操作命令
    • 6.3 内部编码
    • 6.4 使用场景
  • 7. 渐进式遍历

1. redis数据结构和内部编码

数据结构内部编码备注
stringraw基本的字符串(类似于C语言的char数组)
stringint可计数类型
stringembstr短字符串的特殊优化
hashhashtable基本哈希表
hashziplist压缩列表,在hash表元素较少时会使用
listquicklist实际是链表,每个元素是ziplist,类似于std::deque
sethashtable基本哈希表
setintset如果存的都是整数会使用
zsetskiplist跳表
zsetziplist压缩列表

数据类型是redis承诺提供给用户的,但其内部如何实现由redis自动适应选择最合适的编码

可以通过object encoding命令查询内部编码~
在这里插入图片描述
Redis这样设计有两个好处:

  1. 可以改进内部编码,而对外的数据结构和命令没有任何影响,这样一旦开发出更优秀的内部编码,无需改动外部数据结构和命令
  2. 多种内部编码实现可以在不同场景下发挥各⾃的优势,例如ziplist比较节省内存,但是在列表元素比较多的情况下,性能会下降,这时候Redis会根据配置选项将列表类型的内部实现转换为linkedlist,整个过程用户同样无感知。

2. string

在这里插入图片描述
字符串类型是Redis最基础的数据类型,关于字符串需要特别注意:
1)首先Redis中所有的键的类型都是字符串类型,而且其他几种数据结构也都是在字符串类似基础上构建的,例如列表和集合的元素类型是字符串类型,所以字符串类型能为其他4种数据结构的学习奠定基础。
2)其次,字符串类型的值实际可以是字符串,包含一般格式的字符串或者类似JSON、XML格式的字符;数字,可以是整型或者浮点型;甚至是二进制流数据,例如图片、音频、视频等。

不过一个字符串的最大值不能超过512MB。

注:由于Redis内部存储字符串完全是按照二进制流的形式保存的,所以Redis是不处理字符集编码问题的,客户端传⼊的命令中使用的是什么字符集编码,就存储什么字符集编码。在终端输入汉字,是按照utf8编码的,汉字是3字节;可以在启动redis客户端是加上–raw,让客户端尝试将字节流翻译成结果

2.1 常见命令

SET:将string类型的value设置到key中。如果key之前存在,则覆盖,无论原来的数据类型是什么。之前关于此keyTTL也全部失效。

SET key value [expiration EX seconds|PX milliseconds] [NX|XX]
时间复杂度:O(1)
SET命令支持多种选项来影响它的行为:

  1. EX seconds – 设置key的过期时间(秒级)
  2. PX milliseconds – (毫秒级)
  3. NX – 只在key不存在时才进行设置,即如果key之前已经存在,设置不执行。
  4. XX – 只在key存在时才进行设置,即如果key之前不存在,设置不执行。

GET:获取key对应的value。如果key不存在,返回nil。如果value的数据类型不是string,会报错。
GET key
时间复杂度:O(1)

MGET、MSET:分别是GET和SET的多次版,获得多个key对应的value和设置多个key
意义:由于Redis采用客户端服务器模式,每个请求都会封装成一个网络请求,通过网络传输,一次性操作多个key的效率是要高于多次操作一个key的

2.2 计数命令

INCR:将key对应的string表示的数字加⼀。如果key不存在,则视为key对应的value是0。如果key对应的string不是⼀个整型或者范围超过了64位有符号整型,则报错
INCR key
时间复杂度:O(1)
返回值:integer类型的加完后的数值。

INCRBY:将key对应的string表示的数字加上对应的值。如果key不存在,则视为key对应的value是0。如果key对应的string不是⼀个整型或者范围超过了64位有符号整型,则报错。
INCRBY key decrement

DECR、DECYB、INCRBYFLOAT:与INCRINCRBY类似,数值可以为负数

2.3 字符串操作命令

APPEND:如果key已经存在并且是⼀个string,命令会将value追加到原有string的后边。如果key不存在,则效果等同于SET命令。
APPEND KEY VALUE
返回值:追加完成之后string的长度。

GETRANGE:返回key对应的string的⼦串,由start和end确定(左闭右闭)。可以使用负数表示倒数。-1代表倒数第一个字符,-2代表倒数第二个,其他的与此类似。超过范围的偏移量会根据string的长度调整成正确的值。
GETRANGE key start end
返回值:string类型的子串

SETRANGE:覆盖字符串的⼀部分,从指定的偏移开始。如果key不存在也是能操作的,会把offset之前比特位设置为0x00
SETRANGE key offset value

STRLEN:获取key对应的string的⻓度。当key存放的类似不是string时,报错。

2.4 典型使用场景

  1. 缓存(Cache)功能 :Redis作为缓冲层,MySQL作为存储层,绝大部分请求的数据都是从Redis中获取。由于Redis具有支撑高并发的特性,所以缓存通常能起到加速读写和降低后端压力的作用
    有一个问题,当miss次数越来越多,redis中缓存的数据越来越多时,必定会有存不下新数据的时候这个时候有两个解决方法:
    • 每set一个key value就设置一个ttl,ttl由测试而来
    • redis在快存满时有自己的替换策略
  2. 计数(Counter)功能:许多应用都会使用Redis作为计数的基础工具,它可以实现快速计数、查询缓存的功能,同时数据可以异步处理或者落地到其他数据源。
  3. 共享会话(Session):⼀个分布式Web服务将⽤⼾的Session信息(例如用户登录信息)保存在各自的服务器中,但这样会造成⼀个问题:出于负载均衡的考虑,分布式服务会将用户的访问请求均衡到不同的服务器上,并且通常无法保证用户每次请求都会被均衡到同⼀台服务器上,这样当用户刷新⼀次访问是可能会发现需要重新登录,这个问题是用户无法容忍的,可以使用Redis将用户的Session信息进行集中管理
  4. 手机验证码

3. hash

3.1 常见命令

HSET:设置hash中指定的字段(field)的值(value)。
HSET key field value [field value ...]
返回值:添加的字段的个数。

HGET:·获取hash中指定字段的值。
HGET key field
返回值:字段对应的值或者nil

HEXISTS:判断hash中是否有指定的字段。
HEXISTS key field
返回值:1表示存在,0表示不存在。

HDEL:删除hash中指定的字段。
HDEL key field [field ...]
返回值:本次操作删除的字段个数。

HKEYS:获取 hash 中的所有字段。所有类似keys的命令都要慎用!!严重会造成服务器堵塞
HKEYS key
时间复杂度:O(N), N 为 field 的个数.

HVALS:获取 hash 中的所有的值。
HVALS key
返回值:所有的值。

HGETALL:获取 hash 中的所有字段以及对应的值。
HVALS key

HMGET:⼀次获取hash中多个字段的值
HMGET key field [field ...]

HLEN:获取 hash 中的所有字段的个数。
HLEN key

HSETNX:在字段不存在的情况下,设置 hash 中的字段和值。
HSETNX key field value

HINCRBY:将hash中字段对应的数值添加指定的值。
HINCRBY key field increment

HINCRBYFLOAT:HINCRBY 的浮点数版本。

3.2 hash内部编码

哈希的内部编码有两种:
• ziplist(压缩列表):当哈希类型元素个数⼩于 hash-max-ziplist-entries 配置(默认 512 个)、同时所有值都⼩于 hash-max-ziplist-value 配置(默认 64 字节)时,Redis 会使用ziplist 作为哈希的内部实现,ziplist 使用更加紧凑的结构实现多个元素的连续存储,所以在节省内存方面比 hashtable 更加优秀。
• hashtable(哈希表):当哈希类型⽆法满⾜ ziplist 的条件时,Redis 会使用hashtable 作为哈希的内部实现,因为此时 ziplist 的读写效率会下降,而 hashtable 的读写时间复杂度为 O(1)。

普通哈希表,会有空位,可能会浪费一定空间,ziplist会优化这些空间,付出的代价是读取元素的速度会比较慢
ziplist要满足两个情况:1.元素个数比较少 2.value的长度比较短

3.3 使用场景

映射关系表示用户信息:在这里插入图片描述
相比于使用 JSON 格式的字符串缓存用户信息,哈希类型变得更加直观,并且在更新操作上变得更灵活。可以将每个用户的 id 定义为键后缀,多对 field-value 对应用户的各个属性

但是需要注意的是哈希类型和关系型数据库有两点不同之处:
• 哈希类型是稀疏的,而关系型数据库是完全结构化的,例如哈希类型每个键可以有不同的 field,而关系型数据库⼀旦添加新的列,所有行都要为其设置值,即使为 null
• 关系数据库可以做复杂的关系查询,而 Redis 去模拟关系型复杂查询,例如联表查询、聚合查询等基本不可能,维护成本高。

4. list

列表类型是用来存储多个有序的字符串,列表中的每个字符串称为元素(element),⼀个列表最多可以存储 2 32 − 1 2^{32}-1 2321个元素。在 Redis 中,可以对列表两端插⼊(push)和弹出(pop),还可以获取指定范围的元素列表、获取指定索引下标的元素等。列表是⼀种比较灵活的数据结构,它可以充当栈和队列的角色,在实际开发上有很多应用场景。
在这里插入图片描述

4.1 常见命令

LPUSH:将⼀个或者多个元素从左侧放入(头插)到 list 中。
LPUSH key element [element ...]
返回值:插⼊后list的长度。

LPUSHX:在 key 存在时,将一个或者多个元素从左侧放入(头插)到 list 中。不存在,直接返回

RPUSHRPUSHX:类似,是尾插

LRANGE:获取从start到end区间的所有元素,左闭右闭。(在Redis中,如果下标超出范围,是尽可能的获取给定区间的元素,类似于python
LRANGE key start stop
时间复杂度:O(N)

LPOPRPOP:头删和尾删,LPOP和RPOP在Redis5只能删除一个元素,在6.2以后就可以用了LPOP key count删除多个元素了

LINDEX:获取从左数第 index 位置的元素。
LINDEX key index

LINSERT:从左往右找,找到第一个匹配的基准值位置插入
LINSERT key <BEFORE | AFTER> pivot element

LREM:删除count个对应元素
LREM key count element

LLEN:获取list长度

4.2 阻塞版本命令

Redis的初心是做一个消息队列,但是实际上用户总是将它作为缓存,而消息队列功能另有更好的选择。

阻塞版本命令就是为了实现简单的消息队列功能的。

BRPOPBLPOP:这两个命令可以设置一个timeout时间,意味着最多阻塞timeout秒
队列为空时,BLPOP和BRPOP会阻塞,直到插入了新元素或者timeout时间到才会返回

4.3 使用场景

分频道的消息队列:Redis 同样使⽤ lpush + brpop 命令,但通过不同的键模拟频道的概念,不同的消费者可以通过 brpop 不同的键值,实现订阅不同频道的理念。

比如刷抖音,
有一个通道传输视频
有一个通道传输弹幕
有一个通道传输点赞
有一个通道传输评论

这样在有一个通道出现问题的时候,不会影响其他的数据
实现了解耦合

5. set

集合类型也是保存多个字符串类型的元素的,但和列表类型不同的是,集合中 1)元素之间是无序的 2)元素不允许重复,⼀个集合中最多可以存储 2 32 − 1 2^{32}-1 2321 个元素。Redis 除了支持集合内的增删查改操作,同时还支持多个集合取交集、并集、差集,合理地使用好集合类型,能在实际开发中解决很多问题。
在这里插入图片描述

5.1 常见命令

SADD:将⼀个或者多个元素添加到set中。注意,重复的元素无法添加到set中。
SADD key member [member ...]

SMEMBERS:获取⼀个 set 中的所有元素,注意,元素间的顺序是无序的。

SISMEMBER:判断⼀个元素在不在 set 中。
SISMEMBER key member

SCARD:获取⼀个 set 的基数(cardinality),即 set 中的元素个数。

SPOP:从 set 中删除并返回⼀个或者多个元素。注意,由于 set 内的元素是无序的,所以取出哪个元素实际是未定义行为,即可以看作随机的。
类似有SRANDMEMBER,随机选择一个元素,不删

SMOVE:将⼀个元素从源 set 取出并放入目标 set 中。如果移动的元素存在与源set中,一定能成功;如果不存在,就不能成功
SMOVE source destination member

SREM:将指定的元素从 set 中删除。
SREM key member [member ...]

5.2 集合间操作

交集(inter)、并集(union)、差集(diff)
SINTER:获取给定 set 的交集中的元素。
SINTER key [key ...]
时间复杂度:O(N * M), N 是最小的集合元素个数. M 是最大的集合元素个数.

SINTERSTORE:将结果存到目标set中
SINTERSTORE destination key [key ...]

SUNION:获取给定 set 的并集中的元素。
SUNION key [key ...]
时间复杂度:O(N), N 给定的所有集合的总的元素个数.

SUNIONSTORE :类似

SDIFF:获取给定 set 的差集中的元素。
SDIFF key [key ...]
时间复杂度:O(N), N 给定的所有集合的总的元素个数.

SDIFFSTORE:类似

5.3 使用场景

集合类型比较典型的使用场景是标签(tag)。例如 A用户对娱乐、体育板块比较感兴趣,B用户对历史、新闻比较感兴趣,这些兴趣点可以被抽象为标签。有了这些数据就可以得到喜欢同⼀个标签的人,以及用户的共同喜好的标签,这些数据对于增强用户体验和用户黏度都非常有帮助。 例如⼀个电子商务网站会对不同标签的用户做不同的产品推荐。

6. zset

给每个member添加一个score,以score来进行排序,member仍要求是唯一的。
有序集合相对于字符串、列表、哈希、集合来说会有⼀些陌生。它保留了集合不能有重复成员的特点,但与集合不同的是,有序集合中的每个元素都有⼀个唯⼀的浮点类型的分数(score)与之关联,着使得有序集合中的元素是可以维护有序性的,但这个有序不是用下标作为排序依据而是用这个分数。

在这里插入图片描述

6.1 常见命令

ZADD:添加或者更新指定的元素以及关联的分数到 zset 中,分数应该符合 double 类型,+inf/-inf 作为正负极限也是合法的。

ZADD 的相关选项:
• XX:仅仅用于更新已经存在的元素,不会添加新元素。
• NX:仅用于添加新元素,不会更新已经存在的元素。
• CH:默认情况下,ZADD 返回的是本次添加的元素个数,但指定这个选项之后,就会还包含本次更新的元素的个数。
• INCR:此时命令类似 ZINCRBY 的效果,将元素的分数加上指定的分数。此时只能指定⼀个元素和分数

ZADD key [NX | XX] [GT | LT] [CH] [INCR] score member [score member ...]
在这里插入图片描述
时间复杂度:O(log(N))

ZCARD:获取⼀个 zset 的基数(cardinality),即 zset 中的元素个数。

ZCOUNT:返回分数在 min 和 max 之间的元素个数,默认情况下,min 和 max 都是包含的,可以通过 "("排除。
有两个特殊的数值:INF和-INF – 无穷大和负无穷大
不是通过遍历实现的,而是会记录每个member当前的排行/次序max次序-min次序一键得知
ZCOUNT key min max

ZRANGE:返回指定区间里的元素,分数按照升序。带上 WITHSCORES 可以把分数也返回。
ZRANGE key start stop [WITHSCORES]
时间复杂度:O(log(N)+M)

ZREVRANGE:返回指定区间⾥的元素,分数按照降序。带上 WITHSCORES 可以把分数也返回。

ZRANGEBYSCORE:返回分数在 min 和 max 之间的元素,默认情况下,min 和 max 都是包含的,可以通过 “(” 排除。

ZPOPMAX:删除并返回分数最高的count个元素。如果score最高的元素有多个,就按照字典序删除一个
ZPOPMAX key [count]
时间复杂度:O(log(N)*M),此时间复杂度说明需要找一遍分数最高的元素

BZPOPMAX:阻塞版本

ZPOPMINBZPOPMIN:类似,删除分数最小的count个元素

ZRANK:返回指定元素的排名,升序。
ZRANK key member

ZREVRANK:降序排名

ZSCORE:返回指定元素的分数。
ZSCORE key member
时间复杂度:O(1),由于这个操作使用频率非常高,所以做了特殊的处理,付出了额外的空间开销

ZREM:删除指定的元素。
ZREM key member [member ...]

ZREMRANGEBYRANK:按照排序,升序删除指定范围的元素,左闭右闭。
ZREMRANGEBYRANK key start stop

ZREMRANGEBYSCORE:按照分数删除指定范围的元素,左闭右闭。
ZREMRANGEBYSCORE key min max

ZINCRBY:为指定的元素的关联分数添加指定的分数值
ZINCRBY key increment member

6.2 集合操作命令

ZINTERSTORE:求出给定有序集合中元素的交集并保存进目标有序集合中,在合并过程中以元素为单位进行合并,元素对应的分数按照不同的聚合方式和权重得到新的分数。
ZINTERSTORE destination numkeys key [key ...] [WEIGHTS weight [weight ...]] [AGGREGATE <SUM | MIN | MAX>]
numkeys:解决指令间的类似“粘包问题”,分割key和后面的参数
weights:权重,系数,对应集合的每个member的分数会乘上对应的系数

ZUNIONSTORE:并集,类似

6.3 内部编码

有序集合类型的内部编码有两种:
• ziplist(压缩列表):当有序集合的元素个数⼩于 zset-max-ziplist-entries 配置(默认 128 个),同时每个元素的值都⼩于 zset-max-ziplist-value 配置(默认 64 字节)时,Redis 会用ziplist 来作为有序集合的内部实现,ziplist 可以有效减少内存的使用。
• skiplist(跳表):当 ziplist 条件不满足时,有序集合会使用 skiplist 作为内部实现,因为此时ziplist 的操作效率会下降。

有关跳表的详细实现请参考跳表详解

6.4 使用场景

有序集合比较典型的使用场景就是排行榜系统。例如常见的网站上的热榜信息,榜单的维度可能
是多方面的:按照时间、按照阅读量、按照点赞量。本例中我们使用点赞数这个维度,维护每天的热榜:
1)添加用户赞数
例如用户james 发布了⼀篇⽂章,并获得 3 个赞,可以使用有序集合的 zadd 和 zincrby 功能,之后如果再获得赞,可以使用zincrby
2)取消用户赞数
由于各种原因(例如用户注销、用户作弊等)需要将用户删除,此时需要将用户从榜单中删除掉,可以使用zrem。
3)展示获取赞数最多的10个用户
4)展示用户信息以及用户分数

7. 渐进式遍历

Redis在使用keys命令时,如果匹配到的key很多,会造成redis的阻塞,为解决此问题,redis提供了渐进式遍历的方案。

Redis 使用 scan 命令进行渐进式遍历键,进而解决直接使用 keys 获取键时可能出现的阻塞问题。每次 scan 命令的时间复杂度是 O(1),但是要完整地完成所有键的遍历,需要执行多次 scan。

SCAN:以渐进式的方式进行键的遍历。(此遍历,redis不会存储任何状态信息,所以需要自己传入状态)
SCAN cursor [MATCH pattern] [COUNT count] [TYPE type]
COUNT:获取的匹配到的key的个数,和MySQL的limit不一样,limit是精确的,但是此处只是给redis一个建议,实际获取到的key数量不确定
TYPE:只返回符合类型的key
返回值:下⼀次 scan 的游标(cursor)以及本次得到的键。

• ⾸次 scan 从 0 开始.
• 当 scan 返回的下次位置为 0 时, 遍历结束.

相关文章:

  • 内容中台驱动企业CMS架构优化与高效策略
  • springCloud-2021.0.9 之 GateWay 示例
  • Android Studio 打包App问题
  • 从0开始的操作系统手搓教程 4:做好准备,跳到加载器(Loader)
  • Word中设置表格在同一页
  • 深入解析SVG图片原理:从基础到高级应用
  • SpringCloud框架下的注册中心比较:Eureka与Consul的实战解析
  • elasticsearch8 linux版以服务的方式启动
  • 基于javaweb的SpringBoot宠物医院管理系统设计和实现(源码+文档+部署讲解)
  • Ubuntu22.04通过Docker部署Jeecgboot
  • 动态规划dp_4
  • 【天地图】绘制、删除点线面
  • 【kafka系列】Kafka如何实现高吞吐量?
  • 一键安装教程
  • Communications link failure异常分析解决
  • 138,【5】buuctf web [RootersCTF2019]I_<3_Flask
  • 使用 Dockerfile 构建自定义 Nginx 镜像并集成 nginx_upstream_check_module
  • 从零开始-将小爱接入大模型
  • 二叉树(C语言版)
  • vue3--SVG图标的封装与使用
  • 【社论】以法治力量促进民企长远健康发展
  • 大风暴雨致湖南岳阳县6户房屋倒塌、100多户受损
  • 【社论】三个“靠谱”为市场注入确定性
  • 宁合两大都市圈交汇之城含山:要想身体好,常往含山跑
  • 吴清:推动公募基金高质量发展的行动方案今天将会发布
  • 李云泽:房地产“白名单”贷款审批通过金额增至6.7万亿元