Redis 简介
Redis是一种高性能的、开源的、基于内存的数据结构存储系统。它支持多种数据结构,包括字符串、哈希表、列表、集合、有序集合等,而且由于其快速的读写速度,通常被用来做缓存、会话管理、消息队列等应用程序。以下是关于Redis的一些知识点和面试点:
1. Redis的基本数据结构:了解Redis的基本数据结构,包括字符串、哈希表、列表、集合、有序集合等。
2. Redis的持久化:了解Redis的持久化方式,包括RDB和AOF两种方式,以及它们的优缺点。
3. Redis的主从复制:了解Redis的主从复制机制,包括主节点、从节点、数据同步等等。
4. Redis的高可用性:了解如何架构Redis来保证高可用性(HA),例如哨兵模式、集群模式等。
5. Redis的性能优化:了解如何通过一些方式来优化Redis的性能,例如使用pipeline、批处理操作、预分配内存等。
6. Redis的线程安全:了解Redis是如何进行线程安全的,并且知道它的原理和实现。
7. Redis的应用场景:了解Redis的常见应用场景,例如缓存、会话管理、消息队列、计数器等。
在面试中,可能会被问到如何通过Redis实现一个特定的应用场景,例如缓存或实时数据处理等。此外,面试官还可能会问到Redis的性能、扩展性、线程安全、主从复制等方面的问题。因此针对这些问题,需要对Redis有深入的了解和掌握。
一、简单的介绍一下Redis
简单的说Redis
就是一个使用C语言
开发的一个数据库,不过与传统数据库不同的是Redis
的数据是存在内存中的,它是内存数据库,所以读写速度非常快,所以Redis
被广泛应用于缓存方向。
另外,Redis
除了做缓存之外,也还经常用于做分布式锁,甚至是消息队列。
Redis
提供了多种数据类型来支持不同的业务场景。Redis
还支持事务、持久化、Lua脚本、多种集群方案。
二、Redis持久化机制
Redis
是一个支持持久化的内存数据库,通过持久化机制把内存中的数据同步到硬盘文件来保证数据持久化。当Redis
重启后通过把硬盘文件重新加载到内存,就能达到恢复数据的目的。
实现:单独创建一个fork()
子进程,将当前父线程的数据库文件复制到子进程的内存中,然后由子进程写入到临时文件中,持久化的过程就结束了,再用这个临时文件替换上次的快照文件,然后子进程退出,内存释放。
RDB(Redis DataBase)
是Redis
默认的持久化方式。按照一定的时间周期策略把内存的数据以快照的形式保存到硬盘的二进制文件。即Snapshot
快照存储,对应产生的数据文件为dump.rdb
,通过配置文件中的save
参数来定义快照的周期。
AOF(Append Only Field)
方式:Redis
会将每一个写命令都通过write
函数追加到文件最后,类似MySQL的binlog
,当重启Redis
会加载appendonly.aof
文件恢复数据。
三、缓存雪崩、缓存穿透、缓存预热、缓存更新、缓存降级问题
缓存雪崩
缓存雪崩可以理解为:由于原有缓存失效,新缓存未到期间,而形成一些列连锁反应,而造成整个系统崩溃。
举个栗子:
我们设置缓存采用了相同的过期时间,在同一时刻出现大面积的缓存过期,然后所有的请求都去访问数据库了,对数据库CPU
和存在造成了巨大压力,严重的时候造成数据库宕机。
解决方法:
大多数系统设计者考虑用加锁或者队列的方式保证不会有大量的线程对数据库一次性进行读写,从而避免失效时大量的并发请求落到底层存储系统上。还有一个简单方案就时讲缓存失效时间分散开。
缓存穿透
缓存穿透是指用户查询数据库,在数据库没有,自然在缓存中也不会有,这样就导致用户在查询的时候,在缓存中找不到,每次都要去数据库再查询一遍,然后返回空,相当于两次无用的查询,这样请求就直接绕过缓存直接查询数据库,这也是经常说的缓存命中问题。
解决方法:
最常用的就是采用布隆过滤器,将所有可能存在的数据哈希到一个足够大的bitmap
中,不存的数据会被这个bitmap
拦截掉,从而避免了对底层存储系统的查询压力。
另外一个更为简单粗暴的方法,如果一个查询返回的数据为空(不管是数据不存在,还是系统故障),我们仍然把这个空结果进行缓存,但它的过期时间会很短,不会超过五分钟,通过这个直接设置的默认值存放到缓存,这样子二次到缓存中获取就有值了。
缓存预热
缓存预热就是系统上线后,将相关的缓存数据直接加载到缓存系统。这样子就可以避免在用户请求的时候,先查询数据库,然后再将数据缓存的问题!用户直接查询事先被预热的缓存数据!
解决思路:
- 直接写个缓存页面,上线时手工操作下。
- 数据量不大,可以在项目启动时自动进行加载。
- 定时刷新缓存。
缓存更新
除了缓存服务器自带的缓存失效策略之外(Redis默认的有6中策略可供选择),我们还可以根据具体的
业务需求进行自定义的缓存淘汰,常见的策略有两种:
(1)定时去清理过期的缓存;
(2)当有用户请求过来时,再判断这个请求所用到的缓存是否过期,过期的话就去底层系统得到新数
据并更新缓存。
两者各有优劣,第一种的缺点是维护大量缓存的key是比较麻烦的,第二种的缺点就是每次用户请求过
来都要判断缓存失效,逻辑相对比较复杂!具体用哪种方案,可以根据自己的应用场景来权衡。
缓存降级
当访问量剧增、服务出现问题或非核心服务影响到核心流程的性能时仍然需要保证服务还是可用的,即使是有损服务。系统可以根据一些关键数据进行自动降级,也可以配置开关实现人工降级。
降级的最终目的是保证核心服务可用,即使是有损的。而且有些服务是无法降级的(如加入购物车、结
算)。
以参考日志级别设置预案:
(1)一般:比如有些服务偶尔因为网络抖动或者服务正在上线而超时,可以自动降级;
(2)警告:有些服务在一段时间内成功率有波动(如在95~100%之间),可以自动降级或人工降级,
并发送告警;
(3)错误:比如可用率低于90%,或者数据库连接池被打爆了,或者访问量突然猛增到系统能承受的
最大阀值,此时可以根据情况自动降级或者人工降级;
(4)严重错误:比如因为特殊原因数据错误了,此时需要紧急人工降级。
服务降级的目的是为了防止Redis
服务故障,导致数据库跟着一起发生雪崩问题。因此,对于不重要的缓存数据,可以采取服务降级策略,例如一个比较常见的做法就是,Redis出现问题,不去数据库查
询,而是直接返回默认值给用户。
四、单线程的Redis为什么这么快
- 纯内存操作。
- 单线程操作,避免了频繁的上下文切换。
- 采用非阻塞I/O多路复用机制。
五、Redis的数据类型,以及每种数据类型的使用场景
string
string
数据结构是简单的key-value
类型,value
可以是字符串也可以是数字。
一般用于做一下复杂的计数功能,比如用户的访问次数、点赞功能等等。
list
list
即是链表,实现了一个双向链表,可以支持反向查找、遍历,更方便操作,但是带来了额外的内存开销。
常用于发布和订阅或者消息队列、慢查询。
hash
hash
类似JDK1.8
前的HashMap
,内存实现也差不过是数组+链表。特别适合用于储存对象,后续操作的时候,你可以直接仅仅修改这个对象中的某个字段的值。
常用于系统中对象数据的存储。
set
set
类似于Java
中的HashSet
,是一个无序且不重复的集合。可以基于 set 轻易实现交集、并集、差集的操作。
常用于需要存放的数据不能重复以及需要获取多个数据源交集和并集等场景。
sorted set
和set
相比,增加了一个权重参数score
,是的集合中的元素能够按照score
进行有序排列,还可以通过 score 的范围来获取元素的列表。
常用于需要对数据根据某个权重进行排序的场景。比如:各种排行榜、TOP N
。
bitmap
bitmap
存储的是连续的二进制数字(0和1),通过bitmap
,只需一个bit
位来表示某个元素对应的值或者状态,key
就是对应元素本身。
常用于需要保存状态信息(比如是否签到、是否登陆)并需要进一步对这些信息进行分析的场景。比如用户签到情况、活跃用户情况、用户行为统计。
六、Redis的过期策略以及内存淘汰机制
redis
采用的是定期删除+惰性删除策略。
为什么不用定时删除策略?
定时删除用一个定时器来负责监视key
,过期则自动删除。虽然内存及时释放,但是十分消耗CPU
资源。
在大量并发请求下,CPU
要将时间用在处理请求,而不是用来删除key
。
定期删除+惰性删除是怎么工作的呢?
定期删除,redis
默认每隔100ms
检查是否有过期的key
,有的话就删除。需要说明是redis
不是每隔100ms
将所有的key
检查一次,而是随机抽取进行检查,因此如果只采用定期删除策略会导致很多过期的key
没有删除,这个时候惰性删除就派上用场了,获取某个key
的时候,redis
会检查一下这个key
是否过期了,如果过期了就删除。
采用定期删除+惰性删除就没其他问题了吗?
不是的,如果定期删除没删除的key
,也没有请求去获取,这个时候惰性删除就不会生效,redis的内存会越来越高,这个时候就要用到内存淘汰机制了。