余声-个人博客


  • 首页

  • 分类

  • 归档

  • 标签

👌redis有哪些优点?

发表于 2025-05-11 | 更新于 2025-09-14 | 分类于 Redis
字数统计 | 阅读时长

👌redis有哪些优点?

口语化回答

好的,面试官 Redis 是现在非常常用的缓存,相比于其他的缓存,最大的一个优势就是高性能,redis 的速度非常快,操作都是毫秒级,吞吐量非常高,支持每秒数百万的请求。同时各种操作都基于内存进行,读写速度非常快。还有就是他提供了丰富的数据类型,像 string,hash,zset 这些,在我们日常开发都非常常见的使用。还有一些高级数据类型,像 geo,bitmap 这些。另外就是 redis 的持久化机制做的也比较好,不同的策略选择使其可以应对宕机等异常情况。配合分布式集群的支持,整体的可用性也非常高。以上。

题目解析

其实这道题一般是面试官跟你聊 redis 刚开头的开场时候,会和你问问,主要目的其实是想通过你说的一些优点,来继续发问,问你为什么这是优点,以及延伸出一个话题来继续深度聊。所以像这道题,大家答的时候,熟悉哪个优点就多说点,有点不熟悉的就一嘴带过!

面试得分点

高性能,内存,快,高吞吐,持久化,分布式

题目详细答案

Redis 是当前非常主流的缓存,基于键值对来实现的存储,支持多种数据类型。看一下鸡哥这张思维导图就知道优点是什么了。

👌redis有哪些持久化方式?

发表于 2025-05-10 | 更新于 2025-09-14 | 分类于 Redis
字数统计 | 阅读时长

👌redis有哪些持久化方式?

口语化回答

主要是 rdb 和 aof 的方式,另外也可以混合使用。rdb 主要是在指定的时间间隔内生成数据集的快照,并将其保存到磁盘上,生成一个 rdb 格式的二进制文件,容易备份。恢复速度快,适合用于灾难恢复。AOF持久化方式是记录每一个写操作到日志文件中。Redis会将这些写操作以追加的方式写入到AOF文件中。每次恢复的时候,进行重放。aof 相比 rdb 就是文件会大一些,回复速度慢一些,但是数据丢失风险小。在实际中,一般可以选择混合互补的方式使用。以上。

题目解析

经典必问,面试官经常会问你 redis 数据丢不丢啊,如何持久化啊,这类问题。大家要记住 rdb 和 aof 的两个特点即可。一个比较粗,一个比较细致,重点答这一块,最后告诉他混合起来。

面试得分点

rdb,aof,混合持久化

题目详细答案

主要的持久化方式有两种:RDB(Redis Database)和AOF(Append Only File)。

Redis 4.0引入了混合持久化模式。

RDB(Redis Database)

RDB持久化方式会在指定的时间间隔内生成数据集的快照,并将其保存到磁盘上。这个快照文件的默认名称是dump.rdb。

RDB的配置可以在redis.conf文件中进行。例如:

1
2
3
save 900 1      # 如果900秒(15分钟)内至少有1个键发生变化,就触发一次RDB快照
save 300 10 # 如果300秒(5分钟)内至少有10个键发生变化,就触发一次RDB快照
save 60 10000 # 如果60秒(1分钟)内至少有10000个键发生变化,就触发一次RDB快照

优点:

1、RDB文件是一个紧凑的二进制文件,可以很容易地进行备份。

2、 恢复速度快,适合用于灾难恢复。

3、 对Redis性能影响较小,因为生成RDB文件的工作是在子进程中进行的。

缺点:

1、 数据持久化的频率较低,可能会丢失最近一次快照之后的数据。

2、 生成RDB快照时,可能会消耗较多的CPU和内存资源。

AOF(Append Only File)

AOF持久化方式记录每一个写操作到日志文件中(默认名称是appendonly.aof)。Redis会将这些写操作以追加的方式写入到AOF文件中。

AOF的配置可以在redis.conf文件中进行。例如:

1
2
3
4
5
6
appendonly yes         # 启用AOF持久化
appendfilename "appendonly.aof"
appendfsync everysec # 每秒钟同步一次AOF文件
# 其他选项:
# appendfsync always # 每个写操作都同步到AOF文件,性能较差但数据最安全
# appendfsync no # 由操作系统决定何时同步,性能最好但数据安全性较差

优点:

1、数据恢复更可靠,AOF可以记录每一个写操作,数据丢失风险较小。

2、AOF文件是可读的文本文件,方便分析和调试。

缺点:

1、 AOF文件比RDB文件大,恢复速度较慢。

2、 持久化频率高时,可能会影响Redis性能。

3、 需要定期进行AOF重写(rewrite),以避免文件过大。

3. 混合持久化(Hybrid Persistence)

混合持久化模式结合了RDB和AOF的优点。在Redis 4.0及以上版本中,混合持久化模式在生成新的AOF文件时,会首先创建一个RDB快照,然后在快照之后追加AOF日志。这种方式可以在保证数据恢复速度的同时,减少数据丢失的风险。

混合持久化的配置可以在redis.conf文件中进行。

1
aof-use-rdb-preamble yes  # 启用混合持久化模式

优点:

1、 结合了RDB和AOF的优点,既能快速恢复数据,又能减少数据丢失的风险。

选择建议

RDB:适用于对数据一致性要求不高,但需要快速恢复数据的场景,例如缓存服务器。

AOF:适用于对数据一致性要求高的场景,例如金融交易系统。

混合持久化:适用于需要综合考虑数据恢复速度和数据一致性的场景。

👌redis相比memcached有哪些优势?

发表于 2025-05-10 | 更新于 2025-09-14 | 分类于 Redis
字数统计 | 阅读时长

👌redis相比memcached有哪些优势?

口语化回答

redis 相比 memcached,主要提供了丰富的数据类型和各种高级操作。memcached 仅仅支持字符串类型,redis 支持 5 种基础+4 种高级,非常丰富。还有就是 redis 提供了 rdb 和 aof 的持久化机制,memcached 一旦重启,数据就会直接丢失。分布式相关,redis 天然支持主从复制,哨兵,集群,memcached 还需要靠一些第三方库和工具类实现。再高级一点的就是 redis 支持像 lua,事务,发布订阅这些,memcached 统统不支持。

题目解析

最近几年基本上不会考这道题了,这道题在早些时候,memcached 还用的时候,问的比较多。除非碰到还在用memcached 的公司,否则不会被问到。大家稍微了解即可,比较的过程也是了解历史的过程。

面试得分点

丰富数据类型,持久化,分布式,lua,事务

题目详细答案

通过一个表格来进行对比

redis Memcached
数据类型 Redis 支持字符串、哈希、列表、集合、有序集合、位图、HyperLogLog、地理空间和流等多种数据类型 Memcached 仅支持字符串类型的键值对。
持久化机制 Redis 支持将数据持久化到磁盘,可以通过 RDB 快照和 AOF 日志来保存数据,确保在系统重启后数据不会丢失 Memcached不支持持久化,数据仅保存在内存中,一旦服务器重启或宕机,数据就会丢失。
分布式 支持主从,哨兵,集群 Memcached也支持分布式部署,但通常需要依赖第三方库或工具来实现
脚本功能 Redis 支持在服务器端执行 Lua 脚本,能够将多个操作合并为一个原子操作 不支持
事务 Redis 提供 MULTI、EXEC、WATCH 等命令,支持简单的事务操作 不支持事务
内存淘汰策略 Redis 提供多种内存淘汰策略,如 LRU、LFU 等 Memcached 仅支持 LRU 淘汰策略。

👌redis是单进程单线程?

