admin管理员组

文章数量:1794759

Redis常见面试题总结

Redis常见面试题总结

文章目录
  • 0、Redis是什么?
  • 1、redis为啥那么快
  • 2、基本数据类型:
  • 3、reids的内存删除策
  • 4、缓存穿透
  • 5、缓存击穿
      • 解决方案
  • 6、缓存雪崩
      • 总结
  • 7、热key重建
      • 解决方案:
  • 8、Redis的持久化原理
      • 1、RDB(快照)
        • (手动)同步: save
        • (手动)异步: bgsave
        • (自动) 配置文件
          • 触发的机制:
      • 2、AOF (只追加日志文件)
        • AOF重写
  • 9、主从复制
  • 10、哨兵Sentinel机制
  • 10、说一下 Redis 和Memcached 的区别和共同点
  • 11、缓存数据的处理流程是怎样的?
  • 12、为什么要用 Redis为什么要用缓存?
  • 13、Redis 常见数据结构以及使用场景分析
        • string
        • list
        • hash
        • **set**
        • sorted set
        • bitmap
  • 14、Redis为何选择单线程?
  • 15、Redis事
Redis

0、Redis是什么?

Redis(Remote Dictionary Server)是一个使用 C 语言编写的,高性能非关系型的键值对数据库。与传统数据库不同的是,Redis 的数据是存在内存中的,所以读写速度非常快,被广泛应用于缓存方向。Redis可以将数据写入磁盘中,保证了数据的安全不丢失,而且Redis的操作是原子性的。

1、redis为啥那么快
  • 基于内存:Redis是使用内存存储,没有磁盘IO上的开销。数据存在内存中,读写速度快。

  • 单线程实现( Redis 6.0以前):Redis使用单个线程处理请求,避免了多个线程之间线程切换和锁资源争用的开销。

  • IO****多路复用模型:Redis 采用 IO 多路复用技术。Redis 使用单线程来轮询描述符,将数据库的操作都转换成了事件,不在网络I/O上浪费过多的时间。

  • 高效的数据结构:Redis 每种数据类型底层都做了优化,目的就是为了追求更快的速度。

2、基本数据类型:

数据类型HashStringListSetZset内部编码数量2种3种编码3种编码2种3种编码2种3种编码2种3种编码内部编码hashtable、ziplistraw、int、embstrlinkedlist、ziplisthashtable、intsetskiplist、ziplist

