Skip to main content

moregeek program

2022最新版 redis大厂面试题总结(附答案)_10982108的博客-多极客编程

专注于PHP、MySQL、Linux和前端开发,感兴趣的感谢点个关注哟!!!​​文章已收录​​,主要包含的技术有PHP、Redis、MySQL、JavaScript、HTML&CSS、Linux、Java、Golang、Linux和工具资源等相关理论知识、面试题和实战内容。

文章导语

大家好,前段时间一直在忙找工作相关的事情。最近工作稳定了,于是把面试过程中遇到的Redis相关知识问题总结下来,希望能够对大家面试、学习有所帮助。本文重点分享数据类型,其他内容后续章节分享,也可以点击上方收录链接。

本系列面试题会从认识Redis、Redis几大数据类型、常见的使用场景和解决方案、Redis主从复制、Redis哨兵、Redis集群等相关知识点进行总结。不仅仅单纯的从文字方面终结,还会带有更多的图文方面,尽可能的让大家深入的学习。

2022最新版 Redis大厂面试题总结(附答案)_数据类型

2022最新版 Redis大厂面试题总结(附答案)_数据_02

同时我也准备了一份汇总大纲,相对文字来说更加的清晰、明了。需要的小伙伴也可以回复​​Redis面试大纲​​。同时该系列问题也会不断的完善、更新。让大家学习到更多的知识。

2022最新版 Redis大厂面试题总结(附答案)_redis_03

认识Redis

  1. REmote DIctionary Server(Redis) 是一个由 Salvatore Sanfilippo 写的 key-value 存储系统,是跨平台的非关系型数据库。
  2. Redis 是一个开源的使用 ANSI C 语言编写、遵守 BSD 协议、支持网络、可基于内存、分布式、可选持久性的键值对(Key-Value)存储数据库,并提供多种语言的 API。

Redis的数据类型都有哪些

  1. 有五种基本数据类型,分别是string、hash、list、有序集合(zset)、集合(set)。在5.0之后增加了一种Stream类型。
  2. 额外的有GEO、HyperLogLog、BitMap。

Redis使用的场景有哪些

  1. 数据缓存(用户信息、商品数量、文章阅读数量)
  2. 消息推送(站点的订阅)
  3. 队列(削峰、解耦、异步)
  4. 排行榜(积分排行)
  5. 社交网络(共同好友、互踩、下拉刷新)
  6. 计数器(商品库存,站点在线人数、文章阅读、点赞)
  7. 基数计算
  8. GEO计算

Redis功能特点都有哪些

  1. 持久化
  2. 丰富的数据类型(string、list、hash、set、zset、发布订阅等)
  3. 高可用方案(哨兵、集群、主从)
  4. 事务
  5. 丰富的客户端
  6. 提供事务
  7. 消息发布订阅
  8. Geo
  9. HyperLogLog
  10. 事务
  11. 分布式事务锁

Redis如何实现分布式锁

  1. Redis可以使用​​setnx key value​​ +​​expire key expire_time​​来实现分布式锁。
  2. 正常情况下,上面的命令是没有问题的。当Redis出现异常的情况下,很容易出现非原子性操作。
  3. 非原子性操作指的的setnx命令执行成功,但是expire没有执行成功,此时key就成为了一个无过期时间的key,一直保留在Redis中,导致其他的请求就无法执行。
  4. 要解决该问题,可以使用lua脚本实现。通过lua实现命令的原子性操作。

在Redis中使用set命令,加参数也可以实现分布式锁。​​set key vale nx ex|px ttl​