发表于 2025-05-09 | 更新于 2025-09-14 | 分类于 Redis
字数统计 | 阅读时长

👌redis为什么这么快?

口语化回答

redis 的快主要得益于以下几个点,第一个就是纯内存的操作,相比磁盘来说,纯内存带来的速度提升非常大。其次就是合理的数据结构和数据编码设计。像不同的数据类型所对应的底层结构都会有变化,基本上作者让我们在 o1 的复杂度内就可以读取到我们想要的值。还有就是单线程,因为在内存,所以压力瓶颈一般在网络和 cpu,单线程完全足够,这使得 redis 不需要考虑切换的消耗。以及 redis 采取了 io 多路复用,减少了网络压力,提高了吞吐量。以上就是 redis 这么快的原因。

题目解析

经典题目了,主要是看你对内存的速度有没有一个思考性的了解,知道内存比磁盘快的多,有这种思维,可以在平时开发的时候多注意。还有就是考察单线程不需要考虑切换和争抢的性能问题。io 多路复用是 redis 的比较特色的东西。再深一点就是要考察你对数据类型的底层结构有没有具体的了解了。

面试得分点

内存,数据结构,单线程,io 多路复用

题目详细答案

结论

redis 巨快,巨快,操作毫秒级,qps 可达 10w+。

主要原因

纯内存

Redis将数据存储在内存中,避免了大量访问数据库和直接读取磁盘数据的操作。内存的读写速度远超过磁盘I/O,使得Redis的数据访问非常迅速。

数据结构合理

Redis内部的数据结构都是为快速读写而设计的,如跳跃表、SDS(简单动态字符串)、链表和Hash等。基本都能够在 o(1)复杂度下完成大部分操作,比如 hash 的结构,想获取其中一个属性的值,非常的方便,不像数据库查询需要磁盘寻找。

单线程操作

Redis采用单线程模型来处理客户端请求,避免了多线程带来的上下文切换和竞争条件。单线程模型使得Redis不需要考虑各种锁的问题,减少了性能消耗。但是持久化,异步删除等等是异步线程处理,但这不影响性能。不过要注意一个点,因为单线程,某个命令如果耗时太大,可能会产生阻塞,也就是我们经常说的,不要使用 keys,或者直接读整个 hash 大 key。

io 多路复用模型

Redis在网络通信和磁盘写入方面采用了异步式的IO处理,即使用epoll多路复用技术同时处理多个网络请求,减少了I/O阻塞及上下文切换开销,提高了系统的吞吐量和响应时间。

专门设计的数据结构

redis 的每种数据类型对应的底层存储结构都不一样,经历过多种方式的设计。拿 string 类型来进行说,如果存储数字的话,是用int类型的编码。如果存储非数字,小于等于39字节的字符串,是embstr。大于39个字节,则是raw编码。这种根据类型和字节数的设计,在 key 越多的场景下,占用空间越少。

👌redis事务支持原子性,支持回滚?

发表于 2025-05-09 | 更新于 2025-09-14 | 分类于 Redis
字数统计 | 阅读时长

👌redis事务保证原子性吗,支持回滚吗?

题目详细答案

不支持。

先来回顾两个命令:

MULTI:开启一个事务。之后的所有命令都会被放入一个队列中,直到EXEC命令执行。

EXEC:执行之前队列中的所有命令。

在MULTI和EXEC之间的命令会被放入一个队列中,直到EXEC命令执行时,这些命令才会按顺序执行。

在EXEC命令执行时,所有被MULTI命令包裹的命令会按顺序一次性执行。意味着在EXEC执行时,Redis会将所有命令作为一个整体进行处理。Redis保证单个命令的原子性,即每个命令在执行时是不可分割的。

但是,Redis事务并不完全等同于传统关系型数据库的事务。

如果在EXEC执行过程中某个命令失败(例如,命令语法错误),该命令会被跳过,但其他命令仍然会继续执行。这与关系型数据库的事务不同,后者通常会在某个命令失败时回滚整个事务。

Redis事务没有回滚机制。如果某个命令执行失败,已经执行的命令不会被撤销。

redis再执行lua脚本的时候,会封装为一个事务,不可中断;

👌redis回收进程是如何工作的?

发表于 2025-05-09 | 更新于 2025-09-14 | 分类于 Redis
字数统计 | 阅读时长

👌redis回收进程如何工作的?

题目详细答案

Redis的回收进程负责定期扫描和删除过期键。服务器运行时自动执行。主要就是定期回收和惰性回收,其实也可以看之前的 redis 过期策略。来进行合二为一,其实二者答案差不多,就是内容有一定区别。

定期回收任务

Redis回收进程的核心是一个定期任务,这个任务在Redis的事件循环中执行。

事件循环:Redis使用一个单线程的事件循环模型来处理所有的客户端请求和后台任务。

定期任务:在事件循环中,Redis会定期触发一个函数来执行过期键的检查和删除操作。这个函数默认每100毫秒运行一次。

实现步骤如下:

  1. 选择数据库:每次执行定期任务时,Redis会遍历所有的数据库(默认16个)。
  2. 随机抽样:对于每个数据库,Redis会随机选择一部分带有过期时间的键进行检查(默认20个)。
  3. 检查过期键:检查这些键是否已经过期,如果过期则删除。
  4. 重复检查:如果发现超过一定比例的键是过期的(默认25%),则继续进行更多次的检查和删除,直到过期键的比例下降到合理范围内。

惰性回收

惰性回收不是一个独立的进程,而是每次客户端访问键时触发的检查机制。它的工作原理如下:

  1. 访问键:每当客户端访问一个键时,Redis会检查该键是否设置了过期时间。
  2. 检查过期:如果该键已经过期,Redis会立即删除该键,并返回空结果或相应的错误信息。

👌redis如何实现延迟队列?

发表于 2025-05-09 | 更新于 2025-09-14 | 分类于 Redis
字数统计 | 阅读时长

👌redis如何实现延时队列

题目详细答案

可以使用有序集合(Sorted Set)来实现延时队列。有序集合中的每个元素有一个关联的分数,可以用来表示任务的执行时间戳。具体的步骤如下,非常简单

添加任务到延时队列

将任务添加到有序集合中,使用任务的执行时间作为分数(score)。

1
2
3
4
5
6
7
8
// 示例代码:添加任务到延时队列
String queueName = "delay_queue";
String taskId="task_1";
long delay=5000; // 延迟时间(毫秒)
long executionTime= System.currentTimeMillis() + delay;
Jedis jedis = newJedis("localhost");
jedis.zadd(queueName, executionTime, taskId);
jedis.close();

轮询延时队列并执行任务

定期检查有序集合中的任务,找到那些执行时间已经到达或超过当前时间的任务,并执行这些任务。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
// 示例代码:轮询延时队列并执行任务
String queueName = "delay_queue";
Jedis jedis=new Jedis("localhost");
while (true) {
long currentTime= System.currentTimeMillis();
Set<Tuple> tasks = jedis.zrangeByScoreWithScores(queueName, 0, currentTime, 0, 1);

if (tasks.isEmpty()) {
// 没有任务需要执行,休眠一段时间
Thread.sleep(1000);
continue;
}

for (Tuple task : tasks) {
StringtaskId= task.getElement();
// 执行任务
executeTask(taskId);

// 从队列中移除已执行的任务
jedis.zrem(queueName, taskId);
}
}

jedis.close();
private static void executeTask(String taskId) {
// 实现任务执行逻辑
System.out.println("Executing task: " + taskId);
}

👌redis是单进程单线程?

发表于 2025-05-09 | 更新于 2025-09-14 | 分类于 Redis
字数统计 | 阅读时长

👌redis是单进程单线程的吗?

口语化回答

