Cyan Blog Cyan Blog
首页
  • Java (opens new window)
  • JUC (opens new window)
  • JVM (opens new window)
  • Redis

    • Redis安装 (opens new window)
    • Redis基础 (opens new window)
    • Redis实战 (opens new window)
    • Redis集群安装 (opens new window)
    • Redis分布式缓存 (opens new window)
    • Redis多级缓存 (opens new window)
    • Redis原理 (opens new window)
  • 管理工具

    • Maven (opens new window)
    • Git (opens new window)
  • SSM

    • Spring (opens new window)
    • SpringBoot (opens new window)
    • Mybatis (opens new window)
    • MybatisPlus (opens new window)
  • 微服务

    • Docker (opens new window)
    • RabbitMQ (opens new window)
    • SpringCloud (opens new window)
    • Dubbo (opens new window)
    • MongoDB (opens new window)
    • Zookeeper (opens new window)
  • Java面试题 (opens new window)
  • JUC面试题 (opens new window)
  • JVM面试题 (opens new window)
  • Linux面试题 (opens new window)
  • SQL面试题 (opens new window)
  • Maven面试题 (opens new window)
  • Redis面试题 (opens new window)
  • SSM面试题 (opens new window)
  • SpringCloud面试题 (opens new window)
  • Linux (opens new window)
  • C++ (opens new window)
  • 数据库

    • MySQL (opens new window)
    • NoSQL (opens new window)
  • 软件测试

    • 软件测试 (opens new window)
  • 加密解密 (opens new window)
  • bilibili字幕提取 (opens new window)
  • 道理 (opens new window)
  • 关于博主

    • Github (opens new window)
    • CSDN (opens new window)
  • 关于本站

    • 如何搭建博客网站 (opens new window)
首页
  • Java (opens new window)
  • JUC (opens new window)
  • JVM (opens new window)
  • Redis

    • Redis安装 (opens new window)
    • Redis基础 (opens new window)
    • Redis实战 (opens new window)
    • Redis集群安装 (opens new window)
    • Redis分布式缓存 (opens new window)
    • Redis多级缓存 (opens new window)
    • Redis原理 (opens new window)
  • 管理工具

    • Maven (opens new window)
    • Git (opens new window)
  • SSM

    • Spring (opens new window)
    • SpringBoot (opens new window)
    • Mybatis (opens new window)
    • MybatisPlus (opens new window)
  • 微服务

    • Docker (opens new window)
    • RabbitMQ (opens new window)
    • SpringCloud (opens new window)
    • Dubbo (opens new window)
    • MongoDB (opens new window)
    • Zookeeper (opens new window)
  • Java面试题 (opens new window)
  • JUC面试题 (opens new window)
  • JVM面试题 (opens new window)
  • Linux面试题 (opens new window)
  • SQL面试题 (opens new window)
  • Maven面试题 (opens new window)
  • Redis面试题 (opens new window)
  • SSM面试题 (opens new window)
  • SpringCloud面试题 (opens new window)
  • Linux (opens new window)
  • C++ (opens new window)
  • 数据库

    • MySQL (opens new window)
    • NoSQL (opens new window)
  • 软件测试

    • 软件测试 (opens new window)
  • 加密解密 (opens new window)
  • bilibili字幕提取 (opens new window)
  • 道理 (opens new window)
  • 关于博主

    • Github (opens new window)
    • CSDN (opens new window)
  • 关于本站

    • 如何搭建博客网站 (opens new window)
  • 简历项目
  • Java面试题

  • 后端开发
  • 数据库面试题
  • Maven
  • MyBatis面试题
  • Spring面试题
  • SpringBoot面试题
  • SpringCloud面试题

  • Redis面试题

  • 设计模式
  • 中间件

  • 场景题
  • Other

  • Random

    • 随机题01
      • 项目拷打
      • Java
      • MySQL
  • 项目面试题

  • 面试
  • Random
2025-03-18
0
0
目录

随机题01

