来源 | 小白在挨踢 (ID:gh_07ce59a80110)
Redis 在当今的计算机行业,可以说是使用的最为广泛的内存数据库,几乎所有的后端技术面试都会涉及到 Redis 相关的知识,正所谓知己知彼,百战百胜。小白今天精心整理的超全的 Redis 面试题,希望可以帮助到在路上的你们~
什么是 Redis
Redis 是完全开源免费的,遵守 BSD 协议,是一个高性能的 key-value 数据库
是一个使用 C 语言开发的数据库,不过与传统数据库不同的是 Redis 的数据是存在内存中的,它是内存数据库,所以读写速度非常快,因此 Redis 被广泛应用于缓存方向
Redis 除了做缓存之外,也经常用来做分布式锁,甚至是消息队列
Redis 还支持事务 、持久化、Lua 脚本、多种集群方案等,可以使用众多复杂的业务场景
常见的分布式缓存技术
缓存使用的比较多的主要是 Memcached 和 Redis。不过现在使用 Memcached 做缓存的比较少
Memcached 是分布式缓存最开始兴起的时候比较常用的方案,后来随着 Redis 的发展,Redis 逐渐成为了人们的首选
分布式缓存主要解决的是单机缓存的容量受服务器限制并且无法保存通用信息的问题。因为本地缓存只在当前服务里有效,比如如果你部署了两个相同的服务,他们两者之间的缓存数据是无法共享的
Memcached 与 Redis 的异同
共同点
异同点
Redis 的数据类型
Redis 支持五种数据类型:string(字符串),hash(哈希),list(列表),set(集合)及 zsetsorted set:有序集合)
实际中我们最为常用的还是 string 类型,不过还有几种高级数据类型我们也需要了解掌握:BloomFilter,RedisSearch,Redis-ML,还有更高级的数据类型,BloomFilter,RedisSearch,Redis-ML 如果使用过,那么在面试官眼里都是加分项!
缓存数据的处理流程
缓存的简单处理流程如下:
上图就是一个最为简易的缓存流程图,从图中我们也可以看出在使用缓存时的一系列注意事项,比如当缓存失效时,如何更好的保护数据库,如何保证数据库与缓存数据一致等
Redis 运行模式(单进程还是单线程)
Redis 是单线程,利用队列技术将并发访问变为串行访问,消除了传统数据库串行控制的开销问题
一个字符串能存储的最大容量
512 M
为什么要使用缓存
高性能
将高频访问的数据放进缓存,保证高频操作可以快速响应,提高系统响应速度和用户体验
高并发
一般像 MySQL 这类数据库的 QPS 大概都在 1w 左右(4 核 8g) ,但是使用 Redis 缓存之后很容易达到 10w+,甚至最高能达到 30w+(redis 集群的话会更高)
QPS(Query Per Second):服务器每秒可以执行的查询次数
所以增加缓存可以大大提高系统的并发能力
具体说说 Redis 可以做什么
缓存
缓存机制几乎在所有的大型网站都有使用,合理地使用缓存不仅可以加 快数据的访问速度,而且能够有效地降低后端数据源的压力。Redis提供了键值过期时间设置,并且也提供了灵活控制最大内存和内存溢出后的淘汰策略。可以这么说,一个合理的缓存设计能够很好的为一个网站的稳定保驾护航
排行榜系统
排行榜系统几乎存在于所有的网站,例如按照热度排名的排行榜,按照 发布时间的排行榜,按照各种复杂维度计算出的排行榜,Redis提供了列表和有序集合数据结构,合理地使用这些数据结构可以很方便地构建各种排行榜系统
计数器应用
计数器在网站中的作用至关重要,例如视频网站有播放数、电商网站有 浏览数,为了保证数据的实时性,每一次播放和浏览都要做加1的操作,如果并发量很大对于传统关系型数据的性能是一种挑战。Redis天然支持计数功能而且计数的性能也非常好,可以说是计数器系统的重要选择
社交网络
赞/踩、粉丝、共同好友/喜好、推送、下拉刷新等是社交网站的必备功能,由于社交网站访问量通常比较大,而且传统的关系型数据不太适合保存这种类型的数据,Redis 提供的数据结构可以相对比较容易地实现这些功能
消息队列系统
消息队列系统可以说是一个大型网站的必备基础组件,因为其具有业务 解耦、非实时业务削峰等特性。Redis 提供了发布订阅功能和阻塞队列的功能,虽然和专业的消息队列比还不够足够强大,但是对于一般的消息队列功能基本可以满足
哪些是 Redis 不可以(不擅长)做的
我们可以站在数据规模和数据冷热的角度来进行分析
站在数据规模的角度看,数据可以分为大规模数据和小规模数据,我们 知道 Redis 的数据是存放在内存中的,虽然现在内存已经足够便宜,但是如果数据量非常大,例如每天有几亿的用户行为数据,使用 Redis 来存储的话,基本上是个无底洞,经济成本相当的高
站在数据冷热的角度看,数据分为热数据和冷数据,热数据通常是指需 要频繁操作的数据,反之为冷数据,例如对于视频网站来说,视频基本信息基本上在各个业务线都是经常要操作的数据,而用户的观看记录不一定是经常需要访问的数据,这里暂且不讨论两者数据规模的差异,单纯站在数据冷热的角度上看,视频信息属于热数据,用户观看记录属于冷数据。如果将这些冷数据放在 Redis 中,基本上是对于内存的一种浪费,但是对于一些热数据可以放在 Redis 中加速读写,也可以减轻后端存储的负载,可以说是事半功倍
Redis 常见数据结构与使用场景
String
String 数据结构是简单的 key-value 类型,也是我们平时使用的最多的数据类型
常用命令: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 即是链表,链表是一种非常常见的数据结构,特点是易于数据元素的插入和删除并且可以灵活调整链表长度,但是链表的随机访问困难
常用命令: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
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 的所有字段和值
1) "name"
2) "guide"
3) "description"
4) "dev"
5) "age"
6) "24"
127.0.0.1:6379> hkeys userInfoKey # 获取 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 等
应用场景:需要存放的数据不能重复以及需要获取多个数据源交集和并集等场景
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 的范围来获取元素的列表
常用命令:zadd,zcard,zscore,zrange,zrevrange,zrem 等
应用场景:需要对数据根据某个权重进行排序的场景。比如在直播系统中,实时排行信息包含直播间在线用户列表,各种礼物排行榜,弹幕消息等信息
127.0.0.1:6379> zadd myZset 3.0 value1 # 添加元素到 sorted set 中 3.0 为权重
(integer) 1
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
Redis 为什么不用多线程以及为什么 Redis6.0 之后又引入了多线程
使用单线程的原因
Redis6.0 引入多线程主要是为了提高网络 IO 读写性能,因为这个是 Redis 中的一个性能瓶颈
虽然 Redis6.0 引入了多线程,但是 Redis 的多线程只是在网络数据的读写这类耗时操作上使用了,执行命令仍然是单线程顺序执行
设置缓存过期的作用
因为内存是有限的,如果缓存中的所有数据都是一直保存的话,很快就会 Out of memory 了
Redis 中除了字符串类型有自己独有设置过期时间的命令 setex 外,其他方法都需要依靠 expire 命令来设置过期时间 。另外 persist 命令可以移除一个键的过期时间
还有一种情况需要设置过期时间,当我们的业务场景就是需要某个数据只在某一时间段内存在,比如我们的短信验证码可能只在 1 分钟内有效,用户登录的 token 可能只在 1 天内有效,此时设置过期时间就能比较好的处理,如果使用传统的数据库来处理的话,一般都是自己判断过期,这样更麻烦并且性能要差很多
Redis 过期删除策略
更新策略对比
Redis 内存淘汰机制
Redis 提供 6 种数据淘汰策略:
4.0 版本后增加以下两种:
使用策略的规则:
(1)如果数据呈现幂律分布,也就是一部分数据访问频率高,一部分数据访问频率低,则使用 allkeys-lru
(2)如果数据呈现平等分布,也就是所有的数据访问频率都相同,则使用 allkeys-random
Redis 持久化
Redis 的一种持久化方式叫快照(snapshotting,RDB),另一种方式是只追加文件(append-only file, AOF)
快照(snapshotting)持久化(RDB)
Redis 可以通过创建快照来获得存储在内存里面的数据在某个时间点上的副本。Redis 创建快照之后,可以对快照进行备份,可以将快照复制到其他服务器从而创建具有相同数据的服务器副本(Redis 主从结构,主要用来提高 Redis 性能),还可以将快照留在原地以便重启服务器的时候使用
AOF(append-only file)持久化
与快照持久化相比,AOF 持久化的实时性更好,因此已成为主流的持久化方案,是指所有的命令行记录以 Redis 命令请求协议的格式完全持久化存储)保存为 aof 文件
默认没有开启 AOF,可以通过如下命令开启
appendonly yesCopy to clipboardErrorCopied
两种持久化方案对比: