文章目录
链接: 20道Redis经典面试题!
1. Redis与MySQL区别以及应用场景?🌟
- 区别:
MySQL
是关系型数据库,把数据存储在硬盘中,读取速度较慢;不受空间容量限制,性价比较高。Redis
是非关系型数据库,把数据存在内存中,读取速度较快;当数据量超过内存空间时,需要扩充内存,而内存成本比较高。- web 应用中一般采用
MySQL+Redis
的方式,web应用每次先访问 Redis,如果没有找到数据,才去访问MySQL。
- 应用场景:
- 一般是
MySQL
做为主存储,Redis
用于缓存,来加快访问速度。使用MySQL+Redis
时,需要对数据做同步,保证缓存与数据库的数据一致性。
- 一般是
- Redis应用场景:
- 因为Redis数据是存在内存中的,读写数据的效率很高。因此redis被广泛应用于缓存。通常用来存储一些热点数据,也就是需要频繁调取的数据,要用的时候,直接从内存中取,可以提升速度并节约服务器开销。 (String?hash?)
- 可以利用
Bitmap
来统计活跃用户、用户是否在线、用户签到等。 - 利用
zset
做排行榜。一个用户一个排名,就意味着要去重,所以采用zset,因为zset是有序的,不重复的。 - 利用
set
实现抽奖系统。 - 可用于消息队列(支付) (list?):Redis 自带的
list
数据结构可以作为一个简单的队列使用。Redis 5.0 中增加的 Stream 类型的数据结构更加适合用来做消息队列。它比较类似于 Kafka,有主题和消费组的概念,支持消息持久化以及 ACK 机制。 - 商品列表、评论列表 (list?)
为什么要用 Redis/为什么要用缓存?
- 高性能:如果用户访问的数据属于高频数据并且不会经常改变的话,那么我们可以把用户访问的数据存在缓存中。用户下一次再访问这些数据时,就可以直接从缓存中获取了,速度很快。
- 高并发:缓存能够承受的数据库请求数量远远大于直接访问数据库的,所以可以考虑把数据库部分数据存入缓存中,这样一部分请求就直接到缓存这里,而不经过数据库,提高了系统的并发。(一般像 MySQL 这类的数据库每秒可以执行的查询次数大概都在 1w 左右(4 核 8g),使用 Redis 缓存之后可以达到 10w+,甚至最高能达到 30w+(就单机 Redis 的情况,Redis 集群的话会更高))。
2. 什么是Redis?
- Redis,英文全称是Remote Dictionary Server(远程字典服务),是一个
Key-Value数据库
。 - Redis 不仅支持简单的 key-value 类型的数据,同时还提供 list,hash,set,zset等数据结构的存储。
3. 说说Redis的数据类型?🌟
- Redis支持5种基本数据类型:
string(字符串),list(列表),hash(哈希),set(集合)及zset(sorted set:有序集合)
。string(字符串)
:是 Redis 最基本的数据类型,一个 key 对应一个 value;- string类型是二进制安全的,可以存储图片或序列化的对象;string 类型的值最大能存储 512MB。
- 应用场景:共享session、分布式锁,计数器、限流。
list(列表)
:用来存储多个有序且可重复的字符串,按照插入顺序排序。
(可以添加一个元素到列表的头部(左边)或者尾部(右边))- 应用场景:消息队列、文章列表…。比如twitter的关注列表,粉丝列表等都可以用Redis的list结构来实现;
hash(哈希)
:哈希类型是指v(值)本身又是一个键值对(k-v)结构;- 适合用来存储对象;
- 应用场景:缓存用户信息。我们要存储一个用户信息对象数据,其中包括用户ID、用户姓名、年龄和生日,通过用户ID我们希望获取该用户的姓名或者年龄或者生日;
set(集合)
:是string类型的无序集合,不允许重复。
(集合是通过哈希表实现的,所以添加,删除,查找的复杂度都是 O(1)。)- set对外提供的功能与list类似是一个列表的功能,特殊之处在于set是可以自动排重的,当你需要存储一个列表数据,又不希望出现重复数据时,set是一个很好的选择,并且set提供了判断某个成员是否在一个set集合内的重要接口,这个也是list所不能提供的;
zset(有序集合)
:是string类型的有序集合,不允许重复。但是是有序的,在数据插入集合时,已经进行了天然排序。- 它是将Set中的元素增加一个权重参数score,元素按score有序排列
- 应用场景:排行榜,社交需求(如用户点赞)。
- Redis还有三种特殊的数据结构类型:
Geospatial (地理位置)
:地理位置定位,用于存储地理位置信息,并对存储的信息进行操作。HyperLogLog(基数统计)
:用来做基数统计算法的数据结构,如统计网站的UV。Bitmap (位存储)
🌟:可以把它看作是一个bit数组
,只能存储0
和1
;0
表示数据不存在,1
表示数据存在;数组的下标在 Bitmaps 中叫做偏移量。在Redis中,它的底层是基于字符串实现的。Bitmap
是最小的单位存储了,我们知道8个bit
可以组成一个Byte
,所以bitmap本身会极大的节省空间。bitmap
增删时间复杂度都是O(1),时间复杂度也比较低。
- 但这些类型都是基于上述核心数据类型实现的。
String 还是 Hash 存储对象数据更好呢?
- 在绝大部分情况,建议使用
String
来存储对象数据。- 1)如果对象中某些字段需要经常变动或经常需要单独查询对象中的个别字段信息,
Hash
就非常适合。因为String
存储的是序列化后的对象数据,存放的是整个对象。Hash
是对对象的每个字段单独存储,可以获取部分字段的信息,也可以修改或者添加部分字段,节省网络流量。 - 2)
String 存储
相对来说更加节省内存,缓存相同数量的对象数据,String 消耗的内存约是 Hash 的一半。并且,存储具有多层嵌套的对象时也方便很多。如果系统对性能和资源消耗非常敏感的话,String 就非常适合。
- 1)如果对象中某些字段需要经常变动或经常需要单独查询对象中的个别字段信息,
4. Redis应用场景?能做什么?🌟
- 因为Redis数据是存在内存中的,读写数据的效率很高。因此redis被广泛应用于缓存。通常用来存储一些热点数据,也就是需要频繁调取的数据,要用的时候,直接从内存中取,可以提升速度并节约服务器开销。 (String?hash?)
- 可以利用
Bitmap
来统计活跃用户、用户是否在线、用户签到等。 - 利用
zset
做排行榜。一个用户一个排名,就意味着要去重,所以采用zset,因为zset是有序的,不重复的。 - 利用
set
实现抽奖系统。 - 可用于消息队列(支付) (list?):Redis 自带的
list
数据结构可以作为一个简单的队列使用。Redis 5.0 中增加的 Stream 类型的数据结构更加适合用来做消息队列。它比较类似于 Kafka,有主题和消费组的概念,支持消息持久化以及 ACK 机制。 - 商品列表、评论列表 (list?)
- 另外,Redis也经常用来做分布式锁通过 Redis 来做分布式锁是一种比较常见的方式。通常情况下,我们都是基于 Redisson 来实现分布式锁。除此之外,Redis支持事务、持久化、LUA 脚本、LRU 驱动事件、多种集群方案。
Bitmap应用场景?统计活跃用户、用户是否在线、用户签到?
链接: bitmap的妙用
- 1 统计活跃用户
- 2 用户在线状态
- 3 用户签到
- 使用
Bitmap
统计活跃用户:- 日期(精确到天)作为
key
,用户id为offset
,如果当日活跃过value
就设置为1
。 - 命令:
- 1)
setbit key offset value
:设置或者清空key
的value(字符串)
在offset
处的bit值
(只能只0或者1)。 - 2)统计活跃用户:
bitop 操作 destKey key [key ...]
:做多个BitMap的and交集
、or并集
、not非
、xor异或
操作,并将结果保存在destKey
中。- 例如:
bitop and desk1 20220101 20220102
就是统计连续两天活跃的人数; - 如果用
... or ...
就是统计这两天活跃过的人数;
- 例如:
- 3)
bitcount key [start end]
:就可以获取范围内位值为1
的个数,即活跃用户数。
- 1)
- 日期(精确到天)作为
- 用户是否在线:
- 只需要一个key,然后
用户id
为offset
,如果在线value
就设置为1
,不在线就设置为0
。
和上面的场景一样,5000W用户只需要6MB的空间。
- 只需要一个key,然后
- 用户签到:
- 比如最近…的签到情况。
- 根据日期
offset = (今天是一年中的第几天) % (今年的天数)
,key = 年份#用户id
。 - 命令:
setbit key offset value
:设置或者清空key
的value(字符串)
在offset
处的bit值
(只能只0或者1)。bitcount key [start end]
:就可以获取范围内位值为1
的个数,即签到数量。
set实现抽奖系统?
- 使用
Set(无序集合)
实现抽奖系统。 - 命令:
SPOP key count
: 随机移除并获取指定集合中一个或多个元素,适合不允许重复中奖的场景。- 例如:
spop key 2
:就是随机移除并获取2个元素。
- 例如:
srandmember key count
: 随机获取指定集合中指定数量的元素,适合允许重复中奖的场景。
使用 Redis 实现一个排行榜怎么做?zset
- 利用
zset
做排行榜。一个用户一个排名,就意味着要去重,所以采用zset,因为zset是有序的,不重复的。
比如直播间送礼物的排行榜、朋友圈的微信步数排行榜、王者荣耀中的段位排行榜、话题热度排行榜等等。 - 命令:
zrange (从小到大排序)
、zreverange (从大到小排序)
、zrevrank (指定元素排名)
。 zset
只能根据score
进行排序,是单字段排序。
使用 HyperLogLog 统计页面 UV 怎么做?
String应用场景?
- 常规数据(比如 session、token、、序列化后的对象)的缓存;
- 计数比如用户单位时间的请求数(简单限流可以用到)、页面单位时间的访问数;
- 分布式锁(利用 SETNX key value 命令可以实现一个最简易的分布式锁);
- …
购物车信息用 String 还是 Hash 存储更好呢?
- 由于购物车中的商品频繁修改和变动,购物车信息建议使用
Hash
存储:用户 id
为key
;商品 id
为field
,商品数量
为value
- 用户购物车信息的维护具体应该怎么操作呢?
- 用户添加商品就是往 Hash 里面增加新的
field 与 value
; - 查询购物车信息就是遍历对应的
Hash
; - 更改商品数量直接修改对应的
value
值(直接 set 或者做运算皆可); - 删除商品就是删除 Hash 中对应的
field
; - 清空购物车直接删除对应的
key
即可。 - 这里只是以业务比较简单的购物车场景举例,实际电商场景下,field 只保存一个商品 id 是没办法满足需求的。
- 用户添加商品就是往 Hash 里面增加新的
5. 使用Redis好处?
- 速度快。因为Redis数据是存在内存中的,读写数据的效率很高。相对于数据存在磁盘的MySQL数据库,省去磁盘I/O的消耗。
- 可以设置过期时间,过期后会自动删除。
- 支持事务,操作都是原子性的。原子性是指对数据的更改要么全部执行,要么全部不执行。
- 支持持久化。Redis支持
RDB
和AOF
两种持久化机制,持久化功能可以有效地避免数据丢失问题。 - 支持丰富的数据类型,支持
string、list、hash、set、zset
等。 - 支持主从模式。可以配置集群,更利于支撑大型的项目。
- Redis是单线程的,避免线程切换开销及多线程的竞争问题。单线程是指网络请求使用一个线程来处理,即一个线程处理所有网络请求,Redis 运行时不止有一个线程,比如数据持久化的过程会另起线程。
6. Redis为什么这么快?
1、基于内存存储实现。Redis数据是存在内存中的,读写数据的效率很高。相对于数据存在磁盘的MySQL数据库,省去磁盘I/O的消耗。
2、 高效的数据结构。Redis 每种数据类型底层都做了优化,目的就是为了追求更快的速度。
(2.1)字典。Redis是一个Key-Value数据库,键值对就是用字典来存储的,通过key可以直接获取value,O(1)的时间复杂度就可以获取值。
3、IO多路复用模型:Redis 采用 IO 多路复用技术。Redis 使用单线程来轮询描述符,将数据库的操作都转换成了事件,不在网络I/O上浪费过多的时间。
5、虚拟内存机制
合理的数据编码
7、缓存
7.1 什么是缓存穿透?
- 正常使用缓存的流程是:
- 数据查询首先进行缓存查询;
- 缓存有值命中,就直接返回缓存数据;
- 不命中时,就去数据库进行查询:
- 把查询到的数据更新到缓存;然后返回。
- 查不到数据就不写入缓存。
- 数据查询首先进行缓存查询;
- 缓存穿透:就是查询缓存中不存在的数据,导致缓存不命中时,就会去数据库进行查询,查不到数据不写入缓存,这就导致查询不存在的数据每次都要去查询数据库,从而给数据库带来压力。每次查询不存在的数据都会穿透到数据库,这就是缓存穿透。
- 例子:假如有⼈恶意攻击,就可以利⽤这个漏洞,故意制造缓存中不存在的key发起大量请求,对数据库造成压⼒,甚⾄压垮我们的数据库。
- 解决方案?
- 如果从数据库中查询的对象为空,也将它放入缓存,可以设置一个空值,可以设定的缓存过期的时间短一些,⽐如设置为60秒。这样下次⽤户再根据这个key查询redis缓存就可以查询到值了(当然值为null),从⽽保护我们的数据库免遭攻击。
- 或者不合法的请求直接抛出异常信息返回给客户端。比如查询的数据库 id 不能小于 0、传入的邮箱格式不对的时候直接返回错误消息给客户端等等。
7.2 缓存雪崩?
- 缓存雪崩:大规模的key在同一时间段集中失效,请求都直接访问数据库,导致数据库压力过大甚至宕机。(如果在高并发情况下,可能瞬间就会导致数据库宕机)
- 例子:1)系统的缓存模块出了问题比如宕机导致不可用。造成系统的所有访问,都要走数据库。2)秒杀开始 12 个小时之前,我们统一存放了一批商品到 Redis 中,设置的缓存过期时间也是 12 个小时,那么秒杀开始的时候,这些秒杀的商品的访问直接就失效了。导致的情况就是,相应的请求直接就落到了数据库上,就像雪崩一样可怕。
- 解决方案?
- (1)针对 Redis 服务不可用的情况:
- 采用 Redis 集群,避免单机出现问题整个缓存服务都没办法使用。
- 限流,避免同时处理大量的请求。
- (2)针对热点缓存失效的情况:
- 设置不同的失效时间比如随机设置缓存的失效时间。可以将缓存数据的过期时间设置得分散一些。避免相同的过期时间导致缓存雪崩,造成大量的数据库访问。分级缓存:第一级缓存失效的基础上,访问二级缓存,每一级缓存的失效时间不同。
- 缓存永不失效。
- (1)针对 Redis 服务不可用的情况:
7.3 缓存击穿?
- 缓存击穿:是指一个热点key在某时间点过期时,恰好在这个时间点有大量的并发对它进行请求,读缓存没命中,就穿破缓存直接访问数据库了。
- 与缓存雪崩有点类似,但缓存雪崩是大规模的key集中过期失效,缓存击穿是某个热点的key失效。
- 解决方案?
- 将热点数据设置永不过期。
7.4 什么是热Key问题,如何解决热key问题?
- 什么是热key?在Redis中,我们把访问频率高的key,称为热点key。
- 比如大量的并发请求(几十万的请求)去访问redis中某个Key,很可能导致redis的服务器宕机,然后就会去请求数据库,导致数据库压力过大甚至宕机。
- 热点Key问题是怎么产生的呢?主要原因有两个:
- 1)用户消费的数据远大于生产的数据,如秒杀、热点新闻等读多写少的场景。
- 2)请求分片集中,超过单台Redis服务器的性能极限。
- 比如采用固定Hash分片,hash落入到同一台redis服务器,如果瞬间访问量过大,超过机器瓶颈时,就会产生热点Key问题。
- 那么在日常开发中,如何识别到热点key呢?
凭经验判断哪些是热Key;
客户端统计上报;
服务代理层上报 - 如何解决热key问题?
- Redis集群扩容:增加分片副本,来分摊读请求;(将热key分散到不同的服务器中?)
- 使用二级缓存,即JVM本地缓存,减少Redis的读请求。
7.5 项目中缓存优化?🌟
链接: Redis缓存菜品、套餐数据详细
- (先说一下为什么用Redis缓存?)
- 缓解数据库的访问压力,提高访问效率。因为在高并发的情况下,频繁查询数据库会导致系统性能下降,服务端响应时间增长,所以需要进行缓存优化,提高系统的性能。
- 当查询时,会先从
Redis
中获取缓存数据;- 如果存在就直接返回,不需要再查询数据库;
- 不存在,就查询数据库,再将查询到的菜品数据缓存到
Redis
中。
- 使用缓存需要注意的是:缓存中的数据应该与数据库中数据保持一致,如果数据库发生变化,需要及时清理缓存数据。
- .
- 1)如果是查询方法:先从Redis中获取缓存数据;
- 如果存在就直接返回,不需要再查询数据库;
- 不存在,就查询数据库,再将查询到的菜品数据放到缓存中。
- 2)如果是新增或修改更新或删除方法:就先更新Mysql数据库,再清理对应的缓存数据,从而保证缓存中的数据与数据库的数据一致。
比如说新增了一个菜品,那就清理这个菜品所在分类的菜品缓存数据,从而保证缓存中的数据与数据库的数据一致。
- 1)如果是查询方法:先从Redis中获取缓存数据;
7.6 如何保证缓存和数据库的数据一致性?🌟
- 先删除缓存再更新数据库
- 修改更新操作时,先删除缓存,然后更新数据库,后续的请求再次读取时,会从数据库读取后再将新数据更新到缓存。
- 存在的问题:删除缓存数据之后,更新数据库完成之前,这个时间段内如果有新的读请求过来,就会从数据库读取旧数据重新写到缓存中,再次造成不一致,并且后续读的都是旧数据。
- 先更新数据库再删除缓存
- 进行更新操作时,先更新MySQL,成功之后,删除缓存,后续读的时候,再将数据放到缓存中。
- 存在的问题:更新MySQL和删除缓存这段时间内,请求读取的还是缓存的旧数据,不过等数据库更新完成,就会恢复一致,影响相对比较小。
- 缓存延时双删:
(1)先删除缓存;
(2)再更新数据库
(3)休眠1秒,再次删除缓存
这么做,可以将1秒内所造成的缓存脏数据,再次删除。 - 异步更新缓存
- 更新数据库,然后把删除缓存操作放到消息队列,Redis自己消费信息,获得需要删除的key。消息队列可以保证数据操作顺序一致性,确保数据一致性。
- (1)更新数据库;
(2)数据库会将操作信息写入binlog日志当中
(3)订阅程序提取出所需要的数据以及key
(4)另起一段非业务代码,获得该信息
(5)尝试删除缓存操作,发现删除失败
(6)将这些信息发送至消息队列
(7)重新从消息队列中获得该数据,重试操作。
8、持久化
Redis 的持久化机制有哪些?优缺点说说?🌟
链接: 20道Redis经典面试题!
- Redis是基于内存的非关系型K-V数据库,既然它是基于内存的,如果Redis服务器挂了,数据就会丢失。为了避免数据丢失了,Redis提供了持久化,即把数据保存到磁盘。
- Redis提供了
快照(snapshotting, RDB)
、只追加文件(append-only file, AOF)
和混合使用RDB持久化和AOF持久化
持久化机制:
RDB(snapshotting)持久化
:就是把内存数据以快照的形式保存到磁盘上。把某时间点内存中数据快照写入磁盘中;恢复时将快照文件直接读到内存里。是Redis默认的持久化方式。
(执行完操作后,在指定目录下会生成一个dump.rdb文件,Redis 重启的时候,通过加载dump.rdb文件来恢复数据。)- 优点:RDB是二进制文件,内容紧凑,恢复数据集的速度较快;RDB保存了某个时间点上的数据集,很适合用于数据备份。
- 缺点:如果服务器发生故障,可能会丢失数据。比如5分钟保存一次RDB文件,一旦发生故障,就会丢失好几分钟的数据;每次保存RDB,都要fork()一个子线程,并由子线程进行持久化工作,在数据集比较庞大时, fork() 可能会非常耗时,造成服务器在某某毫秒内停止处理客户端。
AOF(append-only file)持久化
:采用日志的形式来记录每个写操作,追加到文件中;重启时再重新执行AOF文件中的命令来恢复数据。它主要解决数据持久化的实时性问题。默认不开启。(已成为主流的持久化方案)AOF 持久化机制是在执行完命令之后再记录日志。- 优点/为什么是在执行完命令之后记录日志呢?
- 可以避免额外的检查开销。AOF 记录日志不用再对命令进行语法检查了;
- 在命令执行完之后再记录,不会阻塞当前的命令执行。
- 缺点:
- 如果刚执行完命令 Redis 就宕机会导致对应的修改丢失;
- 可能会阻塞后续其他命令的执行(AOF 记录日志是在 Redis 主线程中进行的)。
- 优点/为什么是在执行完命令之后记录日志呢?
RDB 和 AOF 的混合持久化
(默认关闭,可以通过配置项 aof-use-rdb-preamble 开启;Redis 4.0 开始支持)。- AOF 重写的时候就直接把 RDB 的内容写到 AOF 文件开头。
- 优点:可以结合 RDB 和 AOF 的优点, 快速加载同时避免丢失过多的数据。
- 缺点: RDB 部分是压缩格式不再是 AOF 格式,可读性较差。(4.0之前版本都不识别该AOF文件)
- AOF 重写的时候就直接把 RDB 的内容写到 AOF 文件开头。
RDB 创建快照时会阻塞主线程吗?
- Redis 提供了两个命令来生成 RDB 快照文件:
save
: 主线程执行,会阻塞主线程;bgsave
: 子线程执行,不会阻塞主线程,默认选项。
AOF 日志是如何实现的?
- 关系型数据库(如 MySQL)通常都是执行命令之前记录日志(方便故障恢复),而 Redis AOF 持久化机制是在执行完命令之后再记录日志。
- 优点/AOF为什么是在执行完命令之后记录日志呢?
- 可以避免额外的检查开销。AOF 记录日志不用再对命令进行语法检查了;
- 在命令执行完之后再记录,不会阻塞当前的命令执行。
- 缺点?
- 如果刚执行完命令 Redis 就宕机会导致对应的修改丢失;
- 可能会阻塞后续其他命令的执行(AOF 记录日志是在 Redis 主线程中进行的)。
AOF 重写了解吗?
- 当AOF太大时,Redis会自动重写AOF 产生一个新的AOF文件,新的AOF文件和原有AOF文件所保存的数据库状态一样,但体积更小。
- AOF重写是通过读取数据库中的键值对实现的。(程序无须对现有AOF文件进行任何读入、分析或写入操作)
-
- 1)在执行
BGREWRITEAOF
命令时,Redis服务器会维护一个AOF重写缓冲区; - 2)在子进程创建新AOF文件期间,缓冲区会记录服务器所有写命令。
- 3)当子进程完成创建新AOF文件后,Redis服务器会将重写缓冲区中的所有内容追加到新AOF文件末尾,使得新AOF文件保存的数据库状态与现有的数据库状态一致。
- 4)最后,Redis服务器用新的AOF文件替换旧的AOF文件,完成AOF文件重写操作。
- 1)在执行
- 但是,Redis7.0版本之前,如果在重写期间有写入命令,AOF可能会使用大量内存,重写期间到达的所有写入命令都会写入磁盘两次。
-
8.2 如何选择合适的持久化方式?/ 如何选择 RDB 和 AOF?
Redis持久化数据和缓存怎样做扩容?
- 如果 Redis 用于缓存,使用一致性哈希实现动态扩容缩容。
- 如果 Redis 用于持久化存储,必须使用固定的
keys-to-nodes
映射关系,节点的数量一旦确定不能变化。否则的话(即 Redis 节点需要动态变化的情况),必须使用可以在运行时进行数据再平衡的一套系统,而当前只有 Redis 集群可以做到这样。
9、Redis内存管理
Redis 给缓存数据设置过期时间有什么用?
- 内存是有限的,缓存中的数据不能一直保存,否则很容易Out Of memory。设置过期时间可以缓解内存的消耗。
- 有些业务场景就是需要某个数据只在某一时间段内存在。
- 比如:我们的短信验证码可能只在 5 分钟内有效,用户登录的 token 可能只在 1 天内有效。
- 如果使用传统的数据库来处理的话,一般都是自己判断过期,这样更麻烦并且性能要差很多
- Redis 自带了给缓存数据设置过期时间的功能:
# Redis 中除了字符串类型有自己独有设置过期时间的命令 setex 外,
# 其他方法都需要依靠 expire 命令来设置过期时间 。
# persist 命令可以移除一个键的过期时间。
127.0.0.1:6379> exp 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) 56
Redis 是如何判断数据是否过期的呢?
- Redis 使用过期字典(可以看作是 hash 表)来保存数据过期的时间。过期字典的键指向 Redis 数据库中的某个 key(键),过期字典的值是一个 long long 类型的整数,保存了 key 所指向的数据库键的过期时间(毫秒精度的 UNIX 时间戳)。
- 过期字典是存储在 redisDb 这个结构里的:
typedef struct redisDb {
...
dict *dict; //数据库键空间,保存着数据库中所有键值对
dict *expires // 过期字典,保存着键的过期时间
...
} redisDb;
Redis 的内存用完了会发生什么?
如果达到设置的上限,Redis 的写命令会返回错误信息(但是读命令还可以正常返回。)或者你可以将 Redis 当缓存来使用配置淘汰机制,当 Redis 达到内存上限时会冲刷掉旧的内容。
Redis过期键的删除策略?
- Redis过期删除策略:是惰性删除和定期删除两种策略配合删除。
- 1) 惰性删除:不主动删除数据,而是在访问数据时,再检查当前键值是否已经过期,如果过期则删除并返回null给客户端,否则返回正常信息给客户端。
- 优点:对CPU友好,只在每次访问时检查是否过期,对于用不到的key不浪费时间进行过期检查。
- 缺点:删除不及时,造成了一定的空间浪费。
- 2) 定期删除:Redis会周期性地测试一批设置了过期时间的key,删除已过期key。
- 优点:对内存更友好,能保证内存的key一旦过期就能从内存中删除。
- 缺点:对CPU不友好,过期键较多时,删除过期键会占用一部分CPU时间,对服务器的响应时间和吞吐量造成影响。
- 1) 惰性删除:不主动删除数据,而是在访问数据时,再检查当前键值是否已经过期,如果过期则删除并返回null给客户端,否则返回正常信息给客户端。
- 惰性删除对CPU更友好,定期删除对内存更友好,两者各有千秋,所以Redis是 惰性删除和定期删除两种策略配合删除。
- 仅仅给key设置过期时间还是有问题的,因为可能存在惰性删除和定期删除漏掉的过期key情况,就会导致大量过期的key堆积在内存中,然后就Out Of memory了。怎么解决这个问题呢❓–>Redis内存淘汰机制
Redis内存淘汰机制?
- 当Redis的内存超过最大允许的内存之后,Redis会触发内存淘汰策略,删除一些不常用的数据,保证Redis服务器的正常运行。
- Redis 4.0版本前提供6种数据淘汰策略:
- 1)
allkeys-lru(least recently used
):从所有数据中选择最近最少使用的 key淘汰。(最常用) - 2)
allkeys-random
:从所有数据中(server.db[i].dict)
中任意选择数据淘汰。 - 3)
volatile-lru(least recently used)
:从设置了过期时间的数据(server.db[i].expires)
中选择最近最少使用的数据淘汰。 - 4)
volatile-random
:从设置了过期时间的数据(server.db[i].expires)中任意选择数据淘汰 - 5)
volatile-ttl
:从设置了过期时间的数据(server.db[i].expires)中选择将要过期的数据淘汰。 - 6)
no-eviction
:禁止驱逐数据,也就是说当内存不足以容纳新写入数据时,新写入操作会报错。(这个应该没人使用吧!)
- 1)
- Redis 4.0版本后增加了2种:
- 7)
volatile-lfu(least frequently used)
:从设置了过期时间的数据(server.db[i].expires)中选择最不经常使用的数据淘汰。 - 8)
allkeys-lfu(least frequently used)
:从所有数据中,选择最不经常使用的 key淘汰。
- 7)
- 内存淘汰策略可以通过配置文件来修改,Redis.conf对应的配置项是maxmemory-policy修改对应的值就行,默认是noeviction。
Redis key的过期时间和永久有效分别怎样设置?
10、Redis线程模型
10.1 Redis是单线程吗?
- 对于读写命令来说,Redis 一直是单线程模型。
- 不过,在 Redis 4.0 版本之后引入了多线程来处理异步任务;
- Redis 6.0 版本之后 在网络模型中实现多线程I/O(提高网络 IO 读写性能)。
- 虽然Redis 在 4.0 之后的版本中就已经加入了对多线程的支持,但执行命令依旧是单线程顺序执行(多线程部分只是用来处理网络数据的读写和协议解析),所以我们一般都说 Redis 是单线程模型。
Redis单线程模型了解吗?
- Redis 基于 Reactor 模式设计开发了一套高效的事件处理模型 (Netty 的线程模型也基于 Reactor 模式,Reactor 模式不愧是高性能 IO 的基石),即文件事件处理器(file event handler)。由于文件事件处理器(file event handler)是单线程方式运行的,所以我们一般都说 Redis 是单线程模型。
- 文件事件处理器使用 I/O 多路复用(multiplexing)程序来同时监听多个套接字socket(客户端的大量连接),并根据套接字目前执行的任务来为套接字关联不同的事件处理器。
- 好处:使用 I/O 多路复用技术,Redis 不需要创建多余的线程来监听客户端的大量连接,降低了资源的消耗(和 NIO 中的 Selector 组件很像)。
- 文件事件处理器(file event handler)主要是包含 4 个部分:
- 多个 socket(客户端连接)
- IO 多路复用程序(支持多个客户端连接的关键)
- 文件事件分派器(将 socket 关联到相应的事件处理器)
- 事件处理器(连接应答处理器、命令请求处理器、命令回复处理器)
10.2 Redis为什么选择单线程?
- 单线程编程简单且容易维护;(如果使用多线程,那么底层数据结构必须实现成线程安全,就会使得Redis的实现变得复杂。)
- 单线程可以避免进程内频繁的线程切换开销。因为程序始终运行在进程中单个线程内,没有多线程切换的场景。
- 多线程可能会存在死锁、线程上下文切换等问题,甚至会影响性能。
10.3 Redis 6.0为何引入多线程?
- 互联网业务系统要处理的线上流量越来越大,Redis的单线程模式会导致系统消耗很多CPU时间在网络I/O上从而降低吞吐量。
- 引入多线程可以:
- 可以充分利用CPU资源;
- 多线程任务可以分摊Redis同步IO读写负荷。提高网络IO读写性能。
10.6 Redis 6.0开启多线程后,是否存在线程并发安全问题?
Redis多线程部分只是用来处理网络数据的读写和协议解析,但执行命令依旧是单线程顺序执行,所以不用担心线程安全问题。
11、事务
如何使用Redis事务?/ Redis事务怎么实现的?🌟
- Redis 可以通过
multi
,exec
,discard
和watch
等命令来实现事务(transaction)功能。 MULTI
命令后可以输入多个命令,Redis会把这些命令放到队列,当调用了exec
命令后,再执行所有的命令。过程:- 1)开始事务(
MULTI
); - 2)命令入队(批量操作 Redis 的命令,先进先出(FIFO)的顺序执行);
- 3)执行事务(
EXEC
)。 - 4)可以通过
DISCARD 命令
取消一个事务,它会清空事务队列中保存的所有命令。 - 5)可以通过
WATCH 命令
监听指定的Key
:- 如果调用
EXEC 命令
执行事务前,被监视的Key
被改动(被其他客户端/Session修改),事务将不会执行。
但是,如果 WATCH 与 事务 在同一个 Session 里,并且被 WATCH 监视的 Key 被修改的操作发生在事务内部,这个事务是可以被执行成功的。
- 如果调用
- 1)开始事务(
Redis事务的三个阶段?
- 开始事务(
MULTI
); - 命令入队(批量操作 Redis 的命令,先进先出(FIFO)的顺序执行);
- 执行事务(
EXEC
)。 - 可以通过
DISCARD 命令
取消一个事务,它会清空事务队列中保存的所有命令。 - 可以通过
WATCH 命令
监听指定的Key
,如果调用EXEC 命令
执行事务前,被监视的Key
被改动(被其他客户端/Session修改),事务将不会执行。
但是,如果 WATCH 与 事务 在同一个 Session 里,并且被 WATCH 监视的 Key 被修改的操作发生在事务内部,这个事务是可以被执行成功的。
事务执行过程中,如果服务端收到有 MULTI
,EXEC
,DISCARD
和 WATCH
之外的请求,会把请求放入队列中排队。
Redis事务?
- 可以将 Redis 中的事务理解为 :Redis 事务将多个命令请求打包。然后,按顺序执行打包的所有命令,并且不会被中途打断。
- 事务中的每条命令都会与 Redis 服务器进行网络交互,这是比较浪费资源的行为。
Redis事务支持原子性吗?
- Redis不支持原子性(而且不满足持久性)。Redis事务中如果有某条命令执行失败,之前的命令不会回滚,后面的命令仍然会继续执行。
- Redis事务不适合在日常开发中使用。
如何解决 Redis 事务的缺陷?/ 事务的其他实现?
- Redis 从 2.6 版本开始支持执行 Lua 脚本,它的功能和事务非常类似。
- 可以利用 Lua 脚本来批量执行多条 Redis 命令,这些 Redis 命令会被提交到 Redis 服务器一次性执行完成,大幅减小了网络开销。
- 一段 Lua 脚本可以视作一条命令执行,一段 Lua 脚本执行过程中不会有其他脚本或 Redis 命令同时执行,保证了操作不会被其他指令插入或打扰。
- 如果 Lua 脚本运行时出错并中途结束,出错之后的命令是不会被执行的。并且,出错之前执行的命令是无法被撤销的。因此,严格来说,通过 Lua 脚本来批量执行 Redis 命令也是不满足原子性的。
Redis事务支持隔离性吗?
Redis为什么不支持事务回滚?
12、集群
为什么要有集群?/ 怎么实现Redis的高可用?
- 如果是单个Redis,那redis一旦宕机就不可用了,会造成数据的丢失。
- 如果数据库复制多个副本,部署到不同的服务器上,其中一台挂了也可以继续提供服务。
- 单个Redis的读写能力也是有限的;
- 还由于互联网的三高架构,高并发,高性能,高可用
- Redis 实现高可用有三种部署模式:主从模式,哨兵模式,集群模式。
12.2 主从模式?
- 主从模式包括一个主数据库(master)和多个从数据库(slave)。
- 主节点可以进行读写操作,如果写操作导致数据库发生变化,会自动把数据同步给从数据库。
- 从节点一般是只读,从节点的数据来自主节点。
- 挂了?
1)从节点如果挂了,不影响其他从节点和主节点的读写,重新启动后会将数据从master主节点同步过来;
2)主节点如果挂了,不影响从节点的读,但是也不会在从节点中重新选一个主节点,只能手动地将从节点晋升为主节点,所以,就有了之后的哨兵模式。
12.3 哨兵模式?
- 哨兵模式:会监视所有的主节点和从节点,在主节点宕机时会自动将从节点晋升为master主节点,继续提供服务。(哨兵之间也会监控)
- 缺点:哨兵模式每个节点存储的数据都一样,浪费内存而且不能动态扩充。所以有了之后的Cluster模式。
哨兵模式作用?
- 发送命令,监控所有主节点和从节点。
- 监测到主节点宕机时会**自动将从节点晋升为主节点,继续提供服务。
- 哨兵之间也会互相监控,从而达到高可用。
故障切换过程?
- 当主节点出现故障时,会通过投票机制选择新的主节点,并将从节点连接到新的主节点。
-
- 比如说哨兵1检测到主节点宕机,仅仅是哨兵1主观认为主服务器不可用,这是主观下线;
- 当后面的哨兵也检测到主服务器不可用,并且数量达到一定值时,哨兵之间就会进行一次投票,投票结果由一个哨兵发起,进行
failover
操作。切换成功后,通过发布订阅模式,通知其他从节点修改配置文件切换主机,这个过程称为客观下线。这样对于客户端而言,一切都是透明的。
12.4 Cluster模式?
- Cluster模式:基于主从模式实现读写分离,而且可以自动切换,每个节点都存储不同的内容 并且与其他所有节点相连,而且这些连接保持活跃,这就保证了我们只需要连接集群中任意一个节点,就可以获取其他节点的数据。
- 每个节点存储不同内容来解决在线扩容的问题。
哈希槽 / 如何合理分配数据到不同的节点上的?
- 哈希槽(hash slot)方式分配数据
- redis cluster默认分配了16384个槽slot,当
set
一个key
时,会用CRC16
算法计算再取模
得到所属哈希槽slot,然后把key
分到对应节点上,具体算法是:CRC16(key) % 16384
。
- 例子:
Redis cluster的主从模式
cluster加入了主从模式
动态扩容?
- 创建新集群;
- 双写两个集群;
- 把数据从老集群迁移到新集群(不存在才设置值,防止覆盖新的值);
- 复制速度要根据实际情况调整,不能影响老集群的性能。
- 切换新集群。