通用后端面试题收集(持续中)

Mysql

数据排序是如何实现

答:首先要分情况讨论,假如select的列有创建索引,那么就使用索引排序,因为索引是有序的,这样排序速度最快。 反之,则用文件排序,此时就要根据文件的大小来决定采取何种方式,如果数据量小,那么就采取内存排序,一般是单路排序和双路排序;如果数据量大,那么采取外部排序,一般是归并排序。

索引有哪些?

答:mysql索引可从数据结构和索引性质两个角度划分;按照数据结构来说,有B+树索引、哈希索引、倒排索引、R-树索引。

而按照索引性质来划分:可以分为普通索引,主键索引,联合索引、唯一索引、全文索引、空间索引。

而日常开发中,使用B+树索引的频率比较高。B+树又可以分为聚簇索引和非聚簇索引。

面试官可能会问:
请问什么是B+树索引

B+树索引就是mysql结合B树和索引顺序升级开发的一种平衡搜索树。实际底层实现跟B树没有关系。特点是非叶子节点只存储索引,而叶子结点存储实际的数据。所有叶子结点都是由一个双向链表管理和连接的,数据都是排好序的。因为这些特点,索引B+树适合进行范围查询。

InnoDB什么采用B+树索引?

答:首先InnoDB的设计目标主要是面向OLTP应用,该应用的特点就是高并发、需要支持事务等。OLTP通常涉及到对数据进行添加、删除、修改和查询等操作。那么就需要尽可能快的执行这些操作,InnoDB给出的方案就是使用B+树,这个树因为非叶子结点只存储索引值,相比于B数的索引+数据来说,可以存储更多的索引值,那么在查询数据时,一次性读入内存所需要查找的关键字也就越多,减少了I/O操作次数。

也因为这个特点,在进行删除或插入时,只需要调整非叶子结点的指针即可,相比其他索引结构更简单快速。

因为叶子结点是按主键顺序排序的,那么进行范围查找的效率更高。

聚簇索引和非聚簇索引有什么区别
  • 在InnoDB中聚簇索引是主键索引和数据结构构成,按照索引就可以获取完整的数据,而非聚簇索引则是由索引字段和数据行的主键构成,如果索引字段不包含搜索所需的字段,那么就要通过主键去查找完整数据,就可能会进行回表查询,从而出现随机I/O操作,会降低mysql的性能。
  • 所以聚簇索引适合范围查询和排序,而非聚簇索引则适合搜索特定列的数据。
什么是回表

答:回表就是使用非聚簇索引搜索时,索引字段不包含搜索所需的字段,那么就要通过主键去查找完整数据的过程。往往会带来随机I/O操作,使得查询慢。

原因是因为非聚簇索引底层是用索引字段进行排序,那么主键id必定是不连续的,所以去主键索id频繁查询会造成大量随机I/O。

什么情况会使索引失效
  • 模糊查询,比如LIKE
  • 对索引字段进行操作(比如索引中使用了运算select * from t1 where id+3=5),可能导致索引结构发生改变,导致无法使用索引。
  • order by的时候,如果发生回表操作,那么对比全文搜索进行排序,回表消耗更大,那么mysql就会选择使用全文搜索,而不是走索引。
  • 表中两个字段进行比较,select * from t1 where id > age
  • 数据类型进行转换,因为mysql有隐式转换规则,大部分情况是由字符串转换成数字,那么假如使用varchar b字段创建了索引,那么当拿b跟一个数字比较时,就会发生类型转换,把b转换成数字,此时索引树的结构就改变了,从而导致索引失效。
  • 但现在mysql有一个优化器,他会进行一个评估,假如他评估索引搜索不快,那么也可能导致索引失效。但这个评估成本会因为不同参数而不同,所以如果自己评估和测试索引搜索更快,是可以强制设置,让mysql使用索引搜索。可以使用FORCE INDEX关键词 比如SELECT * FROM users FORCE INDEX (idx_email) WHERE email = 'example@example.com';
什么情况下可以使用索引

答:

  1. 某些字段搜索频率比较大,且不是数据量大的字段,可以将这些字段组合成联合索引,增加搜索效率,也不用创建过多的索引。
  2. 对经常在order by、group by、distinct后面字段建立索引。
