1. redis数据结构
Redis有8种核心数据结构:
-
String:最常用,底层SDS(简单动态字符串),支持整数和字符串。用于缓存、计数器、分布式锁。
-
Hash:底层是压缩列表或哈希表,适合存对象,如用户信息、购物车。
-
List:底层双向链表或压缩列表,可作为消息队列、栈、最新列表。
-
Set:底层整数集合或哈希表,无序去重,适合标签、共同好友。
-
ZSet(Sorted Set):底层是跳表+哈希表或压缩列表,按score排序,用于排行榜、延迟队列。
-
Bitmap:基于String的位操作,适合签到、活跃统计。
-
HyperLogLog:概率数据结构,用于UV统计,误差约0.81%。
-
Geo:基于ZSet,存地理位置,支持附近的人查询。
2. redis持久化机制
Redis提供两种持久化方式:
RDB(Redis Database)
-
在指定时间间隔内将内存中的数据集快照写入磁盘
-
触发方式:save(阻塞)、bgsave(fork子进程)
-
优点:文件小、恢复快、对性能影响小
-
缺点:可能丢失最后一次快照后的数据
AOF(Append Only File)
-
以日志形式记录每个写操作,追加到文件末尾
-
三种同步策略:always(每次)、everysec(每秒)、no(不主动)
-
优点:数据更完整,最多丢失1秒数据
-
缺点:文件体积大,恢复慢
混合持久化(Redis 4.0+)
- RDB作为AOF文件的前缀,结合两者优势
3. mysql索引底层
MySQL InnoDB引擎的索引底层使用 B+树 数据结构。
B+树特点:
-
所有数据存储在叶子节点,非叶子节点只存索引值
-
叶子节点之间用双向链表连接,支持范围查询
-
树的高度低(3-4层可存千万级数据)
-
磁盘预读友好,每个节点大小等于一页(16KB)
为什么用B+树不用B树?
-
B+树非叶子不存数据,每个节点可存更多索引,树更矮
-
叶子节点链表结构,范围查询和全表扫描更高效
4. 聚簇索引与非聚簇索引
聚簇索引:
-
数据和索引存储在一起
-
InnoDB中,主键索引就是聚簇索引
-
叶子节点存完整行数据
-
一个表只能有一个聚簇索引
非聚簇索引(二级索引):
-
数据和索引分开存储
-
叶子节点存主键值
-
回表:通过非聚簇索引查到主键,再到聚簇索引查完整数据
-
一个表可以有多个非聚簇索引
对比:
| 特性 | 聚簇索引 | 非聚簇索引 |
|---|---|---|
| 数据存储 | 叶子节点存数据 | 叶子节点存主键 |
| 数量 | 1个 | 多个 |
| 查询效率 | 主键查询快 | 需要回表 |
| 插入顺序 | 按主键顺序插入 | 独立维护 |
5. 索引优化
-
选择区分度高的列:如主键、唯一ID,避免性别这类低区分度列
-
最左前缀原则:联合索引(a,b,c),查询条件命中a或a,b才能用索引
-
覆盖索引:查询字段都在索引中,避免回表
-
索引下推(ICP):在索引层面过滤数据,减少回表次数
-
避免索引失效(见第6题)
-
控制索引数量:索引会降低写性能,一般单表不超过5-6个
-
主键设计:自增整型主键,避免UUID作为聚簇索引(页分裂严重)
6. 索引失效
常见导致索引失效的场景:
-
违反最左前缀:联合索引(a,b,c),查询条件只有b或c
-
在索引列上使用函数或计算 :
WHERE DATE(create_time) = '2024-01-01' -
隐式类型转换 :varchar字段传int,如
WHERE phone = 13800000000 -
使用LIKE以通配符开头 :
WHERE name LIKE '%张三' -
OR条件中有非索引列 :
WHERE id=1 OR name='张三'(name无索引) -
索引列参与比较操作 :
WHERE a+1 > 10应改为WHERE a > 9 -
数据分布不均:查询结果超过全表30%左右,优化器可能放弃索引
-
IS NOT NULL :某些情况下会导致索引失效,但
IS NULL通常能用
7. mysql执行一条sql
MySQL执行一条SQL的完整流程:
text
客户端 → 连接器 → 查询缓存(8.0已移除) → 解析器 → 预处理器 → 优化器 → 执行器 → 存储引擎
详细步骤:
-
连接器:建立连接、验证身份和权限
-
查询缓存(8.0删除):命中则直接返回
-
解析器:词法分析(识别SQL关键字)、语法分析(检查语法)
-
预处理器:检查表和字段是否存在,权限验证
-
优化器:选择索引,生成执行计划(如先查哪个表、用哪个索引)
-
执行器:调用存储引擎接口,执行查询,逐行判断条件
-
存储引擎:读写数据、事务处理、锁管理(InnoDB/MyISAM)
一条UPDATE语句额外:写redo log(prepare状态)、binlog,再commit。
8. 那么多索引mysql怎么选(不会)
(按你的要求标记:不会)
这个问题涉及MySQL优化器的索引选择算法。核心逻辑是:
-
优化器基于索引统计信息 (采样估算)计算每个索引的扫描行数 和代价
-
代价包括:IO成本、CPU成本、回表成本
-
选择代价最小的执行计划
但优化器可能选错,原因:
-
统计信息不准确(采样误差)
-
索引基数(cardinality)估算不准
-
不同版本优化器策略差异
解决办法:
-
FORCE INDEX/USE INDEX强制指定索引 -
ANALYZE TABLE重新统计 -
调整优化器参数
9. tcp与udp区别
| 特性 | TCP | UDP |
|---|---|---|
| 连接 | 面向连接(三次握手) | 无连接 |
| 可靠性 | 可靠传输,确认重传 | 不可靠,丢包不重传 |
| 顺序 | 保证顺序 | 不保证 |
| 流量控制 | 有(滑动窗口) | 无 |
| 拥塞控制 | 有 | 无 |
| 头部大小 | 20-60字节 | 8字节 |
| 传输效率 | 较低 | 较高 |
| 典型应用 | HTTP、FTP、SSH | DNS、视频直播、游戏 |
10. tcp为什么可靠
TCP通过以下机制保证可靠传输:
-
确认应答(ACK):接收方收到数据后返回ACK,发送方未收到则重传
-
超时重传:RTO超时未收到ACK就重发
-
快速重传:收到3个重复ACK后立即重传,不等超时
-
序列号和确认号:保证数据有序,解决乱序和重复问题
-
校验和:检测数据损坏
-
流量控制(滑动窗口):接收方控制发送速率,防止接收方缓存溢出
-
拥塞控制:慢启动、拥塞避免、快重传、快恢复
-
三次握手/四次挥手:建立可靠连接,保证连接正常关闭
11. 消息队列作用
-
异步处理:用户注册后发送邮件、短信等异步执行,减少主流程耗时
-
应用解耦:订单系统完成订单后发消息,库存、积分系统订阅,互不直接依赖
-
流量削峰:秒杀场景先写MQ再慢慢消费,保护下游数据库
-
日志处理:业务日志发MQ,消费端异步写入ES或大数据平台
-
分布式事务:结合TCC或本地消息表,实现最终一致性
-
延迟/定时任务:如取消超时未支付订单(RocketMQ/Redis的延迟队列)
12. kafka怎么保证消息有序性
Kafka保证有序的几种方式:
-
单个分区内有序:Kafka保证一个分区内消息按发送顺序存储和消费(offset递增)
-
全局有序:Topic只设置1个分区(牺牲吞吐量)
-
业务键有序 :使用相同的key发送到同一分区,
producer.send(record)中key相同则进入同一分区
消费端保证有序:
-
一个分区只能被一个消费者实例消费(同一个消费者组内)
-
消费端使用单线程处理,或使用
max.poll.records=1保证每次拉取少量消息
注意事项:
-
生产者重试可能导致乱序,可设置
max.in.flight.requests.per.connection=1(吞吐量下降) -
Kafka 0.11+支持幂等生产者,可避免重试导致的乱序
13. mcp是什么?
MCP(Model Context Protocol):
-
由Anthropic(Claude团队)开源的一个协议标准
-
用于AI模型与外部数据源、工具之间的标准化连接
-
类似AI应用的"USB-C接口",让大模型能访问数据库、API、文件系统等
-
MCP Server提供工具能力,MCP Client(如Claude Desktop)调用
(如果面试官不是问这个,可能是其他领域的MCP,比如MCP协议其他含义)
14. skills是什么?
Skills(技能) 在不同上下文中的含义:
AI Agent领域(如Claude、GPT):
-
预定义的一组能力/工具,让AI能执行特定任务
-
例如:代码执行技能、联网搜索技能、文件操作技能
-
Skill = Prompt模板 + 工具调用 + 权限范围
后端/微服务:
- 指开发者的技术能力栈
建议结合你的面试上下文判断。如果是AI方向,Skills是AI应用中的一个概念。
15. jvm内存分配与回收过程
(你回答"从创建对象到判断垃圾对象到垃圾回收",方向是对的)
完整流程:
内存分配
text
新对象 → 栈上分配(逃逸分析) → TLAB分配 → Eden区 → 年龄+1 → Survivor区 → 年龄阈值 → Old区
-
优先栈上分配(不逃逸的对象)
-
再尝试TLAB分配(线程本地缓冲区)
-
然后放Eden区
-
大对象直接进老年代(-XX:PretenureSizeThreshold)
垃圾判断
-
引用计数法(已弃用,循环引用问题)
-
可达性分析(GC Root:栈帧引用、静态变量、JNI引用等)
垃圾回收过程
-
Minor GC(年轻代):Eden满时触发,存活对象复制到Survivor,年龄+1
-
晋升:年龄达到15(默认)或Survivor放不下时,晋升老年代
-
Full GC:老年代满或System.gc()时触发,回收整个堆
分代假设:大多数对象朝生夕灭,存活久的进入老年代
16. fullgc触发机制
Full GC触发场景:
-
老年代空间不足:Minor GC后晋升对象放不进老年代
-
大对象直接进入老年代,老年代放不下
-
元空间(Metaspace)不足(触发时回收类卸载)
-
System.gc()(可用-XX:+DisableExplicitGC禁用)
-
CMS GC的Concurrent Mode Failure:并发收集时老年代被填满,退化为Serial GC(Full GC)
-
统计信息判断:Minor GC的平均晋升大小 > 老年代剩余空间
-
调用RMI或NIO的sun.misc.GC(分布式定时GC)
调优目标:尽量减少Full GC次数,因为STW时间很长
17. tcp的拥塞控制流程(不会了)
(按你的要求标记:不会)
简要答案:
-
慢启动:cwnd从1开始,每收到ACK翻倍,直到ssthresh
-
拥塞避免:cwnd线性增加,每次RTT加1
-
快重传:收到3个重复ACK立即重传,不等超时
-
快恢复:重传后设置cwnd = ssthresh(或ssthresh+3),进入拥塞避免
18. 分布式事务解决方案
-
两阶段提交(2PC):准备阶段+提交阶段,强一致性,性能差,协调者单点
-
三阶段提交(3PC):引入超时机制和CanCommit阶段,解决单点阻塞
-
TCC(Try-Confirm-Cancel):业务层面补偿,高并发场景,需实现回滚逻辑
-
本地消息表:消息持久化+定时轮询,最终一致性
-
RocketMQ事务消息:半消息+本地事务状态回查,最终一致性
-
Saga:长事务拆分为多个本地事务,依次执行,失败则逆序补偿
-
最大努力通知:业务完成后持续重试通知,适合跨平台回调场景
选型原则:强一致选2PC/3PC,高并发选TCC,长事务选Saga,最终一致性选事务消息
手撕:反转双向链表
java
class ListNode {
int val;
ListNode prev;
ListNode next;
ListNode(int val) { this.val = val; }
}
public ListNode reverseDoublyLinkedList(ListNode head) {
if (head == null || head.next == null) return head;
ListNode cur = head;
ListNode prev = null;
while (cur != null) {
// 交换prev和next指针
ListNode next = cur.next;
cur.next = prev;
cur.prev = next;
// 移动
prev = cur;
cur = next;
}
return prev; // 新头节点
}
测试:
text
原链表:1 <-> 2 <-> 3 <-> null
反转后:3 <-> 2 <-> 1 <-> null
1. redis与数据库的缓存一致性
常见方案:
-
Cache Aside Pattern:读先读缓存,未命中读DB再写缓存;写先更新DB,再删除缓存
-
延迟双删:写后删缓存,隔几百毫秒再删一次
-
订阅binlog:Canal监听MySQL变更,异步更新缓存
-
双写一致性要求高:使用Redis自带的redisson读写锁
2. 分布式CAP理论
C(一致性):所有节点同一时间数据一致
A(可用性):非故障节点总能响应
P(分区容忍性):网络分区时系统继续运行
CAP只能三选二,但P必须保证,实际是CP或AP的选择。
3. 什么时候AP,什么时候CP
-
AP:互联网业务(商品浏览、评论),允许短暂不一致,保证高可用。如Eureka。
-
CP:金融、交易系统,强一致性要求,允许部分节点不可用。如Zookeeper、Etcd。
4. 进程线程协程
-
进程:资源分配最小单位,独立内存空间,切换开销大
-
线程:CPU调度最小单位,共享进程内存,切换开销较小
-
协程:用户态轻量线程,切换由用户控制,开销极小,Go goroutine、Kotlin协程
5. Spring的IOC原理及优点
原理:控制反转,通过DI(依赖注入)管理对象生命周期和依赖关系。核心是BeanFactory和ApplicationContext,解析配置→创建Bean→存入容器→注入依赖。
优点:解耦、易测试、管理统一、减少样板代码。
6. 除了解耦还有什么优点?(IOC)
-
单例管理(默认Bean单例)
-
生命周期回调(@PostConstruct、@PreDestroy)
-
循环依赖解决(三级缓存)
-
AOP整合(基于动态代理)
7. SpringBoot与Spring区别
-
Spring需手动配置依赖、web.xml、容器等
-
SpringBoot自动配置、内嵌容器、starter简化依赖
-
源码层面:@SpringBootApplication组合注解、AutoConfigurationImportSelector加载META-INF/spring.factories
Tomcat:SpringBoot内嵌,启动时通过TomcatServletWebServerFactory创建,核心是Connector(接收请求)+Container(处理请求)。
8. MyBatis、Ibatis、SQL写法
MyBatis是半自动ORM,Ibatis是旧版。SQL写在XML或注解中,但团队可规范用MyBatis-Plus、通用Mapper减少手写。
9. 设计秒杀系统(前端→网关→缓存→数据库防超卖)
-
前端:按钮置灰、倒计时、验证码
-
网关:限流(令牌桶、漏桶)、风控、过滤无效请求
-
缓存:Redis预减库存,Lua脚本原子扣减,ZSet记录成功用户
-
数据库防超卖 :乐观锁(
UPDATE ... WHERE stock>0 AND version=xxx)、或redis扣减后异步落库 -
MQ:削峰,下单异步处理
10. 怎么做限流
-
计数器(固定窗口,有临界突发)
-
滑动窗口(更平滑)
-
令牌桶(允许一定突发)
-
漏桶(恒定速率)
-
中间件:Guava RateLimiter、Sentinel、Redis + Lua
11. 缓存一致性 + 异步要用户等吗?
等:同步更新缓存(双写)
不等:先返回成功,异步通过binlog/Canal更新缓存,最终一致
秒杀场景:下单成功直接返回,异步处理后续,用户不感知异步延迟。
12. 负载均衡怎么做
-
四层:LVS、F5,基于IP+端口
-
七层:Nginx、HAProxy,基于HTTP Header、URL
-
算法:轮询、加权轮询、最小连接数、IP哈希、一致性哈希
13. 多数据中心&没卖完怎么处理
-
多数据中心:就近接入,数据同步(数据库双活、DRC),限流需全局配额
-
没卖完:延长活动时间、降价推送、库存释放给其他渠道、预约提醒