以下是我总结的一些redis在面试中必须掌握的关键知识点
根据面试经验将问题一步步的展开,以下总结的问题都有关联性,帮助同学们增加记忆。
1、什么是redis?
Redis 是 C 语言开发的一个开源的高性能键值对(key-value)的内存数据库,可以用作数据库、缓存、消息中间件等。它是一种 NoSQL(not-only sql,泛指非关系型数据库)的数据库。
性能优秀,数据在内存中,读写速度非常快,支持并发 10W QPS。
单进程单线程,是线程安全的,采用 IO 多路复用机制。
丰富的数据类型,支持字符串(strings)、散列(hashes)、列表(lists)、集合(sets)、有序集合(zsets)等。
支持数据持久化。可以将内存中数据保存在磁盘中,重启时加载。
主从复制,哨兵,高可用。
可以用作分布式锁。
可以作为消息中间件使用,支持发布订阅。
2、redis的数据类型都有哪些?
redis有以下几种常用的基本类型
string:常用命令:set,get,setnx 功能:缓存
list:常用命令:lpush(压入元素),linsert(指定元素前后插入元素),lset 功能:是一个双向链表,可以通过 lpush 和 rpop 写入和读取消息,做简单的消息队列
hash:常用命令:hset,hget 功能:单点登录,存储用户信息
set:常用命令:sadd(添加元素),scard()获取成员的数量 功能:全局去重功能,可以实现交集、并集等操作,从而实现共同好友等功能
sorted Set(和set类似但是可以排序):常用命令:zadd,zcard 功能:有序性操作,可以对数据进行排序,可应用于排行榜等
Bitmap: 位图,Redis的位图是一种特殊的字符串数据类型,它可以存储一系列二进制位。位图可以进行位操作,如设置、获取和计数位的操作。它适用于一些特定的应用场景,如统计用户在线状态、记录用户活跃度等
Geo: Redis的地理位置数据类型是一种用于存储地理位置信息的数据结构。它可以将地理位置坐标(经度和纬度)与某个键关联起来,从而可以进行地理位置的查询和计算。地理位置数据类型适用于一些需要根据地理位置进行查询和计算的应用,如周边搜索、地理围栏等
3、讲一下redis的缓存雪崩、缓存穿透、缓存击穿和哨兵?
缓存雪崩:
什么是雪崩:缓存一般都是定时任务去刷新,假如redis的数据在同一时刻全部失效,这个时候正好有大量用户涌入,造成数据库崩溃。
如何避免雪崩:在批量往redis中存数据的时候,把每个key的失效时间都加一个随机值
如果是集群,将数据均匀的分布到不同的redis库也可避免
或者是设置热点数据永不过期
缓存穿透:当查询的数据在redis和数据库中都不存在的时候-----解决:将空值设置到缓存,并设置较短的过期时间或者是在业务前进行验证脏数据
缓存击穿:频繁的去查询访问一个热点数据,当这个数据失效的时候,会有一大批请求访问数据库,造成数据库挂机----解决:可以设置热点数据永不过期,或者是加互斥锁
哨兵:通过发送命令,让redis服务器返回监控及运行状态,包括主服务器和从服务器
当哨兵检测到master宕机,会自动将从切换成主,然后通过发布订阅模式通知其他的从服务器,修改配置文件吗,让他们切换主机。
4、redis为什么快?
(1)完全基于内存,数据存在内存中,绝大部分请求是纯粹的内存操作,非常快速,跟传统的磁盘文件数据存储相比,避免了通过磁盘IO读取到内存这部分的开销。
(2)数据结构简单,对数据操作也简单。Redis中的数据结构是专门进行设计的,每种数据结构都有一种或多种数据结构来支持。Redis正是依赖这些灵活的数据结构,来提升读取和写入的性能。
(3)采用单线程,省去了很多上下文切换的时间以及CPU消耗,不存在竞争条件,不用去考虑各种锁的问题,不存在加锁释放锁操作,也不会出现死锁而导致的性能消耗。
(4)使用基于IO多路复用机制的线程模型,可以处理并发的链接。
5、redis的持久化你了解吗?
redis的持久化有两种方式:
- RDB(快照)方式持久化(默认)
- AOF持久化
6、RDB的持久化是怎么实现的能讲讲吗?
RDB的持久化是将某个时间点的所有数据都放到磁盘上,他的缺点是:如果系统发生故障,将会丢失最后一次创建快照之后的数据。如果数据量很大,保存快照的时间会很长。
快照方式在redis的配置如下:
#在900秒(15分钟)之后,如果至少有1个key发生变化,Redis就会自动触发BGSAVE命令创建快照。 save 900 1 #在300秒(5分钟)之后,如果至少有10个key发生变化,Redis就会自动触发BGSAVE命令创建快照。 save 300 10 #在60秒(1分钟)之后,如果至少有10000个key发生变化,Redis就会自动触发BGSAVE命令创建快照。 save 60 10000
创建快照的方式有以下几种:
BGSAVE命令:客户端向Redis发送 BGSAVE命令 来创建一个快照。对于支持BGSAVE命令的平台来说(基本上所有平台支持,除了Windows平台),Redis会调用fork来创建一个子进程,然后子进程负责将快照写入硬盘,而父进程则继续处理命令请求。
SAVE命令 :客户端还可以向Redis发送 SAVE命令 来创建一个快照,接到SAVE命令的Redis服务器在快照创建完毕之前不会再响应任何其他命令。SAVE命令不常用,我们通常只会在没有足够内存去执行BGSAVE命令的情况下,又或者即使等待持久化操作执行完毕也无所谓的情况下,才会使用这个命令。
save选项 :如果用户设置了save选项(一般会默认设置),比如 save 60 10000,那么从Redis最近一次创建快照之后开始算起,当“60秒之内有10000次写入”这个条件被满足时,Redis就会自动触发BGSAVE命令。
SHUTDOWN命令 :当Redis通过SHUTDOWN命令接收到关闭服务器的请求时,或者接收到标准TERM信号时,会执行一个SAVE命令,阻塞所有客户端,不再执行客户端发送的任何命令,并在SAVE命令执行完毕之后关闭服务器。
一个Redis服务器连接到另一个Redis服务器:当一个Redis服务器连接到另一个Redis服务器,并向对方发送SYNC命令来开始一次复制操作的时候,如果主服务器目前没有执行BGSAVE操作,或者主服务器并非刚刚执行完BGSAVE操作,那么主服务器就会执行BGSAVE命令
7、AOF的持久化是怎么实现的能讲讲吗?
AOF持久化是将写命令添加到AOF文件的末尾。
与快照持久化相比,AOF持久化 的实时性更好,因此已成为主流的持久化方案。默认情况下Redis没有开启AOF(append only file)方式的持久化,可以通过appendonly参数开启:
appendonly yes
开启AOF持久化后每执行一条会更改Redis中的数据的命令,Redis就会将该命令写入硬盘中的AOF文件。AOF文件的保存位置和RDB文件的位置相同,都是通过dir参数设置的,默认的文件名是appendonly.aof
8、AOF每次增加一条命令就要同步到磁盘那岂不是很浪费性能,有什么方案吗?
使用 AOF 持久化需要设置同步选项,从而确定写命令同步到磁盘文件上的时机。这是因为对文件进行写入并不会马上将内容同步到磁盘上,而是先存储到缓冲区,然后由操作系统决定什么时候同步到磁盘。在Redis的配置文件中存在三种同步方式,分别为:
always 每个写命令都同步,这样会严重降低Redis的速度
everysec 每秒同步一次
no 让操作系统来决定何时同步
appendfsync always 可以实现将数据丢失减到最少,不过这种方式需要对硬盘进行大量的写入而且每次只写入一个命令,十分影响Redis的速度。另外使用固态硬盘的用户谨慎使用appendfsync always选项,因为这会明显降低固态硬盘的使用寿命。
appendfsync everysec 为了兼顾数据和写入性能,用户可以考虑 appendfsync everysec选项 ,让Redis每秒同步一次AOF文件,Redis性能几乎没受到任何影响。而且这样即使出现系统崩溃,用户最多只会丢失一秒之内产生的数据。当硬盘忙于执行写入操作的时候,Redis还会优雅的放慢自己的速度以便适应硬盘的最大写入速度。
appendfsync no 选项一般不推荐,这种方案会使Redis丢失不定量的数据而且如果用户的硬盘处理写入操作的速度不够的话,那么当缓冲区被等待写入的数据填满时,Redis的写入操作将被阻塞,这会导致Redis的请求速度变慢。
9、AOF有什么缺点吗?
随着服务器写请求的增多,AOF 文件会越来越大。Redis 提供了一种将 AOF 重写的特性,能够去除 AOF 文件中的冗余写命令。
虽然AOF持久化非常灵活地提供了多种不同的选项来满足不同应用程序对数据安全的不同要求,但AOF持久化也有缺陷——AOF文件的体积太大。
10、如何选择合适的持久化方式?
一般来说, 如果想达到足以媲美PostgreSQL的数据安全性,你应该同时使用两种持久化功能。在这种情况下,当 Redis 重启的时候会优先载入AOF文件来恢复原始的数据,因为在通常情况下AOF文件保存的数据集要比RDB文件保存的数据集要完整。
如果你非常关心你的数据, 但仍然可以承受数分钟以内的数据丢失,那么你可以只使用RDB持久化。
有很多用户都只使用AOF持久化,但并不推荐这种方式,因为定时生成RDB快照(snapshot)非常便于进行数据库备份, 并且 RDB 恢复数据集的速度也要比AOF恢复的速度要快,除此之外,使用RDB还可以避免AOF程序的bug。
如果你只希望你的数据在服务器运行的时候存在,你也可以不使用任何持久化方式。
11、redis过期key的删除策略你了解吗?
立即删除
立即删除能保证内存中数据的最大新鲜度,因为它保证过期键值会在过期后马上被删除,其所占用的内存也会随之释放。但是立即删除对cpu是最不友好的。因为删除操作会占用cpu的时间,如果刚好碰上了cpu很忙的时候,比如正在做交集或排序等计算的时候,就会给cpu造成额外的压力。
而且目前redis事件处理器对时间事件的处理方式–无序链表,查找一个key的时间复杂度为O(n),所以并不适合用来处理大量的时间事件。
惰性删除
惰性删除是指,某个键值过期后,此键值不会马上被删除,而是等到下次被使用的时候,才会被检查到过期,此时才能得到删除。所以惰性删除的缺点很明显:浪费内存。dict字典和expires字典都要保存这个键值的信息。
举个例子,对于一些按时间点来更新的数据,比如log日志,过期后在很长的一段时间内可能都得不到访问,这样在这段时间内就要拜拜浪费这么多内存来存log。这对于性能非常依赖于内存大小的redis来说,是比较致命的。
定时删除
从上面分析来看,立即删除会短时间内占用大量cpu,惰性删除会在一段时间内浪费内存,所以定时删除是一个折中的办法。
定时删除是:每隔一段时间执行一次删除操作,并通过限制删除操作执行的时长和频率,来减少删除操作对cpu的影响。另一方面定时删除也有效的减少了因惰性删除带来的内存浪费。
12、redis使用的删除策略?
redis使用的过期键值删除策略是:惰性删除加上定期删除,两者配合使用
13、redis分布式锁了解吗,讲一下他的实现原理?
Redis分布式锁的实现原理主要基于其原子性操作命令,以下是其实现原理的具体分析:
- SETNX:SETNX 命令用于设置一个键值对,但仅当该键不存在时。这保证了只有一个进程可以成功设置锁。
- EXPIRE:为了确保锁最终会被释放,即使持有锁的进程崩溃或发生异常,通常会使用 EXPIRE 命令为锁设置一个过期时间。
- Lua脚本:为了保证原子性,可以使用 Lua 脚本来执行一系列命令。这样可以确保在执行过程中不会被其他命令打断。
- Redlock算法:在某些情况下,为了提高可用性,会使用 Redlock 算法。这个算法涉及在多个独立的 Redis 实例上尝试获取锁,以确保在某个实例失败时系统仍能继续运行。
- 可靠性和性能:在设计分布式锁时,需要在可靠性和性能之间做出权衡。例如,过短的锁过期时间可能会导致频繁的锁重试,而过长的锁过期时间可能会导致资源长时间未被释放。
- 避免死锁:为了避免死锁,需要确保锁可以被释放。这通常通过设置锁的过期时间或者在进程结束时主动释放锁来实现。
- 监控和诊断:在实际应用中,还需要监控锁的状态和性能,以便及时发现和解决问题。
14、如何在springboot中使用分布式锁的?
要在Spring Boot应用程序中使用Redisson框架实现分布式锁,你需要进行以下步骤:
- 添加依赖:在Spring Boot应用程序的pom.xml文件中添加Redisson的依赖。
<dependency> <groupId>org.redisson</groupId> <artifactId>redisson</artifactId> <version>3.15.0</version> </dependency>
- 配置Redisson连接信息:在application.properties或application.yml文件中配置Redisson的连接信息,例如主机名、端口和密码等。
redisson.address=redis://127.0.0.1:6379 redisson.password=yourpassword
- 创建Redisson客户端:创建一个Redisson客户端的配置类,并使用@Configuration注解进行标识。在该类中,你可以使用
@Bean
注解创建一个Redisson客户端对象。
@Configuration public class RedissonConfig { @Bean public RedissonClient redissonClient() { Config config = new Config(); config.useSingleServer().setAddress("redis://127.0.0.1:6379"); config.usePassword("yourpassword"); return Redisson.create(config); } }
- 使用Redisson分布式锁:在你的服务类中,注入RedissonClient对象,并使用它来获取和释放锁。
@Service public class MyService { private final RedissonClient redissonClient; @Autowired public MyService(RedissonClient redissonClient) { this.redissonClient = redissonClient; } public void doSomething() { // 获取锁 RLock lock = redissonClient.getLock("myLockKey"); boolean locked = lock.tryLock(); // 尝试获取锁,如果成功则返回true,否则返回false if (locked) { try { // 执行需要同步的逻辑... } finally { // 释放锁 lock.unlock(); } } else { // 获取锁失败... } } }
在上面的代码中,我们使用了RedissonClient来获取一个分布式锁对象,然后使用tryLock()方法尝试获取锁。如果获取成功,则执行需要同步的逻辑,并在最后使用unlock()方法释放锁。如果获取锁失败,则进行相应的处理。请注意,你需要确保在操作完成后释放锁,以避免死锁的情况。
15、在使用分布式锁的过程中,如果因为网络卡顿等造成业务逻辑没有执行完,但是锁已经过期了,请问该如何处理?
Redisson 提供了自动续租锁的功能来处理锁的续命问题。具体来讲,当一个线程或进程获得锁之后,在锁未被释放的情况下,如果业务执行时间超过了锁的初始设定过期时间,Redisson 可以自动延长锁的有效期,以避免因锁过期而导致的业务幂等性问题。
以下是 Redisson 锁续命机制的一些关键点:
- 锁续约机制:通过子进程或定时任务的方式,持续检测锁的状态,如果发现锁未被释放且业务仍在执行中,则进行锁的时间续命。
- 避免无限等待:虽然 Redisson 支持自动续期,但不建议无限制地延长锁的有效期。应该根据实际业务需求设置合理的超时和重试策略。
- 可重入特性:Redisson 的可重入锁允许同一个线程多次获取锁而不会导致自己被阻塞,内部通过维护一个计数器来实现重入次数的跟踪。
- Lua 脚本保证原子性:为了确保续命操作的原子性和安全性,Redisson 使用 Lua 脚本来执行相关命令,防止在执行过程中受到其他命令的干扰。
最后送大家一句话白驹过隙,沧海桑田