通过数组定义
--- getLock key
local key = KEYS[1]
local requestId = KEYS[2]
local ttl = tonumber(KEYS[3])
local result = redis.call('setnx', key, requestId)
if result == 1 then
--PEXPIRE:以毫秒的形式指定过期时间
redis.call('pexpire', key, ttl)
else
result = -1;
-- 如果value相同,则认为是同一个线程的请求,则认为重入锁
local value = redis.call('get', key)
if (value == requestId) then
result = 1;
redis.call('pexpire', key, ttl)
end
end
-- 如果获取锁成功,则返回 1
return result
通过数组定义
-- releaseLock key
local key = KEYS[1]
local requestId = KEYS[2]
local value = redis.call('get', key)
if value == requestId then
redis.call('del', key);
return 1;
end
return -1

tips:如果对一个key第一次set添加了过期时间,第二次操作时没有添加过期时间,此时key是没有过期时间的(过期时间被覆盖为永久不过期)。

Redis底层数据结构有哪些

Redis底层数据结构主要有六种,这六种构成了五种常用的数据类型。其他的数据类型,例如bitmap、hyperLogLog也是基于这五大数据类型实现。具体的数据结构图如下:

2022最新版 Redis大厂面试题总结(附答案)_数据类型_04

说说Redis的全局Hash表

为了实现从键到值的快速访问,Redis 使用了一个哈希表来保存所有键值对。结构图如下:

2022最新版 Redis大厂面试题总结(附答案)_redis_05

Hash表应用如此广泛的一个重要原因,就是从理论上来说,它能以 O(1) 的复杂度快速查询数据。Hash 表通过Hash函数的计算,就能定位数据在表中的位置,紧接着可以对数据进行操作,这就使得数据操作非常快速。
那么我们该如何解决哈希冲突呢?可以考虑使用以下两种解决方案:

  1. 第一种方案,就是使用链式哈希。但是链式哈希容易导致Hash的链过长,查询效率降低。
  2. 第二种方案,就是当链式哈希的链长达到一定长度时,我们可以使用rehash。不过,执行rehash本身开销比较大。

del删除大量key有什么问题

  1. 使用del命令可以删除一个key或者多个key,其时间复杂度为O(N),这里的N表示删除的key数量。
  2. 删除单个key时,其时间复杂度为O(1)。
  3. 当删除单个列表、集合、有序集合或者哈希列表类型的key时,时间复杂度为O(M),这里的M表示key对应的内部元素个数。

说说Redis的全局hash实现原理

说说Zset在skiplist和ziplist实现原理

Redis事务都有哪些命令

mutil: 开启事务;exec: 提交事务;discard: 回滚事务。watch: 监听key; unwatch: 取消监听key。

Redis中的事务是否是原子性

严格来说,Redis中的事务并非满足事务的原子性操作。当事务在命令组队时没有发生错误,则事务是原子性;当事务在命令组队时发生错误,则事务是非原子性的。

Redis如何解决事务之间的冲突

  1. 使用watch监听key变化,当key发生变化,事务中的所有操作都会被取消。
  2. 使用乐观锁,通过版本号实现。
  3. 使用悲观锁,每次开启事务时,都添加一个锁,事务执行结束之后释放锁。

悲观锁:悲观锁(Pessimistic Lock),顾名思义,就是很悲观,每次去拿数据的时候都认为别人会修改,所以每次在拿数据的时候都会上锁,这样别人拿到这个数据就会block(阻塞)直到它拿到锁。传统的关系型数据库里面 就用到了很多这种锁机制,比如行锁、表锁、读锁、写锁等,都是在做操作之前先上锁。

乐观锁:乐观锁(Optimistic Lock),顾名思义,就是很乐观,每次去那数据的时候都认为别人不会修改,所以 不会上锁,但是在修改的时候会判断一下在此期间别人有没有去更新这个数据,可以使用版本号等机 制。乐观锁适用于多读的应用类型,这样可以提高吞吐量。redis就是使用这种check-and-set机制实现 事务的。

事务中的watch有什么用

在执行multi之前,先执行watch key1 [key2 ...],可以监视一个或者多个key。若在事务的exec命令之前,这些key对应的值被其他命令所改动了,那么事务中所有命令都将被打断,即事务所有操作将被取消执行。