什么情况下不建议使用索引
  1. 某些字段频繁修改,不建议建立,因为每次增加、删除或修改都可能导致重新维护索引树的结构
  2. 长字段和字段值大量重复的不要建立索引。
什么是联合索引

答:

就是将多个非主键字段组合成一个索引的字段,它属于非聚簇索引,构建的B+树叶子结点只存放了索引字段值和主键id

联合索引的最左前缀原则是什么?不遵守会有什么影响

答:

在使用联合索引查询时,查询条件必须从索引的最左边开始匹配,从第一个一直匹配到最后一个,如果不按照顺序,那么就无法使用索引搜索。比如a,b,c组成的索引,当搜索条件是a=1, c=1,b=1时则无法使用索引。只有a=1,b=1,c=1时才能使用。但在Mysql5.6之后,有一个索引下推的优化,他在引擎层,查询到a=1的数据后,利用c过滤不符合的数据,再返回给server层

事务的隔离级别有哪些?

首先mysql默认使用InnoDB引擎,支持事务,如果使用的是MYISAM引擎,那么就不支持事务。

而InnoDB引擎的事务级别有

  • 读未提交:允许事务读取未提交的数据,可能会导致脏读。
  • 读已提交:允许事务读取已提交的数据,避免了脏读,但可能会出现不可重复读和幻读。
  • 可重复读:保证在一个事务中多次读取同一数据的结果一致,但仍然可能发生幻读。
  • 串行化:要求所有事务串行执行,避免了不可重复读和幻读,但效率最低。

默认级别是可重复读。原因是:

  • 解决不可重复读问题,同一个事务多次查询的结果是一致的。
  • 可以解决大部分幻读问题,就是第一次查询没有返回某些数据,但是第二次查询时却因为其他事务插入了数据,导致查询结果发生变化。InnoDB还使用了Next-key Lock(下一键锁),有效的减少幻读的发生。
  • 相比于 串行化 隔离级别,可重复读 提供了较好的并发性能。虽然 可重复读 允许事务间的并发执行,但它通过保证数据读取的一致性,避免了多个事务对同一数据的修改冲突,同时避免了大范围的锁竞争和资源浪费。

ps:名词解释

  • 脏读:是指一个事务读取了另一个事务未提交的数据,如果未提交的事务被回滚,那么第一个事务读取的数据就是无效的。
  • 不可重复读:是指在一个事务内多次读取同一数据,由于其他事务的更新操作,导致数据不一致。
  • 幻读:指在一个事务中两次执行相同的查询,结果却因为其他事务的插入或删除操作而不同。
面试官可能会问:
什么是事务

答:是一组不可分割的工作单元,通常由多个DML(Data Manipulation Language)语句组成。

它用于处理操作量大、复杂度高且业务上需要保证要么都执行,要么都不执行的场景。确保数据的完整性和一致性

可重复读级别是如何实现

答:通过锁、MVCC(多版本并发控制)、Redo Log、Undo Log。

锁来控制事务对数据的并发访问,从而实现事务的隔离性;

使用MVCC,实现非锁定读的需求,提高了并发度;

Redo Log记录了事务对数据库的所有操作,当数据库宕机会崩溃时,可通过重放Redo Log来恢复数据,用来满足事务的持久性;

Undo Log则是记录了事务对数据的旧版本信息,用于事务回滚和MVCC,使得事务失败时回滚到某个版本的数据,从而实现一致性和隔离性。

InnoDB有哪些锁

答:

常用的有行锁、表锁、间隙锁、下一键锁。

行锁还可以分为共享锁和排它锁。

共享锁是允许多个事务同时读取一个数据,但不允许修改

排它锁则是只允许一个事务修改数据,并禁止其他事务读取或修改该行。如果某行数据被共享锁控制,那么只有等共享锁释放后,才能申请排它锁。

间隙锁则是用于锁定一个范围内的数据,即使该范围内没有数据存在。这确保了在事务执行过程中,其他事务不能在这个范围内插入新数据。从而避免幻读发生

下一键锁则是行级锁和间隙锁的结合,锁定具体行和其一定范围内的空间,确保一个范围内不会出现幻读。

如果是根据数据库操作来区分,可以分为乐观锁和悲观锁

乐观锁是认为数据在处理过程中不会被其他事务修改,因此在操作数据时不加锁 ,但是在更新数据时进行版本控制或校验。如果发现数据被其他事务修改了,那么当前事务就会失败,并重试。这个性质决定了乐观锁适合在读多写少的场景下使用,如果是写操作较多,可能会导致版本冲突和重试。

MVCC就是乐观锁的实现方式之一。

悲观锁是任务数据在处理过程中可能会被其他事务修改,因此在操作数据时就加锁,确保数据不会被其他事务修改。常见于对数据一致性要求较高、写多读少的场景。但他可能会导致性能开销较大。使用时可通过for update加锁。

行锁、表锁是悲观锁的实现方式之一

什么是MVCC

答:是一种并发控制机制,允许多个事务同时读取和写入数据库,而无需互相等待,从而提高数据库的并发性能。

其实现原理是利用Undo Log记录的版本记录。

你上一家公司使用了什么级别的事务实现?

答:并未去了解过项目组使用的事务级别,但是如果希望并发的性能更好以及降低死锁发生的概率,但对于幻读问题导致的问题的影响范围评估适合,那么可以采用RC(读已提交)。否则还是使用可重复读级别比较适合大部分场景。

什么死锁

死锁是多个进程间,互相占用了其他进程所需的资源,导致进入阻塞状态,如果没有外力的介入,将一直处于等待资源的死循环。

如果要减少这种情况发生,那么可以从两方面入手

预防死锁:

  • 调整锁的申请顺序,尽量使一个进程一次性获得所有资源后,其他进程等待他释放后,按顺序一个个申请。
  • 开启死锁检测
  • 合理建立索引,减少加锁范围
  • 避免大事务,将大事务拆分成若干小事务

如果已经发生了,那么可以采用这些方式

  • 手动执行kill命令杀掉发生死锁的语句
  • 使用mysql自带的死锁检测机制,当检测到死锁时,数据库自动回滚一个事物。通常回滚持有资源最小的事务。

如何优化sql语句

  • 首先可以开启mysql的慢sql检测,通过设置set global slow_query_log = 'ON'开启。然后设置一个检测阈值,通过set global long_query_time=3,表示当一个sql语句执行3s,就会被记录到慢日志。这个功能开启时需要谨慎,避免在生产环境机器中使用。最好是在开发或者测试环境,通过压力测试检查。

  • 然后使用explain分析慢sql语句的执行计划。

  • 如果该sql语句查询某些字段比较频繁,且执行计划中显示没有使用索引,那么就可以考虑创建一个联合索引。

  • 检查是否所有查询字段都是必须的,如果有无用的,可以考虑删除。

  • 避免使用LIKE,会导致全表扫描。

  • 如果查询的数据集比较大,建议将一个sql语句拆分成若干个,比如表中有200w个数据,你要查1~100w的数据有哪些,那么就拆分成where id between 1 and 1000,where id between 1001 and 2000,以此类推。

  • 如果使用了join,并且连接的表比较大,那么可以考虑增加冗余字段,减少join的使用,避免大表间联结查询导致性能消耗大。

  • 数据量大,业务上也可以采取分页查询。如果超大规模,那么就要考虑分库分表。

  • 检查数据库实例所在机器的性能配置,是否满足业务需求,如果不满足就适当增加资源。

  • 如果sql语句本身优化无明显提升,且该sql查询的数据更新频率不高,可以考虑使用缓存的方式,减少数据库的压力

InnoDB和MyISAM的区别

  • InnoDB支持事务,MyISAM不支持事务
  • InnoDB支持行级锁、表锁,MyISAM只支持表级锁,因此MyISAM相比InnoDB的写入性能低
  • InnoDB支持redo log实现安全恢复,但MyISAM不支持,只能通过手动恢复。
  • InnoDB适合对数据一致性和并发性有要求的场景,例如电商平台,MyISAM适合读多写少的场景,例如数据仓库
  • InnoDB支持外键,MyISAM不支持。
  • InnoDB的索引和数据都是用一个文件存储,MyISAM则是分开两个文件,MYI文件存储索引,MYD文件存储数据

sql场景题

第一题

题目: MySQL中,有一张成绩表,有学生、班级、成绩三个字段。如何查询每个班级的最高分,并且如果最高分有并列,也要能查出来?

解答:使用窗口函数

sql 复制代码
SELECT student_name, class_name, score
FROM (
    SELECT *,
           DENSE_RANK() OVER (PARTITION BY class_name ORDER BY score DESC) as dense_rank_num
    FROM scores
) ranked_scores
WHERE dense_rank_num = 1;

或者
SELECT student_name, class_name, score
FROM (
    SELECT *,
           RANK() OVER (PARTITION BY class_name ORDER BY score DESC) as rank_num
    FROM scores
) ranked_scores
WHERE rank_num = 1;

窗口函数详解:

  • PARTITION BY class_name:按班级分组计算
  • ORDER BY score DESC:按分数降序排列
  • RANK():分数相同的会得到相同排名,但如果有并列第一,下一个是第三名
  • DENSE_RANK():分数相同的会得到相同排名,即使有并列第一,下一个是第二名

MongoDB

对比文档模型与表模型的优劣势

  • 文档模型(MongoDB)优势:

    • 模式灵活:无需预定义Schema,同一集合内的文档结构可不同。支持快速迭代开发,加字段无需DDL和迁移。
    • 数据局部性:相关数据通常内嵌在单个文档中(如将订单项内嵌在订单文档里),一次读操作即可获取所有关联数据,读性能高。
    • JSON友好:天然适合JSON API,序列化/反序列化简单。
  • 文档模型劣势:

    • 弱事务性:4.0前不支持多文档事务,即便现在支持,其性能也低于关系型数据库。复杂业务逻辑的ACID保证较弱。

    • 关联查询弱:$lookup类似左连接,但性能远不及关系型数据库的优化Join。多对多关系建模复杂。

    • 数据冗余:内嵌文档可能导致数据重复,更新时需要更新多处(反范式化)。

    • 约束靠应用层:唯一性、外键等约束需在代码中维护,易出错。

网络

TCP可靠传输

快速重传

  • TCP 协议为每个发送的字节数据分配了唯一的序列号(Sequence Number)。接收方必须按序列号的顺序将数据包重组为完整的字节流,才会提交给应用层
  • 接收方会通过 ACK(确认应答) 告知发送方已成功接收的数据范围。如果发送方检测到某个序列号的数据包丢失或超时未确认,会触发重传机制,确保数据最终必定到达且有序。

TCP三次握手和四次挥手

三次握手是tcp创建连接时发生的,他的目的是为了建立稳定、安全、可靠的通信通道,解决数据在不可靠的IP网络上传输问题。他的过程大致是这样

  1. 客户端发送一个SYN消息到服务器,客户端转换都SYN_SENT状态
  2. 服务器接收到消息后,切换到LISTEN状态,返回一个确认消息SYN_ACK
  3. 客户端接收到确认消息后,切换到ESTABLISHED状态,再发送一个确认消息通知服务器接受到SYN-ACK消息,从而完成三次握手。然后开始数据传输

四次挥手是tcp关闭连接时所进行的过程,他的目的是确保双方都能正常、可靠地关闭连接,并释放相关资源。大致流程是这样:

  1. 客户端发送FIN消息,进入FIN WAIT1状态。服务器接受到后,表示不会再接受数据,但仍可能继续发送数据给客户端。
  2. 服务器发送ACK消息,确认已收到FIN消息。此时服务器进入CLOSE_WAIT状态,客户端接受到ack消息后进入FIN WAIT2状态。
  3. 服务器完成所有数据传输后,发送FIN消息,进入LAST_ACK状态。客户端接收到FIN消息后,准备关闭连接。
  4. 客户端发送一个ACK消息进行确认,然后进入TIME_WAIT状态。服务器收到ACK消息后关闭连接,客户端则等待一段时间,确保所有数据都正确传输,然后关闭连接。这个时间通常是2MSL,即报文最大生存时间,这样确保报文都超时。
面试官可能问

最好画图讲解

TCP握手和挥手期间发生异常,会有什么影响

假如在第一次握手消息丢失了,比如服务器宕机或者网络拥堵等问题,客户端会触发超时重传机制,如果重传阶段都没有响应,则断开连接。

假如发生在第二次握手时,服务器如果一段时间没有收到回复的报文,那么就会发起重传。而客户端因为无法判断是那次握手失败,所以客户端也会发起重传。

假如发生在第三次握手时,客户端由于发送ACK报文后,会变为ESTABLISH状态。此时ACK丢失,服务端由于没有接受到,就会重传SYN-ACK报文。理论上客户端不会再发ACK报文,而是直接发数据,那么这里服务端则会在接收到数据时,判断已建立连接,并进入ESTABLISHED状态。