这个问题其实是两个角度来看,我们通常说 redis 说单线程,是因为从接收到客户端的请求,然后 redis 进行内部操作,再返回结果。这个过程是单线程的。单线程的吞吐量可以达到 10w/s,来自 redis 的官方数据,由于 redis 的操作都是在内存中完成,所以性能的瓶颈时内存和带宽,而不是 cpu。如果在这种情况下,使用多线程,反而又要去处理线程安全和数据竞争的问题,还有上下文切换的消耗。那么另一个角度就是除了 redis 执行命令的时候,像持久化数据操作,内存释放操作,这些是由多线程来进行实现的。在 redis6.0 之后,为了减轻网络的性能瓶颈,还增加了多个 io 线程来处理请求。也是一个比较大的优化。所以这个问题,两个角度来看,以上。

题目解析

还是问的比较常见的,一道陷阱题。如果你单纯的说,是单线程的,基本就 gg,面试官会认为的你广度是有问题的。所以说大家一定要答出另一个角度。

面试得分点

操作单线程、多线程异步、多路 io

题目详细答案

Redis核心操作是单线程的。Redis在处理并发请求时有简单、高效和一致性的优点。但是Redis在某些方面使用了额外的线程来处理后台任务。

Redis的主要操作,包括网络IO和键值对读写,确实是由一个线程来完成的。这保证了Redis在处理客户端请求时的简单性和一致性,避免了多线程可能带来的上下文切换开销和竞争条件。利用队列技术将并发访问变为串行访问,消除了传统数据库串行控制的开销。这意味着,虽然多个客户端可能同时发送请求,但Redis会将这些请求放入队列中,并逐个处理它们。

Redis 单线程性能高的原因:

1、 高效的 I/O 多路复用:Redis使用网络IO多路复用技术(如epoll)来同时处理多个客户端连接。这使得Redis能够高效地利用系统资源,为大量并发连接提供高性能的服务。官网数据 10w/qps。

2、 由于Redis基于内存操作,并且采用了单线程模型,不需要处理线程切换问题和多线程之间资源竞争,以及锁的问题。

Redis 多线程主要做的事情:

持久化(例如,在保存RDB快照时,Redis会自动fork一个子进程去处理)、异步删除和集群数据同步等。这些任务不会阻塞Redis的主线程,从而确保Redis能够持续地为客户端提供服务。

👌redis的setnx和setex的区别?

发表于 2025-05-09 | 更新于 2025-09-14 | 分类于 Redis
字数统计 | 阅读时长

👌redis过期策略有哪些?

口语化回答

过期策略主要分为主动和被动,主动又分为定时、定期,被动就是常说的惰性清理。先说结论,redis 采取的方案是定期+惰性配合的方式来进行实现。定期策略主要是通过周期性执行的函数来扫描即将过期的键,立马将其进行失效操作。这种方式比较消耗 cpu。于是产生了定期操作,没隔多少 ms 来进行执行,这种减少了 cpu 的消耗。也能比较准时的删除过期的键。算是定时的一种优化,比较难的点就是寻求平衡。最后就是惰性删除,所有的 key 即使过期了也不会立马删除,当这个键过期之后,下一次访问的时候,才会被删除,容易造成内存泄漏的问题。最后 oom 就会触发内存淘汰策略了,优点就是大大减轻了 cpu 的压力。以上两种方式配合,能达到一个平衡。

题目解析

常考题,很多人把过期策略和淘汰策略混在一起。二者既不同,当惰性删除的时候,又有联系。大家要注意多层面来回答,注意辩证 cpu 性能的问题处理。

面试得分点

定期删除,定时删除,惰性删除,主动于被动

题目详细答案

从行为上,我们可以把过期策略分为两大点。主动删除,被动删除。主动删除又分为定时删除和定期删除。

主动删除

定时删除

当设置键的过期时间时,Redis会为该键创建一个定时器,当过期时间到达时自动删除该键。redis.c 下的 activeExpireCycle 函数实现了定期删除粗略,配合 Redis的服务器的 serverCron函数,在服务器周期执行serverCron 的时候,activeExpireCycle函数就会被调用,在一定的时间内,分多次遍历 redis 中的数据库,从数据库的expires字典中检查一部分键的过期时间,此操作是随机性的,然后删除其中的过期键。

优点:删除操作会在数据到期时立即进行,确保内存及时释放。

缺点:定时器的管理会消耗系统资源,特别是在大量键设置过期时间的情况下,删除 key 会对响应时间和吞吐量产生影响。

定期删除

Redis会定期扫描数据库中的键,并删除其中已过期的键。通过随机抽取一定数量的键,并检查它们是否过期,如果过期就删除,Redis默认每隔100ms(可以通过配置文件中的hz参数进行调整)就执行一次过期扫描任务。

配置redis.conf的hz选项,默认为10,1s刷新的频率。即1秒执行10次,相当于100ms执行一次,hz值越大,说明刷新频率越快,Redis性能损耗也越大

优点:通过限制删除操作执行的时长和频率来减少删除操作对CPU的影响,同时能有效释放过期键占用的内存。

缺点:难以确定删除操作执行的时长和频率,如果执行的太频繁,会对CPU造成负担,就变成了定时删除;如果执行的太少,则过期键长时间占用的内存没有及时释放,造成内存浪费。

内存不足

当Redis的内存达到最大限制时,还会触发内存淘汰策略,策略不同决定哪些数据会被删除以腾出空间。
no eviction:禁止淘汰,达到内存限制时拒绝新的写请求。
allkeys-lru:从所有键中淘汰最近最少使用的键。
volatile-lru:从设置了过期时间的键中驱逐最近最少使用的键。
allkeys-random:从所有键中随机驱逐键。
volatile-random:从设置了过期时间的键中随机驱逐键。
volatile-ttl:从设置了过期时间的键中驱逐剩余时间最短的键。

被动删除

惰性删除

Redis不会在键过期时立即删除它,而是在下一次访问这个键时检查其是否过期,然后删除过期的键。假设这个键已经过期,但是后面一直没有被访问,则会永远存在。不会被删除,这就是惰性删除。

惰性删除策略由db.c/expireIfNeeded函数实现,所有读写数据库的Redis命令在执行之前都会调用expireIfNeeded函数对输入键进行检查。如果输入键已经过期,那么expireIfNeeded函数将输入键从数据库中删除;如果输入键未过期,那么expireIfNeeded函数不做动作。

优点:惰性删除不会增加额外的系统开销,不浪费 cpu,只在访问时进行检查。

缺点:如果某个键永远不会被访问,即使设置了过期时间,它也不会被自动删除,造成内存泄漏问题。

Redis 实际使用的是定期删除+惰性删除的方式!定期删除减少 cpu 消耗和浪费,配合惰性删除,二次检查保险。

👌Redis的从服务器的作用?

发表于 2025-05-07 | 更新于 2025-09-14 | 分类于 Redis
字数统计 | 阅读时长

👌Redis的从服务器的作用?

口语化回答

好的,面试官,从服务器在 redis 集群模式下,起着非常重要的角色。一方面可以复制数据,保证数据不丢失,当主节点发生故障的时候,可以切换到从服务器继续提供服务,还可以为主服务器分担压力,做读写分离,一般 redis 的都是读多写少,我们可以把从服务器承担读请求,把主服务器承担写请求。还有就是可以通过添加从节点,来提供额外的存储和计算能力。以上。

题目解析

业务比较大的情况下,redis 一般都会搭建集群,这道题其实可以考察到,你为什么要选择使用从服务器,看你对于高可用有没有自己的理解,以及集群模式的扩展,故障转移这些有没有一定的考虑。

面试得分点

数据冗余备份、故障转移、读写分离、高可用

