mirror of
https://github.com/CyC2018/Interview-Notebook.git
synced 2026-10-31 19:13:42 +00:00
Update 学习路线 - Java 后端.md
同步笔记的修改,调整部分天的导读与参考要点,补充代码较多的几天的略读提示。
This commit is contained in:
+150
-71
@@ -82,6 +82,9 @@
|
||||
* [第 65 天:Kubernetes](#第-65-天kubernetes)
|
||||
* [第 66 天:系统设计题的思路与即时通讯](#第-66-天系统设计题的思路与即时通讯)
|
||||
* [第 67 天:订单与支付](#第-67-天订单与支付)
|
||||
* [十五、大模型应用](#十五大模型应用)
|
||||
* [第 68 天:RAG 与 Agent](#第-68-天rag-与-agent)
|
||||
* [第 69 天:MCP、Skills 与 AI 编程助手](#第-69-天mcpskills-与-ai-编程助手)
|
||||
<!-- GFM-TOC -->
|
||||
|
||||
|
||||
@@ -151,40 +154,43 @@
|
||||
| | **计算机网络** | | |
|
||||
| [36](#第-36-天tcp-的连接管理) | TCP 的连接管理 | 计算机网络 - 传输层:UDP 和 TCP 的特点~TCP 的四次挥手 | 101. 对称二叉树 |
|
||||
| [37](#第-37-天可靠传输与拥塞控制) | 可靠传输与拥塞控制 | 计算机网络 - 传输层:TCP 可靠传输~在 UDP 上实现可靠传输 | 104. 二叉树的最大深度 |
|
||||
| [38](#第-38-天http-基础) | HTTP 基础 | HTTP:HTTP 基础概念~HTTP 首部 | 102. 二叉树的层序遍历 |
|
||||
| [39](#第-39-天连接管理与-cookie) | 连接管理与 Cookie | HTTP:连接管理~Cookie | 103. 二叉树的锯齿形层序遍历 |
|
||||
| [40](#第-40-天缓存websocket-与跨域) | 缓存、WebSocket 与跨域 | HTTP:HTTP 缓存~跨域 | 236. 二叉树的最近公共祖先 |
|
||||
| [41](#第-41-天https) | HTTPS | HTTP:HTTPS | 199. 二叉树的右视图 |
|
||||
| [42](#第-42-天http-的版本get-与-postdns) | HTTP 的版本、GET 与 POST、DNS | HTTP:HTTP/1.1 新特性~GET 和 POST 比较;计算机网络 - 应用层:域名系统 | 105. 从前序与中序遍历序列构造二叉树 |
|
||||
| [43](#第-43-天从输入-url-到显示页面) | 从输入 URL 到显示页面 | 计算机网络 - 应用层:Web 页面请求过程;攻击技术:跨站脚本攻击~SQL 注入攻击 | 98. 验证二叉搜索树 |
|
||||
| [38](#第-38-天http-基础) | HTTP 基础 | HTTP:HTTP 基础概念~HTTP 首部 | 543. 二叉树的直径 |
|
||||
| [39](#第-39-天连接管理与-cookie) | 连接管理与 Cookie | HTTP:连接管理~Cookie | 102. 二叉树的层序遍历 |
|
||||
| [40](#第-40-天缓存websocket-与跨域) | 缓存、WebSocket 与跨域 | HTTP:HTTP 缓存~跨域 | 103. 二叉树的锯齿形层序遍历 |
|
||||
| [41](#第-41-天https) | HTTPS | HTTP:HTTPS | 236. 二叉树的最近公共祖先 |
|
||||
| [42](#第-42-天http-的版本get-与-postdns) | HTTP 的版本、GET 与 POST、DNS | HTTP:HTTP/1.1 新特性~GET 和 POST 比较;计算机网络 - 应用层:域名系统 | 199. 二叉树的右视图 |
|
||||
| [43](#第-43-天从输入-url-到显示页面) | 从输入 URL 到显示页面 | 计算机网络 - 应用层:Web 页面请求过程;攻击技术:跨站脚本攻击~SQL 注入攻击 | 105. 从前序与中序遍历序列构造二叉树 |
|
||||
| | **操作系统** | | |
|
||||
| [44](#第-44-天进程与线程) | 进程与线程 | 计算机操作系统 - 进程管理:进程与线程~进程状态的切换 | 200. 岛屿数量 |
|
||||
| [45](#第-45-天系统调用进程同步与通信) | 系统调用、进程同步与通信 | 计算机操作系统 - 概述:系统调用;计算机操作系统 - 进程管理:进程调度算法~进程通信 | 46. 全排列 |
|
||||
| [46](#第-46-天死锁) | 死锁 | 计算机操作系统 - 死锁:必要条件~程序中的死锁 | 93. 复原 IP 地址 |
|
||||
| [47](#第-47-天内存管理) | 内存管理 | 计算机操作系统 - 内存管理:虚拟内存~分页与分段的比较 | 22. 括号生成 |
|
||||
| [48](#第-48-天io-多路复用) | I/O 多路复用 | Socket:I/O 模型~I/O 复用 | 78. 子集 |
|
||||
| [44](#第-44-天进程与线程) | 进程与线程 | 计算机操作系统 - 进程管理:进程与线程~进程状态的切换 | 98. 验证二叉搜索树 |
|
||||
| [45](#第-45-天系统调用进程同步与通信) | 系统调用、进程同步与通信 | 计算机操作系统 - 概述:系统调用;计算机操作系统 - 进程管理:进程调度算法~进程通信 | 200. 岛屿数量 |
|
||||
| [46](#第-46-天死锁) | 死锁 | 计算机操作系统 - 死锁:必要条件~程序中的死锁 | 46. 全排列 |
|
||||
| [47](#第-47-天内存管理) | 内存管理 | 计算机操作系统 - 内存管理:虚拟内存~分页与分段的比较 | 93. 复原 IP 地址 |
|
||||
| [48](#第-48-天io-多路复用) | I/O 多路复用 | Socket:I/O 模型~I/O 复用 | 22. 括号生成 |
|
||||
| | **Spring 与设计模式** | | |
|
||||
| [49](#第-49-天ioc-与-bean) | IoC 与 Bean | Spring:IoC 与 Bean | 39. 组合总和 |
|
||||
| [50](#第-50-天aop-与事务) | AOP 与事务 | Spring:AOP~事务 | 121. 买卖股票的最佳时机 |
|
||||
| [51](#第-51-天spring-bootspring-mvc-与-mybatis) | Spring Boot、Spring MVC 与 MyBatis | Spring:Spring Boot 自动配置~Spring 中的设计模式 | 70. 爬楼梯 |
|
||||
| [52](#第-52-天设计模式) | 设计模式 | 设计模式 - 目录:设计模式前言;设计模式 - 单例:单例(Singleton);设计模式 - 策略:策略(Strategy);面向对象思想:设计原则 | 53. 最大子数组和 |
|
||||
| [49](#第-49-天ioc-与-bean) | IoC 与 Bean | Spring:IoC 与 Bean | 78. 子集 |
|
||||
| [50](#第-50-天aop-与事务) | AOP 与事务 | Spring:AOP~事务 | 39. 组合总和 |
|
||||
| [51](#第-51-天spring-bootspring-mvc-与-mybatis) | Spring Boot、Spring MVC 与 MyBatis | Spring:Spring Boot 自动配置~Spring 中的设计模式 | 121. 买卖股票的最佳时机 |
|
||||
| [52](#第-52-天设计模式) | 设计模式 | 设计模式 - 目录:设计模式前言;设计模式 - 单例:单例(Singleton);设计模式 - 策略:策略(Strategy);面向对象思想:设计原则 | 70. 爬楼梯 |
|
||||
| | **分布式与消息队列** | | |
|
||||
| [53](#第-53-天微服务) | 微服务 | 微服务:从单体到微服务~网关 | 5. 最长回文子串 |
|
||||
| [54](#第-54-天分布式锁) | 分布式锁 | 分布式:分布式锁 | 300. 最长递增子序列 |
|
||||
| [55](#第-55-天分布式事务与-cap) | 分布式事务与 CAP | 分布式:分布式事务~BASE | 1143. 最长公共子序列 |
|
||||
| [56](#第-56-天rpc分布式-id-与幂等) | RPC、分布式 ID 与幂等 | 分布式:RPC~幂等 | 322. 零钱兑换 |
|
||||
| [57](#第-57-天消息队列的作用与可靠性) | 消息队列的作用与可靠性 | 消息队列:消息模型~消息队列的可靠性 | 64. 最小路径和 |
|
||||
| [58](#第-58-天顺序积压与延迟消息) | 顺序、积压与延迟消息 | 消息队列:顺序性~延迟消息与死信队列 | 198. 打家劫舍 |
|
||||
| [59](#第-59-天kafka) | Kafka | 消息队列:Kafka | 42. 接雨水 |
|
||||
| [53](#第-53-天微服务) | 微服务 | 微服务:从单体到微服务~网关 | 53. 最大子数组和 |
|
||||
| [54](#第-54-天分布式锁) | 分布式锁 | 分布式:分布式锁 | 5. 最长回文子串 |
|
||||
| [55](#第-55-天分布式事务与-cap) | 分布式事务与 CAP | 分布式:分布式事务~BASE | 300. 最长递增子序列 |
|
||||
| [56](#第-56-天rpc分布式-id-与幂等) | RPC、分布式 ID 与幂等 | 分布式:RPC~幂等 | 1143. 最长公共子序列 |
|
||||
| [57](#第-57-天消息队列的作用与可靠性) | 消息队列的作用与可靠性 | 消息队列:消息模型~消息队列的可靠性 | 322. 零钱兑换 |
|
||||
| [58](#第-58-天顺序积压与延迟消息) | 顺序、积压与延迟消息 | 消息队列:顺序性~延迟消息与死信队列 | 64. 最小路径和 |
|
||||
| [59](#第-59-天kafka) | Kafka | 消息队列:Kafka | 62. 不同路径 |
|
||||
| | **系统设计** | | |
|
||||
| [60](#第-60-天性能与可用性) | 性能与可用性 | 系统设计基础:性能~安全性 | 239. 滑动窗口最大值 |
|
||||
| [61](#第-61-天秒杀与抢红包) | 秒杀与抢红包 | 系统设计基础:秒杀系统 | 76. 最小覆盖子串 |
|
||||
| [62](#第-62-天海量数据短链与-feed-流) | 海量数据、短链与 Feed 流 | 系统设计基础:海量数据处理~Feed 流 | 25. K 个一组翻转链表 |
|
||||
| [63](#第-63-天高并发系统的设计) | 高并发系统的设计 | 系统设计基础:高并发系统的设计 | 23. 合并 K 个升序链表 |
|
||||
| [64](#第-64-天docker) | Docker | Docker:解决的问题~镜像与容器 | 32. 最长有效括号 |
|
||||
| [65](#第-65-天kubernetes) | Kubernetes | Docker:Kubernetes | 4. 寻找两个正序数组的中位数 |
|
||||
| [66](#第-66-天系统设计题的思路与即时通讯) | 系统设计题的思路与即时通讯 | 系统设计基础:系统设计题的思路~即时通讯 | 124. 二叉树中的最大路径和 |
|
||||
| [67](#第-67-天订单与支付) | 订单与支付 | 系统设计基础:订单与支付 | 72. 编辑距离 |
|
||||
| [60](#第-60-天性能与可用性) | 性能与可用性 | 系统设计基础:性能~安全性 | 198. 打家劫舍 |
|
||||
| [61](#第-61-天秒杀与抢红包) | 秒杀与抢红包 | 系统设计基础:秒杀系统 | 42. 接雨水 |
|
||||
| [62](#第-62-天海量数据短链与-feed-流) | 海量数据、短链与 Feed 流 | 系统设计基础:海量数据处理~Feed 流 | 239. 滑动窗口最大值 |
|
||||
| [63](#第-63-天高并发系统的设计) | 高并发系统的设计 | 系统设计基础:高并发系统的设计 | 76. 最小覆盖子串 |
|
||||
| [64](#第-64-天docker) | Docker | Docker:解决的问题~镜像与容器 | 25. K 个一组翻转链表 |
|
||||
| [65](#第-65-天kubernetes) | Kubernetes | Docker:Kubernetes | 23. 合并 K 个升序链表 |
|
||||
| [66](#第-66-天系统设计题的思路与即时通讯) | 系统设计题的思路与即时通讯 | 系统设计基础:系统设计题的思路~即时通讯 | 32. 最长有效括号 |
|
||||
| [67](#第-67-天订单与支付) | 订单与支付 | 系统设计基础:订单与支付 | 4. 寻找两个正序数组的中位数 |
|
||||
| | **大模型应用** | | |
|
||||
| [68](#第-68-天rag-与-agent) | RAG 与 Agent | 大模型应用:RAG 的流程;大模型应用:Agent 是什么~Agent 与工作流 | 124. 二叉树中的最大路径和 |
|
||||
| [69](#第-69-天mcpskills-与-ai-编程助手) | MCP、Skills 与 AI 编程助手 | 大模型应用:MCP~Skills;大模型应用:AI 编程助手 | 72. 编辑距离 |
|
||||
|
||||
## 四、Java 基础
|
||||
|
||||
@@ -275,7 +281,7 @@ static 一节的初始化顺序可以对照代码自己推一遍。equals() 和
|
||||
|
||||
### 第 4 天:继承与多态
|
||||
|
||||
先读面向对象的三大特性,再读 Java 中的实现:访问权限、抽象类与接口、重写与重载。多态一节说明了运行时怎样找到被调用的方法。
|
||||
先读面向对象的三大特性,再读 Java 中的实现:访问权限、抽象类与接口、重写与重载。多态一节说明了运行时怎样找到被调用的方法。重写一节的三条限制对照第一个示例理解;后面较长的示例重点看编译时选定方法、运行时确定实现的两步过程。
|
||||
|
||||
阅读:《面向对象思想》的[三大特性](面向对象思想.md#一三大特性);《Java 基础》的[继承](Java%20基础.md#六继承)。
|
||||
|
||||
@@ -319,7 +325,7 @@ static 一节的初始化顺序可以对照代码自己推一遍。equals() 和
|
||||
|
||||
1. 类加载时 JVM 根据 .class 文件创建 Class 对象,反射通过它在运行时取得类的信息,类名可以到运行时才确定;用 Field 读写字段,Method 的 invoke() 调用方法,Constructor 的 newInstance() 创建对象;应用:Spring 创建 Bean 并注入、MyBatis 写入结果字段、JUnit 调用 @Test 方法、JDK 动态代理;缺点是有性能开销(框架会缓存 Method 对象),类名和方法名写成字符串、失去编译期检查,JDK 16 起默认不能反射访问 JDK 内部的私有成员,会破坏封装。
|
||||
2. 会执行,finally 在离开 try 语句之前执行;返回值在执行 finally 之前已经保存,返回基本类型时 finally 修改变量不影响返回值,返回引用时能看到对对象的修改;finally 中的 return 会覆盖返回值并吞掉异常;调用 System.exit()、kill -9 或 JVM 崩溃时 finally 不执行。
|
||||
3. 泛型只在编译时检查类型,编译后类型参数替换为上界(没有上界时为 Object),取出元素处由编译器插入强制类型转换;运行时 List<String> 和 List<Integer> 是同一个类,所以类型参数不能是基本类型,不能 new T() 或用 instanceof List<String>;类、字段、方法声明上的泛型信息保存在 Signature 属性中,可以通过反射读取。
|
||||
3. 泛型只在编译时检查类型,编译后类型参数替换为上界(没有上界时为 Object),取出元素处由编译器插入强制类型转换;运行时 List\<String> 和 List\<Integer> 是同一个类,所以类型参数不能是基本类型,不能 new T() 或用 instanceof List\<String>;类、字段、方法声明上的泛型信息保存在 Signature 属性中,可以通过反射读取。
|
||||
|
||||
</details>
|
||||
|
||||
@@ -622,7 +628,7 @@ AQS 一节先看 state 和等待队列怎样实现 ReentrantLock 的加锁、重
|
||||
|
||||
### 第 15 天:ThreadLocal 与锁优化
|
||||
|
||||
ThreadLocal 重点看它的数据结构、内存泄漏的原因,以及线程池复用线程时的数据串用。锁优化讲 JVM 怎样降低 synchronized 的开销,按轻量级锁、偏向锁、锁升级的过程读。
|
||||
ThreadLocal 重点看它的数据结构、内存泄漏的原因,以及线程池复用线程时的数据串用。向线程池传递 traceId 时,看懂 wrap() 中捕获、设置和恢复上下文的过程即可,TaskDecorator 和 TTL 的介绍可以略读。锁优化讲 JVM 怎样降低 synchronized 的开销,按轻量级锁、偏向锁、锁升级的过程读。
|
||||
|
||||
阅读:《Java 并发》从[无同步方案](Java%20并发.md#无同步方案)读到[多线程开发良好的实践](Java%20并发.md#十三多线程开发良好的实践)。
|
||||
|
||||
@@ -794,7 +800,7 @@ ThreadLocal 重点看它的数据结构、内存泄漏的原因,以及线程
|
||||
<details>
|
||||
<summary>参考要点</summary>
|
||||
|
||||
1. 加载:按全限定名取得字节流,转换为方法区的运行时结构,生成 Class 对象;验证字节流符合规范、不危害虚拟机;准备:为类变量分配内存并设为零值(基本类型或 String、用常量表达式初始化的 static final 常量不经 <clinit>() 直接取 ConstantValue 的值,《深入理解 Java 虚拟机》把这一步归在准备阶段,JVMS SE 21 写在初始化过程中);解析:把常量池中的符号引用替换为直接引用;初始化:执行 <clinit>(),即类变量赋值和静态语句块,父类先于子类,多线程下只有一个线程执行。
|
||||
1. 加载:按全限定名取得字节流,转换为方法区的运行时结构,生成 Class 对象;验证字节流符合规范、不危害虚拟机;准备:为类变量分配内存并设为零值(基本类型或 String、用常量表达式初始化的 static final 常量不经 \<clinit>() 直接取 ConstantValue 的值,《深入理解 Java 虚拟机》把这一步归在准备阶段,JVMS SE 21 写在初始化过程中);解析:把常量池中的符号引用替换为直接引用;初始化:执行 \<clinit>(),即类变量赋值和静态语句块,父类先于子类,多线程下只有一个线程执行。
|
||||
2. 类加载器先把加载请求交给父加载器,父加载器无法完成时才自己加载;好处是基础类统一,例如自己写的 java.lang.Object 不会替换启动类加载器加载的那个,程序中所有的 Object 都是同一个类;父加载器已经加载过的类不会重复加载,避免同一个类出现两个互不相等的 Class 对象。
|
||||
3. 重写 loadClass(),先自己加载、找不到再交给父加载器;例子:JDBC 的 DriverManager 由平台类加载器加载,通过 ServiceLoader 用线程上下文类加载器加载 classpath 上的驱动;Tomcat 每个应用的 WebappClassLoader 先在本应用中查找,不同应用可以用同一个库的不同版本;热部署用新的类加载器加载修改后的类。
|
||||
|
||||
@@ -823,8 +829,8 @@ ThreadLocal 重点看它的数据结构、内存泄漏的原因,以及线程
|
||||
<details>
|
||||
<summary>参考要点</summary>
|
||||
|
||||
1. 先看错误信息判断是哪块内存:Java heap space、GC overhead limit exceeded、Metaspace、direct buffer memory、unable to create native thread;元空间、直接内存、线程创建失败分别检查类的生成与卸载、堆外内存的释放、线程数和容器的限制,不能靠增大堆解决;Java heap space 时,启动时加 -XX:+HeapDumpOnOutOfMemoryError 保存堆转储,或用 jcmd <pid> GC.heap_dump 导出;用 MAT 按 Retained Heap 排序,查看大对象到 GC Roots 的引用链;引用链不该存在就是内存泄漏,改代码,否则增大堆或减少一次加载的数据。
|
||||
2. top 找到 CPU 占用高的 Java 进程;top -H -p <pid> 找到占用最高的线程 ID;jstack <pid> 导出线程栈,按 nid 找到这个线程(JDK 18 前 nid 是十六进制,要先用 printf "%x" 转换);查看它正在执行的代码,常见是死循环或频繁 GC。
|
||||
1. 先看错误信息判断是哪块内存:Java heap space、GC overhead limit exceeded、Metaspace、direct buffer memory、unable to create native thread;元空间、直接内存、线程创建失败分别检查类的生成与卸载、堆外内存的释放、线程数和容器的限制,不能靠增大堆解决;Java heap space 时,启动时加 -XX:+HeapDumpOnOutOfMemoryError 保存堆转储,或用 jcmd \<pid> GC.heap_dump 导出;用 MAT 按 Retained Heap 排序,查看大对象到 GC Roots 的引用链;引用链不该存在就是内存泄漏,改代码,否则增大堆或减少一次加载的数据。
|
||||
2. top 找到 CPU 占用高的 Java 进程;top -H -p \<pid> 找到占用最高的线程 ID;jstack \<pid> 导出线程栈,按 nid 找到这个线程(JDK 18 前 nid 是十六进制,要先用 printf "%x" 转换);查看它正在执行的代码,常见是死循环或频繁 GC。
|
||||
3. 先明确目标(在线服务看最长停顿,批处理看吞吐量),默认用 G1 并开启 GC 日志观察;-Xms 与 -Xmx 设为相同,-Xmx 要给元空间、线程栈、直接内存等堆外内存留出余量,不能等于容器的内存,按需设置 MaxGCPauseMillis、MaxMetaspaceSize、HeapDumpOnOutOfMemoryError;频繁 Full GC 时用 jstat -gcutil 看老年代使用率,降不下来查泄漏,能降下来就增大堆或调整新生代;GC 问题多数是代码造成的,只调参数解决不了。
|
||||
|
||||
</details>
|
||||
@@ -1169,7 +1175,7 @@ redo log、undo log 和 binlog 分别用于崩溃恢复、回滚和复制。最
|
||||
<summary>参考要点</summary>
|
||||
|
||||
1. 惰性删除:访问键时检查,过期就删除并当作不存在;定期删除:每秒 10 次,每次抽 20 个设置了过期时间的键,过期的超过 10% 就继续下一批,每次最多占 25% 的 CPU;内存达到 maxmemory 后按淘汰策略处理,有 volatile 和 allkeys 两个范围的 lru、lfu、random,以及 volatile-ttl 和默认的 noeviction(SET 这类可能增加内存的命令返回 OOM 错误,GET、DEL 照常执行),Redis 4.0 到 7.x 共 8 种,8.6 又增加了按最近修改时间淘汰的 allkeys-lrm、volatile-lrm;只作缓存时常用 allkeys-lru。
|
||||
2. RDB 由 fork 出的子进程把某一时刻的全部数据写入紧凑的二进制文件,适合备份、恢复快,但会丢失最后一次快照之后的数据,可能有几分钟;AOF 把写命令追加到文件,everysec 时约丢 1 秒,文件更大,需要定期重写;Redis 7.0 起 AOF 的基础文件默认用 RDB 格式(混合持久化);数据重要时两种都开启,重启时用 AOF 恢复。
|
||||
2. RDB 由 fork 出的子进程把某一时刻的全部数据写入紧凑的二进制文件,适合备份、恢复快,但会丢失最后一次快照之后的数据,可能有几分钟;AOF 把写命令追加到文件,everysec 时约丢 1 秒,文件更大,需要定期重写;开启 AOF 后,Redis 5.0 起默认使用混合持久化,重写生成的基础文件用 RDB 格式;数据重要时两种都开启,重启时用 AOF 恢复。
|
||||
3. Redis 用一个线程执行命令,执行脚本期间不处理其它客户端的命令,整个脚本就像一条命令,可以根据前一条命令的结果决定后续操作,例如先 GET 再判断是否 INCR;这里的原子性是不被打断,不是出错回滚,中途出错时已执行的写命令不会回滚。
|
||||
|
||||
</details>
|
||||
@@ -1366,7 +1372,7 @@ redo log、undo log 和 binlog 分别用于崩溃恢复、回滚和复制。最
|
||||
|
||||
预期:curl -v 中以 > 开头的是请求行(GET / HTTP/1.1)和请求首部,以 < 开头的是状态行(HTTP/1.1 200 OK)和响应首部,curl -I 只输出状态行和响应首部,没有响应体。
|
||||
|
||||
算法(二叉树,中等):[102. 二叉树的层序遍历](https://leetcode.cn/problems/binary-tree-level-order-traversal/),题解见[剑指 Offer 32.2](32.2%20把二叉树打印成多行.md)。
|
||||
算法(二叉树,简单):[543. 二叉树的直径](https://leetcode.cn/problems/diameter-of-binary-tree/),题解见[两节点的最长路径](Leetcode%20题解%20-%20树.md#3-两节点的最长路径)。
|
||||
|
||||
明天:第 39 天,连接管理与 Cookie(HTTP:连接管理~Cookie)。
|
||||
|
||||
@@ -1395,7 +1401,7 @@ redo log、undo log 和 binlog 分别用于崩溃恢复、回滚和复制。最
|
||||
|
||||
预期:登录后响应中有形如 Set-Cookie: <名称>=<值>; Path=/; Secure; HttpOnly; SameSite=Lax 的首部,保存登录状态的 Cookie 通常带 HttpOnly 和 Secure。
|
||||
|
||||
算法(二叉树,中等):[103. 二叉树的锯齿形层序遍历](https://leetcode.cn/problems/binary-tree-zigzag-level-order-traversal/),题解见[剑指 Offer 32.3](32.3%20按之字形顺序打印二叉树.md)。
|
||||
算法(二叉树,中等):[102. 二叉树的层序遍历](https://leetcode.cn/problems/binary-tree-level-order-traversal/),题解见[剑指 Offer 32.2](32.2%20把二叉树打印成多行.md)。
|
||||
|
||||
明天:第 40 天,缓存、WebSocket 与跨域(HTTP:HTTP 缓存~跨域)。
|
||||
|
||||
@@ -1420,11 +1426,11 @@ HTTP 缓存分为强缓存和协商缓存。内容协商到通信数据转发几
|
||||
|
||||
</details>
|
||||
|
||||
动手:用 curl -I 请求一个静态资源(例如 https://www.baidu.com/favicon.ico),记下响应中的 ETag,再用 -H 把它放进 If-None-Match 首部请求一次,观察 304 响应,并与[附录 A2](HTTP.md#a2-强缓存与协商缓存)中 b.js 的第 2 次请求对照。
|
||||
动手:用 curl -I 请求一个静态资源(例如 `https://www.baidu.com/favicon.ico`),记下响应中的 ETag,再用 -H 把它放进 If-None-Match 首部请求一次,观察 304 响应,并与[附录 A2](HTTP.md#a2-强缓存与协商缓存)中 b.js 的第 2 次请求对照。
|
||||
|
||||
预期:第一次请求返回 200 和 ETag,带上相同的 If-None-Match 再请求返回 304 Not Modified;把 ETag 改动一个字符再请求,返回 200。
|
||||
|
||||
算法(二叉树,中等):[236. 二叉树的最近公共祖先](https://leetcode.cn/problems/lowest-common-ancestor-of-a-binary-tree/),题解见[二叉树的最近公共祖先](Leetcode%20题解%20-%20树.md#5-二叉树的最近公共祖先)。
|
||||
算法(二叉树,中等):[103. 二叉树的锯齿形层序遍历](https://leetcode.cn/problems/binary-tree-zigzag-level-order-traversal/),题解见[剑指 Offer 32.3](32.3%20按之字形顺序打印二叉树.md)。
|
||||
|
||||
明天:第 41 天,HTTPS(HTTP:HTTPS)。
|
||||
|
||||
@@ -1453,7 +1459,7 @@ HTTPS 在 HTTP 和 TCP 之间加了一层 TLS。按加密、认证、握手的
|
||||
|
||||
预期:Protocol 一行为协商出的版本(如 TLSv1.2),Cipher 一行为密码套件(如 ECDHE-RSA-AES128-GCM-SHA256),证书链按 depth=2、1、0 列出根证书、中间证书和网站证书,Verify return code 为 0 (ok)。
|
||||
|
||||
算法(二叉树,中等):[199. 二叉树的右视图](https://leetcode.cn/problems/binary-tree-right-side-view/)。
|
||||
算法(二叉树,中等):[236. 二叉树的最近公共祖先](https://leetcode.cn/problems/lowest-common-ancestor-of-a-binary-tree/),题解见[二叉树的最近公共祖先](Leetcode%20题解%20-%20树.md#5-二叉树的最近公共祖先)。
|
||||
|
||||
明天:第 42 天,HTTP 的版本、GET 与 POST、DNS(HTTP:HTTP/1.1 新特性~GET 和 POST 比较;计算机网络 - 应用层:域名系统)。
|
||||
|
||||
@@ -1482,7 +1488,7 @@ HTTP/2 和 HTTP/3 分别解决了 HTTP/1.1 的哪些问题是常见的追问。G
|
||||
|
||||
预期:依次看到根服务器返回顶级域(如 com)服务器的 NS 记录,顶级域服务器返回该域名权威服务器的 NS 记录,权威服务器返回最终的 A 记录(或先返回 CNAME),每条记录都带 TTL。
|
||||
|
||||
算法(二叉树,中等):[105. 从前序与中序遍历序列构造二叉树](https://leetcode.cn/problems/construct-binary-tree-from-preorder-and-inorder-traversal/),题解见[剑指 Offer 7](7.%20重建二叉树.md)。
|
||||
算法(二叉树,中等):[199. 二叉树的右视图](https://leetcode.cn/problems/binary-tree-right-side-view/)。
|
||||
|
||||
明天:第 43 天,从输入 URL 到显示页面(计算机网络 - 应用层:Web 页面请求过程;攻击技术:跨站脚本攻击~SQL 注入攻击)。
|
||||
|
||||
@@ -1507,7 +1513,7 @@ Web 页面请求过程把前几天的内容串了起来。攻击技术中 XSS、
|
||||
|
||||
</details>
|
||||
|
||||
算法(二叉树,中等):[98. 验证二叉搜索树](https://leetcode.cn/problems/validate-binary-search-tree/)。
|
||||
算法(二叉树,中等):[105. 从前序与中序遍历序列构造二叉树](https://leetcode.cn/problems/construct-binary-tree-from-preorder-and-inorder-traversal/),题解见[剑指 Offer 7](7.%20重建二叉树.md)。
|
||||
|
||||
综合题:在浏览器输入 https://example.com 并回车,按 DNS、TCP、TLS、HTTP 的顺序说出每一步发送了什么,以及在哪些地方可能变慢。(见[Web 页面请求过程](计算机网络%20-%20应用层.md#web-页面请求过程)、[TCP 的三次握手](计算机网络%20-%20传输层.md#tcp-的三次握手)、[TLS 握手](HTTP.md#tls-握手))
|
||||
|
||||
@@ -1551,7 +1557,7 @@ Web 页面请求过程把前几天的内容串了起来。攻击技术中 XSS、
|
||||
|
||||
预期:1 万个平台线程启动约 1.4 秒、RSS 增加约 290 MB,1 万个虚拟线程约 30 毫秒、RSS 增加约 40 MB(堆中每个约 2 KB),100 万个虚拟线程约 0.5 秒、堆增加约 690 MB。
|
||||
|
||||
算法(回溯与搜索,中等):[200. 岛屿数量](https://leetcode.cn/problems/number-of-islands/),题解见[矩阵中的连通分量数目](Leetcode%20题解%20-%20搜索.md#2-矩阵中的连通分量数目)。
|
||||
算法(二叉树,中等):[98. 验证二叉搜索树](https://leetcode.cn/problems/validate-binary-search-tree/)。
|
||||
|
||||
明天:第 45 天,系统调用、进程同步与通信(计算机操作系统 - 概述:系统调用;计算机操作系统 - 进程管理:进程调度算法~进程通信)。
|
||||
|
||||
@@ -1580,7 +1586,7 @@ Web 页面请求过程把前几天的内容串了起来。攻击技术中 XSS、
|
||||
|
||||
预期:普通函数调用约 1 ns,glibc 的 getppid() 约 167 ns、直接陷入约 165 ns,系统调用约为函数调用的近 180 倍,经 vDSO 的 clock_gettime 约 18 ns,绕过 vDSO 约 202 ns。
|
||||
|
||||
算法(回溯与搜索,中等):[46. 全排列](https://leetcode.cn/problems/permutations/),题解见[排列](Leetcode%20题解%20-%20搜索.md#5-排列)。
|
||||
算法(回溯与搜索,中等):[200. 岛屿数量](https://leetcode.cn/problems/number-of-islands/),题解见[矩阵中的连通分量数目](Leetcode%20题解%20-%20搜索.md#2-矩阵中的连通分量数目)。
|
||||
|
||||
明天:第 46 天,死锁(计算机操作系统 - 死锁:必要条件~程序中的死锁)。
|
||||
|
||||
@@ -1601,7 +1607,7 @@ Web 页面请求过程把前几天的内容串了起来。攻击技术中 XSS、
|
||||
|
||||
1. 互斥:资源要么已分配给一个进程,要么可用;占有和等待:已持有资源的进程还能请求新资源;不可抢占:资源只能由持有者显式释放;环路等待:多个进程形成环,每个都在等下一个持有的资源。
|
||||
2. 设计时破坏任一必要条件:一次申请全部资源(破坏占有和等待,利用率低)、申请失败就释放已有资源(破坏不可抢占,如 tryLock 超时)、资源编号后按顺序加锁(破坏环路等待);最常用的是按固定顺序加锁,代价最小,例如转账时总是先锁 id 小的账户。
|
||||
3. 用阻塞式的锁时,死锁线程几乎不耗 CPU(例如等待 synchronized 的线程处于 BLOCKED),现象是 CPU 低而请求全部卡住;用 jstack 或 jcmd <pid> Thread.print 打印线程栈,末尾会报告 Found one Java-level deadlock,列出各线程等待的锁和持有者;程序中也可以定时调用 ThreadMXBean.findDeadlockedThreads() 检测并告警,但它检测不到包含虚拟线程的死锁环和 Semaphore 这类不可重入锁的自死锁;JVM 不会自动解除死锁,卡在 synchronized 上的线程无法中断,通常只能重启,所以要靠固定加锁顺序或 tryLock 预防。
|
||||
3. 用阻塞式的锁时,死锁线程几乎不耗 CPU(例如等待 synchronized 的线程处于 BLOCKED),现象是 CPU 低而请求全部卡住;用 jstack 或 jcmd \<pid> Thread.print 打印线程栈,末尾会报告 Found one Java-level deadlock,列出各线程等待的锁和持有者;程序中也可以定时调用 ThreadMXBean.findDeadlockedThreads() 检测并告警,但它检测不到包含虚拟线程的死锁环和 Semaphore 这类不可重入锁的自死锁;JVM 不会自动解除死锁,卡在 synchronized 上的线程无法中断,通常只能重启,所以要靠固定加锁顺序或 tryLock 预防。
|
||||
|
||||
</details>
|
||||
|
||||
@@ -1609,7 +1615,7 @@ Web 页面请求过程把前几天的内容串了起来。攻击技术中 XSS、
|
||||
|
||||
预期:原写法每次都卡住(stuck),jstack 末尾报告 Found one Java-level deadlock:T1 等待的锁被 T2 持有,T2 等待的锁被 T1 持有,两者都是 BLOCKED,改为按 id 顺序加锁后都能完成,两个余额仍为 1000。
|
||||
|
||||
算法(回溯与搜索,中等):[93. 复原 IP 地址](https://leetcode.cn/problems/restore-ip-addresses/),题解见[IP 地址划分](Leetcode%20题解%20-%20搜索.md#2-ip-地址划分)。
|
||||
算法(回溯与搜索,中等):[46. 全排列](https://leetcode.cn/problems/permutations/),题解见[排列](Leetcode%20题解%20-%20搜索.md#5-排列)。
|
||||
|
||||
明天:第 47 天,内存管理(计算机操作系统 - 内存管理:虚拟内存~分页与分段的比较)。
|
||||
|
||||
@@ -1634,11 +1640,11 @@ Web 页面请求过程把前几天的内容串了起来。攻击技术中 XSS、
|
||||
|
||||
</details>
|
||||
|
||||
动手:运行[附录 A3](计算机操作系统%20-%20内存管理.md#a3-进程的地址空间布局)的代码,查看 /proc/<pid>/maps 中代码段、堆和栈的位置。
|
||||
动手:运行[附录 A3](计算机操作系统%20-%20内存管理.md#a3-进程的地址空间布局)的代码,查看 /proc/\<pid>/maps 中代码段、堆和栈的位置。
|
||||
|
||||
预期:maps 中 r-xp 的可执行文件区域是代码段,之后 rw-p 的是数据段和 BSS,[heap] 紧随其后,1 MB 的 malloc 和共享库在中间的映射区,[stack] 在最高处,再运行一次起始地址都变,上下顺序不变。
|
||||
|
||||
算法(回溯与搜索,中等):[22. 括号生成](https://leetcode.cn/problems/generate-parentheses/),题解见[括号生成](Leetcode%20题解%20-%20搜索.md#16-括号生成)。
|
||||
算法(回溯与搜索,中等):[93. 复原 IP 地址](https://leetcode.cn/problems/restore-ip-addresses/),题解见[IP 地址划分](Leetcode%20题解%20-%20搜索.md#2-ip-地址划分)。
|
||||
|
||||
明天:第 48 天,I/O 多路复用(Socket:I/O 模型~I/O 复用)。
|
||||
|
||||
@@ -1667,7 +1673,7 @@ Web 页面请求过程把前几天的内容串了起来。攻击技术中 XSS、
|
||||
|
||||
预期:LT 下 3 次 epoll_wait 依次读到 1024、1024、952 字节后第 4 次超时,ET 每次只读一次时第 2 次 epoll_wait 就超时、剩 1976 字节,ET 循环读时一次事件内读完 1024、1024、952 字节后返回 EAGAIN。
|
||||
|
||||
算法(回溯与搜索,中等):[78. 子集](https://leetcode.cn/problems/subsets/),题解见[子集](Leetcode%20题解%20-%20搜索.md#11-子集)。
|
||||
算法(回溯与搜索,中等):[22. 括号生成](https://leetcode.cn/problems/generate-parentheses/),题解见[括号生成](Leetcode%20题解%20-%20搜索.md#16-括号生成)。
|
||||
|
||||
综合题:一个 Java 服务要同时维持 10 万个长连接:用每个连接一个线程的方式会遇到什么问题?用 epoll 或虚拟线程怎样解决?(见[上下文切换](计算机操作系统%20-%20进程管理.md#4-上下文切换)、[epoll](Socket.md#epoll)、[协程](计算机操作系统%20-%20进程管理.md#5-协程))
|
||||
|
||||
@@ -1710,7 +1716,7 @@ IoC 一节先说明为什么需要容器。Bean 的生命周期要能对照调
|
||||
|
||||
预期:构造函数的栈中 doCreateBean 调用 createBeanInstance、经 Constructor.newInstance 反射创建对象,Setter 的栈中 doCreateBean 调用 populateBean、经 postProcessProperties 和 Method.invoke 注入,构造函数先打印,即先实例化再注入。
|
||||
|
||||
算法(回溯与搜索,中等):[39. 组合总和](https://leetcode.cn/problems/combination-sum/),题解见[组合求和](Leetcode%20题解%20-%20搜索.md#8-组合求和)。
|
||||
算法(回溯与搜索,中等):[78. 子集](https://leetcode.cn/problems/subsets/),题解见[子集](Leetcode%20题解%20-%20搜索.md#11-子集)。
|
||||
|
||||
明天:第 50 天,AOP 与事务(Spring:AOP~事务)。
|
||||
|
||||
@@ -1739,7 +1745,7 @@ AOP 基于动态代理,自调用时切面不生效的原因也在这里。事
|
||||
|
||||
预期:从外部调用 a(),b() 写入数据后抛出 RuntimeException,数据没有回滚,把 b() 移到另一个 Bean(或通过注入的自身代理调用)后,同样的异常使写入被回滚。
|
||||
|
||||
算法(动态规划,简单):[121. 买卖股票的最佳时机](https://leetcode.cn/problems/best-time-to-buy-and-sell-stock/),题解见[买卖股票最大的收益](Leetcode%20题解%20-%20贪心思想.md#5-买卖股票最大的收益)。
|
||||
算法(回溯与搜索,中等):[39. 组合总和](https://leetcode.cn/problems/combination-sum/),题解见[组合求和](Leetcode%20题解%20-%20搜索.md#8-组合求和)。
|
||||
|
||||
明天:第 51 天,Spring Boot、Spring MVC 与 MyBatis(Spring:Spring Boot 自动配置~Spring 中的设计模式)。
|
||||
|
||||
@@ -1768,13 +1774,13 @@ AOP 基于动态代理,自调用时切面不生效的原因也在这里。事
|
||||
|
||||
预期:CONDITIONS EVALUATION REPORT 中,没有 JDBC 相关依赖时 DataSourceAutoConfiguration 在 Negative matches 中,原因是 @ConditionalOnClass 找不到所需的类,引入 spring-boot-starter-jdbc 和数据库驱动后出现在 Positive matches 中。
|
||||
|
||||
算法(动态规划,简单):[70. 爬楼梯](https://leetcode.cn/problems/climbing-stairs/),题解见[爬楼梯](Leetcode%20题解%20-%20动态规划.md#1-爬楼梯)。
|
||||
算法(动态规划,简单):[121. 买卖股票的最佳时机](https://leetcode.cn/problems/best-time-to-buy-and-sell-stock/),题解见[买卖股票最大的收益](Leetcode%20题解%20-%20贪心思想.md#5-买卖股票最大的收益)。
|
||||
|
||||
明天:第 52 天,设计模式(设计模式 - 目录:设计模式前言;设计模式 - 单例:单例(Singleton);设计模式 - 策略:策略(Strategy);面向对象思想:设计原则)。
|
||||
|
||||
### 第 52 天:设计模式
|
||||
|
||||
先看前言中下单流程用到的几种模式,再读单例和策略两篇。设计原则说明了这些模式背后的目标。
|
||||
先看前言中下单流程用到的几种模式,再读单例和策略两篇。单例的几种写法先比较线程安全和创建时机的差别,双重校验锁逐行细读,其余代码看懂结构即可;策略一篇重点看用策略替代 if-else 前后的对比。设计原则说明了这些模式背后的目标。
|
||||
|
||||
阅读:《设计模式 - 目录》的[设计模式前言](设计模式%20-%20目录.md#一前言);《设计模式 - 单例》的[单例(Singleton)](设计模式%20%20-%20单例.md#单例singleton);《设计模式 - 策略》的[策略(Strategy)](设计模式%20-%20策略.md#9-策略strategy);《面向对象思想》的[设计原则](面向对象思想.md#三设计原则)。选读:[工厂方法](设计模式%20-%20工厂方法.md)、[代理](设计模式%20-%20代理.md)、[装饰](设计模式%20-%20装饰.md)、[观察者](设计模式%20-%20观察者.md)、[模板方法](设计模式%20-%20模板方法.md)、[责任链](设计模式%20-%20责任链.md)。
|
||||
|
||||
@@ -1797,7 +1803,7 @@ AOP 基于动态代理,自调用时切面不生效的原因也在这里。事
|
||||
|
||||
预期:每种支付方式一个 PayStrategy 实现类,用 @Component("WECHAT") 这样的名称标注,注入的 Map 以 Bean 名称为 key,按支付方式取出策略调用、未知方式抛异常,结果与原 if-else 一致,新增支付方式只需加一个类。
|
||||
|
||||
算法(动态规划,中等):[53. 最大子数组和](https://leetcode.cn/problems/maximum-subarray/),题解见[子数组最大的和](Leetcode%20题解%20-%20贪心思想.md#10-子数组最大的和)。
|
||||
算法(动态规划,简单):[70. 爬楼梯](https://leetcode.cn/problems/climbing-stairs/),题解见[爬楼梯](Leetcode%20题解%20-%20动态规划.md#1-爬楼梯)。
|
||||
|
||||
综合题:用户下单接口中,Controller 调用 Service,Service 带有 @Transactional 并调用了日志切面:从请求到达 DispatcherServlet 到事务提交,依次经过了哪些对象和代理?(见[请求的处理过程](Spring.md#请求的处理过程)、[动态代理](Spring.md#动态代理)、[事务的实现原理](Spring.md#事务的实现原理))
|
||||
|
||||
@@ -1837,7 +1843,7 @@ AOP 基于动态代理,自调用时切面不生效的原因也在这里。事
|
||||
|
||||
</details>
|
||||
|
||||
算法(动态规划,中等):[5. 最长回文子串](https://leetcode.cn/problems/longest-palindromic-substring/),题解见[最长回文子串](Leetcode%20题解%20-%20字符串.md#10-最长回文子串)。
|
||||
算法(动态规划,中等):[53. 最大子数组和](https://leetcode.cn/problems/maximum-subarray/),题解见[子数组最大的和](Leetcode%20题解%20-%20贪心思想.md#10-子数组最大的和)。
|
||||
|
||||
明天:第 54 天,分布式锁(分布式:分布式锁)。
|
||||
|
||||
@@ -1866,7 +1872,7 @@ AOP 基于动态代理,自调用时切面不生效的原因也在这里。事
|
||||
|
||||
预期:lock() 后 PTTL 约 30000 并逐渐减小,约每 10 秒被重置回 30 秒附近,持有 30 秒锁一直存在,lock(10s) 不续期、约 10 秒后键消失,进程崩溃后约 30 秒过期,其它客户端能拿到锁。
|
||||
|
||||
算法(动态规划,中等):[300. 最长递增子序列](https://leetcode.cn/problems/longest-increasing-subsequence/),题解见[最长递增子序列](Leetcode%20题解%20-%20动态规划.md#1-最长递增子序列)。
|
||||
算法(动态规划,中等):[5. 最长回文子串](https://leetcode.cn/problems/longest-palindromic-substring/),题解见[最长回文子串](Leetcode%20题解%20-%20字符串.md#10-最长回文子串)。
|
||||
|
||||
明天:第 55 天,分布式事务与 CAP(分布式:分布式事务~BASE)。
|
||||
|
||||
@@ -1891,7 +1897,7 @@ AOP 基于动态代理,自调用时切面不生效的原因也在这里。事
|
||||
|
||||
</details>
|
||||
|
||||
算法(动态规划,中等):[1143. 最长公共子序列](https://leetcode.cn/problems/longest-common-subsequence/),题解见[最长公共子序列](Leetcode%20题解%20-%20动态规划.md#1-最长公共子序列)。
|
||||
算法(动态规划,中等):[300. 最长递增子序列](https://leetcode.cn/problems/longest-increasing-subsequence/),题解见[最长递增子序列](Leetcode%20题解%20-%20动态规划.md#1-最长递增子序列)。
|
||||
|
||||
明天:第 56 天,RPC、分布式 ID 与幂等(分布式:RPC~幂等)。
|
||||
|
||||
@@ -1920,7 +1926,7 @@ RPC 一节对比了它和 HTTP 的取舍。雪花算法要注意时钟回拨;
|
||||
|
||||
预期:每轮 10 个线程并发提交同一个 request_id,先查后插的写法每轮创建 2~3 个订单,直接 INSERT 依靠唯一索引时每轮只创建 1 个,其余 9 次报错 1062。
|
||||
|
||||
算法(动态规划,中等):[322. 零钱兑换](https://leetcode.cn/problems/coin-change/),题解见[找零钱的最少硬币数](Leetcode%20题解%20-%20动态规划.md#4-找零钱的最少硬币数)。
|
||||
算法(动态规划,中等):[1143. 最长公共子序列](https://leetcode.cn/problems/longest-common-subsequence/),题解见[最长公共子序列](Leetcode%20题解%20-%20动态规划.md#1-最长公共子序列)。
|
||||
|
||||
明天:第 57 天,消息队列的作用与可靠性(消息队列:消息模型~消息队列的可靠性)。
|
||||
|
||||
@@ -1949,7 +1955,7 @@ RPC 一节对比了它和 HTTP 的取舍。雪花算法要注意时钟回拨;
|
||||
|
||||
预期:消费者完成 3 笔扣款后、自动提交偏移量(默认每 5 秒)之前被 kill -9,组的偏移量仍为空,约 45 秒后重启的消费者又收到这 3 条消息、重复扣款,去重方法是在同一个本地事务中先插入去重表(键如 order:1024:pay)再扣款,重复消息报 1062 后回滚并直接确认。
|
||||
|
||||
算法(动态规划,中等):[64. 最小路径和](https://leetcode.cn/problems/minimum-path-sum/),题解见[矩阵的最小路径和](Leetcode%20题解%20-%20动态规划.md#1-矩阵的最小路径和)。
|
||||
算法(动态规划,中等):[322. 零钱兑换](https://leetcode.cn/problems/coin-change/),题解见[找零钱的最少硬币数](Leetcode%20题解%20-%20动态规划.md#4-找零钱的最少硬币数)。
|
||||
|
||||
明天:第 58 天,顺序、积压与延迟消息(消息队列:顺序性~延迟消息与死信队列)。
|
||||
|
||||
@@ -1978,7 +1984,7 @@ RPC 一节对比了它和 HTTP 的取舍。雪花算法要注意时钟回拨;
|
||||
|
||||
预期:分区 0、1、2 分别积压 9237、9577、8186 条,LAG = LOG-END-OFFSET - CURRENT-OFFSET(日志末尾偏移量减去组已提交的偏移量),例如 11186 - 3000 = 8186。
|
||||
|
||||
算法(动态规划,中等):[198. 打家劫舍](https://leetcode.cn/problems/house-robber/),题解见[强盗抢劫](Leetcode%20题解%20-%20动态规划.md#2-强盗抢劫)。
|
||||
算法(动态规划,中等):[64. 最小路径和](https://leetcode.cn/problems/minimum-path-sum/),题解见[矩阵的最小路径和](Leetcode%20题解%20-%20动态规划.md#1-矩阵的最小路径和)。
|
||||
|
||||
明天:第 59 天,Kafka(消息队列:Kafka)。
|
||||
|
||||
@@ -2007,7 +2013,7 @@ RPC 一节对比了它和 HTTP 的取舍。雪花算法要注意时钟回拨;
|
||||
|
||||
预期:默认配置(批量)每秒约 5.7 万~6.8 万条、平均延迟 21~54 ms;batch.size=0、linger.ms=0 关闭批量后每秒只有约 6500~6800 条,平均延迟升到约 1.4 秒;原因是每条消息单独成为一个批次,网络请求、写日志和副本复制的次数大幅增加,发不出去的请求在生产者端排队,所以吞吐下降的同时延迟也升高。
|
||||
|
||||
算法(双指针,困难):[42. 接雨水](https://leetcode.cn/problems/trapping-rain-water/),题解见[接雨水](Leetcode%20题解%20-%20双指针.md#9-接雨水)。
|
||||
算法(动态规划,中等):[62. 不同路径](https://leetcode.cn/problems/unique-paths/),题解见[矩阵的总路径数](Leetcode%20题解%20-%20动态规划.md#2-矩阵的总路径数)。
|
||||
|
||||
综合题:下单后需要扣库存、发优惠券、发短信:哪些步骤放在同一个事务中,哪些通过消息队列异步完成?消息丢失或重复时分别会怎样?(见[异步处理](消息队列.md#异步处理)、[本地消息表](分布式.md#本地消息表)、[幂等](分布式.md#九幂等))
|
||||
|
||||
@@ -2051,7 +2057,7 @@ RPC 一节对比了它和 HTTP 的取舍。雪花算法要注意时钟回拨;
|
||||
|
||||
预期:新建后连续 acquire() 第 1 次不等待、之后每次等待约 0.2 秒,空闲 2 秒后前 6 次都不等待、第 7 次等待约 0.2 秒,令牌不足时 tryAcquire() 立即返回 false。
|
||||
|
||||
算法(滑动窗口,困难):[239. 滑动窗口最大值](https://leetcode.cn/problems/sliding-window-maximum/),题解见[剑指 Offer 59](59.%20滑动窗口的最大值.md)。
|
||||
算法(动态规划,中等):[198. 打家劫舍](https://leetcode.cn/problems/house-robber/),题解见[强盗抢劫](Leetcode%20题解%20-%20动态规划.md#2-强盗抢劫)。
|
||||
|
||||
明天:第 61 天,秒杀与抢红包(系统设计基础:秒杀系统)。
|
||||
|
||||
@@ -2080,7 +2086,7 @@ RPC 一节对比了它和 HTTP 的取舍。雪花算法要注意时钟回拨;
|
||||
|
||||
预期:Lua 脚本成功 100、库存不足 1800、重复购买 100、剩余库存 0,没有超卖;先 GET 再 DECR 时成功 106~114 次、剩余库存 -6~-14,出现超卖。
|
||||
|
||||
算法(滑动窗口,困难):[76. 最小覆盖子串](https://leetcode.cn/problems/minimum-window-substring/),题解见[最小覆盖子串](Leetcode%20题解%20-%20双指针.md#10-最小覆盖子串)。
|
||||
算法(双指针,困难):[42. 接雨水](https://leetcode.cn/problems/trapping-rain-water/),题解见[接雨水](Leetcode%20题解%20-%20双指针.md#9-接雨水)。
|
||||
|
||||
明天:第 62 天,海量数据、短链与 Feed 流(系统设计基础:海量数据处理~Feed 流)。
|
||||
|
||||
@@ -2109,7 +2115,7 @@ RPC 一节对比了它和 HTTP 的取舍。雪花算法要注意时钟回拨;
|
||||
|
||||
预期:301 的响应被浏览器缓存,第二次访问 /a 时浏览器直接请求 /target,服务器只收到一次 /a,而 302 的 /b 两次都到达服务器,所以 301 会漏掉后续点击,需要统计点击量时用 302。
|
||||
|
||||
算法(链表,困难):[25. K 个一组翻转链表](https://leetcode.cn/problems/reverse-nodes-in-k-group/),题解见[K 个一组翻转链表](Leetcode%20题解%20-%20链表.md#11-k-个一组翻转链表)。
|
||||
算法(滑动窗口,困难):[239. 滑动窗口最大值](https://leetcode.cn/problems/sliding-window-maximum/),题解见[剑指 Offer 59](59.%20滑动窗口的最大值.md)。
|
||||
|
||||
明天:第 63 天,高并发系统的设计(系统设计基础:高并发系统的设计)。
|
||||
|
||||
@@ -2134,7 +2140,7 @@ RPC 一节对比了它和 HTTP 的取舍。雪花算法要注意时钟回拨;
|
||||
|
||||
</details>
|
||||
|
||||
算法(链表,困难):[23. 合并 K 个升序链表](https://leetcode.cn/problems/merge-k-sorted-lists/),题解见[合并 K 个有序链表](Leetcode%20题解%20-%20链表.md#13-合并-k-个有序链表)。
|
||||
算法(滑动窗口,困难):[76. 最小覆盖子串](https://leetcode.cn/problems/minimum-window-substring/),题解见[最小覆盖子串](Leetcode%20题解%20-%20双指针.md#10-最小覆盖子串)。
|
||||
|
||||
明天:第 64 天,Docker(Docker:解决的问题~镜像与容器)。
|
||||
|
||||
@@ -2163,7 +2169,7 @@ RPC 一节对比了它和 HTTP 的取舍。雪花算法要注意时钟回拨;
|
||||
|
||||
预期:多阶段构建的镜像只含 JRE 和 jar,约 236 MB,在 Maven 镜像中单阶段构建并运行的镜像约 680 MB,多出了 JDK、Maven 和下载的依赖。
|
||||
|
||||
算法(栈和队列,困难):[32. 最长有效括号](https://leetcode.cn/problems/longest-valid-parentheses/),题解见[最长有效括号](Leetcode%20题解%20-%20动态规划.md#3-最长有效括号)。
|
||||
算法(链表,困难):[25. K 个一组翻转链表](https://leetcode.cn/problems/reverse-nodes-in-k-group/),题解见[K 个一组翻转链表](Leetcode%20题解%20-%20链表.md#11-k-个一组翻转链表)。
|
||||
|
||||
明天:第 65 天,Kubernetes(Docker:Kubernetes)。
|
||||
|
||||
@@ -2188,7 +2194,7 @@ Kubernetes 重点看 Pod 的创建过程、Service 和 Ingress 的作用,以
|
||||
|
||||
</details>
|
||||
|
||||
算法(二分查找,困难):[4. 寻找两个正序数组的中位数](https://leetcode.cn/problems/median-of-two-sorted-arrays/),题解见[两个有序数组的中位数](Leetcode%20题解%20-%20二分查找.md#7-两个有序数组的中位数)。
|
||||
算法(链表,困难):[23. 合并 K 个升序链表](https://leetcode.cn/problems/merge-k-sorted-lists/),题解见[合并 K 个有序链表](Leetcode%20题解%20-%20链表.md#13-合并-k-个有序链表)。
|
||||
|
||||
明天:第 66 天,系统设计题的思路与即时通讯(系统设计基础:系统设计题的思路~即时通讯)。
|
||||
|
||||
@@ -2217,7 +2223,7 @@ Kubernetes 重点看 Pod 的创建过程、Service 和 Ingress 的作用,以
|
||||
|
||||
预期:第二次 INSERT 报错 ERROR 1062 Duplicate entry '...' for key 'video_like.PRIMARY';INSERT IGNORE 不报错,影响 0 行;EXPLAIN 中 type 为 ref,按 user_id 等值查找,扫描的行数约等于这个用户点过赞的视频数。
|
||||
|
||||
算法(二叉树,困难):[124. 二叉树中的最大路径和](https://leetcode.cn/problems/binary-tree-maximum-path-sum/)。
|
||||
算法(栈和队列,困难):[32. 最长有效括号](https://leetcode.cn/problems/longest-valid-parentheses/),题解见[最长有效括号](Leetcode%20题解%20-%20动态规划.md#3-最长有效括号)。
|
||||
|
||||
明天:第 67 天,订单与支付(系统设计基础:订单与支付)。
|
||||
|
||||
@@ -2246,7 +2252,7 @@ Kubernetes 重点看 Pod 的创建过程、Service 和 Ingress 的作用,以
|
||||
|
||||
预期:只改本地状态的写法,到期时把订单改为已关闭,但平台上的交易仍然可以支付,用户在关单前后付款时就出现平台已付、本地已关(300 轮中二百多轮),只能退款;先向平台关单、失败只等回调的写法,关单因用户已付款而失败后只等回调,回调又有 10% 丢失,订单就停在待支付(300 轮中二十多轮),要靠查单或对账补上;按三步的写法关单失败时查单,300 轮全部一致。
|
||||
|
||||
算法(动态规划,困难):[72. 编辑距离](https://leetcode.cn/problems/edit-distance/),题解见[编辑距离](Leetcode%20题解%20-%20动态规划.md#2-编辑距离)。
|
||||
算法(二分查找,困难):[4. 寻找两个正序数组的中位数](https://leetcode.cn/problems/median-of-two-sorted-arrays/),题解见[两个有序数组的中位数](Leetcode%20题解%20-%20二分查找.md#7-两个有序数组的中位数)。
|
||||
|
||||
综合题:设计一个秒杀系统:从页面、网关、服务、缓存、消息队列到数据库,每一层分别做什么来扛住流量、防止超卖?出现故障时怎样降级?(见[秒杀系统](系统设计基础.md#六秒杀系统)、[读路径的逐层分流](系统设计基础.md#读路径的逐层分流)、[限流、降级与熔断](系统设计基础.md#限流降级与熔断))
|
||||
|
||||
@@ -2260,3 +2266,76 @@ Kubernetes 重点看 Pod 的创建过程、Service 和 Ingress 的作用,以
|
||||
5. 降级:依赖持续失败时熔断,库存服务被熔断时详情页显示“有货”、下单以数据库为准,关闭评论等非核心功能,无法降级时快速失败提示稍后再试。
|
||||
|
||||
</details>
|
||||
|
||||
明天:第 68 天,RAG 与 Agent(大模型应用:RAG 的流程;大模型应用:Agent 是什么~Agent 与工作流)。
|
||||
|
||||
## 十五、大模型应用
|
||||
|
||||
### 第 68 天:RAG 与 Agent
|
||||
|
||||
后端面试中的大模型问题大多停留在概念上,这两天只读最常被问到的部分,想深入的读者转到大模型应用开发路线。RAG 只读流程一节,记住“先检索资料、再让模型按资料回答”这条主线;Agent 的四节要细读,重点是模型始终只输出文本、工具由应用代码执行,以及什么时候用工作流就够了。
|
||||
|
||||
阅读:《大模型应用》的[RAG 的流程](大模型应用.md#流程);《大模型应用》从[Agent 是什么](大模型应用.md#agent-是什么)读到[Agent 与工作流](大模型应用.md#agent-与工作流)。选读:[混合检索](大模型应用.md#混合检索)、[重排](大模型应用.md#重排)。
|
||||
|
||||
自测:
|
||||
|
||||
1. RAG 的完整流程是什么?它为什么能减少幻觉?(见[RAG 的流程](大模型应用.md#流程))
|
||||
2. Agent 和普通的大模型调用有什么区别?模型是怎样调用工具的?(见[Agent 是什么](大模型应用.md#agent-是什么)、[工具调用](大模型应用.md#工具调用))
|
||||
3. 什么时候用工作流,什么时候用 Agent?(见[Agent 与工作流](大模型应用.md#agent-与工作流))
|
||||
|
||||
<details>
|
||||
<summary>参考要点</summary>
|
||||
|
||||
1. 离线阶段解析文档、切成小段,用 Embedding 模型把每段转为向量存入向量数据库,同时建立关键词索引;在线阶段先改写问题,向量检索和关键词检索同时召回候选,合并后重排,把最相关的几段资料和问题一起放进提示词,生成带引用的回答;能减少幻觉,是因为模型从凭记忆回答变为根据给定的资料回答,每个结论都能按编号核对;但资料没找到或找错时模型仍会编造,所以要用重排分数和相似度阈值判断有没有答案,没有时改写问题再检索、向用户追问,或者如实回答没找到。
|
||||
2. 普通调用是一问一答,模型根据输入生成文本,不能自行查询数据库或执行操作,外部资料要由应用放进输入;Agent 由模型决定下一步调用哪个工具,应用代码执行后把结果交还模型,如此循环直到任务完成;工具调用的过程:请求中附上工具的名称、用途和参数的 JSON Schema,推理服务按对话模板把它们拼进提示词,模型生成一段特定格式的文本,推理服务解析成 tool_calls 返回,应用执行后用 tool 角色的消息把结果发回;模型始终只输出文本,可能写错参数或编造订单号,所以执行前要校验,循环要限制最大步数。
|
||||
3. 工作流的步骤和顺序由代码事先写好,可预测、成本可控,适合步骤固定的业务;Agent 的下一步由模型临时决定,适合事先确定不了步骤的开放任务,代价是成本更高、错误会逐步累积,每步成功率 95% 时连续 10 步都成功只有约 60%;判断标准是步骤和步骤之间的转移规则能否事先写清;实际系统常把两者组合:先用意图路由分流,退款这类涉及资金、步骤固定的流程写成工作流,开放的咨询交给 Agent。
|
||||
|
||||
</details>
|
||||
|
||||
动手:参照[Agent 循环](大模型应用.md#agent-循环)中并发执行工具调用的片段和[附录 A25](大模型应用.md#a25-并发执行工具调用)的预期输出,在 JDK 21 中自行定义 ToolCall 记录、每次休眠 500 毫秒的模拟查询方法和线程池,分别串行、并发执行两个查询订单的调用,比较耗时。
|
||||
|
||||
预期:串行执行约 1000 毫秒(两次各休眠 500 毫秒),并发执行约 520 毫秒(附录 A25 中为 519 毫秒);两条 tool 消息的 toolCallId 分别为 call_1 和 call_2,与模型给出的调用一一对应。
|
||||
|
||||
算法(二叉树,困难):[124. 二叉树中的最大路径和](https://leetcode.cn/problems/binary-tree-maximum-path-sum/),题解见[二叉树中的最大路径和](Leetcode%20题解%20-%20树.md#15-二叉树中的最大路径和)。
|
||||
|
||||
明天:第 69 天,MCP、Skills 与 AI 编程助手(大模型应用:MCP~Skills;大模型应用:AI 编程助手)。
|
||||
|
||||
### 第 69 天:MCP、Skills 与 AI 编程助手
|
||||
|
||||
MCP 和 Skills 常被拿来和工具调用比较:工具是模型能调用的函数,MCP 解决工具怎样接入应用,Skill 告诉 Agent 怎样完成一类任务。AI 编程助手一节把前一天的 Agent 循环落到日常开发上,后半部分的使用方法对应“你平时怎么用 AI 写代码”这类问题,回答时要结合自己的习惯。
|
||||
|
||||
阅读:《大模型应用》从[MCP](大模型应用.md#mcp)读到[Skills](大模型应用.md#skills);《大模型应用》的[AI 编程助手](大模型应用.md#ai-编程助手)。选读:[Harness](大模型应用.md#harness)。
|
||||
|
||||
自测:
|
||||
|
||||
1. MCP 解决了什么问题?它和 Function Calling 有什么区别?(见[MCP](大模型应用.md#mcp))
|
||||
2. Skill 是什么?它和工具、MCP 有什么区别?(见[Skills](大模型应用.md#skills))
|
||||
3. 你平时怎样用 AI 编程工具?怎样保证生成的代码质量?(见[AI 编程助手](大模型应用.md#ai-编程助手))
|
||||
|
||||
<details>
|
||||
<summary>参考要点</summary>
|
||||
|
||||
1. 同一个工具要接入多个 AI 应用时,原本要在每个应用里分别适配;MCP 定义了 AI 应用与外部工具、数据之间的统一接口,工具实现一次服务端就能被所有支持 MCP 的应用使用;三种角色是主机、客户端(一个客户端只连接一个服务端)和服务端,服务端提供工具、资源和提示词模板,本地用 stdio 通信,远程用 Streamable HTTP;Function Calling 是模型一侧的能力,模型根据工具描述给出调用,MCP 是应用与工具之间的协议,解决工具怎样被发现和调用,两者配合使用:主机把服务端的工具转换成工具定义交给模型,模型给出调用后再通过 MCP 发给服务端执行;代价是工具定义都会占用上下文,工具多时要按需加载。
|
||||
2. Skill 是一个包含 SKILL.md、脚本和参考资料的文件夹,教 Agent 完成某一类任务;核心是渐进式加载:平时只把名称和描述放进上下文(每个约 100 Token),模型判断与当前任务相关时才读入正文,正文引用的文档和脚本需要时才读取或执行;工具是可调用的函数,提供原子能力,MCP 解决工具怎样接入,Skill 提供做事的步骤和规范,执行时仍要调用工具;需要接入外部系统、要求权限控制的能力做成 MCP 服务端,组合已有工具的流程和规范写成 Skill。
|
||||
3. 给出可以验证的目标,例如让某个测试通过,修 bug 时先写一个能复现问题的测试;先让它阅读代码、给出方案,确认后再修改;任务拆小,一次只改一个功能,便于审查;生成的代码同样要做代码审查和测试,测试用例按需求写,由人检查边界;反复纠正的要求写进 CLAUDE.md、AGENTS.md 这类项目约定文件;AI 生成的代码占多大比例不是关键,开发者仍要负责需求、方案、审查和上线的结果,涉及资金和数据安全的修改每一处都要审查。
|
||||
|
||||
</details>
|
||||
|
||||
动手:阅读[附录 A24](大模型应用.md#a24-mcp-服务端的最小实现)的输出,说明工具抛出 ToolError 和普通异常时模型分别收到什么,参数类型不符合 Schema 时工具函数会不会执行。
|
||||
|
||||
预期:工具抛出 ToolError 时,错误信息作为 isError 为 true 的结果返回给模型,例如“Error executing tool get_order: 订单 9999 不存在,请向用户确认订单号”,模型可以据此向用户确认;换成普通的 ValueError 后,模型只收到 Error executing tool get_order,具体内容只在服务端的日志中;参数不符合 Schema 时(例如 order_id 传成数字 1024、缺少参数或参数名写错),SDK 先按 Schema 校验并返回 validation error,isError 为 true,工具函数不会执行。
|
||||
|
||||
算法(动态规划,困难):[72. 编辑距离](https://leetcode.cn/problems/edit-distance/),题解见[编辑距离](Leetcode%20题解%20-%20动态规划.md#2-编辑距离)。
|
||||
|
||||
综合题:要在订单系统中加一个客服助手,既能回答“订单到哪了”,也能办理退款,你会怎样设计?哪些事情交给模型,哪些必须由代码控制?(见[设计一个客服 Agent](大模型应用.md#设计一个客服-agent)、[Agent 与工作流](大模型应用.md#agent-与工作流)、[支付回调与重复支付](系统设计基础.md#支付回调与重复支付))
|
||||
|
||||
<details>
|
||||
<summary>答题思路</summary>
|
||||
|
||||
1. 接入与权限:从登录会话中取出 user_id,工具都用它鉴权,不作为参数让模型填写,用户只能查自己的订单。
|
||||
2. 路由:退款、改地址这类涉及资金、步骤固定的请求交给工作流,咨询类交给 Agent,置信度低时反问澄清。
|
||||
3. Agent:一个 ReAct 循环,工具有查询订单、查询物流、检索知识库和创建工单,参数先校验再执行,限制最大步数。
|
||||
4. 退款:由代码按校验、生成申请、用户确认、执行的顺序处理,模型只负责从对话中提取订单号和原因;退款单号作为幂等键,超时重试沿用原单号,不会退两次。
|
||||
5. 上线:用历史会话做评测集,写操作检查数据库的最终状态;先只上线查询类,退款先由人工客服确认;主模型出错时切到备用模型,再不行退回固定答案并转人工。
|
||||
|
||||
</details>
|
||||
|
||||
Reference in New Issue
Block a user