挥手阶段:

假如第一次挥手时,客户端FIN包丢失,那么客户端会发起重传

如果是服务器第一次回复的ACK包丢失,客户端会触发重传机制,而服务器则进入CLOSE_WAIT状态,开始做断开连接前的准备工作。准备好后,会回复FIN_ACK报文,只要这个消息没有丢失,客户端可以凭借其中的ACK序号直接从FIN_WAIT1状态进入TIME_WAIT状态。

如果FIN_ACK丢失,服务端超时后会触发重传,那么客户端有两种情况,要么处于 FIN-WAIT-2 状态(之前的 ACK 也丢了),会一直等待,如果超过系统设置的tcp_fin_timeout 的限制后,就会断开连接;要么处于 TIME-WAIT 状态,会等待 2MSL 时间后再断开连接。

如果是最后客户端回复的ACK报文丢失,那么服务端会开启重传机制,发送FIN_ACK,超过重传次数后仍未收到,服务器则断开连接。客户端则是等待2MSL后断开。

TCP超时重传机制是什么,解决了什么问题

超时重传机制tcp为了解决传输过程中,报文丢失或延迟到达导致数据不完整的问题。此机制一般设有重传次数,避免无限重传,浪费资源。

重传机制的工作流程一般是:

  1. 发送数据包并启动一个计时器,等待接受接收方的ACK包到达
  2. 接收方接收到数据包并发送确认包
  3. 如果发送方未接受到确认包,则重发数据并重启计时器。如果接收到确认包,则停止计时器。
  4. tcp通常设有一个阈值,避免无线重传。当重试次数超过阈值,tcp可能会报告错误并断开连接。

所以,超时重传机制可能会降低网络性能,增加网络负载。但tcp还使用了拥塞控制算法来调整发送速率,避免网络拥塞。

TCP拥塞控制的流程你可以说下吗

拥塞窗口(Congestion Window,cwnd)是 TCP 协议中用于拥塞控制的核心参数,它的主要作用是限制发送方在未收到确认的情况下可以发送的数据量,从而避免网络拥塞。

大致进行这几步:

  1. 慢启动:发送方在连接建立初期,缓慢地增加数据发送速率。此阶段会初始化一个拥塞窗口并设置有一个阈值,每次接受到ACK报文后,拥塞窗口会成倍增长。当窗口规模达到阈值时,tcp会进入拥塞避免阶段。
  2. 拥塞避免:此时拥塞窗口的增长速度从指数调整为线性增长。
  3. 快速重传:发送方在收到3个重复的ACK后,立即重传被认为丢失的报文段,无需等待超时。这个步骤是为了减少重传的延迟,迅速应对数据丢失。
  4. 快速恢复:在快速重传后,tcp不进入慢启动阶段,而是将拥塞窗口缩减到阈值的一半,然后开始线性增长,以快速恢复到丢包前的传输速率
TCP粘包和拆包可以说下吗

粘包是指多个数据包被接收方合并成一个数据包接收,导致接收方无法正确区分这些信息的边界,原因是发送方一般会缓存数据包,然后统一发送,而数据包之间没有明确的分界线,导致该问题。

拆包则是指接收方因为不知道数据的边界在哪,导致将一个数据包拆分成多个数据包,无法一次性接收完整的数据。原因可能是网络传输过程中MTU(最大传输单元)限制或者发送方缓存区大小限制,一个打包拆成几个小包发送。

解决这两个问题的方法,主要有使用定长消息、添加消息分隔符、在消息头添加一个长度字段,指示接受方根据该长度读取相应长度的数据。

TCP挥手为什么需要TIME_WAIT状态

这样做可以防止旧的重复分段干扰新连接,同时确保旧连接的所有报文都已超时失效后才允许新的连接使用相同的IP地址和端口。‌

如果代码中大量出现time_wait会有什么影响?可能是什么问题?

影响:

  • 资源浪费:会占用系统的文件描述符和其他资源,可能导致新创建的 socket 遇到"资源不足"的情况。
  • 端口不可用:当一个 socket 在关闭后进入 TIME_WAIT 状态时,该端口在一段时间内将无法被重新使用。这意味着,如果你的应用程序尝试频繁地绑定到同一端口,将会失败,直至 TIME_WAIT 状态的过期。
  • 性能问题: 大量的连接处于 TIME_WAIT 状态可能引起应用程序性能下降,尤其是在高并发场景下,因为它限制了可用于新连接的端口数量。
  • 服务不稳定: 对于需要快速重启或频繁重新建立连接的服务(例如 Web 服务器),大量处于 TIME_WAIT 的连接可能导致服务的不稳定性和可用性问题。