题目详细答案

Redis集群中的从服务器(也称为副本或副本节点)在Redis的高可用性和扩展性中扮演着重要角色。主要的作用有以下几点。

提供数据冗余和备份

在集群模式下,Redis集群中的每个主节点可以有一个或多个从节点。这些从节点会复制主节点的数据,从而提供数据的冗余和备份。当主节点出现故障或不可用时,从节点可以接管其工作,确保数据不会丢失,并提高系统的容错能力。

支持故障自动转移

在Redis集群中,当主节点发生故障时,系统可以自动将其中一个从节点升级为主节点,继续处理客户端的请求。这种自动转移的过程称为故障转移,可以确保Redis集群在节点故障时仍然保持高可用性和稳定性。

支持读写分离

在Redis集群配置中,可以将读请求发送到从节点,而将写请求发送到主节点。这种读写分离的配置可以提高系统的并发处理能力和读性能,因为从节点可以处理更多的读请求,而主节点则可以专注于处理写请求。

扩展性能和容量:

通过添加从节点,Redis集群可以扩展其性能和容量。从节点可以分担主节点的负载,并提供额外的存储和计算能力。在Redis集群中,可以动态地添加或删除从节点,以扩展或缩小系统的规模和容量。这种灵活性使得Redis集群能够适应不同的业务需求和工作负载。

👌redis常见性能问题和解决方案

发表于 2025-05-07 | 更新于 2025-09-14 | 分类于 Redis
字数统计 | 阅读时长

👌redis常见性能问题和解决方案?

口语化回答

redis 常见性能问题主要有内存空间不足,大 key 问题,阻塞操作等等。像内存空间问题,主要发生在大规模的数据量下产生,针对这种我们可以采取数据结构层面的优化,或者集群模式的水平扩。大 key 问题一般就是最初设计的时候,没有考虑清楚,导致业务日积月累,一个小的 key 变成了大 key。会导致我们的性能下降,耗时增加,这种需要从根上进行业务的梳理和处理拆分。还有就是阻塞,如果执行一些 keys 命令会大致阻塞,生产要避免进行这些操作。以上

题目解析

这道题大家主要是从以下的几个问题中,选取常见的 3 个即可。建议大家选择空间不足,大 key 和阻塞,这三点比较好回答,也好解释和扩展。

面试得分点

内存不足,大 key,阻塞,网络延迟,慢查询,持久化性能

题目详细答案

问题一、redis 内存空间不足

****由于Redis的数据存储在内存中,当数据量增大时,可能会出现内存不足的情况,导致性能下降或服务不可用。

解决方案:

内存优化:使用更高效的数据结构(如哈希表、压缩列表)来存储数据,减少内存占用。

水平扩展:使用Redis集群模式,将数据分片存储在多个节点上,扩展内存容量。

问题二、redis 的大 key

****某些键可能存储了大量数据(如大列表、大哈希表),操作这些大键可能导致阻塞,影响性能。

解决方案:

拆分大键:将大键拆分成多个小键,减少单个键的操作时间。

分批处理:对于需要迭代处理的大键,使用SCAN、SSCAN、HSCAN、ZSCAN等命令进行分批处理,避免单次操作时间过长。

监控和预警:定期监控Redis中的大键,及时发现并处理。

问题三、阻塞操作

某些Redis命令(如KEYS、FLUSHALL、SAVE等)会阻塞服务器,导致其他操作无法执行。

解决方案:

避免阻塞命令:尽量避免使用阻塞命令,使用非阻塞的替代命令(如SCAN代替KEYS)。

异步操作:对于需要执行的阻塞操作,尽量使用异步方式(如FLUSHALL ASYNC)。

问题四、网络延迟

Redis是基于TCP协议的网络服务,高网络延迟会影响Redis的性能。

解决方案:

本地部署:尽量将Redis服务器部署在与应用服务器同一内网,减少网络延迟。

连接池:使用连接池来复用Redis连接,减少连接建立和关闭的开销。

问题五、慢查询

****某些复杂的查询或数据操作可能会导致Redis响应变慢,影响整体性能。

解决方案:

慢查询日志:启用Redis的慢查询日志功能,定期检查慢查询并优化。

索引优化:合理使用Redis的数据结构和索引,优化查询性能。

问题六、主从复制延迟

****在主从复制架构中,从服务器可能会因为网络或负载问题导致复制延迟,影响数据一致性。

解决方案:

优化网络:确保主从服务器之间的网络连接稳定,带宽充足。

调整复制参数:优化Redis的复制参数(如repl-backlog-size、repl-timeout等),减少复制延迟。

监控复制状态:定期监控主从复制状态,及时发现并处理延迟问题。

问题七、持久化性能问题

****Redis的持久化操作(如RDB快照和AOF日志)可能会影响性能,尤其是在大数据量或高并发情况下。

解决方案:

合理配置持久化策略:根据业务需求配置合理的持久化策略,平衡性能和数据安全性。

异步持久化:使用异步持久化方式(如AOF的fsync策略),减少对主线程的影响。

原文: https://www.yuque.com/jingdianjichi/xyxdsi/eg6c7clpzgsazknn

👌redis的setnx和setex的区别?

发表于 2025-05-07 | 更新于 2025-09-14 | 分类于 Redis
字数统计 | 阅读时长

👌redis的setnx和setex的区别?

口语化答案

setnx 和 setex 都是在最基础的 set 命令上面所做的升级。setnx 的一个特性就是如果 key 已经存在,在此放入会放不进去,这种特性也常常用来做分布式锁。如果 setnx 成功,则证明获取到锁成功,没有 set 成功,则证明获取锁失败。setex 只多了一个过期时间的概念,比如我希望本次放入的缓存 3 秒后自动失效,就可以通过这个命令来实现。

题目解析

比较基础的一道题,面试官主要是借这个问题,聊到分布式锁相关的一些处理。注意二者的区别即可。

面试得分点

不存在则设置,过期时间,分布式锁

题目详细答案

SET:最基础的命令,setnx 和 setex 都是在此基础上进行变种。set 命令就是设置键值对,如果已经有值则覆盖,没值就放进去,不涉及过期时间的概念。

SETNX:是一个设置键-值对的命令,但仅在键不存在时才设置该键。如果键已经存在,则不进行任何操作。它是“Set if Not Exists”的缩写,即“如果不存在则设置”。

SETEX:这个命令用于为指定的键设置值及其过期时间。如果键已经存在,SETEX命令将会替换旧的值和过期时间。

命令使用

SETNX的语法为:SETNX key value。其中,key是要设置的键名,value是要设置的值。如果key不存在,则返回1表示设置成功;如果key已经存在,则返回0表示设置失败。

SETEX的语法为:SETEX key seconds value。其中,key是要设置的键名,seconds是过期时间(以秒为单位),value是要设置的值。如果设置成功,则返回“OK”。

应用场景

SETNX常用于分布式场景中的锁机制。例如,在多个客户端同时访问共享资源或执行关键操作时,可以使用SETNX命令尝试在Redis中设置一个特定的键作为锁键,从而确保只有一个客户端能够成功设置该键并执行关键操作。其他执行命令因为设置不成功,所以就可以认为是未获得到锁。

SETEX则用于为键设置值和过期时间。这在需要临时存储数据或限制数据有效期的场景中非常有用。例如,可以使用SETEX命令存储会话信息或缓存数据,并为其设置适当的过期时间以自动删除过期的数据。

👌JVM主要组成部分有哪些?

发表于 2025-05-06 | 更新于 2025-09-14 | 分类于 笔记
字数统计 | 阅读时长

👌JVM主要组成部分有哪些?

题目详细答案