项目拷打 为什么不用Session认证? 你的项目使用 MQ 做了哪些事情? 消息重复消费问题如何避免? 你项目中怎么向前端传数据的? 项目用 Redis 做了什么? Redis 性能强大的原因是? 数据库和缓存一致性如何保证的? Java 为什么要用线程池? 怎么创建线程池? 线程池处理任务的过程? 对象的相等和引用相等有什么区别? String#equals()和 Object#equals()有何区别?MySQL 覆盖索引是什么? 你遇到慢SQL怎么办? MySQL 性能怎么优化?

# 项目拷打

# 为什么不用 Session 认证?

JWT 相较于 Session 认证,主要有以下优势:

  1. 无状态: 服务端无需存储 Session 信息,减轻服务器压力,提高可用性和伸缩性。但也因此存在 JWT 失效不可控的缺点,需要额外处理。
  2. 防 CSRF 攻击: JWT 通常存储在 localStorage 中,不依赖 Cookie,避免了 CSRF 攻击。但需注意 XSS 攻击风险,可通过过滤可疑字符串等方式防范。
  3. 适合移动端: JWT 可被客户端存储,且可跨语言使用,解决了 Session 认证在移动端状态管理、兼容性和安全性上的问题。
  4. 单点登录友好: JWT 保存在客户端,避免了 Session 认证中 Cookie 跨域等问题,更易实现单点登录。

关于 JWT 的详细介绍,强烈推荐看看我写的这两篇文章:

  • 什么是 JWT? 如何基于 JWT 进行身份验证? (opens new window)
  • JWT 有什么问题?真的是最好的选择吗? (opens new window)

# 你的项目使用 MQ 做了哪些事情?

在我们项目中,主要将 MQ 用于:

  • 异步处理: 例如用户下单成功后,发送消息通知邮件服务和短信服务异步发送通知,主流程无需等待,提高接口响应速度。
  • 服务解耦: 订单状态变更时,通过 MQ 发布事件,相关的下游服务(如积分服务、物流服务)可以独立订阅并处理,降低服务间的直接依赖。
  • 延迟消息: 实现订单超时未支付自动取消的功能。

# 消息重复消费问题如何避免?

对于消息重复消费问题,一个常用的解决办法是引入一个专门的“消费记录表” (或称为“幂等控制表”) 来跟踪哪些消息已经被成功处理过了。这种方法的核心思想是:在真正执行业务逻辑之前,先检查一下这条消息是不是已经被消费过。

这个表至少需要包含一个唯一的消息标识符(可以用 MQ 提供的 Message ID,或者业务上的唯一流水号)。消费者收到消息后,先去查这个消费记录表,看这个消息 ID 是否已存在:

  • 如果存在: 说明消息已经被处理过,直接返回 ACK,避免重复处理。
  • 如果不存在: 说明是第一次消费,记录该消息 ID,并执行业务逻辑。

消费记录的插入和业务逻辑的执行应该在同一个事务中完成,避免消费记录插入失败或业务逻辑执行失败导致的重复消费。

消费记录表会持续增长,需要定期清理过期的记录(比如只保留最近几天或几个月的记录),避免表过大影响查询性能。

除了消费记录表这种方式之外,还有数据库唯一约束、分布式锁等方法。另外,对于有明确状态流转的业务(如订单状态:待支付 -> 已支付 -> 已发货 -> 已完成),可以配合状态来做幂等。

# 你项目中怎么向前端传数据的?