可能得原因是:

  • 每次连接都要创建新的tcp而不是复用
  • 错误的 socket 使用方式: 比如没有适当地调用 shutdown() 和 close(),导致操作系统认为仍需保持该状态以确保数据完整性。

优化的方法:

  • 对于协议层(如 HTTP/1.1 或更高版本),可以考虑使用持久连接(keep-alive),避免频繁建立和断开 TCP 连接。
  • 利用负载均衡器或反向代理来分散流量,减少单个节点上产生的大量 TIME_WAIT 连接。
TCP滑动窗口的作用是什么

用来进行流量控制中控制传输速率,即协调发送方和接收方的数据传输效率,防止接收方因为处理能力有限而被数据流淹没。

操作系统

什么是进程和线程

答:进程是资源配置的基本单元,进程之间互相独立,各自拥有自己的内存空间。

线程是CPU调度的基本单元,一个进程里可以开启多个线程。线程间共享进程的内存空间和资源,但是每个线程有自己独立的栈和寄存器。

可能问
他们的区别是什么

答:通信方式不同,进程之间的通信需要通过管道、消息队列、共享内存、套接字等。而同一个进程中的线程因为共享内存空间,因此可以直接读取内存,但是要注意使用同步机制避免数据错误。

请问你还听过协程吗?它是操作系统中存在吗?(或者问讲讲他是什么以及和进程线程有什么区别)

协程是程序员创造出来的,是一种用户态内的上下文切换技术。它能够在单线程内并发执行多个任务。可以成为轻量级线程。它是通过程序控制,而不是通过操作系统调度。通常在特定的点,比如I/O操作进行切换,这使得协程具有非抢占式的特性。它适用于高并发网络应用,比如聊天服务器、游戏等。

服务器中断是什么,分别是什么?

指CPU在正常运行程序时,由于内部/外部事件(或由程序)引起CPU中断正在运行的程序,而转到

为中断事件服务的程序中去,服务完毕,再返回执行原程序的这一过程。

可以分为硬中断和软中断:

  • 硬中断:由于外部硬件设备,比如例如磁盘、网卡、键盘等设备在检测到某种事件时会向CPU发送中断信号。
  • 软中断:由操作系统内核程序触发的,用于处理一些不能被中断的工作或延迟执行的任务。

虚拟地址的意义

  • 虚拟内存技术扩大了各自进程的地址空间
  • 每个进程运行在各自的虚拟地址空间,互相不干扰对方。虚拟内存为特定的内存地址提供写保护。可以防止代码或数据被恶意篡改
  • 内存中可以保留多个进程,当一个进程等待在等待数据读入内存的过程中,可以将cpu资源交给另外一个进程使用,提高并发性

Redis

为什么要用Redis

答:使用redis主要从两个角度考量,第一个是提高服务器的相应速度,提高用户的体验,比如说有些sql查询比较耗时,并且其更新频率不高,那么将其缓存在redis中,由于redis是基于内存存储的,读写速度高于mysql,那么用户再一次获取相同的数据时,就可以从redis中读取,从而减少查询所需的时间,但是需要保持数据库和缓存的数据一致性。

第二个角度则是提高系统的并发性,相同配置的服务器下,mysql的QPS明显低于Redis,那么使用Redis可以提高系统处理大量请求的能力。

Redis 常见数据结构以及使用场景分析

redis常见的数据结构有string字符串,list链表,set无序集合,hash哈希,sorted set有序集合。

说明下其常见的使用场景:

  • string:因redis提供incr、decr指令,可以对整数字符串进行增减操作,所以可以用来实现用户的访问次数、热点文章的点赞转发数量等等。
  • list:redis是用双向链表的数据结构实现的,所以他进行插入和删除的效率较高,但是随机搜索效率较低。常用于发布订阅或者作为消息队列使用,因为这些功能一般不需要提供随机访问。
  • hash:其是key-value结构,那么适合保存某个特定值下的数据,例如保存用户数据、商品信息
  • set:当你需要存储一个列表数据,又不希望出现重复数据时,set 是一个很好的选择,并且set 提供了判断某个成员是否在一个 set 集合内的重要接口。Redis 可以非常方便的实现如共同关注、共同粉丝、共同喜好等功能。
  • sorted set:: 和 set 相比,sorted set 增加了一个权重参数 score,使得集合中的元素能够按 score 进行有序排列,还可以通过 score 的范围来获取元素的列表。