Redis事务的三大特性

  1. 事务中的所有命令都会序列化、按顺序地执行,事务在执行过程中,不会被其他客户端发送来的命令请求所打断。
  2. 队列中的命令没有提交(exec)之前,都不会实际被执行,因为事务提交前任何指令都不会被实际执行。
  3. 事务中如果有一条命令执行失败,后续的命令仍然会被执行,没有回滚。如果在组队阶段,有1个失败了,后面都不会成功;如果在组队阶段成功了,在执行阶段有那个命令失败 就这条失败,其他的命令则正常执行,不保证都成功或都失败。

如何使用Redis实现队列功能

  1. 可以使用list实现普通队列,lpush添加到嘟列,lpop从队列中读取数据。
  2. 可以使用zset定期轮询数据,实现延迟队列。
  3. 可以使用发布订阅实现多个消费者队列。
  4. 可以使用stream实现队列。(推荐使用该方式实现)。

如何用Redis实现异步队列

  1. 一般使用list结构作为队列,rpush生产消息,lpop消费消息。当lpop没有消息的时候,要适当sleep一会再重试。
  2. 如果对方追问可不可以不用sleep呢?list还有个指令叫blpop,在没有消息的时候,它会阻塞住直到消息到来。
  3. 如果对方追问能不能生产一次消费多次呢?使用pub/sub主题订阅者模式,可以实现1:N的消息队列。
  4. 如果对方追问pub/sub有什么缺点?在消费者下线的情况下,生产的消息会丢失,可以使用Redis6增加的stream数据类型,也可以使用专业的消息队列如rabbitmq等
  5. 如果对方追问redis如何实现延时队列?使用sortedset,拿时间戳作为score,消息内容作为key调用zadd来生产消息,消费者用zrangebyscore指令获取N秒之前的数据轮询进行处理。

Stream与list、zset和发布订阅区别

  1. list可以使用lpush向队列中添加数据,lpop可以向队列中读取数据。list作为消息队列无法实现一个消息多个消费者。如果出现消息处理失败,需要手动回滚消息。
  2. zset在添加数据时,需要添加一个分值,可以根据该分值对数据进行排序,实现延迟消息队列的功能。消息是否消费需要额外的处理。
  3. 发布订阅可以实现多个消费者功能,但是发布订阅无法实现数据持久化,容易导致数据丢失。并且开启一个订阅者无法获取到之前的数据。
  4. stream借鉴了常用的MQ服务,添加一个消息就会产生一个消息ID,每一个消息ID下可以对应多个消费组,每一个消费组下可以对应多个消费者。可以实现多个消费者功能,同时支持ack机制,减少数据的丢失情况。也是支持数据值持久化和主从复制功能。

如何设计一个网站每日、每月和每天的PV和UV

实现这样的功能,如果只是统计一个汇总数据,推荐使用HyperLogLog数据类型。Redis HyperLogLog 是用来做基数统计的算法,HyperLogLog 的优点是,在输入元素的数量或者体积非常非常大时,计算基数所需的空间总是固定 的、并且是很小的。在 Redis 里面,每个 HyperLogLog 键只需要花费 12 KB 内存,就可以计算接近 2^64 个不同元素的基 数。这和计算基数时,元素越多耗费内存就越多的集合形成鲜明对比。

Redis如何实现距离检索功能

实现距离检索,可以使用Redis中的GEO数据类型。GEO 主要用于存储地理位置信息,并对存储的信息进行操作,该功能在 Redis 3.2 版本新增。但是GEO适合精度不是很高的场景。由于GEO是在内存中进行计算,具备计算速度快的特点。

list和发布订阅实现队列有什么问题

  1. list可以使用lpush向队列中添加数据,lpop可以向队列中读取数据。list作为消息队列无法实现一个消息多个消费者。如果出现消息处理失败,需要手动回滚消息。
  2. 发布订阅可以实现多个消费者功能,但是发布订阅无法实现数据持久化,容易导致数据丢失。并且开启一个订阅者无法获取到之前的数据。

