Redis是一种基于内存的数据库,所有对数据的读写操作都是在内存中完成的,因此读写速度非常快,常用于缓存,消息队列,分布式锁等场景。
- 缓存: 最常用的场景是作为缓存层,可以减少数据库的负载,提升数据的读写速度。
- 实时系统: Redis支持快速的数据读写,适用于实时分析。
- 消息队列: 利用Redis的List和Pub/Sub功能,可以实现轻量级的消息队列,适用于任务处理和异步消息传递。
- 分布式锁: Redis可以作为分布式锁实现,确保分布式系统资源的安全访问,避免竞态条件。
- 计数器: 由于Redis的原子操作非常适合用作计数器。通过INCR命令可以实现高效计数。
1. Redis为什么这么快?(为什么用 Redis作为MySQL的缓存?)
主要有三个方面原因,分别是存储方式、优秀的线程模型以及IO模型、高效的数据结构:
- Redis将数据存储在内存中,提供快速的读写速度,相比传统的磁盘数据库,内存访问速度快得多。
- Redis使用单线程事件驱动模型结合I/O多路复用,避免了多线程上下文切换和竞争条件,提高了并发效率。
- Redis提供多种高效的数据结构,这些结构经过优化,能够快速完成各种操作。
2. Redis为什么使用跳表而不使用B+树?
主要从内存占用、对范围查找的支持、实现难易程度这三方面总结:
- 从内存占用比较,跳表比B+树更加灵活。
- 从范围查找来比较,跳表比B+树操作更加简单。
- 从算法实现难度来说,跳表比B+树更简单。
3. Redis是单线程的吗?
Redis是单线程的。 从接收客户端请求、解析请求、进行数据读写操作、发送数据给客户端整个过程是由一个主线程来完成的。
但是,Redis程序并不是单线程的,在Redis启动的时候是会启动后台线程(BIO) 的:
Redis2.6版本后,会增加2个后台线程,分别来处理关闭文件、AOF刷盘这两个任务。
Redis4.0版本后,增加了1个后台线程,用来异步释放Redis内存,也就是lavzyfree线程。
后台线程相当于一个消费者,生产者把耗时任务丢到任务队列中,消费者(BIO)不停地轮询这个队列,拿出任务去执行对应的方法即可。
4. Redis如何是数据不丢失?
Redis有三种数据持久化的方式:
- AOF日志: 每执行一条写操作命令,就把该命令以追加的方式写入一个文件里。
- RDB快照: 将某一时刻的内存数据,以二进制的方式写入磁盘。
- 混合持久化方式: Redis4.0新增的方式,集合了AOF和RBD的优点。
27. Redis的内存淘汰策略有哪些?
Redis的内存淘汰策略共有八种,这八种策略大体分为不进行数据淘汰和进行数据淘汰两类策略。
- 不进行数据淘汰策略:
noeviction: 它表示当运行内存超过最大设置内存时,不淘汰任何数据,而是不再提供服务,直接返回错误。(默认)
- 进行数据淘汰策略:
这一次层级又分为设置了过期时间的数据中进行淘汰和所有数据范围内进行淘汰。
设置了过期时间的数据中进行淘汰:
-
- volatile-random: 随机淘汰设置了过期时间的任意键值。
- volatile-ttl: 优先淘汰更早过期的键值。
- volatile-lru: 淘汰设置了过期时间的键值中,最久未使用的键值。
- volatile-lfu: 淘汰设置了过期时间的键值中,最少未使用的键值。
所有数据范围内进行淘汰:
-
- allkeys-random: 随机淘汰任意键值。
- allkeys-lru: 淘汰整个键值中最久未使用的键值。
- allkeys-lfu: 淘汰整个键值中最久未使用的键值。
5. 如何避免缓存雪崩、缓存击穿、缓存穿透?
如何避免缓存雪崩?
大量缓存数据在同一时间失效时,此时如果有大量的用户请求,都无法在Redis中处理,于是请求都直接访问数据库,从而导致数据库的压力骤增,严重的会造成数据库宕机,从而形成一系列连锁反应,造成系统的崩溃,这就是缓存雪崩。
对于缓存雪崩问题,可以采用两种方案解决:
- 将缓存失效时间随机打散: 可以在原有的失效时间基础上增加一个随机值,这样每个缓存的过期时间都不重复了,也就降低了缓存集体失效造成的缓存雪崩,也可以在一定程度上避免缓存并发问题。
- 设置缓存不过期: 通过后台服务来更新缓存数据,从而避免缓存失效造成的缓存雪崩,也可以一定程度上避免缓存并发问题。
业务中通常有几个数据会被频繁的访问,比如秒杀活动,这类数据被称为热点数据。
如何避免缓存击穿?
如果缓存中某个热点数据过期了,此时大量请求访问了该热点数据,就无法从缓存中读取,直接访问数据库,数据库很容易被高并发请求冲垮,这就是缓存击穿问题。
这类问题和根缓存雪崩类似,可以认为是缓存雪崩的一个子集。应对缓存击穿可以采取前面说的两种方案:
- 互斥锁方案(Redis中使用setNX方法设置一个状态位,表示这是一种锁定状态),保证同一时间只有一个业务线程请求缓存,未能获取互斥锁的请求,要么等待锁释放后重新读取缓存,要么返回控制或者默认值。
- 不给热点数据设置过期时间,由后台异步更新缓存,或者在热点数据准备要过期前,提前通知后台线程更新缓存以及重新设置过期时间。
如何避免缓存穿透?
当用户访问的数据既不在缓存中,也不在数据库中,导致请求在访问缓存时,发现缓存缺失,再去访问数据库时,发现数据库也没有要访问的数据没办法构建缓存数据,来服务后续的请求。那么当有大量这样的请求到来时,数据库的压力骤增,这就是缓存穿透。
缓存穿透一般发生在以下两种场景:
- 业务误操作,缓存数据和数据库中的数据被误删除了,所以导致缓存和数据库中都没有数据。
- 黑客恶意攻击,故意大量访问某些读取不存在数据的业务。
应对缓存穿透方案,常见的方案有三种:
- 非法请求的限制: 可以在API的入口处判断请求参数是否合理,请求参数是否含有非法值、请求字段是否存在,如果判断出是恶意请求就直接返回错误,避免进一步访问缓存和数据库。
- 设置空值或默认值: 当检测到缓存穿透现象时,可以针对查询的数据在缓存中设置一个空值或默认值,这样后续的请求就可以从缓存中读取到空值或者默认值,返回给应用,而不是继续查询数据库。
- 使用布隆过滤器快速判断数据是否存在,避免直接查询数据库来判断数据是否存在: 可以在数据库写入数据时,使用布隆过滤器做个标记,然后在用户请求时,业务确认缓存失效后,可以通过布隆过滤器来判断数据是否存在,如果不存在,就不用通过查询数据库来判断数据是否存在。