为什么redis采用单线程模式,他又是怎么应对大量客户端请求?

答:redis6.0之前,Redis 选择使用单线程模式是为了避免了多线程上下文切换和竞争条件,提高了并发处理效率。其能处理大量客户端请求的原理是因为他使用了I/O多路复用监听客户端请求。

但是为了应对日益增长的数据量和网络请求,redis在6.0之后引入了多线程,提高网络I/O的处理速度。而指令的执行仍然保持单线程,所以无需担心线程安全问题。

redis的缓存删除策略

答:redis采取的策略是惰性删除和定时删除。

  • 惰性删除 :只会在取出 key 的时候才对数据进行过期检查。这样对 CPU 最友好,但是可能会造成
    太多过期 key 没有被删除。
  • 定期删除 :每隔一段时间抽取一批 key 执行删除过期 key 操作。并且,Redis 底层会通过限制删除
    操作执行的时长和频率来减少删除操作对 CPU 时间的影响。
    但是这样还可能漏删某些数据,这时redis给出的方案是内存淘汰机制。
可能会问
redis的内存淘汰机制是什么

相关问题:

答:可分为基于时间过期和全部淘汰。

基于时间过期:

  • volatile-lru(least recently used):从已设置过期时间的数据集中挑选最近最少使用的数据淘汰
  • volatile-ttl:从已设置过期时间的数据集中挑选将要过期的数据淘汰
  • volatile-random:从已设置过期时间的数据集中任意选择数据淘汰
  • volatile-lfu(least frequently used):从已设置过期时间的数据集中挑选最不经常使用的数据淘汰

全部淘汰策略:

  • allkeys-lru(least recently used):当内存不足以容纳新写入数据时,在键空间中,移除最近
    最少使用的 key(这个是最常用的)
  • allkeys-random:从数据集(server.db[i].dict)中任意选择数据淘汰
  • allkeys-lfu(least frequently used):当内存不足以容纳新写入数据时,在键空间中,移除最不经常使用的 key
MySQL 里有 2000w 数据,Redis 中只存 20w 的数据,如何保证 Redis 中的数据都是热点数据?

答:可以使用redis的内存淘汰机制中的allkeys-lfu。采取全部淘汰策略的原因是因为题目中的场景中,redis能存储数据量比mysql中的少很多,如果采取基于时间过期淘汰,如果非热点数据可能因为没到过期时间而无法删除,导致热点数据没有足够空间保存。所以采取全部淘汰策略。

怎么保证 Redis 挂掉之后再重启数据可以进行恢复

答:redis提供了2种持久化的操作,分别是RDB和AOF。默认采用RDB

RDB:通过创建快照来获得存储在内存里面的数据在某个时间点上的副本。

优点:

  • 创建一个子线程专门处理,不会影响redis继续处理客户端请求。
  • 生成的文件是压缩的二进制文件,适合备份和灾难恢复。

缺点:

  • 由于是间隔保存,如果redis崩溃,可能会丢失上次保存快照后的数据

AOF:通过将每个写操作追加到日志文件中实现持久化,支持将所有写操作记录下来以便恢复。

他提供了三种写回策略来决定何时将数据同步到磁盘:

appendfsync always #每次有数据修改发生时都会写入AOF文件,这样会严重降低Redis的速度

appendfsync everysec #每秒钟同步一次,显示地将多个写命令同步到硬盘

appendfsync no #让操作系统决定何时进行同步Copy to clipboardErrorCopied

注意:always并不能保证一定不丢失数据。因为redis是先执行命令再写入AOF,所以如果在写入AOF过程中redis宕机,重启后也无法利用AOF恢复。所以出于兼顾数据和写入性能的考虑,可以考虑 appendfsync everysec 选项 ,即使宕机,也只是丢失1s内产生的数据。