1725898764439-e7db4bb9-4d60-4b85-9b3d-02c5dbfd311c.png

类加载子系统

类加载子系统负责将 .class 文件加载到内存中,并进行验证、准备、解析和初始化。

主要功能:

加载:从文件系统或网络中读取 .class 文件。

验证:确保字节码文件的正确性和安全性。

准备:为类的静态变量分配内存并设置默认初始值。

解析:将符号引用转换为直接引用。

初始化:执行类的静态初始化块和静态变量的初始化。

运行时数据区

JVM 在运行时将内存划分为多个不同的数据区域,每个区域都有特定的用途。

方法区(Method Area):存储已加载的类信息、常量、静态变量、即时编译器编译后的代码等。

堆(Heap):存储所有对象实例和数组,是垃圾收集的主要区域。

Java 栈(Java Stacks):每个线程都有自己的 Java 栈,存储局部变量表、操作数栈、动态链接、方法返回地址等信息。

本地方法栈(Native Method Stacks):为本地方法调用服务,存储本地方法调用的状态。

程序计数器(Program Counter Register):每个线程都有自己的程序计数器,指示当前线程执行的字节码行号。

执行引擎

执行引擎负责执行字节码指令。

主要组件:

解释器(Interpreter):逐条解释执行字节码指令,速度较慢。

即时编译器(Just-In-Time Compiler, JIT):将热点代码(频繁执行的代码)编译为本地机器码,提高执行速度。

垃圾收集器(Garbage Collector, GC):自动管理内存,回收不再使用的对象,防止内存泄漏。

本地接口

本地接口(通常是 Java Native Interface, JNI)允许 Java 代码与本地(非 Java)代码进行交互。

主要功能:

调用本地方法(通常是用 C 或 C++ 编写的)。

允许 Java 代码使用操作系统特性或访问硬件。

本地方法库

本地方法库是存储本地方法实现的动态链接库(如 .dll 文件或 .so 文件)。

主要功能:

提供本地方法的具体实现。

由本地接口调用以执行本地代码。

JVM 主要组成部分的图示

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
+---------------------------+
| 类加载子系统 (Class Loader Subsystem) |
+---------------------------+
| 运行时数据区 (Runtime Data Area) |
| - 方法区 (Method Area) |
| - 堆 (Heap) |
| - Java 栈 (Java Stacks) |
| - 本地方法栈 (Native Method Stacks) |
| - 程序计数器 (Program Counter) |
+---------------------------+
| 执行引擎 (Execution Engine) |
| - 解释器 (Interpreter) |
| - 即时编译器 (Just-In-Time Compiler, JIT) |
| - 垃圾收集器 (Garbage Collector, GC) |
+---------------------------+
| 本地接口 (Native Interface) |
+---------------------------+
| 本地方法库 (Native Method Libraries) |
+---------------------------+

👌redis的内存用完了会发生什么?

发表于 2025-05-06 | 更新于 2025-09-14 | 分类于 Redis
字数统计 | 阅读时长

👌redis的内存用完了会发生什么?

redis 内存用完之后发生的现象主要取决于我们配置的内存回收策略。默认是noeviction,这个策略不会删除任何的键,当内存不足的时候,就会报错。这种策略,我们一般不使用。常见使用的就是 lru,回收最近最少使用的有过期时间的键。其他的策略还比如 randow,可以回收随机的键。ttl 按照最短的过期时间来进行回收。以上。

面试得分点

lru,lfu,random,无过期

题目详细答案

当Redis的内存用完时,会根据配置的内存回收策略采取不同的措施。可以在内存达到限制时决定如何处理新的写请求。主要的策略有如下 8 种。

内存回收策略

  1. noeviction:不删除任何键,当内存不足时返回错误。这是默认策略。

当内存达到限制时,Redis将不再接受任何写请求,并返回错误。例如,客户端尝试设置新键时,会收到类似以下的错误信息:

1
(error) OOM command not allowed when used memory > 'maxmemory'.
  1. allkeys-lru:使用最近最少使用(LRU)算法回收所有键。
  2. volatile-lru:使用最近最少使用(LRU)算法回收设置了过期时间的键。

Redis将根据LRU算法选择最近最少使用的键进行删除,以腾出空间存储新的数据。allkeys-lru会在所有键中选择,volatile-lru只会在设置了过期时间的键中选择。

  1. allkeys-random:随机回收所有键。
  2. volatile-random:随机回收设置了过期时间的键。

Redis会随机选择一些键进行删除,以腾出空间。allkeys-random会在所有键中选择,volatile-random只会在设置了过期时间的键中选择。

  1. volatile-ttl:回收那些剩余生存时间(TTL)最短的键。

Redis将选择那些剩余生存时间(TTL)最短的键进行删除。

  1. volatile-lfu:使用最长时间没有被使用(LFU)算法回收设置了过期时间的键。
  2. allkeys-lfu:使用最长时间没有被使用(LFU)算法回收所有键。

Redis将根据LFU算法选择最近最少使用的键进行删除。volatile-lfu只会在设置了过期时间的键中选择,allkeys-lfu会在所有键中选择。

配置内存回收策略的方式

redis.conf文件中配置内存回收策略,例如:

1
2
maxmemory 100mb
maxmemory-policy allkeys-lru

也可通过命令行参数设置:

1
redis-server --maxmemory 100mb --maxmemory-policy allkeys-lru

👌redis的同步机制是什么

发表于 2025-05-06 | 更新于 2025-09-14 | 分类于 Redis
字数统计 | 阅读时长

👌redis的同步机制是什么?

口语化回答

redis 的同步机制主要是主从同步,一开始从服务器发送同步命令,主服务器接收到之后,就会生成一个 rdb 的文件,然后传输给从服务器,从服务器接收到之后,立马进行数据的恢复。然后当主服务器再次接收到写命令的时候,会发给从服务器。这个过程是一个异步复制,主服务器不会等待结果。这样就完成了主从复制,主要的核心步骤就是这些。如果同步机制发生问题的话,从服务器可以进行断线重连。还可以做集群、哨兵,来自动切换。以上。

题目解析

主要还是考主从同步原理和如何进行配置,还可以带一点故障的处理。面试官主要是想看看你对集群有没有一定的了解,redis 主从的数据复制有没有了解。

面试得分点

rdb 快照,增量同步,故障机制

题目详细答案

redis的同步机制主要涉及主从复制,主从复制机制允许一个服务器(主服务器)将数据复制到一个或多个服务器(从服务器)。从服务器可以是只读的,也可以接受写操作,但这些写操作不会被同步回主服务器。

初次同步

当从服务器第一次连接到主服务器时,或者当从服务器与主服务器的连接中断后重新连接时,会触发一次全量同步过程。

  1. 从服务器发送SYNC命令:从服务器向主服务器发送SYNC命令,请求进行同步。
  2. 主服务器生成RDB快照:主服务器接收到SYNC命令后,会生成一个RDB(Redis Database)快照文件,并在生成过程中将所有新写入的命令记录到一个缓冲区中。
  3. 传输RDB文件:主服务器将生成的RDB文件发送给从服务器。从服务器接收到RDB文件后,会清空自身的数据库并加载这个RDB文件。
  4. 传输缓冲区中的命令:主服务器将缓冲区中的所有写命令发送给从服务器,从服务器依次执行这些命令,以确保数据完全同步。

增量同步

在初次同步完成后,主从服务器会保持连接状态,主服务器会将后续的所有写命令实时发送给从服务器,从服务器执行这些命令以保持数据的一致性。

同步机制如何配置

主服务器配置

主服务器的配置通常不需要特别设置,只需要确保其能够接受从服务器的连接请求。

从服务器配置

在从服务器的配置文件中,需要指定主服务器的IP地址和端口号:

