Redis入门指南:从零到分布式缓存-hash与list类型

文章目录

  • [一. hash类型](#一. hash类型)
    • [1. hset和hget](#1. hset和hget)
    • [2. hexists和hdel](#2. hexists和hdel)
    • [3. hkeys和hvals](#3. hkeys和hvals)
    • [4. hgetall和hmget](#4. hgetall和hmget)
    • [5. hlen和hsetnx](#5. hlen和hsetnx)
    • [6. hincrby和hincrbyfloat和hstrlen](#6. hincrby和hincrbyfloat和hstrlen)
    • [7. hash编码方式](#7. hash编码方式)
    • [8. 应用场景](#8. 应用场景)
  • [二. list类型](#二. list类型)
    • [1. list类型的特性](#1. list类型的特性)
    • [2. lpush和lrange和lpushx](#2. lpush和lrange和lpushx)
    • [3. rpush和rpushx](#3. rpush和rpushx)
    • [4. lpop和rpop](#4. lpop和rpop)
    • [5. lindex和linsert和llen](#5. lindex和linsert和llen)
    • [6. lrem](#6. lrem)
    • [7. ltrim](#7. ltrim)
    • [8. blpop和brpop](#8. blpop和brpop)
    • [9. list编码方式](#9. list编码方式)
    • [10. 应用场景](#10. 应用场景)

一. hash类型

Redis本身就是以键值对形式存储的数据, 可以说底层就是一个哈希表, 这里value又可以存一个哈希表, 相当于嵌套, 所以在Redis中value的哈希类型中的键叫做field, 值叫做value

1. hset和hget

复制代码
hset key field val [field val...]

1. hset 用来给key对应的值插入field和value, 同样的支持多组插入用来节省网络开销, 返回值是成功插入的(field-val)个数, 同时我们要注意hash类型中filed对应的val只能是string类型


复制代码
hget key field

2. hget 用来获取key对应的field对应的val, 不存在的话返回nil, 时间复杂度O(1)


2. hexists和hdel

复制代码
hexists key field

1. 判断当前key所对应的field是否存在, 存在返回1, 不存在返回0, 注意是要当key与field都存在才算存在, 时间复杂度O(1)


复制代码
hdel key field [field...]

2. 删除key所对应的field, 返回值是删除成功的个数, key不存在导致删除失败的话会返回0, 每个field的删除时间复杂度O(1), 这里我们需要注意, hdel删除的是key所对应的field, 而之前的del删除的是整个key所对应的哈希表


3. hkeys和hvals

复制代码
hkeys key

1. hkeys 作用是查询key中所有的field, 时间复杂度是O(K), k是哈希表中所有field个数, 当然这个命令和key * 一样危险, 工作中可以忘掉了~


复制代码
hvals key

2. hvals 作用是查询key所对应的哈希表中所有的value元素, 时间复杂度O(K), k取决于哈希表中元素value个数, 当然这个命令和key * 一样危险, 工作中可以忘掉了~


4. hgetall和hmget

复制代码
hgetall key

1. 获取key所对应的哈希表所有的field与value值, 两两配对, 当然也是十分危险的, 数据量一大就容易阻塞


复制代码
hmget key field [field...]

2. 一次获取key对应的哈希表中的多个value


5. hlen和hsetnx

复制代码
hlen key

1. 获取的是哈希表所有元素个数, 即所有哈希表中所有键值对个数


复制代码
hsetnx key field value

2. 当key对应的哈希表中field不存在时进行插入, 插入成功返回1, 失败返回0, 当然这里当key不存在时也可以进行创建哈希表并插入


6. hincrby和hincrbyfloat和hstrlen

复制代码
hincrby key field num

1. 对哈希表中的整型元素进行加减整数操作, 返回值是加减后的结果值, 这里需要注意哈希表的value必须是整型, 否则会报错


复制代码
hincrbyfloat key field num

2. 对哈希表中的整型或者浮点型元素进行加减小数的操作, 返回值是加减后的结果值


复制代码
hstrlen key field

3. 计算哈希表中value的字符串长度, 因为在Redis中哈希表的value必是string类型, 所以倒是不用注意类型


7. hash编码方式

1. 在初始篇我们已经谈论过hash的两种编码方式, ziplist和hashtable, 当哈希表中数元素个数较少时并且value的值都比较短时, 才会采用ziplist, 两个条件一个不满足就会转化为hashtable编码
2. ziplist是什么?
ziplist实际上就是通过一种压缩算法改变数据的排列方式, 将其变得更紧凑, 从而达到节省空间的作用, 相应的操作效率就变慢了, 但话又说回来了, 因为我们是在数据量较少且value长度较短时才使用这种压缩编码方式, 也就意味这就算慢也慢不了多少~

3. 我们说了元素较少, 长度较短, 究竟是多小多短?
其实这都是可以配置的, 实际开发中是根据业务场景来灵活配置的, 具体的配置文件目录是/etc/redis/redis.conf

8. 应用场景

适用于需要存储类似于表这种形式的数据

例如在一张student学生表中, 有id,name等字段, 每个字段又有对应的value值, 这时候hash类型就很适合, key用学生的uuid表示, 哈希表的每个field对应字段, value对应值

二. list类型

1. list类型的特性

1. list类型我们应该很熟悉, 毕竟当初我们学习编程第一个接触的就是list, 当时我们学的是动态数组的上层封装, 而Redis中的list类型底层不是一个简单的数组, 而是一个类似于Java中的双向链表, 这里的头插尾插都是O(1), 并且是一个有序的列表, 这里的有序指的不是'升序'/'降序'这样的有序, 而是插入顺序, 你怎么插入元素的那这个元素的先后顺序已经固定, 如果改变那么改变后的list与原先的list不等价~

2. lindex 和 lrem 的区别
lindex 返回获取到该下标元素的值
lrem 返回被删除元素的值
结果可能一样, 导致容易混淆, 但一个是删除元素, 一个是获取元素, 还是有很大差别
3. list列表中的元素是可以重复的, 这一点和我们在Java中学过的列表一样, 要注意的是在hash类型中field时不能重复的, value是可以重复的


2. lpush和lrange和lpushx

复制代码
lpush key element [element...]

1. 头插方法, 同时也支持多组插入, 多组插入时最后插入的元素位于首位, 返回值是插入后list的长度, 同时我们需要注意:插入时key如果不存在会创建list类型的value, 但如果key已经存在且value不是list类型, 那么lpush头插就会报错(在Redis中所有类型都是如此), 对应的类型必须使用对应的命令


复制代码
lrange key start stop

2. 用于获取对应区间范围的元素, 这里的区间是闭区间, 同样支持负数下标, 会自动转化为正数下标->len+负数

3. 这里给出的下标中如果超出限制的话, Redis中会如何处理呢?
答案是尽可能返回给定的范围内的一些合理的结果, 就算区间非法也会尽可能去获取可能存在的内容, 鲁棒性很强, 更类似于Python的处理方式


复制代码
lpushx key element [element ...]

4. 当key存在时才可以把元素进行头插进list, 如果key不存在则直接返回0, 这里同样支持多组插入, 返回值是头插后list的长度


3. rpush和rpushx

复制代码
rpush key element [element ...]

1. 将尾插元素到list, 同时也支持多组插入, 返回值是尾插后的list长度, key不存在时会自动创建key, 如果key存在必须确保value是list类型, 与上面lpush特性一样, 时间复杂度O(1)


复制代码
rpushx key element [element ...]

2. 当key存在时才可以把元素进行尾插进list, 如果key不存在则直接返回0, 这里同样支持多组插入, 返回值是头插后list的长度


4. lpop和rpop

复制代码
lpop key [count]

1. 用来删除头部元素, 返回值是删除的元素或者nil(当list中元素为空时), 同时在Redis6.2版本开始支持了批量删除, 加上count参数时, 会根据要求一个命令删除多个元素


复制代码
rpop key [count]

2. 用来删除尾部元素, 返回值是删除的元素或者nil(当list中元素为空时), 同时在Redis6.2版本开始支持了批量删除, 加上count参数时, 会根据要求一个命令删除多个元素


3. 因为Redis中的list与Java中的双向链表十分相似, 头尾插入和删除时间复杂度都是O(1), 因此当rpush和lpop搭配时, 相当于队列这个数据结构, rpush和rpop搭配时, 就相当于栈这个数据结构


5. lindex和linsert和llen

复制代码
lindex key index

1. 获取指定下标的元素, 支持负数下标, 返回值是对应的值或nil(下标非法), 时间复杂度O(N), N指的是索引的偏移量, 和Java中的双向链表更像了


复制代码
linsert key <before | after> pivot element

2. 在指定的基准值前面/后面插入一个元素, <before | after> 指定是在基准值的前/后插入, 必须且只能写一个, pivot 基准值, 该值是list中的元素值, 不是下标, 时间复杂度O(N), N是pivot距离头尾的距离

3. 有人可能有疑问, 因为我们已知在list中可以存在重复元素, 如果有多个值一样的元素, 那么插入时会选择哪个作为基准值呢?
答案就是会遍历list元素, 选择第一个作为基准值, 这也是为什么插入是O(N)的原因


复制代码
llen key

获取列表list的长度(元素个数), 返回值是int类型, 当key不存在时, 返回0, 时间复杂度O(1)


6. lrem

复制代码
lrem key count element

这个命名就可要好好说道说道了, 先说整体, 是一个删除list中元素的命令, count表示删除的个数, element表示删除的元素值, 时间复杂度O(N×M), N是元素个数, M是被删除的元素个数
下面我们讨论下count不同值的含义

1. count > 0 : 从左到右遍历list, 并删除count个值等于element的元素

2. count < 0 : 从右到左遍历list, 并删除count个值等于element的元素

3. count = 0 : 删除list中所有等于element的元素


7. ltrim

复制代码
ltrim key start stop

trim单词是修剪的意思, 在Redis中就是将除了区间外的元素全部删除, 删除成功返回ok, 时间复杂度O(N)


复制代码
lset key index element

将对应下标的值修改为element, 修改成功返回ok, 同样支持负数下标, 时间复杂度O(N), 毕竟要遍历list中所有元素到对应下标, 这里当给定的下标越界时, 会报错!


8. blpop和brpop

复制代码
blpop key [key...] timeout
brpop key [key...] timeout

1. 阻塞版本的删除元素, 看命令的写法我们可以知道一个是删除头元素一个是删除尾部元素, 返回值是一个二元组, 即key和对应的弹出的元素, 但这里当元素为空的时候, 如果运行阻塞版本的删除, 就会陷入阻塞状态, 在超时时间内有元素添加进list(立即返回该元素), 或者到达超时时间(返回nil), 阻塞才停止, 有点类似于阻塞队列BlockingQueue(如果对阻塞队列有疑问请移步这篇位置->多线程(五) ~ 阻塞队列与线程池)
2. 阻塞队列最大的特点就是队列为空, 继续弹出元素线程会陷入阻塞, 或者队列满时, 继续添加元素, 线程会陷入阻塞
3. 在Redis中因为并没有明确list中什么时候算作满的状态, 同时在工作中这个值很大一般也不会到达满的状态, 因此只有list为空的时候的阻塞版本命令
4. 在使用blpop或brpop的时候, 我们是需要指定超时时间的, 这意味着可以不必要一直等待新元素添加, 同时这也是我们常用的手段, 一般我们都不会设置无限制的等待
5. 因为Redis是单线程模型, 所以Redis对这个阻塞版本的命令有了新的优化, 当list陷入阻塞的时候, Redis可以进行执行命令的操作, 不会说整个Redis服务陷入阻塞
6. 如果命令中设置了多个键, 就会从左到右遍历键key, 每个key都是一个list列表, 一旦有一个键的对应的list中有元素就弹出哪个元素, 命令直接结束且返回弹出的值
7. 如果多个客户端同时去对一个键执行弹出操作, 那么最先执行的客户端就会返回弹出的元素, 其他的没门...
8. 这里timeout的单位是秒(s), 当然我们也可以设置小数来表示毫秒(从Redis6开始支持)
9. 相信大家已经有所猜测, 这里的命令其实最早是作为消息队列使用的, 但是实在是支持的功能有限, 现在这俩命令已经很少用了, 因为有更成熟和专业的消息队列, 并且就算是使用Redis作为消息队列, 也是使用的stream类型


9. list编码方式

1. 在通识篇我们提到过, 初始版本list在元素个数较少, 且每个元素又不是很大的情况下是ziplist编码方式, 否则就是linkedlist编码方式, 但是Redis的list类型现在已经全部换为了quicklist编码方式
2. quicklist编码结合了链表linkedlist和压缩列表ziplist的优点, 总的来说就是每个节点是一个ziplist, 但是又是通过链式结构连在一起的, 因此可以确保每个节点的value不是很大, 查找效率也不会说很低, 既节省了空间, 时间上也不会很低效~

10. 应用场景

1. 消息队列

这里如果list中为空时, 三个消费者都发送了brpop命令, 会全部阻塞, 假设消费者执行顺序是1,2,3, 在阻塞期间, 如果有生产者向list中push了一个元素, 那么消费者1会先获得元素, 同时brpop命令结束, 返回弹出的元素, 消费者2,3还在等待, 如果这期间消费者1再次执行了brpop, 还想要再消费一个元素, 这时就排在了消费者2,3的后面, 当有生产者再次push元素时, 会优先分给消费者2

2. 适用于一对多的场景
例如存储班级和学生信息的时候, 一个班级会有多个学生, 这时我们可以使用班级id作为key, value存储该班级所有学生的学号id
3. 适用于频繁查询的分页场景
①我们如果写一个竞赛系统, 会给每个进入系统的用户展示前十个竞赛, 且可以根据用户的操作进行分页, 这种简单的分页场景就很适合用lrange操作, 我们只需要在存入Redis的时候按照一定顺序(如时间顺序)存入, 取得时候只需要指定下标, 即可达到分页查询的效果
②当一个list的数据量过于庞大的时候, 我们可以进行拆分list, 1w条数据我们拆分成10份, 这样当用户需要获取5k个数据的时候, 我们就不需要从0遍历到5k, 但是在后端的代码维护上成本就要提升
③当需要进行多个Redis命令, 只为了一个结果集的时候, 我们可以采用pipeline(流水线)的方式, 相当于多个Redis命令合并, 通过一次网络通信就可以达到目的


我的博客即将同步至腾讯云开发者社区,邀请大家一同入驻:https://cloud.tencent.com/developer/support-plan?invite_code=66dzj29mmwz

相关推荐
jiayong234 小时前
RabbitMQ 完全指南
分布式·rabbitmq
小股虫4 小时前
Redis数据结构底层深度解析:写入与读取的高效逻辑
数据结构·redis·bootstrap
库库林_沙琪马4 小时前
9、缓存与Session共享
缓存
小股虫4 小时前
Redis实现轻量级消息队列:实操手册与项目应用指南
数据库·redis
Full Stack Developme4 小时前
Nginx 代理 mysql redis MQ 等各种软件,供客户端访问链接
redis·mysql·nginx
不会kao代码的小王5 小时前
openEuler容器化实战:用Docker和iSulad部署Redis
redis·docker·容器
BullSmall5 小时前
JDK17下Kafka部署全指南
分布式·kafka
qq_316837755 小时前
uniapp 缓存请求文件时 判断是否有文件缓存 并下载和使用
前端·缓存·uni-app
雨中飘荡的记忆5 小时前
MySQL + Redis 分布式事务消息中间件:保证最终一致性
redis·mysql