说说你对Redis的理解
Redis是一个基于Key-Value存储结构的开源内存数据库,也是一种NoSQL数据库。
它支持多种数据类型,包括String、Map、Set、ZSet和List,以满足不同应用场景的需求。
Redis以内存存储和优化的数据结构为基础,提供了快速的读写性能和高效的数据访问。常被用作应用与数据库之间的缓存组件,提升数据IO效率。
此外,Redis支持主从复制、哨兵机制和集群方式,实现高可用性和水平扩展。
总而言之,Redis是一款功能强大、灵活且可靠的数据库解决方案,适用于各种企业级应用开发场景。
你是如何解决热Key问题的
热 Key 问题是指在缓存系统中,某些特定的缓存key受到高频访问,导致对这些热门数据的读取/写入操作集中在少数几个缓存节点上,使得这些节点的负载过高,而其他节点负载较轻甚至空闲。这会造成系统性能不均衡,可能导致部分请求响应变慢或服务不可用。
解决热 Key 问题有这些方案:
- 缓存预热:在系统启动或业务低峰期,通过批量加载或预先访问热门数据,将这些热门数据提前加载到缓存中。这样可以避免大量请求同时涌入导致的热点问题,提高系统的稳定性和性能。
- 动态散列:将缓存节点组织成一个哈希环,根据缓存键的哈希值将数据分散存储在多个节点上。通过增加缓存节点的数量,让请求更均匀地分布在各个节点上,减轻热 Key 对单个节点的压力。当节点数量发生变化时,可以通过一致性哈希算法进行平滑迁移,避免数据大规模迁移带来的负载过高。
- 数据分片:将数据按特定规则(如数据范围、业务维度等)分成多个片段,分别存储在不同的缓存节点上。这样可以使热 Key 所在的数据尽量均匀地分布在多个节点上,减轻单个节点的压力。
Redis为什么这么快
- 内存存储:Redis将数据存储在内存中,实现了快速的读写操作。
- 单线程模型:Redis采用单线程处理请求,避免了多线程的竞争和上下文切换开销。
- 高效的数据结构:Redis内部使用了高效的数据结构,如哈希表、跳跃表等,提供了快速的数据访问和操作。
- 异步IO:Redis利用异步IO来处理网络请求,能够同时处理多个请求,提高并发性能。
- 事件驱动架构:Redis基于事件驱动的模型,通过事件循环机制处理请求和操作,提高系统的效率。
- 优化的操作:Redis对常用操作进行了优化,如批量操作和管道技术,减少了网络通信开销。
Redis为什么把所有数据都放内存
Redis将所有数据放到内存中的主要原因是为了提供高性能的读写操作。
- 高速读写:内存访问速度快,相比于磁盘和数据库,内存操作速度更快,能够更迅速地响应读写请求。将数据存储在内存中可以大大缩短读写的延迟,提高系统的响应速度和吞吐量。
- 简单数据结构:Redis使用简单的数据结构来存储数据,如字符串、列表、哈希、集合和有序集合等。这些数据结构直接映射到内存,不需要进行复杂的数据转换和序列化操作,提高了读写效率。
- 数据持久化:尽管Redis将数据存储在内存中,但它也支持数据的持久化。通过使用RDB快照和AOF日志两种方式,Redis可以将内存中的数据定期或实时写入磁盘,以保证数据的持久性和安全性。
怎么实现Redis的高可用
- 主从复制:通过设置主从复制,将主节点的数据同步到多个从节点上。主节点负责处理写操作,并将写操作的日志复制给从节点,从节点则负责处理读请求。如果主节点发生故障,可以将一个从节点升级为新的主节点,从而实现故障转移和高可用。
- 哨兵机制:使用Redis 哨兵来监控主节点和从节点的状态。哨兵是一组独立运行的进程,它会监控Redis实例的健康状态,并在主节点出现故障时自动进行故障转移。它还能够监控从节点,并在需要时将其提升为主节点。
- 集群模式:Redis 集群是一种分布式方案,可以将多个Redis节点组成一个逻辑集群,提供数据分片和自动故障恢复。每个节点负责存储和处理部分数据,通过节点间的数据分片和分布式算法保证数据的可用性和负载均衡。当集群中的某个节点出现故障时,集群会自动进行故障转移和恢复。
为什么Redis 单线程模型效率也能那么高
- 非阻塞IO:Redis使用了事件驱动的非阻塞IO机制。它通过事件循环处理来自客户端的请求,在等待数据IO时并不会阻塞主线程,而是继续处理其他请求。这种机制允许Redis以高效地方式处理大量的并发连接。
- 内存操作:Redis主要将数据存储在内存中,并且由于单线程模型的存在,在内存操作的情况下,Redis可以通过简单的指针操作来实现快速读写,而不需要考虑复杂的数据同步和竞争条件。
- 单线程避免的开销:与多线程模型相比,单线程模型避免了线程间的上下文切换、锁竞争和资源管理开销。这使得Redis可以更高效地使用CPU资源,并减少了大量与线程相关的开销。
说说 Redis 常用的数据类型
- String:String是最常用的数据类型,在Redis中以二进制安全的方式存储字符串值。它可以包含任何类型的数据,比如文本、整数或二进制数据。
- Hash:Hash是一个键值对的集合,其中每个键都与一个值相关联。在Redis中,Hash可以用于存储和操作对象,每个键值对相当于对象的字段和值。
- List:List是一个按照插入顺序排序的字符串元素集合。集合中的元素可以重复,可以从列表的两端进行插入和删除操作,可用于实现队列、栈等数据结构。
- Set:Set是一个无序、唯一的字符串集合,不允许重复的成员。可以对集合执行添加、删除和判断成员是否存在等操作,也支持集合间的交集、并集和差集运算。
- Sorted Set:Sorted Set是一个有序的字符串集合,每个成员都关联着一个分数。集合中的成员根据分数的大小进行排序,可以进行范围查询和按分数排名操作。
使用 Redis 有哪些好处
- 缓存功能:作为高性能缓存系统,Redis能够将热门数据存储在内存中,提升数据访问速度和减轻数据库负载。
- 高性能:Redis采用内存存储和高效数据结构,具备快速读写速度,适用于处理大量请求和实时数据需求。
- 多样化数据类型:Redis支持多种数据类型,如字符串、哈希、列表、集合和有序集合,满足各种数据存储和操作需求。
- 支持持久化:Redis支持数据持久化,可将数据写入磁盘,确保数据的可靠性和持久存储。
- 支持分布式:Redis提供集群和分片机制,实现数据分布和水平扩展,提供高可用性和可扩展性。
- 简单易用:Redis具有简洁的命令接口,易于使用和管理,同时提供丰富的内置命令和客户端库。
说说你对Redis操作原子性的理解
Redis 的操作是原子性的,这是因为 Redis 的每个命令都是以单线程的方式执行的,整个命令的执行过程是不可中断的,要么全部执行成功,要么全部执行失败。
在 Redis 中,每个命令都会被转换成一个或多个底层操作,这些操作会基于数据结构的特定实现来执行。比如,对于字符串类型,获取一个键值对、设置一个键值对等操作都是原子性的。在执行这些底层操作时,Redis 会使用一些技术来保证原子性,主要包括以下两点:
- Redis 使用单线程模型,避免了多线程之间的竞争条件和锁开销,从而保证了操作的原子性。
- Redis 在执行一些复杂的操作时,比如事务、Lua 脚本等,会将多个底层操作打包成一个原子性操作,这些底层操作要么全部执行成功,要么全部执行失败。在事务和 Lua 脚本中,Redis 同时支持回滚操作,即当一些命令执行成功,后面的命令出错时,Redis 可以自动撤销已经执行的命令。
Redis持久化机制:
1. RDB(Redis Data Base) :内存快照
2. AOF(Append Only File): 增量日志
3. 混合持久化:RDB + AOF
RDB持久化
在指定的时间间隔内将内存中的数据集快照写入磁盘,RDB是内存快照(内存数据的二进制序列化形式)的方式持久化,每次都是从Redis中生成一个快照进行数据的全量备份。
RDB持久化方案进行备份时,Redis会单独fork一个子进程来进行持久化,会将数据写入一个临时文件中,持久化完成后替换旧的RDB文件。
在整个持久化过程中,主进程(为客户端提供服务的进程)不参与IO操作,这样能确保Redis服务的高性能,RDB持久化机制适合对数据完整性要求不高但追求高效恢复的使用场景
优点:
- 性能高:RDB持久化是通过生成一个快照文件来保存数据,因此在恢复数据时速度非常快。
- 文件紧凑:RDB文件是二进制格式的数据库文件,相对于AOF文件来说,文件体积较小。
缺点:
- 可能丢失数据:由于RDB是定期生成的快照文件,如果Redis意外宕机,最近一次的修改可能会丢失。
AOF持久化
AOF持久化需要手动修改conf配置开启。
AOF持久化方案进行备份时,客户端所有请求的写命令都会被追加到AOF缓冲区中,缓冲区中的数据会根据Redis配置文件中配置的同步策略来同步到磁盘上的AOF文件中,同时当AOF的文件达到重写策略配置的阈值时,Redis会对AOF日志文件进行重写,给AOF日志文件瘦身。Redis服务重启的时候,通过加载AOF日志文件来恢复数据。
AOF配置:
AOF默认不开启,默认为appendonly no,开启则需要修改为appendonly yes
关闭AOF+RDB混合模式,设为no:
AOF同步策略:
AOF重写
重写其实是针对AOF存储的重复性冗余指令进行整理,比如有些key反复修改,又或者key反复修改后最终被删除,这些过程中的指令都是冗余且不需要存储的。
自动重写:
当AOF日志文件达到阈值时会触发自动重写。
重写阈值配置:
- auto-aof-rewrite-percentage 100:当AOF文件体积达到上次重写之后的体积的100%时,会触发AOF重写。
- auto-aof-rewrite-min-size 64mb:当AOF文件体积超过这个阈值时,会触发AOF重写。
当AOF文件的体积达到或超过上次重写之后的比例,并且超过了最小体积阈值时,Redis会自动触发AOF重写操作,生成一个新的AOF文件。
优点:
- 数据更加可靠:AOF持久化记录了每个写命令的操作,因此在出现故障时,可以通过重新执行AOF文件来保证数据的完整性。
- 可以保留写命令历史:AOF文件是一个追加日志文件,可以用于回放过去的写操作。
缺点:
- 文件较大:由于记录了每个写命令,AOF文件体积通常比RDB文件要大。
- 恢复速度较慢:当AOF文件较大时,Redis重启时需要重新执行整个AOF文件,恢复速度相对较慢
混合持久化
Redis4.0版本开始支持混合持久化,因为RDB虽然加载快但是存在数据丢失,AOF数据安全但是加载缓慢。
混合持久化通过aof-use-rdb-preamble yes开启,Redis 4.0以上版本默认开启
开启混合持久化之后:appendonlydir文件下存在一个rdb文件与一个aof文件
存入数据,然后执行bgrewriteaof重写文件。
总结
- 推荐两者均开启
- 如果对数据不敏感,可以选单独用RDB
- 不建议单独用AOF,因为可能会出现Bug
- 如果只是做纯内存缓存,可以都不用
说说Redis的过期策略
- 惰性删除: 惰性删除是Redis默认的过期键删除策略。当客户端尝试访问一个已过期的键时,Redis会立即将该键删除,并返回空值。这种策略的优点是删除操作是在需要时进行,减少了不必要的删除开销。但是,如果大量过期键在一次性被访问之前没有被访问过,这些键会一直占据内存空间。
- 定期删除:Redis会每隔一段时间执行一次检查,删除那些已过期的键。默认情况下,Redis每秒执行10次检查。定期删除通过释放过期键所占据的内存空间,使得内存能够及时被回收。但这种方式可能会导致内存占用较高,因为Redis并不保证在每次定期删除操作中都会删除足够数量的过期键。
- 定期淘汰: 定期淘汰是Redis 4.0版本引入的一种新的过期策略。与定期删除不同的是,定期淘汰不仅删除已过期的键,而且会主动查找并淘汰一些尚未过期但是由于内存不足而需要释放的键。通过定期淘汰,Redis可以更主动地管理内存,避免因为内存持续增长而导致系统性能下降。
Redis的过期策略
1. 惰性删除(Lazy expiration)
- 当客户端尝试访问某个键时,Redis会先检查该键是否设置了过期时间,并判断是否过期。
- 如果键已过期,则Redis会立即将其删除。这就是惰性删除策略。
该策略可以最大化地节省CPU资源,却对内存非常不友好。极端情况可能出现大量的过期key没有再次被访问,从而不会被清除,占用大量内存。
2. 定期删除(Active expiration)
- Redis会每隔一段时间(默认100毫秒)随机检查一部分设置了过期时间的键。
- 定期过期策略通过使用循环遍历方式,逐个检查键是否过期,并删除已过期的键值对。
通过调整定时扫描的时间间隔和每次扫描的限定耗时,可以在不同情况下使得CPU和内存资源达到最优的平衡效果
Redis中同时使用了惰性过期和定期过期两种过期策略。
- 假设Redis当前存放20万个key,并且都设置了过期时间,如果你每隔100ms就去检查这全部的key,CPU负载会特别高,最后可能会挂掉。
- 因此redis采取的是定期过期,每隔100ms就随机抽取一定数量的key来检查和删除的。
- 但是呢,最后可能会有很多已经过期的key没被删除。这时候,redis采用惰性删除。在你获取某个key的时候,redis会检查一下,这个key如果设置了过期时间并且已经过期了,此时就会删除。
需要注意如果定期删除漏掉了很多过期的key,然后也没走惰性删除。就会有很多过期key积在内存中,可能会导致内存溢出,或者是业务量太大,内存不够用然后溢出了,为了应对这个问题,Redis引入了内存淘汰策略进行优化。
说说Redis的内存淘汰策略
- LRU(最近最少使用): LRU是Redis默认的内存淘汰策略。根据最近使用的时间戳来判断键的热度,将最久未被使用的键淘汰出去。这种策略保留了最近较常访问的键,适合于热点数据的场景。
- LFU(最不经常使用): LFU根据键被访问的频率来判断热度,淘汰访问频率最低的键。这种策略适用于访问模式稳定但不同键的访问频率差异明显的场景。
- Random(随机淘汰): 随机淘汰策略是一种基于概率的淘汰方法,随机选择一个键进行淘汰。这种策略简单高效,但可能导致较高的缓存命中率下降。
- TTL(生存时间): TTL策略基于键的过期时间,淘汰剩余生存时间最短的键。适用于关注数据实效性的场景。
- Maxmemory Policy(最大内存策略): Redis提供了几种最大内存策略,包括noeviction(禁止淘汰)、allkeys-lru、allkeys-random等。这些策略是在达到设定的最大内存限制后,对写操作返回错误,避免继续写入导致系统崩溃。
Redis的内存淘汰策略
内存淘汰策略允许Redis在内存资源紧张时,根据一定的策略主动删除一些键值对,以释放内存空间并保持系统的稳定性。
1. noeviction(不淘汰策略)
当内存不足以容纳新写入数据时,Redis 将新写入的命令返回错误。这个策略确保数据的完整性,但会导致写入操作失败。
2. volatile-lru(最近最少使用)
从设置了过期时间的键中选择最少使用的键进行删除。该策略优先删除最久未被访问的键,保留最常用的键。
3. volatile-ttl(根据过期时间优先)
从设置了过期时间的键中选择剩余时间最短的键进行删除。该策略优先删除剩余时间较短的键,以尽量保留剩余时间更长的键。
4. volatile-random(随机删除)
从设置了过期时间的键中随机选择一个键进行删除。
5. allkeys-lru(全局最近最少使用)
从所有键中选择最少使用的键进行删除。无论键是否设置了过期时间,都将参与淘汰。
6. allkeys-random(全局随机删除)
从所有键中随机选择一个键进行删除。
Redis有哪些常用应用场景
- 缓存:作为高性能缓存层,提供快速数据访问。
- 分布式会话管理:实现跨服务器的会话共享。
- 消息队列:用作中间件实现异步通信和任务队列。
- 实时排行榜/计数器:用有序集合实现实时排名和计数功能。
- 地理位置信息存储与查询:支持存储地理位置信息并进行位置查询。
- 实时数据分析:存储实时生成的数据,进行快速统计和分析。
什么是缓存击穿、缓存穿透、缓存雪崩
- 缓存击穿:指当一个缓存键(key)对应的数据在缓存中不存在,同时又有大量并发请求访问该缓存键时,这些请求会直接绕过缓存,查询数据库或其他存储系统,导致数据库压力增大。缓存击穿通常在缓存过期后发生。
- 缓存穿透:指当一个查询请求访问一个不存在于缓存中且也不存在于数据库中的数据时,这个请求会无效地继续访问数据库,而不会被缓存。如果黑客故意发送大量非法请求,则缓存层无法起到过滤作用,可能导致数据库负载过大。
- 缓存雪崩:指当缓存集中在某个时间点失效或由于某个原因发生故障,导致大量的请求直接打到后端数据库,造成数据库瞬时压力过大,甚至引起数据库崩溃。在缓存雪崩期间,系统性能急剧下降,无法正常提供服务。
为了应对以上问题,可以采取以下措施:
- 对热点数据采用永不过期策略,避免缓存击穿。
- 在缓存层进行空值缓存,即将查询结果为空的数据也缓存一段时间,避免缓存穿透。
- 设置合理的缓存过期时间,并使用分布式缓存的多节点部署,避免缓存雪崩。
- 引入限流、熔断等机制,控制并发访问量,保护后端系统。
- 对重要数据做冷备份,确保即使缓存失效或故障,仍能从其他系统中恢复数据。
REDIS 集群的原理是什么
Redis 集群通过数据分片和主从复制实现了横向扩展和高可用性。它将数据划分为 16384 个哈希槽,并将这些槽均匀地分配到多个节点上。每个节点负责处理一部分槽的数据,实现了数据的分散存储和负载均衡。
1、在集群中,每个哈希槽有一个主节点和多个从节点。主节点负责处理读写请求,而从节点则通过主从复制机制复制主节点的数据,提供数据的冗余备份和故障恢复功能。
2、当主节点发生故障时,集群会自动进行故障转移。它会选举一个从节点升级为新的主节点,保证服务的持续可用性。同时,集群管理节点负责监控节点的状态,并协调故障转移过程。
3、客户端在与 Redis 集群交互时,根据键的哈希值将请求发送到相应的节点。客户端还可以通过集群管理节点获取整个集群的拓扑信息,了解哪些键存储在哪个节点上。
4、通过数据分片和主从复制,Redis 集群实现了数据水平切分、负载均衡和高可用性。它允许数据规模和吞吐量的线性扩展,并能自动处理节点故障。集群管理节点协调集群状态,客户端通过哈希槽映射与集群交互,从而实现了一个稳定和可靠的 Redis 分布式系统。
什么是Redis哨兵机制
Redis 哨兵是一种用于高可用性的解决方案,它可以监控 Redis 主从复制模式下的主节点和从节点,发现节点故障,并自动进行故障转移,保证 Redis 系统的稳定性和可靠性。
Redis 哨兵机制由多个相互独立的进程组成,这些进程使用 TCP/IP 协议相互通信,实现 Redis 节点的监控和故障转移。哨兵机制的关键进程包括:
- sentinel:主进程,用于监控 Redis 节点的状态,并执行故障转移操作。
- monitor:哨兵进程,用于监控 Redis 的主节点和从节点是否正常工作,并在需要时通知其他哨兵进程和客户端。
- judge:哨兵进程,用于对节点的健康状况进行评估,并根据预定义的阈值决定是否要将一个不健康的节点标记为“主观下线”。
- failover:哨兵进程,负责执行故障转移操作,将主节点故障时选举出来的从节点晋升为新的主节点,并通知其他 Redis 节点更新配置信息。
MySQL里有2000w数据Redis中只存20w的数据,如何保证 redis 中的数据都是热点数据?
首先我们可以看到Redis的空间时间上比我们MySQL少的多,那么Redis如何能够筛选出热点数据,这道题主要考察的是Redis的数据淘汰策略(这里有个误区,很多人容易混淆把数据淘汰策略当做数据过期策略),在Redis 4.0之后是为我们提供了8种淘汰策略,4.0之前则是提供的6种,主要是新增了LFU算法。其实说说是有8种,但是真正意义上是5种,针对random、lru、lfu是提供了两种不同数据范围的策略,一种是针对设置了过期时间的,一种是没有设置过期时间的。具体的五种策略分别为:
- noeviction 选择这种策略则代表不进行数据淘汰,同时它也是redis中默认的淘汰策略,当缓存写满时redis就不再提供写服务了,写请求则直接返回失败。
- random 随机策略这块则是分为两种,一种是volatile,这种是设置了过期时间得数据集,而另外一种是allkeys,这种是包含了所有的数据,当我们缓存满了的时候,选用这种策略就会在我们的数据集中进行随机删除。
- volatile-ttl 这种策略是针对设置了过期时间的数据,并且按照过期时间的先后顺序进行删除,越早过期的越先被删除
- lru 这里的lru策略和我们上面random策略一样也是提供了两种数据集进行处理,LRU算法全程为(最近最少使用)简单一句话来概括就是“如果数据最近被访问过,那么将来被访问的几率也就越高”。这种算法其实已经比较符合我们的实际业务需求了,但是还是存在一些缺陷。
- lfu 最后一种策略就是我们的LFU算法,它是在我么LRU算法基础上增加了请求数统计,这样能够更加精准的代表我们的热点数据。
我们再回看我们的这个问题,我们能很清楚的知道,我们需要的策略是LFU算法。选择volatile还是allkeys就要根据具体的业务需求了。
Redis如何保证与数据库的双写一致性
首先我们来看看一致性:
● 强一致性
● 弱一致性
解决双写一致性方案:
● 延迟双删
○ 延迟双删策略是分布式系统中数据库存储和缓存数据保持一致性的常用策略,但它不是强一致。
○ 实现思路:也是非常简单的,先删除缓存然后更新DB在最后延迟 N 秒去再去执行一次缓存删除
○ 弊端:小概率会出现不一致情况、耦合程度高
● 通过MQ进行重试删除
○ 更新完DB之后进行删除,如果删除失败则向MQ发送一条消息,然后消费者不断进行删除尝试。
● binlog异步删除
○ 实现思路:低耦合的解决方案是使用canal。canal伪装成mysql的从机,监听主机mysql的二进制文件,当数据发生变化时发送给MQ。最终消费进行删除