在我们开发的项目里,后端给前端传数据主要有这么几种方式:

  1. RESTful API (最常用):

    • 这是目前最主流、最常用的方式。简单来说,就是前端通过 HTTP 请求(比如 GET 获取数据、POST 提交数据、PUT 更新数据、DELETE 删除数据)来访问后端定义好的接口 (API)。
    • 后端收到请求后,处理相应的业务逻辑(比如查询数据库),然后把结果(通常是 JSON 格式的数据)打包在 HTTP 响应里返回给前端。
    • 这种方式是无状态的,每次请求都是独立的,非常适合绝大部分的网页数据展示、表单提交等场景。
  2. WebSocket:

    • 这个技术能在后端和前端之间建立一个持久的、双向的连接。一旦连接建立,双方可以随时互相发送消息,实现实时通信。
    • 特别适合需要即时互动或数据更新的场景,比如:在线聊天室、多人协作编辑、实时显示股票价格或订单状态、游戏状态同步等。
  3. Server-Sent Events (SSE):

    • 这个主要是服务器单向地向前端推送信息。前端发起一次连接请求后,服务器可以持续地把更新的数据流式地发送给前端。
    • 相比 WebSocket,它更轻量一些,实现也相对简单,因为它只是单向推送。
    • 适用于只需要服务器主动通知前端更新的情况,比如:站内信通知、新闻 Feed 实时推送、监控仪表盘的数据更新等。

# 项目用 Redis 做了什么?

Redis 除了可以用来缓存高频访问的数据之外,还以用来实现:

  • 分布式锁:通过 Redis 来做分布式锁是一种比较常见的方式。通常情况下,我们都是基于 Redisson 来实现分布式锁。关于 Redis 实现分布式锁的详细介绍,可以看我写的这篇文章:如何基于 Redis 实现分布式锁? (opens new window)。
  • 限流:一般是通过 Redis + Lua 脚本的方式来实现限流。如果不想自己写 Lua 脚本的话,也可以直接利用 Redisson 中的 RRateLimiter 来实现分布式限流,其底层实现就是基于 Lua 代码+令牌桶算法。
  • 消息队列:Redis 自带的 List 数据结构可以作为一个简单的队列使用。Redis 5.0 中增加的 Stream 类型的数据结构更加适合用来做消息队列。它比较类似于 Kafka,有主题和消费组的概念,支持消息持久化以及 ACK 机制。
  • 延时队列:Redisson 内置了延时队列(基于 Sorted Set 实现的)。
  • 分布式 Session :利用 String 或者 Hash 数据类型保存 Session 数据,所有的服务器都可以访问。
  • 复杂业务场景:通过 Redis 以及 Redis 扩展(比如 Redisson)提供的数据结构,我们可以很方便地完成很多复杂的业务场景比如通过 Bitmap 统计活跃用户、通过 Sorted Set 维护排行榜。

面试中,根据你项目的实际情况去回答即可!

相关阅读:

  • Redis 除了缓存还能做什么?可以做消息队列吗? (opens new window)
  • Redis 八种常用数据类型常用命令和应用场景 (opens new window)

# Redis 性能强大的原因是?

Redis 的高性能主要得益于几个核心设计和优化:

  1. 纯内存操作 (Memory-Based Storage) :这是最主要的原因。Redis 数据读写操作都发生在内存中,访问速度是纳秒级别,而传统数据库频繁读写磁盘的速度是毫秒级别,两者相差数个数量级。
  2. 高效的 I/O 模型 (I/O Multiplexing & Single-Threaded Event Loop) :Redis 使用单线程事件循环配合 I/O 多路复用技术,让单个线程可以同时处理多个网络连接上的 I/O 事件(如读写),避免了多线程模型中的上下文切换和锁竞争问题。虽然是单线程,但结合内存操作的高效性和 I/O 多路复用,使得 Redis 能轻松处理大量并发请求。
  3. 优化的内部数据结构 (Optimized Data Structures) :Redis 提供多种数据类型(如 String, List, Hash, Set, Sorted Set 等),其内部实现采用高度优化的编码方式(如 ziplist, quicklist, skiplist, hashtable 等)。Redis 会根据数据大小和类型动态选择最合适的内部编码,以在性能和空间效率之间取得最佳平衡。
  4. 简洁高效的通信协议 (Simple Protocol - RESP) :Redis 使用的是自己设计的 RESP (REdis Serialization Protocol) 协议。这个协议实现简单、解析性能好,并且是二进制安全的。客户端和服务端之间通信的序列化/反序列化开销很小,有助于提升整体的交互速度。

# 数据库和缓存一致性如何保证的?

我个人觉得引入缓存之后,如果为了短时间的不一致性问题,选择让系统设计变得更加复杂的话,完全没必要。

