文章目录
- Redis是什么?
- Redis一般有哪些使用场景?
- Redis的优缺点?
- Redis支持的数据类型有哪些?
- Redis为什么这么快?
- 什么是缓存穿透?怎么解决?
- 什么是缓存雪崩,该如何解决?
- 怎么保证缓存和数据库数据的一致性?
- Redis持久化有几种方式?
- Redis如何实现分布式锁?
- Redis内存淘汰策略?
- Redis的过期键的删除策略?
- 哈希冲突怎么办?
- Redis哨兵机制?
- Redis主从架构?
- Redis集群?
- Redis集群如何实现故障转移?
- Redis如何做内存优化?
- Redis线程模型?
- Redis事务?
Redis是什么?
Redis(Remote Dictionary Server,远程字典服务)是一个使用C语言编写的开源的高性能的key-value非关系缓存数据库,它支持存储的value类型相对更多,包括string、list、set、zset和hash。Redis的数据都基于缓存,所以很快,每秒可以处理超过10万次读写操作。Redis也可以实现数据写入磁盘中,保证了数据的安全不丢失。
Redis一般有哪些使用场景?
- 缓存:将热点数据放到内存中,减轻MySQL的查询压力,提升系统性能。
- 排行榜:利用Redis的有序集合实现。
- 计算器/限速器:利用Redis中原子性的自增操作,我们可以统计类似用户点赞数、用户访问数等,这类操作如果用MySQL,频繁的读写会带来相当大的压力;限速器比较经典的使用场景是限制某个用户访问某个API的频率,常用的场景有抢购时,防止用户疯狂点击带来不必要的压力。
- 好友关系:利用集合的一些命令,比如求交集、并集、差集等,可以方便地解决共同好友、共同爱好之类的功能。
- 消息队列:除了Redis自身的发布/订阅模式,也可以利用List实现队列来读取和写入消息。
- 共享Session:Session是保存在服务器端的,如果只有一个服务器,则没有问题,但是在多个服务器的情况下,如果用户在A服务器登陆成功,下次请求又被转发到了B服务器,但是B服务器没有用户信息,用户就得重新登陆,所以我们需要将session信息放在一个公共的地方,不管后续请求到了哪个服务器,我们都从公共的位置去取,可以采用Redis保存Session。
数据量太大、数据访问频率非常低的业务都不适合使用Redis,数据太大会增加成本,访问频率太低会浪费资源。
Redis的优缺点?
优点:
- 读写性能优异;
- 支持数据持久化;
- 支持的数据结构丰富;
- 支持事务:Redis中所有操作都是原子性的,要么成功执行,要么不执行;
- 支持主从复制,主机可以自动将数据同步到从机,进行读写分离。
缺点:
- Redis将数据存在内存当中,所以会受到内存大小的限制,不能用作海量数据的读写;
- Redis不具备自动容错和恢复功能,主机或从机宕机会导致前端部分读写请求失败,需要重启机器或者手动切换前端IP才能切换。
Redis支持的数据类型有哪些?
- 字符串string:可以存储字符串、整数、浮点数,使用场景包括:缓存、计数器、共享Session、限速。
- 列表list:存储列表,可以像列表两端添加元素,使用场景包括:消息队列、存储文章或评论列表。
- 集合set:存储集合,可以增加、删除元素、取交集并集等,使用场景包括共同好友、共同兴趣。
- 有序集合zset:它保留了集合不能有重复成员的特性,但是集合中的元素是可以排序的,排行榜是有序集合经典的使用场景。
- 哈希hash:包含键值对的无序散列表,常常用于用户信息管理。
Redis为什么这么快?
- 完全基于内存,绝大部分请求是纯粹的内存操作,非常快速;
- 数据结构简单,对数据的操作也简单;
- 采用单线程,避免了不必要的上下文切换和竞争条件,也不存在多进程或多线程导致的切换而消耗CPU,不用去考虑各种锁的问题,没有因为可能出现死锁而导致的性能消耗问题;
- 使用多路I/O复用模型,非阻塞IO。
什么是缓存穿透?怎么解决?
缓存穿透指的是查询一个一定不存在的数据,由于缓存是不命中时需要从数据库查询,查不到数据则不写入缓存,这将导致这个不存在的数据每次请求都要到数据库取查询(查询请求穿透到数据库),这就是缓存穿透。
避免缓存穿透的方法:
- 如果是非法请求,我们在API入口,对参数进行校验,过滤非法值。
- 如果查询数据为空,我们可以给缓存设置个空值或者默认值,但如果有写请求进来的话,需要更新缓存以保证缓存一致性,同时给缓存设置适当的过期时间。
- 使用布隆过滤器快速判断数据是否存在,从而避免了对底层存储系统的查询压力。
布隆过滤器:由初始值为0的位图数组和N个哈希函数组成,对一个key进行N个哈希算法获取N个值,在位图数组中将N个值散列后设定为1,然后查的时候如果特定的这几个位置都为1,那么布隆过滤器判断该key存在。
什么是缓存雪崩,该如何解决?
缓存雪崩是指缓存中的数据大批量到过期时间,而查询数据量巨大,请求都直接访问数据库,引起数据库压力过大甚至down机。
解决方法:
- 均匀的设置过期时间,即让过期时间相对离散一点;
- 加锁排队,在缓存失效后,通过加锁或者队列来空值读数据库写缓存的线程数量,比如某个key只允许一个线程查询数据和写缓存,其他线程等待;
- 做二级缓存,缓存1失效时可以访问缓存2,将缓存1失效时间设置为短期,缓存2失效时间设置为长期。
怎么保证缓存和数据库数据的一致性?
一般来说,只要设置了合理的键过期时间,就能够保证缓存和数据库的数据最终是一致的,因为只要缓存数据过期,就会被删除。随后读的时候,因为缓存里没有,就会查询数据库,然后将查出来的数据写入到缓存中。
除了设置过期时间,我们还需要做更多的措施来尽量避免数据库与缓存处于不一致的情况发生。
新增、更改、删除数据库操作同时更新Redis,可以使用事务机制来保证数据的一致性。
Redis持久化有几种方式?
持久化就是把内存的数据写到磁盘中去,防止服务宕机了内存数据丢失
。
Redis提供两种持久化机制RDB(默认)和AOF机制:
- RDB是Redis默认的持久化方式,按照一定的时间将内存的数据以快照的形式保存到硬盘中,对应产生的数据文件为dump.rdb。通过配置文件中的save参数来定义快照的周期。
- AOF(Append-only File)持久化则是将Redis执行的每次写命令都通过write函数追加到到单独的日志文件中,当重启Redis时会通过重新执行日志文件的写命令来恢复数据。当两种方式同时开启时,数据恢复Redis会优先选择AOF恢复。
两种优化方式的优缺点:
- AOF文件比RDB更新频率高,优先使用AOF还原数据。
- AOF比RDB更安全也更大。
- RDB性能比AOF好。
- 如果两个都配了则优先加载AOF。
Redis如何实现分布式锁?
分布式锁就是控制分布式系统中不同进程共同访问同一共享资源的一种锁的实现。分布式锁需要我们在分布式应用的外面使用第三方组件进行全局锁的监控,由这个组件决定什么时候加锁,什么时候释放锁。
- Redis为单线程模式,采用队列模式将并发访问变成串行访问,且多客户端对Redis的连接并不存在竞争关系。
- Redis可以使用SETNX命令实现分布式锁,SETNX是SET IF NOT EXISTS的简写,它的意思是:如果key不存在,那么我们进行set操作,否则不做任何动作。如果SETNX命令返回的结果为1,则表示获得锁,可以继续执行业务逻辑;如果结果是0,则表示获取锁失败。使用完成后,调用delete命令释放锁。
但是如果程序在发送delete命令之前宕机了,那么其他客户端则永远无法获取到这把锁,所以我们可以在此基础上,同时给key设置一个过期时间。
Redis内存淘汰策略?
Redis的内存淘汰策略是指Redis在用于缓存的内存空间不足时,怎么处理需要新写入且需要申请额外空间的数据。
全局的key选择性移除:
- noeviction:当内存不足以容纳写入数据时,新写入操作会报错。
- allkeys-lru:当内存不足以容纳新写入数据时,在键空间中,移除最少使用的key。
- allkeys-random:当内存不足以容纳新写入数据时,在键空间中,随机移除某个key。
设置过期时间的key选择性移除:
- volatile-lru:当内存不足以容纳写入新数据时,在设置了过期时间的键空间中,移除最近最少使用的key。
- volatile-random:当内存不足以容纳写入新数据时,在设置了过期时间的键空间中,随机移除某个key。
- volatile-ttl:当内存不足以容纳写入新数据时,在设置了过期时间的键空间中,有更早过期时间的key优先移除。
Redis的过期键的删除策略?
过期策略通常有以下三种:
- 立即过期:每个设置过期时间的key都需要创建一个定时器,到过期时间就会立即清除。该策略可以立即清除过期的数据,对内存很友好;但是会占用大量CPU资源去处理过期的数据,从而影响缓存的响应时间和吞吐量。
- 惰性过期:只有当访问一个key时,才会判断该key是否过期,过期则清除。该策略可以最大化地节省CPU资源,却对内存非常不友好。极端情况下可能出现大量的过期key没有再次被访问,占用大量内存。
- 定期过期:每隔一定的时间,会扫描expires字典中一定数量的key,并清除其中已过期的key。该策略是前两者的一个折中方案,通过调整定时扫描的时间间隔和每次扫描的限定耗时,可以在不同情况下使得CPU和内存资源达到最优的平衡效果。
Redis中同时使用了惰性过期和定期过期两种过期策略
。
哈希冲突怎么办?
Redis为了保存所有的键值对关系,默认使用了2个全局哈希表,哈希表其实就是数组,数组中的每个元素称为一个哈希桶,哈希桶中元素保存的并不是值本身,而是指向具体值的指针。
Redis通过链式哈希
解决冲突,也就是同一个桶里的元素使用链表保存。但是当往哈希表插入元素很多时,链表会越长,查询效率就会降低。
为了高效,Redis使用两个全局哈希表,用于rehash操作,增加现有的哈希桶的数量。
开始时默认使用哈希表1来保存键值对,哈希表2并没有分配空间,但是当越来越多的数据触发rehash操作时,则给哈希表2分配更大的空间;然后将哈希表1的数据拷贝到哈希表2中;释放哈希表1的空间。
Redis哨兵机制?
sentinel,哨兵是Redis集群机构中非常重要的一个组件,主要有以下功能:
- 集群监控,负责监控Redis Master和Slave进程是否正常工作;
- 消息通知,如果某个Redis实例有故障,那么哨兵负责发送消息作为报警通知给管理员;
- 故障转移,如果Master节点挂掉,会启动故障恢复流程,选择一个Slave节点作为新的Master节点;
- 配置中心,如果故障转移发生了,通知客户端新的Master节点地址。
Redis主从架构?
- 为了支持读高并发,Rediss架构做成主从架构,一主多从,主负责写,并且将数据复制到其它的从节点,从节点负责读,所有的读请求全部走从节点。
Redis主从复制的核心原理:
- 当启动一个从节点的时候,它会发送一个PSYNC命令给主节点;
- 如果这是从节点初次连接到主节点,那么会触发一次全同步,否则执行部分同步。
Redis集群?
Redis集群是一种分布式数据库方案,集群通过分片来进行数据管理,并提供复制和故障转移。
Redis集群是去中心化的,集群中每个节点负责集群的一部分数据
。
Redis集群中内置了16384个哈希槽,当需要在Redis集群中放置一个key-value时,redis先对key使用CRC16算法计算出一个结果,然后把结果对16384求余数,这样每个key都会对应一个编号在0-16384之间的哈希槽,Redis会根据节点数量大致均等将哈希槽映射到不同的节点。
当客户端向节点发送命令时,接受命令的节点会检查命令要处理的数据库键属于哪个槽,若这个槽在当前节点,则当前节点执行命令,否则节点向客户端返回一个MOVED错误,客户端根据MOVED错误提供的信息转向至正确的节点。
Redis集群如何实现故障转移?
Redis集群节点采用Gossip协议来广播自己的状态以及自己对整个集群认知的改变。
比如一个节点发现某个节点失联了,它会将这条信息向整个集群广播,其他节点也就可以收到这条失联信息。
如果一个节点收到了某个节点失联的数量已经达到了集群的大多数,就可以标记该节点为确定下线状态,然后向整个集群广播,强迫其他节点接收该节点下线的事实,并立即对该节点进行主从切换。
Redis如何做内存优化?
- 可以好好利用hash、list、set和sorted set等集合类型数据,因为通常情况下很多小的key-value可以用更紧凑的方式存放到一起,散列表使用的内存非常小,所以应该尽可能的将你的数据模型抽象到一个散列表里面。
- 比如Web系统中有一个用户对象,不要为这个用户的名称、邮箱、密码设置单独的key,而是应该把这个用户的所有信息存储到一张散列表里 。
Redis线程模型?
Redis基于Reactor模式开发了网络事件处理器,这个处理器被称为文件事件处理器(file event handler),它的组成结构分为4部分:多个套接字、IO多路复用程序、文件事件分派器、事件处理器。因为文件事件分派器队列的消费是单线程的,所以Redis才叫单线程模型
。
- IO多路复用来同时监听多个套接字,并根据套接字目前执行的任务来为套接字关联不同的事件处理器。
- 当被监听的套接字准备好执行应答、读取、写入、关闭等操作时,与操作相对应的文件事件就会产生,这时文件分派器就会调用套接字之前关联好的事件处理器来处理这些事件。
虽然文件事件处理器以单线程方式运行,但通过使用IO多路复用程序来监听多个套接字,文件事件处理器既实现了高性能的网络通信模型,又可以很好地与Redis服务器中其他同样以单线程方式运行的模块进行对接,这保持了Redis内部单线程设计的简单性。
Redis事务?
Redis事务是一个单独的隔离操作,事务中的所有命令都会序列化、按顺序执行。事务在执行的过程中不会被其他客户端发送来的命令请求所打断。所以Redis事务是在一个队列中,一次性、顺序性、排他性地执行一系列命令。
Redis事务具有如下特性:
- Redis事务不保证原子性,单条命令是原子性的,但事务不保证原子性;
- Redis事务是有隔离性的,事务在执行过程中,不会被其他客户端发送来的命令打断;(顺序性、排他性)
- Redis事务不支持回滚,执行过程中的任意命令执行失败,其他命令仍然可以执行。(一次性)
参考
Redis高频面试题
如有侵权,请联系作者删除。