1
2
replicaof <master-ip> <master-port>
replicaof 192.168.1.100 6379

同步机制的故障处理

断线重连

当从服务器与主服务器的连接中断时,从服务器会自动尝试重连。在重连成功后,从服务器会根据情况选择进行全量同步或增量同步。

主从切换

在高可用环境中,可以使用Redis Sentinel或Redis Cluster来实现自动主从切换。当主服务器发生故障时,Sentinel或Cluster会自动选举一个新的主服务器,并通知其他从服务器进行同步。

👌redis的高级数据类型有哪些?

发表于 2025-05-06 | 更新于 2025-09-14 | 分类于 Redis
字数统计 | 阅读时长

👌redis的高级数据类型有哪些?

口语化回答

面对一些复杂的场景,redis提供了一些高级数据类型,来进行了功能的扩展。主要有四种,bitmaps,hyperloglog,geo,stream。stream 不是非常常用,主要是用来实现消息队列功能。常用的就是 bitmap,bitmap 的 0,1 特性,非常实用于签到,或者存在,不存在这种类型判断,以及在大量数据下,快速统计是否结果。bitmap 非常节省空间,相比于传统的存储数据后,在 mysql 等层面统计,bitmap 更加适用。其次就是hyperloglog 主要是用于一些数量的统计,不过要允许误差,他不会存具体的内容,会帮助我们进行数据的统计,像常见的网站访问统计,就非常适合这个数据结构。geo 主要是做地理位置的计算,通过经度和纬度来定位位置,经过运算可以得到距离,附近范围的坐标等等。像比如美团外卖的附近商家,地图的距离测算,都可以通过 geo 的结构来进行实现,以上。

题目解析

这道题问的比较少,如果在问你基础数据类型的时候,你补了一句,还有三种高级类型,如果面试官感兴趣的话,会继续的追问你。不过三种里面最常用的就是 bitmap,其他用的比较少,重点关注 bitmap 即可。hyperloglog,geo 都不常见,无需关注。作为了解即可。

面试得分点

bitmap,二进制位统计,签到功能,hyperloglog,大数据量统计,geo,地理位置,经纬度,附近的人

题目详细答案

一、 Bitmaps

位图就是一个用二进制位(0和1)来表示数据的结构。可以把它想象成一排开关,每个开关只能是开(1)或者关(0)。这些开关排成一行,从左到右编号,编号从0开始。

目的就是操作某一个位置的数据变成 1 或者 0。

主要操作命令

1
SETBIT jichi 4 1

按照上图,我们其实就是把 4 位设置成了 1。

1
GETBIT jichi 4
1
BITCOUNT jichi  //获取bitmap里面有多少个1

举个例子

基于上面我们按照大家常见的比如用户签到系统,来做一个例子的说明。

假设我们有一个用户签到系统,我们可以用 bitmap 来记录每个用户每天是否签到。比如,一个月有30天,我们可以用30个位来表示这个月的签到情况,我们就可以如此设计。

第1天签到:第0位设为1。第2天没签到:第1位设为0。第3天签到:第2位设为1。以此类推…

这个例子就用上面三个命令即可完成,setbit 设置签到位置,getbit 判断某一天有没有签到,bitcount 获取总共签了多少次到。

假设用户在第1天和第3天签到,那么 bitmap 的值就是下面这样的:

1
101000000000000000000000000000

为什么用 bitmap

类似签到,活跃情况,这些场景,假设我们用数据库存储,可能是一条一条的,统计起来也费时和麻烦,如果使用 bitmap,可以进行非常快速的统计,并且 bitmap 每个位只是二进制位,非常节省空间。

扩展起来,其实比如判断用户有没有权限,假设把某个权限作为一个位置,新增作为 1,删除作为 2,那么这种场景也是可以很快知道用户是否有权限的一种方式。

总之涉及单位置判断的,是否的场景,bitmap 比较靠谱。


二、HyperLogLog

HyperLogLog 用于计算数据集中不重复元素的数量,是 Redis 提供的一种基数统计的数据结构。当我们需要统计大量数据中有多少不同的元素时,直接存储所有元素会占用大量内存。例如,统计一个网站一天内有多少不同的IP地址访问。如果直接存储所有IP地址,内存消耗会非常大。HyperLogLog通过巧妙的数学方法,可以在很小的内存占用下,提供一个非常接近的估算值。在 Redis 里面,每个 HyperLogLog 键只需要花费 12 KB 内存,就可以计算接近 2^64 个不同元素的基 数。这和计算基数时,元素越多耗费内存就越多的集合形成鲜明对比。

什么是基数??

比如数据集 {1, 3, 5, 7, 5, 7, 8}, 那么这个数据集的基数集为 {1, 3, 5 ,7, 8}, 基数(不重复元素)为5。 基数估计就是在误差可接受的范围内,快速计算基数。

常用命令

HyperLogLog 在 Redis 中以字符串的形式存在,但是只能作为计数器来使用,并不能获取到集合的原始数据。

主要涉及三个命令:

添加元素:

1
2
PFADD key element1 element2 ...
例如:PFADD jichihll jichi jitui

估算基数:

1
2
3
4
PFCOUNT key
PFCOUNT jichihll

返回的就是 2

合并多个HyperLogLog:

1
PFMERGE destkey sourcekey1 sourcekey2 ...

应用场景

凡是大量的数据下,统计不同数据的数量的情况都可以使用,非常的方便,同时要接受误差的场景。比如

网站访问统计:估算鸡翅 club 网站每天有多少独立访客。

日志分析:估算日志文件中有多少不同的错误类型。

三、 Geospatial Indexes

Geo数据指的是与地理位置相关的数据。简单来说,就是关于“东西在哪里”的数据。它可以描述物体的位置、形状和关系,比如城市的坐标、商店的位置、路线的路径等等。

有主要的三个要素,经度,纬度,和位置名称。

比如鸡哥所在的位置

1
GEOADD jichi 16.281231 37.1231241 jd

常用命令

添加地理位置:

1
2
GEOADD key longitude latitude member [longitude latitude member ...]
GEOADD cities 116.4074 39.9042 "Beijing"

获取地理位置:

1
2
3
4
5
GEOPOS key member
GEOPOS cities "Beijing"
会返回
116.4074
39.9042

计算距离:

1
2
GEODIST key member1 member2 [unit]
GEODIST cities "Beijing" "Shanghai" km(计算北京和上海之间的距离,单位为公里)

查找附近的位置:

1
2
GEORADIUS key longitude latitude radius [unit]
GEORADIUS cities 116.4074 39.9042 100 km(查找北京附近100公里内的所有城市)

查找某个位置附近的位置:

1
2
GEORADIUSBYMEMBER key member radius [unit]
GEORADIUSBYMEMBER cities "Beijing" 100 km(查找北京附近100公里内的所有城市)

georadius 以给定的经纬度为中心, 返回键包含的位置元素当中, 与中心的距离不超过给定最大距离的所有位置元素。

georadiusbymember 和 GEORADIUS 命令一样, 都可以找出位于指定范围内的元素, 但是 georadiusbymember 的中心点是由给定的位置元素决定的, 而不是使用经度和纬度来决定中心点。

应用场景

附近的人:比如类似微信的附近的人,以自己为中心,找其他的人,这种场景,就可以使用GEORADIUS 。

基于地理位置推荐:比如推荐某个位置附近的餐厅,都可以实现

计算距离:大家会遇到这种场景,比如当你购物的时候,美团外卖会告诉你商家距您多远,也可以通过 geo 来进行实现。

四、Stream(不是重点)

