Redis 基础
- Redis 有什么作用?为什么要用Redis/为什么要用缓存。
Redis通常拿过来做缓存和分布式锁。
下面我们主要从“高性能”和“高并发”这两点来回答这个问题。
1、高性能
假如用户第一次访问数据库中的某些数据的话,这个过程是比较慢,毕竟是从硬盘中读取的。但是,如果说,用户访问的数据属于高频数据并且不会经常改变的话,那么我们就可以很放心地将该用户访问的数据存在缓存中。
这样有什么好处呢? 那就是保证用户下一次再访问这些数据的时候就可以直接从缓存中获取了。操作缓存就是直接操作内存,所以速度相当快。
2、高并发
一般像 MySQL 这类的数据库的 QPS 大概都在 1w 左右(4 核 8g) ,但是使用 Redis 缓存之后很容易达到 10w+,甚至最高能达到 30w+(就单机 Redis 的情况,Redis 集群的话会更高)。
QPS(Query Per Second):服务器每秒可以执行的查询次数;
由此可见,直接操作缓存能够承受的数据库请求数量是远远大于直接访问数据库的,所以我们可以考虑把数据库中的部分数据转移到缓存中去,这样用户的一部分请求会直接到缓存这里而不用经过数据库。进而,我们也就提高了系统整体的并发 - Redis除了做缓存,还能做什么?
分布式锁、限流、消息队列5.0之后有的功能不推荐(专业的事情交给专业的人来做)、分布式Session、复杂业务场景(比如通过 Bitmap 统计活跃用户、通过 Sorted Set 维护排行榜。)
Redis 应用
- Redis 除了做缓存,还能做什么?
- 如何基于 Redis 实现分布式锁?
- Redis 可以做消息队列么?
- Redis 可以做搜索引擎么?
Redis 数据结构 - Redis 有哪些数据结构?
- String 的底层实现是什么?为什么实现了 SDS?
- Redis 可以做消息队列么?
- Redis 可以做搜索引擎么?
Redis 持久化机制(重要)
- 宕机了,Redis 如何避免数据丢失?
Redis持久化机制,Redis中有三种持久化方式:
快照(snapshotting,RDB)
只追加文件(append-only file,AOF)
RDB和AOF的混合持久化(Redis4.0后增加) - 什么是 RDB 持久化?
RDB(Redis DataBase)持久化是Redis用于在磁盘上保存数据的一种持久化机制。它将内存中的数据定期写入到磁盘上的RDB文件中,实现了数据的持久化存储
快照持久化是 Redis 默认采用的持久化方式,在 redis.conf 配置文件中默认有此下配置:
save 900 1 #在900秒(15分钟)之后,如果至少有1个key发生变化,Redis就会自动触发bgsave命令创建快照。
save 300 10 #在300秒(5分钟)之后,如果至少有10个key发生变化,Redis就会自动触发bgsave命令创建快照。
save 60 10000 #在60秒(1分钟)之后,如果至少有10000个key发生变化,Redis就会自动触发bgsave命令创
- 什么是 AOF 持久化?
与 RDB相比,AOF持久化更有实时性。默认情况下是没有开启 AOF (Redis 6后已经默认开启了)。AOF开启后每执行一条写命令会写入到AOF缓冲区server.aof_buf 中,然后会写入到AOF文件中(此时还处于系统内核缓冲区中还未同步到磁盘中),最后在根据持久化的配置(fsync配置)来决定什么时候同步到磁盘中。
在 Redis 的配置文件中存在三种不同的 AOF 持久化方式( fsync策略),它们分别是: - appendfsync always:主线程调用 write 执行写操作后,后台线程( aof_fsync 线程)立即会调用 fsync 函数同步 AOF 文件(刷盘),fsync 完成后线程返回,这样会严重降低 Redis 的性能(write + fsync)。
- appendfsync everysec:主线程调用 write 执行写操作后立即返回,由后台线程( aof_fsync 线程)每秒钟调用 fsync 函数(系统调用)同步一次 AOF 文件(write+fsync,fsync间隔为 1 秒)
- appendfsync no:主线程调用 write 执行写操作后立即返回,让操作系统决定何时进行同步,Linux 下一般为 30 秒一次(write但不fsync,fsync 的时机由操作系统决定)。
可以看出:这 3 种持久化方式的主要区别在于 fsync 同步 AOF 文件的时机(刷盘)。 - 如何选择 RDB 和 AOF
● Redis 保存的数据丢失一些也没什么影响的话,可以选择使用 RDB。
● 不建议单独使用 AOF,因为时不时地创建一个 RDB 快照可以进行数据库备份、更快的重启以及解决 AOF 引擎错误。
● 如果保存的数据要求安全性比较高的话,建议同时开启 RDB 和 AOF 持久化或者开启 RDB 和 AOF 混合持久化。
Redis 线程模型(重要)
-
Redis 是单线程,那怎么监听⼤量的客户端连接呢?
Redis 通过 IO 多路复用程序 来监听来自客户端的大量连接(或者说是监听多个 socket),它会将感兴趣的事件及类型(读、写)注册到内核中并监听每个事件是否发生。
这样的好处非常明显:I/O 多路复用技术的使用让 Redis 不需要额外创建多余的线程来监听客户端的大量连接,降低了资源的消耗(和 NIO 中的 Selector 组件很像)。
文件事件处理器(file event handler)主要是包含 4 个部分:
● 多个 socket(客户端连接)
● IO 多路复用程序(支持多个客户端连接的关键)
● 文件事件分派器(将 socket 关联到相应的事件处理器)
● 事件处理器(连接应答处理器、命令请求处理器、命令回复处理器) -
Redis6.0 之前为什么不使⽤多线程?
● 单线程编程容易并且更容易维护;
● Redis 的性能瓶颈不在 CPU ,主要在内存和网络;
● 多线程就会存在死锁、线程上下文切换等问题,甚至会影响性能。 -
Redis6.0 之后为何引⼊了多线程?
Redis6.0 引入多线程主要是为了提高网络 IO 读写性能
Redis 内存管理
4. Redis 给缓存数据设置过期时间有啥⽤? Redis 是如何判断数据是否过期的呢?过期数据是如何被删除的?
如果每个缓存数据都没有设置过期时间,那redis不是分分钟钟内存溢出。
Redis通过一个叫做过期字典(可以看作是hash表)来保存数据过期的时间。
Redis采用的是定期删除+惰性删除方式。
5. Redis 内存满了怎么办?
Redis中有内存淘汰机制
6. Redis 内存淘汰算法除了 LRU 还有哪些?
Redis 提供 6 种数据淘汰策略:
- volatile-lru(least recently used):从已设置过期时间的数据集(server.db[i].expires)中挑选最近最少使用的数据淘汰。
- volatile-ttl:从已设置过期时间的数据集(server.db[i].expires)中挑选将要过期的数据淘汰。
- volatile-random:从已设置过期时间的数据集(server.db[i].expires)中任意选择数据淘汰。
- allkeys-lru(least recently used):当内存不足以容纳新写入数据时,在键空间中,移除最近最少使用的 key(这个是最常用的)。
- allkeys-random:从数据集(server.db[i].dict)中任意选择数据淘汰。
- no-eviction:禁止驱逐数据,也就是说当内存不足以容纳新写入数据时,新写入操作会报错。这个应该没人使用吧!
4.0 版本后增加以下两种: - volatile-lfu(least frequently used):从已设置过期时间的数据集(server.db[i].expires)中挑选最不经常使用的数据淘汰。
- allkeys-lfu(least frequently used):当内存不足以容纳新写入数据时,在键空间中,移除最不经常使用的 key。
Redis 事务
Redis 事务实际开发中使用的非常少,功能比较鸡肋,因此,Redis 事务是不建议在日常开发中使用的。
- Redis 事务⽀持原⼦性吗?
- Redis 事务⽀持持久性吗?
- Redis 事务有什么缺陷?如何解决?
Redis 性能优化(重要)
- Redis 批量操作的⽅式有哪些?
原生命令自带、pipeline 、lua脚本 - Redis ⼤ key 有什么危害?如何排查和处理?
bigkey 除了会消耗更多的内存空间和带宽,还会对性能造成比较大的影响。
主要体现在下面三个方面: - 客户端超时阻塞:由于 Redis 执行命令是单线程处理,然后在操作大 key 时会比较耗时,那么就会阻塞 Redis,从客户端这一视角看,就是很久很久都没有响应。
- 网络阻塞:每次获取大 key 产生的网络流量较大,如果一个 key 的大小是 1 MB,每秒访问量为 1000,那么每秒会产生 1000MB 的流量,这对于普通千兆网卡的服务器来说是灾难性的。
- 工作线程阻塞:如果使用 del 删除大 key 时,会阻塞工作线程,这样就没办法处理后续的命令。
大 key 造成的阻塞问题还会进一步影响到主从同步和集群扩容。
如何进行发现bigkey
1、使用Redis自带的 --bigkeys 参数来查找
2、使用 Redis 自带的 SCAN 命令
3、借助开源工具分析 RDB 文件。
4、借助公有云的 Redis 分析服务。
出来bigkey
● 分割 bigkey:将一个 bigkey 分割为多个小 key。例如,将一个含有上万字段数量的 Hash 按照一定策略(比如二次哈希)拆分为多个 Hash。
● 手动清理:Redis 4.0+ 可以使用 UNLINK 命令来异步删除一个或多个指定的 key。Redis 4.0 以下可以考虑使用 SCAN 命令结合 DEL 命令来分批次删除。
● 采用合适的数据结构:例如,文件二进制数据不使用 String 保存、使用 HyperLogLog 统计页面 UV、Bitmap 保存状态信息(0/1)。
● 开启 lazy-free(惰性删除/延迟释放) :lazy-free 特性是 Redis 4.0 开始引入的,指的是让 Redis 采用异步方式延迟释放 key 使用的内存,将该操作交给单独的子线程处理,避免阻塞主线程。 - 如何发现 Redis 热 Key,有哪些解决⽅案?
- 为什么会有 Redis 内存碎⽚?如何清理 Redis 内存碎⽚?
- 为什么会有慢查询命令?为什么会有慢查询命令?
Redis ⽣产问题(重要)
- 什么情况会出现缓存穿透?有哪些解决办法?
大量请求一个不存在的数据。
缓存空值
布隆过滤器
设置黑名单
2. 什么情况会出现缓存击穿?有哪些解决办法?
请求的数据是热点key,但是不存在缓存,存在数据库,大量的请求发送到数据库中。
提前预热
设置热点key过期时间永不过期或者比较长
加锁,第一次只允许一个请求进行
3. 什么情况会出现缓存雪崩?有哪些解决办法?
缓存中大量的key失效,导致大量的请求落在了数据库。
针对 Redis 服务不可用的情况:
- 采用 Redis 集群,避免单机出现问题整个缓存服务都没办法使用。
- 限流,避免同时处理大量的请求。
- 多级缓存,例如本地缓存+Redis 缓存的组合,当 Redis 缓存出现问题时,还可以从本地缓存中获取到部分数据。
针对热点缓存失效的情况: - 设置不同的失效时间比如随机设置缓存的失效时间。
- 缓存永不失效(不太推荐,实用性太差)。
- 缓存预热,也就是在程序启动后或运行过程中,主动将热点数据加载到缓存中。
- 缓存穿透和缓存击穿有什么区别?缓存雪崩和缓存击穿有什么区别?
- 缓存预热如何实现?
常见的缓存预热方式有两种: - 使用定时任务,比如 xxl-job,来定时触发缓存预热的逻辑,将数据库中的热点数据查询出来并存入缓存中。
- 使用消息队列,比如 Kafka,来异步地进行缓存预热,将数据库中的热点数据的主键或者 ID 发送到消息队列中,然后由缓存服务消费消息队列中的数据,根据主键或者 ID 查询数据库并更新缓存。
Redis 集群
6. 什么是 Sentinel? 有什么⽤?
7. Sentinel 如何检测节点是否下线?主观下线与客观下线的区别?
8. Sentinel 是如何实现故障转移的?
9. Sentinel 如何选择出新的 master(选举机制)?
10. 如何从 Sentinel 集群中选择出 Leader ?
11. Sentinel 可以防⽌脑裂吗?
12. 为什么需要 Redis Cluster?解决了什么问题?有什么优势?
13. Redis Cluster 是如何分⽚的?
14. 为什么 Redis Cluster 的哈希槽是 16384 个?
15. 如何确定给定 key 的应该分布到哪个哈希槽中?
16. Redis Cluster ⽀持重新分配哈希槽吗?
17. Redis Cluster 扩容缩容期间可以提供服务吗?
18. Redis Cluster 中的节点是怎么进⾏通信的?