3、reids的内存删除策
  • 定期清理:随机选择dicEntry出来判断是否过期,过期就清理掉,没有过期则返回重新随机选择。清理超过25%的数量,则循环前面的步骤<=20次。
  • 随机的测试20个带有timeout(过期时间)信的key;

  • 删除其中已经过期的key;

  • .如果超过25%的key被删除,则重复执行步骤1

  • 惰性删除:被动删除策yu,访问时候判断是否过期。目的主要删除策与节省CPU资源

  • LRU:最近最久未使用,从设置过期时间的数据集中选择最近最久未使用的数据进行释放

  • LFU:对LRU的优化的算法

  • 4、缓存穿透

    key对应的数据在数据源中并不存在,每次针对此KEY的请求从缓存中获取不到,请求就会到数据源中,从而可能压垮数据源

    • 解决方案:

    1、使用缓存空数据:会占用更过的key,还有可能带来短期的数据不一致。

    如果一个查询返回的数据为空(不管是数 据不存在,还是系统故障),我们仍然把这个空结果进行缓存,但它的过期时间会很短,最长不超过五分钟。

    2、使用布隆过滤器bloom filter:是一种预防的方案,占用空间少、误差可控。

    将所有可能存在的数据哈希到一个足够大的bitmap中,一个一定不存在的数据会被 这个bitmap拦截掉,从而避免了对底层存储系统的查询压力

    布隆过滤器:

    它实际上是一个很长的二进制向量和一系列随机映射函数。布隆过滤器可以用于检索一个元素是否在一个集合中

    5、缓存击穿

    对于一些设置了过期时间的key,如果这些key可能会在某些时间点被超高并发地访问,是一种非常“热点”的数据。这个时候,需要考虑一个问题:缓存被“击穿”的问题,这个和缓存雪崩的区别在于这里针对某一key缓存,前者则是很多key。

    缓存在某个时间点过期的时候,恰好在这个时间点对这个Key有大量的并发请求过来,这些请求发现缓存过期一般都会从后端DB加载数据并回设到缓存,这个时候大并发的请求可能会瞬间把后端DB压垮。

    解决方案

    缓存失效时的雪崩效应对底层系统的冲击非常可怕。大多数系统设计者考虑用加锁或者队列的方式保证缓存的单线 程(进程)写,从而避免失效时大量的并发请求落到底层存储系统上。这里分享一个简单方案就时讲缓存失效时间分散开,比如我们可以在原有的失效时间基础上增加一个随机值,比如1-5分钟随机,这样每一个缓存的过期时间的重复率就会降低,就很难引发集体失效的事件。

    6、缓存雪崩

    当cache缓存服务器异常的时候,这时又有大量的请求涌入,而cache异常,直接涌向后端组件造成级联故障,还有造成数据源崩溃

    解决方案

    1.使用互斥锁(mutex key)

    业界比较常用的做法,是使用mutex。简单地来说,就是在缓存失效的时候(判断拿出来的值为空),不是立即去load db,而是先使用缓存工具的某些带成功操作返回值的操作(比如Redis的SETNX或者Memcache的ADD)去set一个mutex key,当操作返回成功时,再进行load db的操作并回设缓存;否则,就重试整个get缓存的方法。

    SETNX,是「SET if Not eXists」的缩写,也就是只有不存在的时候才设置,可以利用它来实现锁的效果。在redis2.6.1之前版本未实现setnx的过期时间

    2."提前"使用互斥锁(mutex key):

    在value内部设置1个超时值(timeout1), timeout1比实际的memcache timeout(timeout2)小。当从cache读取到timeout1发现它已经过期时候,马上延长timeout1并重新设置到cache。然后再从数据库加载数据并设置到cache中。

    3.“永远不过期”:

    这里的“永远不过期”包含两层意思:

    (1) 从redis上看,确实没有设置过期时间,这就保证了,不会出现热点key过期问题,也就是“物理”不过期。

    (2) 从功能上看,如果不过期,那不就成静态的了吗?所以我们把过期时间存在key对应的value里,如果发现要过期了,通过一个后台的异步线程进行缓存的构建,也就是“逻辑”过期

    从实战看,这种方法对于性能非常友好,唯一不足的就是构建缓存时候,其余线程(非构建缓存的线程)可能访问的是老数据,但是对于一般的互联网功能来说这个还是可以忍受。

    总结

    穿透:缓存不存在,数据库不存在,高并发,少量key

    击穿:缓存不存在,数据库存在,高并发,少量key

    雪崩:缓存不存在,数据库存在,高并发,大量key

    7、热key重建

    高并发多线程的情况下,热Key重键是使用redis比较典型的一个问题:

    解决方案:
    • 加锁重键(互斥锁):
    • 热键不过期:在缓存中创建一个时间戳,先判断时间戳是否过期,如果没有过期返回原数据,过期了则访问数据源
    8、Redis的持久化原理

    将Redis内存中的数据异步地保存在磁盘上就是Redis的持久化

    1、RDB(快照)

    RDB是 Redis 默认的持久化方案。RDB持久化时会将内存中的数据写入到磁盘中,在指定目录下生成一个

    dump.rdb文件。Redis 重启会加载

    dump.rdb文件恢复数据。

    有三种触发机制

    (手动)同步: save

    redis服务器收到客户端发送的SAVE命令,会创建快照。在创建结束之前,不会响应客户端的请求。

    (手动)异步: bgsave

    当接收到客户端的BGSAVE命令时,redis会调用fork创建一个子进程,然后由子进程负责将快照写入磁盘中,而父进程继续处理客户端请求

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-JUJ8iAj8-1650014652077)(pizximfzuc.feishu/space/api/box/stream/download/asynccode/?code=NWViZDAwYmQwNGE5MTRjMWI3MjZjYWJkNTc4MzE5YjlfNTVQTjhaU1dhaDBIV0pqT1RRd2pkcklpVXh4RVpTcGhfVG9rZW46Ym94Y25ORTNFMzFZN1R6SmN4V3NEcmF1UlRwXzE2NTAwMTQ2MDQ6MTY1MDAxODIwNF9WNA)]

    • 执行BGSAVE命令

    • Redis 父进程判断当前是否存在正在执行的子进程,如果存在,BGSAVE命令直接返回。

    • 父进程执行fork操作创建子进程,fork操作过程中父进程会阻塞。

    • 父进程fork完成后,父进程继续接收并处理客户端的请求,而子进程开始将内存中的数据写进硬盘的临时文件;

    • 当子进程写完所有数据后会用该临时文件替换旧的 RDB 文件。

    Redis启动时会读取RDB快照文件,将数据从硬盘载入内存。通过 RDB 方式的持久化,一旦Redis异常退出,就会丢失最近一次持久化以后更改的数据。

    **Fork操作:**开启一个子进程,使用linux的copy-on-write机制用于同步操作

    (自动) 配置文件

    如果用户在redis.conf中设置了save配置选项,redis会在save选项条件满足之后自动触发一次BGSAVE命令(当设置多个save配置选项的时候,任意满足一个都会触发一次BGSAVE)

    触发的机制:
    • 全量复制

    • debug reload

    • shutdown

    优点:

  • Redis 加载 RDB 恢复数据远远快于 AOF 的方式。
  • 使用单独子进程来进行持久化,主进程不会进行任何 IO 操作,保证了 Redis 的高性能。
  • 缺点:

  • RDB方式数据无法做到实时持久化。因为
  • BGSAVE每次运行都要执行

    fork操作创建子进程,属于重量级操作,频繁执行成本比较高。

  • RDB 文件使用特定二进制格式保存,Redis 版本升级过程中有多个格式的 RDB 版本,存在老版本 Redis 无法兼容新版 RDB 格式的问题。
  • 2、AOF (只追加日志文件)

    将所有客户端执行的写命令记录打牌日志文件中,AOF持久化会将被执行的写命令写到AOF的文件末尾,以此来记录数据发生的变化。因此只要redis从头到尾的执行一次AOF文件,就可以恢复AOF中记录的数据集。

    • 开启AOF持久化

      • a.修改 appendonly yes 开启持久化
    • b.修改 appendfilename “appendonly.aof” 指定生成文件名称

    • 日志追加频率

    appendfsync always //每次写入aof文件都会执行同步,最安全最慢,不建议配置 appendfsync everysec //既保证性能也保证安全,建议配置 appendfsync no //由操作系统决定何时进行同步操作

    接下来看一下 AOF 持久化执行流程:

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-yYFMwukY-1650014652078)(pizximfzuc.feishu/space/api/box/stream/download/asynccode/?code=OGFhY2JhODZjMTI1ZjEzMDQzYWZlYWM2NmI3ODJjNTBfY0FLeWk3ektsNTBiMkhpNlM4d21HOXVHSXl0Z2xmSUVfVG9rZW46Ym94Y25lNkxMbm9Mdlp2ZXplUkZFb0tmWmc4XzE2NTAwMTQ2MDQ6MTY1MDAxODIwNF9WNA)]

  • 所有的写入命令会追加到 AOP 缓冲区中。
  • AOF 缓冲区根据对应的策略向硬盘同步。
  • 随着 AOF 文件越来越大,需要定期对 AOF 文件进行重写,达到压缩文件体积的目的。AOF文件重写是把Redis进程内的数据转化为写命令同步到新AOF文件的过程。
  • 当 Redis 服务器重启时,可以加载 AOF 文件进行数据恢复。
  • 优缺点:

    • 优点:

  • Redis 加载 RDB 恢复数据远远快于 AOF 的方式。

  • 使用单独子进程来进行持久化,主进程不会进行任何 IO 操作,保证了 Redis 的高性能。

    • 缺点:

  • RDB方式数据无法做到实时持久化。因为BGSAVE每次运行都要执行fork操作创建子进程,属于重量级操作,频繁执行成本比较高。

  • RDB 文件使用特定二进制格式保存,Redis 版本升级过程中有多个格式的 RDB 版本,存在老版本 Redis 无法兼容新版 RDB 格式的问题。

  • AOF重写
    • AOF策yu可以将每条命令都写入到AOF中,但是随着时间的推移命令越来越多oaof,AOF的体积也会你越来越大。在我们aof来恢复的时候也会很慢。对硬盘也占用很多。

    bgrewriteaof: bgrewriteaof命令是AOF的异步的持久化重写的命令

    9、主从复制

    Redis的复制功能是支持多个数据库之间的数据同步。主数据库可以进行读写操作,当主数据库的数据发生变化时会自动将数据同步到从数据库。从数据库一般是只读的,它会接收主数据库同步过来的数据。一个主数据库可以有多个从数据库,而一个从数据库只能有一个主数据库。

    主从复制原理?

    主从复制的原理?

  • 当启动一个从节点时,它会发送一个 PSYNC 命令给主节点;

  • 如果是从节点初次连接到主节点,那么会触发一次全量复制。此时主节点会启动一个后台线程,开始生成一份 RDB 快照文件;

  • 同时还会将从客户端 client 新收到的所有写命令缓存在内存中。RDB 文件生成完毕后, 主节点会将RDB文件发送给从节点,从节点会先将RDB文件写入本地磁盘,然后再从本地磁盘加载到内存中;

  • 接着主节点会将内存中缓存的写命令发送到从节点,从节点同步这些数据;

  • 如果从节点跟主节点之间网络出现故障,连接断开了,会自动重连,连接之后主节点仅会将部分缺失的数据同步给从节点。

    • 其他:

    主从复制架构仅仅用来解决数据的冗余备份,从节点仅仅用来同步数据串的命令流来保持对 slave 的更新,以便于将自身数据集的改变复制给 slave ,包括客户端的写入、key 的过期或被逐出等等。

    当 master 和 slave 之间的连接断开之后,因为网络问题、或者是主从意识到连接超时, slave 重新连接上 master 并会尝试进行部分重同步:这意味着它会尝试只获取在断开连接期间内丢失的命令流。

    当无法进行部分重同步时, slave 会请求进行全量重同步。这会涉及到一个更复杂的过程,例如 master 需要创建所有数据的快照,将之发送给 slave ,之后在数据集更改时持续发送命令流到 slave 。

    10、哨兵Sentinel机制

    主从复制存在不能自动故障转移、达不到高可用的问题。哨兵模式解决了这些问题。通过哨兵机制可以自动切换主从节点。

    客户端连接Redis的时候,先连接哨兵,哨兵会告诉客户端Redis主节点的地址,然后客户端连接上Redis并进行后续的操作。当主节点宕机的时候,哨兵监测到主节点宕机,会重新推选出某个表现良好的从节点成为新的主节点,然后通过发布订阅模式通知其他的从服务器,让它们切换主机。

    解决高可用性方案:

    由一个或者多个Sentinel实例组成系统可以监视任意多个主服务器,以及这些住服务器属下的所有从服务器,并且被监视的住服务器下线状态时,自动将属下的某个从服务器升级为住服务器。简单的说就是哨兵就是带有自动故障转移功能的主从架构。

    • 无法解决:

    1、单节点并发压力问题

    2、单节点内存和磁盘物理上限问题

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-qzXar1MB-1650014652079)(pizximfzuc.feishu/space/api/box/stream/download/asynccode/?code=MTkyMmY0NzRjNjExMTM1M2U5NmZmOGFkM2RlMjc1N2FfTjZwcWpwaXNjeUtoSDNMUlQ1R1pEWmNZRTQ4NmRMU1lfVG9rZW46Ym94Y25zNTdrclZYeTdqbzl3WFdiU1Y3MHpCXzE2NTAwMTQ2MDQ6MTY1MDAxODIwNF9WNA)]

    工作原理

    • 每个Sentinel以每秒钟一次的频率向它所知道的Master,Slave以及其他 Sentinel实例发送一个 PING命令。

    • 如果一个实例距离最后一次有效回复 PING 命令的时间超过指定值, 则这个实例会被 Sentine 标记为主观下线。

    • 如果一个Master被标记为主观下线,则正在监视这个Master的所有 Sentinel要以每秒一次的频率确认Master是否真正进入主观下线状态。

    • 当有足够数量的 Sentinel(大于等于配置文件指定值)在指定的时间范围内确认Master的确进入了主观下线状态, 则Master会被标记为客观下线 。若没有足够数量的 Sentinel同意 Master 已经下线, Master 的客观下线状态就会被解除。若 Master重新向 Sentinel 的 PING 命令返回有效回复, Master 的主观下线状态就会被移除。

    • 哨兵节点会选举出哨兵 leader,负责故障转移的工作。

    • 哨兵 leader 会推选出某个表现良好的从节点成为新的主节点,然后通知其他从节点更新主节点信。

    10、说一下 Redis 和Memcached 的区别和共同点
  • Redis 只使用单核,而 Memcached 可以使用多核。

  • MemCached 数据结构单一,仅用来缓存数据,而 Redis 支持多种数据类型。

  • MemCached 不支持数据持久化,重启后数据会消失。Redis 支持数据持久化。

  • Redis 提供主从同步机制和 cluster 集群部署能力,能够提供高可用服务。Memcached 没有提供原生的集群模式,需要依靠客户端实现往集群中分片写入数据。

  • Redis 的速度比 Memcached 快很多。

  • Redis 使用单线程的多路 IO 复用模型,Memcached使用多线程的非阻塞 IO 模型。

  • 11、缓存数据的处理流程是怎样的?

    简单来说就是:

  • 如果用户请求的数据在缓存中就直接返回。
  • 缓存中不存在的话就看数据库中是否存在。
  • 数据库中存在的话就更新缓存中的数据。
  • 数据库中不存在的话就返回空数据。
  • 12、为什么要用 Redis为什么要用缓存?

    简单,来说使用缓存主要是为了提升用户体验以及应对更多的用户。

    下面我们主要从“高性能”和“高并发”这两点来看待这个问题。

    高性能 :

    对照上面 👆 我画的图。我们设想这样的场景:

    假如用户第一次访问数据库中的某些数据的话,这个过程是比较慢,毕竟是从硬盘中读取的。但是,如果说,用户访问的数据属于高频数据并且不会经常改变的话,那么我们就可以很放心地将该用户访问的数据存在缓存中。

    这样有什么好处呢? 那就是保证用户下一次再访问这些数据的时候就可以直接从缓存中获取了。操作缓存就是直接操作内存,所以速度相当快。

    不过,要保持数据库和缓存中的数据的一致性。 如果数据库中的对应数据改变的之后,同步改变缓存中相应的数据即可!

    高并发:

    一般像 MySQL 这类的数据库的 QPS 大概都在 1w 左右(4 核 8g) ,但是使用 Redis 缓存之后很容易达到 10w+,甚至最高能达到 30w+(就单机 redis 的情况,redis 集群的话会更高)。

    QPS(Query Per Second):服务器每秒可以执行的查询次数;

    由此可见,直接操作缓存能够承受的数据库请求数量是远远大于直接访问数据库的,所以我们可以考虑把数据库中的部分数据转移到缓存中去,这样用户的一部分请求会直接到缓存这里而不用经过数据库。进而,我们也就提高了系统整体的并发。

    13、Redis 常见数据结构以及使用场景分析
  • 缓存热点数据,缓解数据库的压力。
  • 利用 Redis 原子性的自增操作,可以实现计数器的功能,比如统计用户点赞数、用户访问数等。
  • 简单的消队列,可以使用Redis自身的发布/订阅模式或者List来实现简单的消队列,实现异步操作。
  • 限速器,可用于限制某个用户访问某个接口的频率,比如秒杀场景用于防止用户快速点击带来不必要的压力。
  • 好友关系,利用集合的一些命令,比如交集、并集、差集等,实现共同好友、共同爱好之类的功能。
  • string
  • 介绍 :string 数据结构是简单的 key-value 类型。虽然 Redis 是用 C 语言写的,但是 Redis 并没有使用 C 的字符串表示,而是自己构建了一种 简单动态字符串(simple dynamic string,SDS)。相比于 C 的原生字符串,Redis 的 SDS 不光可以保存文本数据还可以保存二进制数据,并且获取字符串长度复杂度为 O(1)(C 字符串为 O(N)),除此之外,Redis 的 SDS API 是安全的,不会造成缓冲区溢出。

  • 常用命令 set,get,strlen,exists,decr,incr,setex 等等。

  • 应用场景: 一般常用在需要计数的场景,比如用户的访问次数、热点文章的点赞转发数量等等。

  • 下面我们简单看看它的使用!

    普通字符串的基本操作:

    127.0.0.1:6379> set key value #设置 key-value 类型的值 > OK > 127.0.0.1:6379> get key # 根据 key 获得对应的 value > "value" > 127.0.0.1:6379> exists key # 判断某个 key 是否存在 > (integer) 1 > 127.0.0.1:6379> strlen key # 返回 key 所储存的字符串值的长度。 > (integer) 5 > 127.0.0.1:6379> del key # 删除某个 key 对应的值 > (integer) 1 > 127.0.0.1:6379> get key > (nil)Copy to clipboardErrorCopied

    批量设置 :

    > 127.0.0.1:6379> mset key1 value1 key2 value2 # 批量设置 key-value 类型的值 > OK > 127.0.0.1:6379> mget key1 key2 # 批量获取多个 key 对应的 value > 1) "value1" > 2) "value2"Copy to clipboardErrorCopied

    计数器(字符串的内容为整数的时候可以使用):

    127.0.0.1:6379> set number 1 > OK > 127.0.0.1:6379> incr number # 将 key 中储存的数字值增一 > (integer) 2 > 127.0.0.1:6379> get number > "2" > 127.0.0.1:6379> decr number # 将 key 中储存的数字值减一 > (integer) 1 > 127.0.0.1:6379> get number > "1"Copy to clipboardErrorCopied > **过期(默认为永不过期)**: > 127.0.0.1:6379> expire key 60 # 数据在 60s 后过期 > (integer) 1 > 127.0.0.1:6379> setex key 60 value # 数据在 60s 后过期 (setex:[set] + [ex]pire) > OK > 127.0.0.1:6379> ttl key # 查看数据还有多久过期 > (integer) 56Copy to clipboardErrorCopied list
  • 介绍 :list 即是 链表。链表是一种非常常见的数据结构,特点是易于数据元素的插入和删除并且可以灵活调整链表长度,但是链表的随机访问困难。许多高级编程语言都内置了链表的实现比如 Java 中的 LinkedList,但是 C 语言并没有实现链表,所以 Redis 实现了自己的链表数据结构。Redis 的 list 的实现为一个 双向链表,即可以支持反向查找和遍历,更方便操作,不过带来了部分额外的内存开销。

  • 常用命令:

  • rpush,lpop,lpush,rpop,lrange,llen 等。

  • 应用场景: 发布与订阅或者说消队列、慢查询。

  • 下面我们简单看看它的使用!

    通过

    **rpush/lpop** 实现队列:

    > 127.0.0.1:6379> rpush myList value1 # 向 list 的头部(右边)添加元素 > (integer) 1 > 127.0.0.1:6379> rpush myList value2 value3 # 向list的头部(最右边)添加多个元素 > (integer) 3 > 127.0.0.1:6379> lpop myList # 将 list的尾部(最左边)元素取出 > "value1" > 127.0.0.1:6379> lrange myList 0 1 # 查看对应下标的list列表, 0 为 start,1为 end > 1) "value2" > 2) "value3" > 127.0.0.1:6379> lrange myList 0 -1 # 查看列表中的所有元素,-1表示倒数第一 > 1) "value2" > 2) "value3"Copy to clipboardErrorCopied

    通过

    **rpush/rpop** 实现栈:

    127.0.0.1:6379> rpush myList2 value1 value2 value3 > (integer) 3 > 127.0.0.1:6379> rpop myList2 # 将 list的头部(最右边)元素取出 > "value3"Copy to clipboardErrorCopied

    我专门画了一个图方便小伙伴们来理解: 通过

    **lrange** 查看对应下标范围的列表元素:

    > 127.0.0.1:6379> rpush myList value1 value2 value3 > (integer) 3 > 127.0.0.1:6379> lrange myList 0 1 # 查看对应下标的list列表, 0 为 start,1为 end > 1) "value1" > 2) "value2" > 127.0.0.1:6379> lrange myList 0 -1 # 查看列表中的所有元素,-1表示倒数第一 > 1) "value1" > 2) "value2" > 3) "value3"Copy to clipboardErrorCopied

    通过

    lrange 命令,你可以基于 list 实现分页查询,性能非常高!

    通过

    **llen** 查看链表长度:

    > 127.0.0.1:6379> llen myList > (integer) 3Copy to clipboardErrorCopied hash
  • 介绍 :hash 类似于 JDK1.8 前的 HashMap,内部实现也差不多(数组 + 链表)。不过,Redis 的 hash 做了更多优化。另外,hash 是一个 string 类型的 field 和 value 的映射表,特别适合用于存储对象,后续操作的时候,你可以直接仅仅修改这个对象中的某个字段的值。 比如我们可以 hash 数据结构来存储用户信,商品信等等。

  • 常用命令:

  • hset,hmset,hexists,hget,hgetall,hkeys,hvals 等。

  • 应用场景: 系统中对象数据的存储。

  • 下面我们简单看看它的使用!

    127.0.0.1:6379> hmset userInfoKey name “guide” description “dev” age “24”

    OK

    127.0.0.1:6379> hexists userInfoKey name # 查看 key 对应的 value中指定的字段是否存在。

    (integer) 1

    127.0.0.1:6379> hget userInfoKey name # 获取存储在哈希表中指定字段的值。

    “guide”

    127.0.0.1:6379> hget userInfoKey age

    “24”

    127.0.0.1:6379> hgetall userInfoKey # 获取在哈希表中指定 key 的所有字段和值

  • “name”
  • “guide”
  • “description”
  • “dev”
  • “age”
  • “24”
  • 127.0.0.1:6379> hkeys use

    rInfoKey # 获取 key 列表 > 1) "name" > 2) "description" > 3) "age" > 127.0.0.1:6379> hvals userInfoKey # 获取 value 列表 > 1) "guide" > 2) "dev" > 3) "24" > 127.0.0.1:6379> hset userInfoKey name "GuideGeGe" # 修改某个字段对应的值 > 127.0.0.1:6379> hget userInfoKey name > "GuideGeGe"Copy to clipboardErrorCopied set
  • 介绍 : set 类似于 Java 中的

  • HashSet 。Redis 中的 set 类型是一种无序集合,集合中的元素没有先后顺序。当你需要存储一个列表数据,又不希望出现重复数据时,set 是一个很好的选择,并且 set 提供了判断某个成员是否在一个 set 集合内的重要接口,这个也是 list 所不能提供的。可以基于 set 轻易实现交集、并集、差集的操作。比如:你可以将一个用户所有的关注人存在一个集合中,将其所有粉丝存在一个集合。Redis 可以非常方便的实现如共同关注、共同粉丝、共同喜好等功能。这个过程也就是求交集的过程。

  • 常用命令:
  • sadd,spop,smembers,sismember,scard,sinterstore,sunion 等。

    1.应用场景:需要存放的数据不能重复以及需要获取多个数据源交集和并集等场景

    下面我们简单看看它的使用!

    > 127.0.0.1:6379> sadd mySet value1 value2 # 添加元素进去 > (integer) 2 > 127.0.0.1:6379> sadd mySet value1 # 不允许有重复元素 > (integer) 0 > 127.0.0.1:6379> smembers mySet # 查看 set 中所有的元素 > 1) "value1" > 2) "value2" > 127.0.0.1:6379> scard mySet # 查看 set 的长度 > (integer) 2 > 127.0.0.1:6379> sismember mySet value1 # 检查某个元素是否存在set 中,只能接收单个元素 > (integer) 1 > 127.0.0.1:6379> sadd mySet2 value2 value3 > (integer) 2 > 127.0.0.1:6379> sinterstore mySet3 mySet mySet2 # 获取 mySet 和 mySet2 的交集并存放在 mySet3 中 > (integer) 1 > 127.0.0.1:6379> smembers mySet3 > 1) "value2"Copy to clipboardErrorCopied sorted set
  • 介绍: 和 set 相比,sorted set 增加了一个权重参数 score,使得集合中的元素能够按 score 进行有序排列,还可以通过 score 的范围来获取元素的列表。有点像是 Java 中 HashMap 和 TreeSet 的结合体。

  • 常用命令:

  • zadd,zcard,zscore,zrange,zrevrange,zrem 等。

  • 应用场景: 需要对数据根据某个权重进行排序的场景。比如在直播系统中,实时排行信包含直播间在线用户列表,各种礼物排行榜,弹幕消(可以理解为按消维度的消排行榜)等信。

  • > 127.0.0.1:6379> zadd myZsi > 127.0.0.1:6379> zadd myZset 2.0 value2 1.0 value3 # 一次添加多个元素 > (integer) 2 > 127.0.0.1:6379> zcard myZset # 查看 sorted set 中的元素数量 > (integer) 3 > 127.0.0.1:6379> zscore myZset value1 # 查看某个 value 的权重 > "3" > 127.0.0.1:6379> zrange myZset 0 -1 # 顺序输出某个范围区间的元素,0 -1 表示输出所有元素 > 1) "value3" > 2) "value2" > 3) "value1" > 127.0.0.1:6379> zrange myZset 0 1 # 顺序输出某个范围区间的元素,0 为 start 1 为 stop > 1) "value3" > 2) "value2" > 127.0.0.1:6379> zrevrange myZset 0 1 # 逆序输出某个范围区间的元素,0 为 start 1 为 stop > 1) "value1" > 2) "value2"Copy to clipboardErrorCopied bitmap
  • 介绍: bitmap 存储的是连续的二进制数字(0 和 1),通过 bitmap, 只需要一个 bit 位来表示某个元素对应的值或者状态,key 就是对应元素本身 。我们知道 8 个 bit 可以组成一个 byte,所以 bitmap 本身会极大的节省储存空间。

  • 常用命令:

  • setbit 、

    getbit 、

    bitcount、

    bitop
  • 应用场景: 适合需要保存状态信(比如是否签到、是否登录…)并需要进一步对这些信进行分析的场景。比如用户签到情况、活跃用户情况、用户行为统计(比如是否点赞过某个视频)。

  • # SETBIT 会返回之前位的值(默认是 0)这里会生成 7 个位

    > 127.0.0.1:6379> setbit mykey 7 1 > (integer) 0 > 127.0.0.1:6379> setbit mykey 7 0 > (integer) 1 > 127.0.0.1:6379> getbit mykey 7 > (integer) 0 > 127.0.0.1:6379> setbit mykey 6 1 > (integer) 0 > 127.0.0.1:6379> setbit mykey 8 1 > (integer) 0 > \\# 通过 bitcount 统计被被设置为 1 的位的数量。 > 127.0.0.1:6379> bitcount mykey > (integer) 2Copy to clipboardErrorCopied

    针对上面提到的一些场景,这里进行进一步说明。

    使用场景一:用户行为分析 很多网站为了分析你的喜好,需要研究你点赞过的内容。

    # 记录你喜欢过 001 号小姐姐

    127.0.0.1:6379> setbit beauty_girl_001 uid 1Copy to clipboardErrorCopied

    使用场景二:统计活跃用户

    使用时间作为 key,然后用户 ID 为 offset,如果当日活跃过就设置为 1

    那么我该如何计算某几天/月/年的活跃用户呢(暂且约定,统计时间内只要有一天在线就称为活跃),有请下一个 redis 的命令

    # 对一个或多个保存二进制位的字符串 key 进行位元操作,并将结果保存到 destkey 上。

    # BITOP 命令支持 AND 、 OR 、 NOT 、 XOR 这四种操作中的任意一种参数

    BITOP operation destkey key [key …]Copy to clipboardErrorCopied

    初始化数据:

    > 127.0.0.1:6379> setbit 20210308 1 1 > (integer) 0 > 127.0.0.1:6379> setbit 20210308 2 1 > (integer) 0 > 127.0.0.1:6379> setbit 20210309 1 1 > (integer) 0Copy to clipboardErrorCopied > 统计 20210308~20210309 总活跃用户数: 1 > 127.0.0.1:6379> bitop and desk1 20210308 20210309 > (integer) 1 > 127.0.0.1:6379> bitcount desk1 > (integer) 1Copy to clipboardErrorCopied > 统计 20210308~20210309 在线活跃用户数: 2 > 127.0.0.1:6379> bitop or desk2 20210308 20210309 > (integer) 1 > 127.0.0.1:6379> bitcount desk2 > (integer) 2Copy to clipboardErrorCopied

    使用场景三:用户在线状态

    对于获取或者统计用户在线状态,使用 bitmap 是一个节约空间且效率又高的一种方法。

    只需要一个 key,然后用户 ID 为 offset,如果在线就设置为 1,不在线就设置为 0。

    14、Redis为何选择单线程?
    • 避免过多的上下文切换开销。程序始终运行在进程中单个线程内,没有多线程切换的场景。

    • 避免同步机制的开销:如果 Redis选择多线程模型,需要考虑数据同步的问题,则必然会引入某些同步机制,会导致在操作数据过程中带来更多的开销,增加程序复杂度的同时还会降低性能。

    • 实现简单,方便维护:如果 Redis使用多线程模式,那么所有的底层数据结构的设计都必须考虑线程安全问题,那么 Redis 的实现将会变得更加复杂。

    15、Redis事

    事务的原理是将一个事务范围内的若干命令发送给 Redis,然后再让 Redis 依次执行这些命令。

    事务的生命周期:

    • 使用MULTI开启一个事务;

    • 在开启事务的时候,每次操作的命令将会被插入到一个队列中,同时这个命令并不会被真正执行;

    • EXEC命令进行提交事务。

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

    一个事务范围内某个命令出错不会影响其他命令的执行,不保证原子性:

    127.0.0.1:6379> multi OK 127.0.0.1:6379> set a 1 QUEUED 127.0.0.1:6379> set b 1 2 QUEUED 127.0.0.1:6379> set c 3 QUEUED 127.0.0.1:6379> exec 1) OK 2) (error) ERR syntax error 3) OK

    WATCH命令

    WATCH命令可以监控一个或多个键,一旦其中有一个键被修改,之后的事务就不会执行(类似于乐观锁)。执行EXEC命令之后,就会自动取消监控。

    127.0.0.1:6379> watch name OK 127.0.0.1:6379> set name 1 OK 127.0.0.1:6379> multi OK 127.0.0.1:6379> set name 2 QUEUED 127.0.0.1:6379> set gender 1 QUEUED 127.0.0.1:6379> exec (nil) 127.0.0.1:6379> get gender (nil)

    比如上面的代码中:

    • watch name开启了对name这个key的监控

    • 修改name的值

    • 开启事务aMULTI

    • 在事务a中设置了name和gender的值

    • 使用EXEC命令进提交事务

    • 使用命令get gender发现不存在,即事务a没有执行UNWATCH可以取消WATCH命令对key的监控,所有监控锁将会被取消。

    这是本人今年春招找实习工作准备总结,记录在此,如有需要的老铁可以看看,如有问题可以留言指导

    本文标签: 面试题常见Redis