stream 是 redis5.0 版本后面加入的。比较新,以至于很多老八股题目,都没有提到这个类型。还有就是本身应用度的场景真的不多,类似 mq,但是如果 mq 的场景,大家一般会选择正宗的 rokcetmq 或者 rabbit 或者 kafka,所以这种类型,大家稍微知道即可。

Redis中的流结构用来处理连续不断到达的数据。你可以把它想象成一条流水线,数据像流水一样源源不断地流过来,我们可以在流水线的不同位置对这些数据进行处理。

主要目的是做消息队列,在此之前 redis 曾经使用发布订阅模式来做,但是发布订阅有一个缺点就是消息无法持久化。非常脆弱,redis 宕机,断开这些,都会产生造成丢失。stream 提供了持久化和主备同步机制。

概念解析

消息(Message):流中的每一条数据。每条消息都有一个唯一的ID和一组字段和值。

流(Stream):存储消息的地方。可以把它看作一个消息队列。

消费者组(Consumer Group):一个或多个消费者组成的组,用来处理流中的消息。

消费者(Consumer):处理消息的终端,可以是应用程序或服务。

应用场景

如果需要轻量级,很轻很轻,没有 mq 的情况下,可以使用 redis 来做,适合处理需要实时处理和快速响应的数据。比如做成用户消息实时发送和接收、服务器日志实时记录和分析、传感器数据实时收集和处理。

不过需要注意的是,正常来说 mq,mqtt 等等在各自场景有比较好的应用。

常见命令

添加消息到流:

1
2
3
XADD stream-name * field1 value1 [field2 value2 ...]
XADD mystream * user jichi message "Hello, world!"
他会向流mystream添加一条消息,消息内容是user: jichi, message: "Hello, world!"。

读取消息:

1
2
3
XREAD COUNT count STREAMS stream-name ID
XREAD COUNT 2 STREAMS mystream 0
会从流mystream中读取前两条消息,也就是读取到jichi 的hello world

创建消费者组:

1
2
3
XGROUP CREATE stream-name group-name ID
XGROUP CREATE mystream mygroup 0
会为流mystream创建一个名为mygroup的消费者组。

消费者组读取消息:

1
2
3
XREADGROUP GROUP group-name consumer-name COUNT count STREAMS stream-name ID
XREADGROUP GROUP mygroup consumer1 COUNT 2 STREAMS mystream >
会让消费者组mygroup中的消费者consumer1读取流mystream中的前两条消息。

确认消息处理完成:

消费者处理完成,应该进行 ack。

1
2
3
XACK stream-name group-name ID
XACK mystream mygroup 1526569495631-0
确认消费者组mygroup已经处理完了ID为1526569495631-0的消息。

👌什么情况下redis哨兵模式会产生数据丢失

发表于 2025-05-06 | 更新于 2025-09-14 | 分类于 Redis
字数统计 | 阅读时长

👌什么情况下redis哨兵模式会产生数据丢失?

口语化答案

其实 redis 在这种情况下的数据丢失,主要就是 4 点,第一点是主从复制的延迟,如果本身复制的过程存在延迟,然后数据还没有从节点的时候,就发生故障,会导致数据丢失。第二个就是故障转移,比如在从节点升级为新主节点的过程中,可能还有部分数据请求写入老节点。也会造成数据丢失。第三代呢就是同步故障,比如一个新的节点刚加入,正同步数据呢,结果主节点挂了,也会丢失。最后一个就是万恶的网络,网络分区会导致集群节点无法通信。以上。

题目解析

这个主要是看你对数据丢失这块,是否有过一定的完整性思考。常见的问题,无非总结就是 延迟,切换,网络三大点。其他的数据丢失题,其实也可以用这个往上套。

面试得分点

主从复制延迟,故障转移写入,同步故障,网络分区

预防措施

为了尽量减少哨兵模式下的数据丢失,可以采取以下措施:

1、 尽量使用半同步复制:通过配置min-slaves-to-write和min-slaves-max-lag参数,确保主节点在写入数据时,至少有一定数量的从节点已同步数据。

2、 优化故障检测和切换参数:根据实际情况优化哨兵的故障检测和切换参数,确保能够及时、准确地进行故障检测和切换。

什么是redis事务机制

发表于 2025-05-06 | 更新于 2025-09-14 | 分类于 Redis
字数统计 | 阅读时长

👌什么是redis事务机制?

口语化回答

好的,面试官,redis 的事务机制不算是一个像 mysql 一样的正常的事务,只是保证了正常情况下的原子性,一场情况不提供回滚机制。redis 主要是将事务的命令,放在一个队列,一起准备顺序执行。主要是 multi,exec,watch 命令配合使用。使用非常简单,先标记事务开始后,放入命令,再通过 exec 执行即可。以上。

题目解析

实际场景的事务还是用的很少的,这道题也不是特别重点,了解一下 redis 这个事务特性即可。

面试得分点

原子性、不支持回滚、watch 乐观锁

题目详细答案

redis中事务是一组命令的集合,一组命令要么全部执行,要么全部不执行。事务在Redis中是通过流水线(Pipeline)技术实现的,所有命令在执行之前都会被放入一个队列中,直到执行EXEC命令时,所有命令才会按顺序执行。

命令操作

MULTI

MULTI命令用于标记一个事务的开始。执行MULTI后,所有的命令都会被放入一个队列中,而不是立即执行。

1
MULTI

EXEC

EXEC命令用于执行从MULTI命令开始后放入队列中的所有命令。所有命令会按顺序执行,并且在执行过程中不会被其他客户端的命令打断。Redis事务在执行EXEC命令时具有原子性,即所有命令要么全部执行,要么全部不执行。Redis事务并不支持回滚机制。如果在事务执行过程中发生错误,已经执行的命令不会被回滚。

1
EXEC

DISCARD

DISCARD命令用于放弃从MULTI命令开始后放入队列中的所有命令,并且取消事务。

1
DISCARD

WATCH

WATCH命令用于监视一个或多个键,在事务执行之前,如果这些键被其他客户端修改,事务将被中止。WATCH命令通常用于实现乐观锁。这样可以防止事务中的数据竞争问题。

1
WATCH key1 key2

事务的工作原理

事务的执行过程

1、 开始事务:使用MULTI命令开始一个事务。

2、 命令入队:在事务开始之后,所有的命令都会被放入队列中,而不是立即执行。

4、 执行事务:使用EXEC命令执行队列中的所有命令。如果在使用WATCH监视的键在事务执行前被修改,事务将被中止。

4、 放弃事务:使用DISCARD命令可以放弃当前事务队列中的所有命令。

代码示例

1
2
3
4
5
6
7
# 开始事务
MULTI
# 添加命令到事务队列
SET key1 value1
SET key2 value2
# 执行事务
EXEC

如果在事务执行之前,使用WATCH命令监视了某个键,并且该键在事务执行前被修改,事务将被中止:

1
2
3
4
5
6
7
8
9
# 监视键
WATCH key1
# 开始事务
MULTI
# 添加命令到事务队列
SET key1 value1
SET key2 value2
# 执行事务(如果key1在此之前被修改,事务将被中止)
EXEC

什么是redis哈希槽

发表于 2025-05-06 | 更新于 2025-09-14 | 分类于 Redis
字数统计 | 阅读时长

👌什么是redis哈希槽的概念?

口语化回答

好的,面试官,redis 的哈希槽是集群模式下的一个概念,目的是为了实现数据的分布和管理,水平扩展,哈希槽总共有 16384 个,每个节点管理一部分的哈希槽,然后 key 通过取模算法后映射过来。再根据哈希槽对应的管理节点,就可以找到数据。初始的分配哈希槽可以采用手动指定的方式,也可以采用 redis 的工具的形式分配。有了哈希槽这种方式,能够将数据均匀分布到集群中的各个节点上,避免某些节点过载。同时实现负载均衡,确保每个节点的负载大致均衡。以上。

