1. Redis为什么这么快
- 完全基于内存,绝大部分请求是纯粹的内存操作,非常快速。
- 数据结构简单,对数据操作也简单。
- 采用单线程,避免了
不必要的上下文切换和竞争
,也不存在多进程或者多线程导致的切换而消耗 CPU,不用
去考虑各种锁
的问题,不存在加锁释放锁操作
,没有因为可能出现死锁而导致的性能消耗;
- 使用
I/O 多路复用
模型,非阻塞 IO
;
2. Redis的应用场景
- 计数器(文章阅读的次数):可以对 String 进行自增自减运算,从而实现计数器功能。Redis 这种内存型数据库的读写性能非常高,很适合存储频繁读写的计数量。
- String类型的短信验证码。
- 缓存:将热点数据放到内存中,设置内存的最大使用量以及淘汰策略来保证缓存的命中率。
- 分布式锁实现:在分布式的环境下,无法使用单机环境下的锁,可以使用 Redis 自带的
SETNX
命令实现分布式锁,除此之外,还可以使用官方提供的 RedLock
分布式锁实现。
- LIST类型( 有序的,是一个双向链表,可以通过 lpush 和 rpop 写入和读取消息):存储一些列表型的数据结构,类似粉丝列表、文章的评论列表之类的数据
- HASH类型:可以用在对购物车进行存储,用户id保存在大key,商品id保存小key,至于hash中的value就是+1或是-1的操作
- SET(无序不重复)类型,能实现交集、并集、差集的操作,比如交集,可以把两个人的粉丝列表整一个交集,能实现共同好友;还能够点赞;抽奖品
- ZSET(有序,增加了一个分值score参数)类型:对于热搜有序的可以使用,比较适合类似于top 10等不根据插入的时间来排序的数据。
- 附近的人
3.Redis 的持久化机制是什么?各自的优缺点?
Redis 提供两种持久化机制 RDB快照(默认) 和 AOF 机制
- ==RDB持久化==:是Redis DataBase缩写,快照
RDB是Redis默认的持久化方式。按照一定的时间间隔
将内存的数据
以快照的形式保存到硬盘中
,对应产生的数据文件为dump.rdb。通过配置文件中的save参数来定义快照的周期。
# 表示 60 秒内如果至少有 1000 个 key 的值变化,则保存
save 60 1000
- ==RDB==工作流程
- redis根据配置尝试去生成rdb快照文件
- redis主进程fork一个子进程出来
- 子进程尝试将内存中的数据dump到临时的rdb快照文件中
- 完成rdb快照文件的生成之后,覆盖旧的快照文件
- ==RDB==优点
- 只有一个.rdb文件,容易管理,容灾性好
- 进程方面:Redis派出一个子线程,让子线程去操作.rdb文件,主线程继续操作数据的读写操作,性能最大化
- ==RDB==缺点
- 数据安全性低。RDB 是间隔一段时间进行持久化,如果持久化之间 redis 发生故障,会发生数据丢失。
AOF
持久化:Append Only File缩写
将Redis执行的
每条写命令
记录到单独的aof日志文件中,当重启Redis服务时,会从持久化的日志文件中恢复数据。
当两种方式同时开启时,数据恢复时,Redis会优先选择AOF恢复。
AOF
工作流程
- 所有的
写入命令
会追加到AOF缓冲中。
- AOF缓冲区根据
对应的策略
向硬盘做同步
操作。
- 随着AOF文件越来越大,需要定期对AOF文件进行重写,达到压缩的目的。
- 当Redis服务器重启时,可以加载AOF文件进行数据恢复。
AOF
优点
- 数据安全,可以配置
每进行一次命令
操作就记录到 aof 文件中一次。
- 通过 append 模式写文件,即使中途服务器宕机,可以通过 redis-check-aof 工具解决数据一致性问题。
AOF
缺点
- AOF 文件比 RDB 文件大,且恢复速度慢。
- 数据集大时,比 rdb 启动效率低。
4. 如何选择合适的持久化方式
- 如果想达到数据量很少很少级别的丢失,可以考虑两种持久化方式都打开
- 如果可以对几分钟内的数据允许丢失,那么可以采用RDB默认的持久化机制
- 如果只是希望在服务器中运行时用到的缓存,可以关闭两种持久化机制,这样能提高Redis的效率
- 有很多用户都只使用AOF持久化,但并不推荐这种方式,因为定时生成RDB快照(snapshot)非常便于进行数据库备份, 并且 RDB 恢复数据集的速度也要比AOF恢复的速度要快,除此之外,使用RDB还可以避免AOF程序的bug。
5. 过期键
的删除策略
Redis是key-value数据库,我们可以设置Redis中缓存key的过期时间。Redis的过期策略就是指当Redis中缓存的key过期了,Redis如何处理。
- 过期策略通常有以下三种:
- 定时过期:每个设置过期时间的key都需要创建一个定时器,到过期时间就会立即清除。该策略可以立即清除过期的数据,
对内存很友好
;但是会占用大量的CPU资源
,从而影响缓存的响应时间
和吞吐量。
- 惰性过期:只有当访问一个key时,才会判断该key是否已过期,过期则清除。该策略可以最大化地节省CPU资源,却对内存非常不友好。极端情况可能出现大量的过期key没有再次被访问,从而不会被清除,占用大量内存。
- 定期过期:每隔一段时间,对一些key进行检查,删除里面过期的key。该策略是前两者的一个折中方案。通过调整定时扫描的时间间隔和每次扫描的限定耗时,可以使得CPU和内存资源达到最优的平衡效果。
Redis
中同时使用了惰性过期和定期过期
两种过期策略。通过配合使用这两种过期键的删除策略,服务器可以很好地在合理使用CPU时间和避免浪费内存空间之间取得平衡。
6.内存
淘汰策略有哪些
Redis的内存淘汰策略是指在Redis服务器用于缓存的内存不足时,怎么处理需要新写入且需要申请额外空间的数据。
- 全局的键空间选择性移除
- allkeys-lru(常用):当内存不足以容纳新写入数据时,在全局键空间中,移除最近最少使用的key。
- allkeys-random:当内存不足以容纳新写入数据时,在全局键空间中,随机移除某个key。
- noeviction:当内存不足以容纳新写入数据时,新写入操作会报错。
- 设置过期时间的键空间选择性移除
- volatile-lru(常用):当内存不足以容纳新写入数据时,在设置了过期时间的键空间中,移除最近最少使用的key。
- volatile-random:当内存不足以容纳新写入数据时,在设置了过期时间的键空间中,随机移除某个key。
- volatile-ttl:当内存不足以容纳新写入数据时,在设置了过期时间的键空间中,有更早过期时间的key优先移除。
7. Redis事务
7.1. Redis事务的三个阶段
- 事务开始 MULTI
- 命令入队
- 事务执行 EXEC
7.2. Redis事务命令
项目 |
Value |
描述 |
1 |
WATCH |
WATCH 命令是一个乐观锁,可以为 Redis 事务提供 check-and-set (CAS)行为。可以监控一个或多个键,一旦其中有一个键被修改(或删除),之后的事务就不会执行,监控一直持续到EXEC命令。 |
2 |
UNWATCH |
UNWATCH命令可以取消watch对所有key的监控。 |
3 |
MULTI |
MULTI命令用于开启一个事务,它总是返回OK。MULTI执行之后,客户端可以继续向服务器发送任意多条命令,这些命令不会立即被执行,而是被放到一个队列中,当EXEC命令被调用时,所有队列中的命令才会被执行。 |
4 |
EXEC |
EXEC:执行所有事务块内的命令。返回事务块内所有命令的返回值,按命令执行的先后顺序排列。当操作被打断时,返回空值 nil 。 |
5 |
DISCARD |
通过调用DISCARD,客户端可以清空事务队列,并放弃执行事务, 并且客户端会从事务状态中退出。 |
8.Redis实现分布式锁
Redis为单进程单线程模式,采用队列模式将并发访问变成串行访问,且多客户端对Redis的连接并不存在竞争关系,Redis中可以使用SETNX命令实现分布式锁。
- 使用SETNX完成同步锁的流程及事项如下:
- 使用SETNX命令获取锁,若返回0(key已存在,锁已存在)则获取失败,若返回1则获取成功
- 为了防止获取锁后程序出现异常,导致其他线程/进程调用SETNX命令总是返回0而进入死锁状态,需要为该key设置一个合理的过期时间
- 释放锁,使用DEL命令将锁数据删除
9.缓存雪崩
缓存雪崩是指缓存同一时间大面积的失效,导致所有的请求都会落到数据库上,造成数据库短时间内承受大量请求而崩掉。
缓存数据过期时间随机:过期时间设置随机,防止同一时间大量数据过期现象发生。
- 热点数据不设置过期时间,主动刷新缓存:缓存设置成永不过期,在更新或删除 DB 中的数据时,也主动地把缓存中的数据更新或删除掉。
- 检查更新:缓存依然保持设置过期时间,每次 get 缓存的时候,都和数据的过期时间和当前时间进行一下对比,当间隔时间小于一个阈值的时候,主动更新缓存。
- 使用锁:通过互斥锁或者队列,控制读数据库和写缓存的线程数量。
10.缓存穿透
缓存穿透是指缓存和数据库中都没有的数据,导致所有的请求都落到数据库上,造成数据库短时间内承受大量请求而崩掉。
- 接口层增加逻辑校验,如用户鉴权校验,id做基础校验,id<=0的直接拦截;
- 从缓存取不到的数据,在数据库中也没有取到,这时也可以将key-value对写为key-null,缓存有效时间可以设置短点,如30秒(设置太长会导致正常情况也没法使用)。这样可以防止攻击用户反复用同一个id暴力攻击
- 采用布隆过滤器,将所有可能存在的数据哈希到一个足够大的 bitmap 中,一个一定不存在的数据会被这个 bitmap 拦截掉,从而避免了对底层存储系统的查询压力
11.缓存击穿
缓存击穿是指缓存中没有但数据库中有的数据(一般是缓存时间到期),这时由于并发用户特别多,读缓存没读到数据,造成数据库短时间内承受大量请求而崩掉。和缓存雪崩不同的是,缓存击穿指并发
查同一条数据
,缓存雪崩是缓存同一时间大面积失效。
- 设置热点数据永远不过期。
- 加互斥锁
12. 缓存预热
缓存预热就是系统上线后,将相关的缓存数据直接加载到缓存系统。这样就可以避免在用户请求的时候,先查询数据库,然后再将数据缓存的问题!用户直接查询事先被预热的缓存数据!
- 直接写个缓存刷新页面,上线时手工操作一下;
- 数据量不大,可以在项目启动的时候自动进行加载;
- 定时刷新缓存;
13. Redis 主从复制的原理
- 从节点执行 slaveof 命令
- 从节点只是保存了 slaveof 命令中主节点的信息,并没有立即发起复制
- 从节点内部的定时任务发现有主节点的信息,开始使用 socket 连接主节点
- 连接建立成功后,发送 ping 命令,希望得到 pong 命令响应,否则会进行重连
- 如果主节点设置了权限,那么就需要进行权限验证;如果验证失败,复制终止。
- 权限验证通过后,进行数据同步,这是耗时最长的操作,主节点将把所有的数据全部发送给从节点。
- 当主节点把当前的数据同步给从节点后,便完成了复制的建立流程。接下来,主节点就会持续的把写命令发送给从节点,保证主从数据一致性。
13.如何保证缓存与数据库双写时的数据一致性?
缓存与数据库双存储双写,就一定会有数据一致性的问题
- 数据强一致性方案:读请求和写请求串行化,串到一个内存队列里去,这样就可以保证一定不会出现不一致的情况,串行化之后,就会导致系统的吞吐量会大幅度的降低
- 还有一种方式就是可能会暂时产生数据不一致的情况,但是发生的几率特别小,就是先更新数据库,然后再删除缓存。
- 几种方案的说明:
问题场景 |
描述 |
解决 |
先写缓存,再写数据库,缓存写成功,数据库写失败 |
缓存写成功,但写数据库失败或者响应延迟,则下次读取(并发读)缓存时,就出现脏读 |
这个写缓存的方式,本身就是错误的,需要改为先写数据库,把旧缓存置为失效;读取数据的时候,如果缓存不存在,则读取数据库再写缓存 |
先写数据库,再写缓存,数据库写成功,缓存写失败 |
写数据库成功,但写缓存失败,则下次读取(并发读)缓存时,则读不到数据 |
缓存使用时,假如读缓存失败,先读数据库,再回写缓存的方式实现 |
需要缓存异步刷新 |
指数据库操作和写缓存不在一个操作步骤中,比如在分布式场景下,无法做到同时写缓存或需要异步刷新(补救措施)时候 |
确定哪些数据适合此类场景,根据经验值确定合理的数据不一致时间,用户数据刷新的时间间隔 |