下面单独对 Cache Aside Pattern(旁路缓存模式) 来聊聊。

Cache Aside Pattern 中遇到写请求是这样的:更新 DB,然后直接删除 cache 。

如果更新数据库成功,而删除缓存这一步失败的情况的话,简单说两个解决方案:

  1. 缓存失效时间变短(不推荐,治标不治本):我们让缓存数据的过期时间变短,这样的话缓存就会从数据库中加载数据。另外,这种解决办法对于先操作缓存后操作数据库的场景不适用。
  2. 增加 cache 更新重试机制(常用):如果 cache 服务当前不可用导致缓存删除失败的话,我们就隔一段时间进行重试,重试次数可以自己定。如果多次重试还是失败的话,我们可以把当前更新失败的 key 存入队列中,等缓存服务可用之后,再将缓存中对应的 key 删除即可。

详细介绍可以参考这篇文章:缓存和数据库一致性问题,看这篇就够了 - 水滴与银弹 (opens new window)。

# Java

# 为什么要用线程池?

池化技术想必大家已经屡见不鲜了,线程池、数据库连接池、HTTP 连接池等等都是对这个思想的应用。池化技术的思想主要是为了减少每次获取资源的消耗,提高对资源的利用率。

线程池提供了一种限制和管理资源(包括执行一个任务)的方式。 每个线程池还维护一些基本统计信息,例如已完成任务的数量。使用线程池主要带来以下几个好处:

  1. 降低资源消耗:线程池里的线程是可以重复利用的。一旦线程完成了某个任务,它不会立即销毁,而是回到池子里等待下一个任务。这就避免了频繁创建和销毁线程带来的开销。
  2. 提高响应速度:因为线程池里通常会维护一定数量的核心线程(或者说“常驻工人”),任务来了之后,可以直接交给这些已经存在的、空闲的线程去执行,省去了创建线程的时间,任务能够更快地得到处理。
  3. 提高线程的可管理性:线程池允许我们统一管理池中的线程。我们可以配置线程池的大小(核心线程数、最大线程数)、任务队列的类型和大小、拒绝策略等。这样就能控制并发线程的总量,防止资源耗尽,保证系统的稳定性。同时,线程池通常也提供了监控接口,方便我们了解线程池的运行状态(比如有多少活跃线程、多少任务在排队等),便于调优。

# 怎么创建线程池?

在 Java 中,创建线程池主要有两种方式:

方式一:通过 ThreadPoolExecutor 构造函数直接创建 (推荐)

图片

这是最推荐的方式,因为它允许开发者明确指定线程池的核心参数,对线程池的运行行为有更精细的控制,从而避免资源耗尽的风险。

方式二:通过 Executors 工具类创建 (不推荐用于生产环境)

Executors工具类提供的创建线程池的方法如下图所示:

图片

可以看出,通过Executors工具类可以创建多种类型的线程池,包括:

  • FixedThreadPool:固定线程数量的线程池。该线程池中的线程数量始终不变。当有一个新的任务提交时,线程池中若有空闲线程,则立即执行。若没有,则新的任务会被暂存在一个任务队列中,待有线程空闲时,便处理在任务队列中的任务。
  • SingleThreadExecutor: 只有一个线程的线程池。若多余一个任务被提交到该线程池,任务会被保存在一个任务队列中,待线程空闲,按先入先出的顺序执行队列中的任务。
  • CachedThreadPool: 可根据实际情况调整线程数量的线程池。线程池的线程数量不确定,但若有空闲线程可以复用,则会优先使用可复用的线程。若所有线程均在工作,又有新的任务提交,则会创建新的线程处理任务。所有线程在当前任务执行完毕后,将返回线程池进行复用。
  • ScheduledThreadPool:给定的延迟后运行任务或者定期执行任务的线程池。

# 线程池处理任务的过程?