Redis如何实现秒杀功能

  1. 在秒杀场景下,超卖是一个非常严重的问题。常规的逻辑是先查询库存在减少库存。但在秒杀场景中,无法保证减少库存的过程中有其他的请求读取了未减少的库存数据。
  2. 由于Redis是单线程的执行,同一时刻只有一个线程进行操作。因此可以使用Redis来实现秒杀减少库存。
  3. 在Redis的数据类型中,可以使用lpush,decr命令实现秒杀减少库存。该命令属于原子操作。

Redis如何实现用户签到功能

  1. 使用Redis实现用户签到可以使用bitmap实现。bitmap底层数据存储的是1否者0,占用内存小。
  2. Redis提供的数据类型BitMap(位图),每个bit位对应0和1两个状态。虽然内部还是采用String类型存储,但Redis提供了一些指令用于直接操作BitMap,可以把它看作一个bit数组,数组的下标就是偏移量。
  3. 它的优点是内存开销小,效率高且操作简单,很适合用于签到这类场景。
  4. 缺点在于位计算和位表示数值的局限。如果要用位来做业务数据记录,就不要在意value的值。

Redis如何实现延迟队列

  1. 使用Redis实现延迟队列,可以使用zset数据类型。
  2. zset在添加数据时,需要添加一个分值,将时间作为分值,根据该分值对数据进行排序。
  3. 单独开启线程,根据分值大小定期实行数据。

Redis实现一个积分排行功能

  1. 使用Redis实现积分排行,可以使用zset数据类型。
  2. zset在添加数据时,需要添加一个分值,将积分作为分值,值作为用户ID,根据该分值对数据进行排序。

字符串类型存储最大容量是多少

一个字符串最大可存储512M。

©著作权归作者所有:来自51CTO博客作者7small7的原创作品,请联系作者获取转载授权,否则将追究法律责任

时间复杂度与空间复杂度_萌新的日常的博客-多极客编程

(文章目录) 一、时间复杂度 1.概念 即时间复杂度计算的是执行次数 2.大O的渐进表示法 1.用常数1取代时间中的所有加法常数 2.在修改后的运行次数函数中,只保留最高项 3.如果最高项存在而且不是1,则去除与这个项目相乘的常数,得到的结果就是大O阶 3.练习题 1.常规情况 void Func1(int N)//Func1的操作次数 { int count=0; for( i

操作符详解(1)_hanwang的博客-多极客编程

一、算术操作符/除法的注意事项:若/两端的操作数都是整数,执行整除法;若想得到小数,则要保证除数和被除数至少有一个是浮点数,并且用double/float定义。%取模操作的注数意事项:必须要保证取模两端都是整数。二、移位操作符1、右移操作符:分为算术右移(右边丢弃,左边补原符号位),和逻辑右移(右边丢弃,左边补0),注意我们在使用时,大部分都是算术右移例:对-1进行右移操作,打印输出结果还是-1.

12306抢票爬虫实战_蓦然的博客-多极客编程

12306抢票爬虫先直接上一下效果图吧:图片上信息是抢票成功后的界面1、技术路线selenium + chromedriver2、思路分析(1)、模拟浏览器登录抢票界面,手动进行登录(2)、登录完成后让浏览器跳转到购票界面(3)、手动输入出发地、目的地、 出发日,检查上面三个信息输入完成后,找到查询按钮,进行车次查询(4)、查找我们需要的车次,看下是否有余票(显示有或数子),找到车次的预定的按钮,

多线程必知必会的知识点_浅羽技术的博客-多极客编程