题目解析

集群模式必考题,如果简历写了 redis 的集群模式,这道题必考,大家一定注意。

面试得分点

哈希槽,取模,集群,数据分配

题目详细答案

哈希槽是Redis集群里的一个概念,主要用于实现数据的分布和管理。集群模式中数据是分布在多个节点上的,哈希槽的机制是确定每个键应该存储在哪个节点上。Redis集群中有16384个哈希槽(编号从0到16383)。每个键在存储之前都会根据其哈希值被映射到其中一个哈希槽。

哈希槽的工作原理

Redis使用CRC16算法对每个键进行哈希计算,然后对16384取模,得到一个哈希槽编号。例如,假设键为”mykey”,其哈希值计算结果为12345,那么12345 % 16384的结果就是哈希槽编号。集群中的每个节点负责管理一定范围的哈希槽。Redis可以把数据分布到多个节点上。例如,节点A可能负责哈希槽0到5000,节点B负责哈希槽5001到10000,节点C负责哈希槽10001到16383。

哈希槽的分配和迁移

初始分配: 集群启动时哈希槽会被分配到不同的节点上。可以手动指定每个节点负责的哈希槽范围,也可以使用Redis提供的工具自动分配。

数据迁移: 当集群中的节点发生变化(如新增节点或删除节点)时,Redis会重新分配哈希槽(重新分片resharding),相应的数据从一个节点迁移到另一个节点。

哈希槽的作用

数据分布: 通过哈希槽机制,Redis能够将数据均匀分布到集群中的各个节点上,避免某些节点过载。

负载均衡: 当集群扩展或缩减时,Redis可以通过重新分配哈希槽来实现负载均衡,确保每个节点的负载大致均衡。

高可用性: Redis集群中的每个节点可以有一个或多个副本(从节点),这些副本也会根据哈希槽进行数据复制,从而提高系统的高可用性和容错能力。

示例

假设我们有一个Redis集群,包含三个节点:节点A、节点B和节点C。哈希槽的分配如下:

  • 节点A:负责哈希槽0到5460
  • 节点B:负责哈希槽5461到10922
  • 节点C:负责哈希槽10923到16383

当我们插入一个键”mykey”时,Redis会计算其哈希值,并映射到相应的哈希槽。例如:

1
2
CRC16("mykey") = 12345
12345 % 16384 = 12345

键”mykey”会被存储在负责哈希槽12345的节点C上。

👌什么是缓存击穿?

发表于 2025-05-06 | 更新于 2025-09-14 | 分类于 Redis
字数统计 | 阅读时长

👌什么是缓存击穿?

口语化回答

好的,面试官,缓存击穿主要是高并发情况下,某个热点key突然失效或者未被缓存,导致大量请求直接穿透到后端数据库,从而使得数据库负载过高,甚至崩溃的问题。经常会有小伙伴和缓存穿透弄混,一个比较好的区分点就是可以理解为单 key,同时重建缓存需要时间。解决这个问题,一般常见的两种方案,一个是互斥锁,在多请求情况下,只有一个请求会去构建缓存,其他的进行等待,这种主要是要考虑好死锁的问题和请求阻塞的问题。另一种就是设置一个逻辑过期时间,去进行异步的缓存更新,缓存本身永远不会过期,这样也就避免了击穿的问题。但是复杂性和逻辑时间的设置就比较考验设计。一般情况下互斥锁方案即可。以上。

题目解析

redis 经典三问之一,要注意和穿透的区分,很多小伙伴分不清,这是不行的。主要是考核大家对于互斥锁的死锁隐患的考虑以及数据一致性的考虑。看是否有这些方面的一些思考。一定要细致理解其中的图和数据。

面试得分点

互斥锁,不过期,一致性,高并发吞吐量

题目详细答案

缓存击穿是指在高并发的情况下,某个热点key突然失效或者未被缓存,导致大量请求直接穿透到后端数据库,从而使得数据库负载过高,甚至崩溃的问题。

这里要注意一个点就是比如构建这个 key 的缓存需要一定的时间,例如当缓存没有,查询数据后,重新放入缓存的过程需要一定的时间,如果这个时候,不进行控制,可能有很多请求都在做同一件事构建缓存,可能会引发数据库的压力剧增,或者影响到第三方服务。

解决方案

1、互斥锁

在缓存失效时,通过加锁机制保证只有一个线程能访问数据库并更新缓存,其他线程等待该线程完成后再读取缓存。核心重点 :只有一个线程访问数据库和建立缓存。

根据上面的流程图,我们可以看到一个非常具体的实现步骤:

  1. 当缓存失效时,尝试获取一个分布式锁。
  2. 获取锁的线程去数据库查询数据并更新缓存。
  3. 其他未获取锁的线程等待锁释放后,再次尝试读取缓存。

代码实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
public String getValue(String key) {
String value = redis.get(key);
if (value == null) {
// 尝试获取锁
boolean lockAcquired = redis.setnx("lock:" + key, "1");
if (lockAcquired) {
try {
// 双重检查锁,防止重复查询数据库
value = redis.get(key);
if (value == null) {
value = database.query(key);
redis.set(key, value, 3600); // 1小时过期
}
} finally {
// 释放锁
redis.del("lock:" + key);
}
} else {
// 等待锁释放,再次尝试获取缓存
while ((value = redis.get(key)) == null) {
try {
Thread.sleep(100); // 等待100毫秒
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}
}
}
return value;
}

注意:锁的实现要确保高效和可靠,避免死锁和性能瓶颈。可以设置锁的过期时间,防止因异常情况导致锁无法释放。

2、不过期

设置一个较长的缓存过期时间,同时在缓存中存储一个逻辑过期时间。当逻辑过期时间到达时,后台异步更新缓存,而不是让用户请求直接穿透到数据库。这种方案可以彻底防止请求打到数据库,不过就是造成了代码实现过于复杂,因为你需要尽可能的保持二者的一致。

实现步骤:

  1. 在缓存中存储数据时,附带一个逻辑过期时间。
  2. 读取缓存时,检查逻辑过期时间是否到达。
  3. 如果逻辑过期时间到达,异步线程去数据库查询新数据并更新缓存,但仍返回旧数据给用户,避免缓存失效时大量请求直接访问数据库。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
class CacheEntry {
private String value;
private long expireTime;

public CacheEntry(String value, long expireTime) {
this.value = value;
this.expireTime = expireTime;
}

public String getValue() {
return value;
}

public boolean isExpired() {
return System.currentTimeMillis() > expireTime;
}
}

public String getValue(String key) {
CacheEntry cacheEntry = redis.get(key);
if (cacheEntry == null || cacheEntry.isExpired()) {
// 异步更新缓存
executorService.submit(() -> {
String newValue = database.query(key);
redis.set(key, new CacheEntry(newValue, System.currentTimeMillis() + 3600 * 1000)); // 1小时逻辑过期
});
}
return cacheEntry != null ? cacheEntry.getValue() : null;
}

方案对比

互斥锁要注意的点是,阻塞等待可能会存在死锁或者请求阻塞的情况,降低了高并发的吞吐量。

不过期这种方式,设置逻辑时间是一个非常考验功底的情况,设置的过程,数据不一致性的时间就越长,所以要考虑好方案和业务情况。互斥锁,就不存在这种问题。各有优势,按照情况来进行选择。

<i class="fa fa-angle-left"></i>123…12<i class="fa fa-angle-right"></i>

239 日志
22 分类
30 标签
GitHub
© 2025 javayun
由 Hexo 强力驱动
主题 - NexT.Gemini