图解线程池实现原理图解线程池实现原理

  1. 如果当前运行的线程数小于核心线程数,那么就会新建一个线程来执行任务。
  2. 如果当前运行的线程数等于或大于核心线程数,但是小于最大线程数,那么就把该任务放入到任务队列里等待执行。
  3. 如果向任务队列投放任务失败(任务队列已经满了),但是当前运行的线程数是小于最大线程数的,就新建一个线程来执行任务。
  4. 如果当前运行的线程数已经等同于最大线程数了,新建线程将会使当前运行的线程超出最大线程数,那么当前任务会被拒绝,拒绝策略会调用RejectedExecutionHandler.rejectedExecution()方法。

再提一个有意思的小问题:线程池在提交任务前,可以提前创建线程吗?

答案是可以的!ThreadPoolExecutor 提供了两个方法帮助我们在提交任务之前,完成核心线程的创建,从而实现线程池预热的效果:

  • prestartCoreThread():启动一个线程,等待任务,如果已达到核心线程数,这个方法返回 false,否则返回 true;
  • prestartAllCoreThreads():启动所有的核心线程,并返回启动成功的核心线程数。

# 对象的相等和引用相等有什么区别?

  • 对象的相等一般比较的是内存中存放的内容是否相等。
  • 引用相等一般比较的是他们指向的内存地址是否相等。

这里举一个例子:

String str1 = "hello";
String str2 = new String("hello");
String str3 = "hello";
// 使用 == 比较字符串的引用相等
System.out.println(str1 == str2);
System.out.println(str1 == str3);
// 使用 equals 方法比较字符串的相等
System.out.println(str1.equals(str2));
System.out.println(str1.equals(str3));

输出结果:

false
true
true
true

从上面的代码输出结果可以看出:

  • str1 和 str2 不相等,而 str1 和 str3 相等。这是因为 == 运算符比较的是字符串的引用是否相等。
  • str1、 str2、str3 三者的内容都相等。这是因为equals 方法比较的是字符串的内容,即使这些字符串的对象引用不同,只要它们的内容相等,就认为它们是相等的。

相关阅读:== 与 equals 有什么区别?为什么要有 hashCode? (opens new window)。

# String#equals() 和 Object#equals() 有何区别?

String 中的 equals 方法是被重写过的,比较的是 String 字符串的值是否相等。 Object 的 equals 方法是比较的对象的内存地址。

# MySQL

# 覆盖索引是什么?

如果一个索引包含(或者说覆盖)所有需要查询的字段的值,我们就称之为 覆盖索引(Covering Index) 。

在 InnoDB 存储引擎中,非主键索引的叶子节点包含的是主键的值。这意味着,当使用非主键索引进行查询时,数据库会先找到对应的主键值,然后再通过主键索引来定位和检索完整的行数据。这个过程被称为“回表”。

覆盖索引即需要查询的字段正好是索引的字段,那么直接根据该索引,就可以查到数据了,而无需回表查询。

如主键索引,如果一条 SQL 需要查询主键,那么正好根据主键索引就可以查到主键。再如普通索引,如果一条 SQL 需要查询 name,name 字段正好有索引, 那么直接根据这个索引就可以查到数据,也无需回表。

覆盖索引覆盖索引

# 你遇到慢 SQL 怎么办?

我们可以通过 EXPLAIN 命令分析对应的 SELECT 语句:

mysql> EXPLAIN SELECT`score`,`name`FROM`cus_order`ORDERBY`score`DESC;
+----+-------------+-----------+------------+------+---------------+------+---------+------+--------+----------+----------------+
| id | select_type | table     | partitions | type | possible_keys | key  | key_len | ref  | rows   | filtered | Extra          |
+----+-------------+-----------+------------+------+---------------+------+---------+------+--------+----------+----------------+
|  1 | SIMPLE      | cus_order | NULL       | ALL  | NULL          | NULL | NULL    | NULL | 997572 |   100.00 | Using filesort |
+----+-------------+-----------+------------+------+---------------+------+---------+------+--------+----------+----------------+
1 row in set, 1warning (0.00 sec)