1)现在有 T1、T2、T3 三个线程,你怎样保证 T2 在 T1 执行完后执行,T3 在 T2 执行完后执行? 这个线程问题通常会在第一轮或电话面试阶段被问到,目的是检测你对”join”方法是否熟悉。 这个多线程问题比较简单,可以用 join 方法实现。 2)在 Java 中 Lock 接口比 synchronized 块的优势是什么? 你需要实现一个高效的缓存,它允许多个用户读,但只允许一个用

30天python入门到进阶——第3天:数据类型(Ⅱ)_freestu的博客-多极客编程

昨天讲到了字符串的基本定义、连接和类型转换,继续昨天学习的字符串数据类型,今天我探索了其他一些特性。字符串格式化字符串字符串格式化是一个简洁的功能,它允许我们动态更新字符串内容。假设我们从服务器获取用户信息,并希望根据该信息显示自定义消息。第一个想法是应用字符串连接,例如:first_name = 'Tom'last_name = 'Cruise'welcome_message = "Welcom

[ 数据结构进阶 - c++ ] 二叉搜索树 bstree_小白又菜的博客-多极客编程

 到如今,C++的基本语法已经了解过了。在之前,​​数据结构初阶​​​是使用C语言实现的,我们进入进阶数据结构之后,将使用C++语言来实现。本篇文章我们将学习了解二叉搜索树-​​二叉树​​的进阶。1.二叉搜索树1.1二叉搜索树的概念二叉搜索数又称二叉排序树(BST,Binary Search Tree),它或者是一颗空树,或者是有以下性质的二叉树:若它的左子树不为空,则左子树上的所有节点的值都小于

go语言之整型_zzxiaoma的博客-多极客编程

go提供了10种类型用于表示整数,由于每种整数类型的取值福范围不同,所以应该根据场景所需的范围来决定使用何种整数类型。 还有2个是int和uint,这2个类型是根据计算机架构自动选择合适的位长。在32位机器上就是32位值,在64位机器上就是64位值。 可以使用Printf函数的&T来查看变量的类型 abc := 43223fmt.Printf("type %T for %v\n",abc

go语言之大数_zzxiaoma的博客-多极客编程

计算机编程经常需要权衡利弊,做相应的取舍,例如浮点数可以存储任意大小的数字,但是会不精确,整数虽然准确,但是取值范围受到限制。go中有个big包可以存储大数据,big.Int存储整数,big.Float存储浮点数,big.Rat存储分数。 创建big.Int使用NewInt函数 abc := big.NewInt(54232432)cde := big.NewInt(43222233)但是如果数

js:动态导入script脚本文件_彭世瑜的博客-多极客编程

实现原理 模仿百度统计的代码,将其封装成一个可重用的函数 <script> var _hmt = _hmt || []; (function() { var hm = document.createElement("script"); hm.src = "//hm.baidu.com/hm.js?64ecd82404c51e03dc91cb9e8c

判断两个cidr是否有ip冲突_5201314的博客-多极客编程

1.校验网段是否合法1.1函数import ( "fmt" "net")func IsNetWorkOk(network string ) bool{ _, _, err := net.ParseCIDR(network) if err != nil { return false } return true}1.2分析第一步就是先split变成ip

go语言之浮点数_zzxiaoma的博客-多极客编程

go语言拥有两种浮点类型,一种是float64,每个64位的浮点数需要占用8字节,另一种是float32,占用4字节。 var p = 3.54fmt.Printf("%T",p)输出float64,也就是默认浮点数是float64,如果想定义float32,需要明确指定。 如果不为float64赋值,默认就是0.0。 下面的浮点数的格式化输出,%4.2f中4代表总宽度,这里需要注意,点也算宽度

go语言之类型转换_zzxiaoma的博客-多极客编程

go语言的类型不能混合使用 fmt.Println("abc" + 1)会报字符串和整数不能这么操作 se := 12.0wu := 5fmt.Println(se * wu)(mismatched types float64 and int)一个是float一个是int不允许操作 例如上面,我们需要把wu这个变量类型转换为float64. fmt.Println(se * float64(