缓存击穿、缓存穿透、缓存雪崩的概念和解决方案

  1. 缓存穿透

    现象:查询一个数据库中一定不存在的数据。比如请求一个不存在的用户ID。缓存不命中,导致每次请求都穿透到数据库。

    解决方案:

    • 布隆过滤器 (Bloom Filter):在缓存之前加一层BF。查询时,先查BF,如果BF说"不存在",则直接返回空,避免后续所有查询。但BF有误判率(假阳性),且删除元素困难。

    • 缓存空对象 (Cache Null Object):更常用、更简单。即使从数据库没查到,也在缓存中存入一个特殊的空值(如user:9999: NULL),并设置一个较短的TTL(如30秒)。后续请求在TTL内会直接命中这个空值。注意:需要防止恶意攻击用大量不同的Key打满你的缓存。

追问

你说"缓存空对象"更常用,那如果有恶意用户用海量随机不存在的Key来攻击,你的缓存会不会被这些无用的空值占满?

  1. 设置很短的TTL:比如5-10秒,让无用Key快速自动清理。
  2. 监控与清理:监控缓存中"空值"Key的数量和占比,如果超过阈值,启动一个后台任务扫描并清理。
  3. 限流与风控:在应用层或网关节针对请求频率异常高的IP或用户进行限流,从源头扼杀攻击。
  1. 缓存击穿

    现象:某个热点Key突然过期的瞬间,大量请求同时涌向数据库,仿佛在缓存上"凿了一个洞"。

    解决方案:

    • 永不过期 + 异步更新:这是工程上最常见的方案。不给Key设置过期时间,而是用一个后台Job定时更新缓存。业务永远读到的是"可能稍旧但绝对可用"的数据。

    • 互斥锁 (Mutex Lock):当缓存失效时,只让一个请求去回源加载数据库,其他请求等待,加载完成后通知所有请求。在Redis中,可以用 SETNX命令实现分布式锁。缺点是增加复杂度,且可能有死锁风险。
      追问

在"缓存击穿"的"互斥锁"方案中,如果去更新缓存的那个请求挂了或者特别慢,会怎么样?

这会引发死锁或长时间阻塞问题,是这个方案的主要风险。

  1. 设置锁超时:获取锁时,必须设置一个合理的超时时间(如3秒)。即使持有锁的请求挂了,锁也会自动释放。
  2. 设置回源超时与降级:负责回源的请求,对数据库查询也要设置超时。如果超时,应释放锁并返回降级数据(如旧缓存或默认值)。
  3. 双重检查:在获取锁后、回源前,再次检查缓存是否已被其他请求更新,避免重复工作。
  1. 缓存雪崩

    现象:大量缓存Key在同一时刻或短时间内集中过期,导致所有请求直接打到数据库,造成数据库压力骤增甚至宕机。

    解决方案:

    • 随机过期时间:这是基础。在设置缓存过期时间时,加上一个随机值,如 TTL = base_time + random(0, 300)秒,让Key分散过期。

    • 二级缓存架构:引入本地缓存(如Caffeine)作为一级缓存,Redis作为二级缓存。即使Redis集群全挂,本地缓存也能扛一会儿。

    • 服务熔断与降级:当发现数据库压力过大时,启动熔断机制,暂时拒绝部分非核心请求,或返回降级内容(如默认榜单),保护数据库。

相关推荐
笨鸟先飞的橘猫8 小时前
自己实现自定义协议的思考
面试
女生也可以敲代码21 小时前
AI时代下的50道前端开发面试题:从基础到大模型应用
前端·面试
Cosolar1 天前
告别无脑循环:深入解析 ReWOO 与 Plan-and-Execute Agent 架构
人工智能·面试·全栈
Fuly10241 天前
技术经理面试相关--技术篇
面试·职场和发展
逻辑驱动的ken1 天前
Java高频面试考点18
java·开发语言·数据库·算法·面试·职场和发展·哈希算法
研究点啥好呢1 天前
高德多模态算法工程师面试题精选:10道高频考题+答案解析
python·面试·llm·求职招聘·笔试·高德
fzil0011 天前
自动投递简历 + 面试进度跟踪
人工智能·面试·职场和发展
其实防守也摸鱼1 天前
面试常问问题总结--护网蓝队方向
网络·笔记·安全·面试·职场和发展·护网·初级蓝队
one_love_zfl1 天前
java面试-微服务组件篇
java·微服务·面试