比较重要的字段说明:

  • select_type :查询的类型,常用的取值有 SIMPLE(普通查询,即没有联合查询、子查询)、PRIMARY(主查询)、UNION(UNION 中后面的查询)、SUBQUERY(子查询)等。
  • table :表示查询涉及的表或衍生表。
  • type :执行方式,判断查询是否高效的重要参考指标,结果值从差到好依次是:ALL < index < range ~ index_merge < ref < eq_ref < const < system。
  • rows : SQL 要查找到结果集需要扫描读取的数据行数,原则上 rows 越少越好。
  • ......

详细介绍可以参考这篇文章:MySQL 怎么解决慢查询问题? (opens new window)。

# MySQL 性能怎么优化?

MySQL 性能优化是一个系统性工程,涉及多个方面,在面试中不可能面面俱到。因此,建议按照“点-线-面”的思路展开,从核心问题入手,再逐步扩展,展示出你对问题的思考深度和解决能力。

1. 抓住核心:慢 SQL 定位与分析

性能优化的第一步永远是找到瓶颈。面试时,建议先从 慢 SQL 定位和分析 入手,这不仅能展示你解决问题的思路,还能体现你对数据库性能监控的熟练掌握:

  • 监控工具: 介绍常用的慢 SQL 监控工具,如 MySQL 慢查询日志、Performance Schema 等,说明你对这些工具的熟悉程度以及如何通过它们定位问题。
  • EXPLAIN 命令: 详细说明 EXPLAIN 命令的使用,分析查询计划、索引使用情况,可以结合实际案例展示如何解读分析结果,比如执行顺序、索引使用情况、全表扫描等。

2. 由点及面:索引、表结构和 SQL 优化

定位到慢 SQL 后,接下来就要针对具体问题进行优化。 这里可以重点介绍索引、表结构和 SQL 编写规范等方面的优化技巧:

  • 索引优化: 这是 MySQL 性能优化的重点,可以介绍索引的创建原则、覆盖索引、最左前缀匹配原则等。如果能结合你项目的实际应用来说明如何选择合适的索引,会更加分一些。
  • 表结构优化: 优化表结构设计,包括选择合适的字段类型、避免冗余字段、合理使用范式和反范式设计等等。
  • SQL 优化: 避免使用 SELECT *、尽量使用具体字段、使用连接查询代替子查询、合理使用分页查询、批量操作等,都是 SQL 编写过程中需要注意的细节。

3. 进阶方案:架构优化

当面试官对基础优化知识比较满意时,可能会深入探讨一些架构层面的优化方案。以下是一些常见的架构优化策略:

  • 读写分离: 将读操作和写操作分离到不同的数据库实例,提升数据库的并发处理能力。
  • 分库分表: 将数据分散到多个数据库实例或数据表中,降低单表数据量,提升查询效率。但要权衡其带来的复杂性和维护成本,谨慎使用。
  • 数据冷热分离:根据数据的访问频率和业务重要性,将数据分为冷数据和热数据,冷数据一般存储在存储在低成本、低性能的介质中,热数据高性能存储介质中。
  • 缓存机制: 使用 Redis 等缓存中间件,将热点数据缓存到内存中,减轻数据库压力。这个非常常用,提升效果非常明显,性价比极高!

4. 其他优化手段

除了慢 SQL 定位、索引优化和架构优化,还可以提及一些其他优化手段,展示你对 MySQL 性能调优的全面理解:

  • 连接池配置: 配置合理的数据库连接池(如 连接池大小、超时时间 等),能够有效提升数据库连接的效率,避免频繁的连接开销。
  • 硬件配置: 提升硬件性能也是优化的重要手段之一。使用高性能服务器、增加内存、使用 SSD 硬盘等硬件升级,都可以有效提升数据库的整体性能。
#面试
上次更新: 2025/4/13 14:39:08
Algorithm
黑马点评面试题

← Algorithm 黑马点评面试题→

最近更新
01
项目优化
05-06
02
项目优化
05-06
03
延迟消息
05-05
更多文章>
Theme by Vdoing | Copyright © 2025-2025 Cyan Blog
  • 跟随系统
  • 浅色模式
  • 深色模式
  • 阅读模式