Technical Documentation: Snailclimb/JavaGuide
ℹ️ Provenance: Multi-Tier Ingestion · CodeWiki Reference
Overview (`README.md`)
- 推荐在线阅读(体验更好,速度更快):javaguide.cn
- 面试突击版本(只保留重点,附带精美 PDF 下载):interview.javaguide.cn
- 大模型实战项目: ⭐AI 智能面试辅助平台 + RAG 知识库(基于 Spring Boot 4.0 + Java 21 + Spring AI 2.0,非常适合作为学习和简历项目,学习门槛低)。
- 面试资料补充:
- 《Java 面试指北》:四年打磨,和 JavaGuide 开源版的内容互补,带你从零开始系统准备面试!
- 《后端面试高频系统设计&场景题》:30+ 道高频系统设计和场景面试,助你应对当下中大厂面试趋势。
- 使用建议 :如果你想要系统准备 Java 后端面试但又不知道如何开始的,可以参考 Java 后端面试通关计划(后端通用)。
- 求个 Star:如果觉得 JavaGuide 的内容对你有帮助的话,还请点个免费的 Star,这是对我最大的鼓励,感谢各位一起同行,共勉!传送门:GitHub | Gitee。
- 转载须知:以下所有文章如非文首说明为转载皆为 JavaGuide 原创,转载请在文首注明出处。如发现恶意抄袭/搬运,会动用法律武器维护自己的权益。让我们一起维护一个良好的技术创作环境!
AI 应用开发面试指南
面向后端开发者的 AI 应用开发、AI 编程实战与面试指南已开源,涵盖 LLM、Agent、RAG、MCP、Claude Code、Codex 等核心技术与工程实践。对标 JavaGuide!有帮助的话,欢迎 Star!
后端面试准备
- ⭐Java 后端面试通关计划(涵盖后端通用体系) (一定要看 :+1:)
- 如何高效准备 Java 面试?
- Java 后端面试重点总结
- Java 学习路线(最新版,4w+ 字)
- 程序员简历编写指南
- 项目经验指南
- 面试太紧张怎么办?
- 校招没有实习经历怎么办?实习经历怎么写?
Java
基础
知识点/面试题总结(必看:+1:):
重要知识点详解:
- 为什么 Java 中只有值传递?
- Java 序列化详解
- 泛型&通配符详解
- Java 反射机制详解
- Java 代理模式详解
- BigDecimal 详解
- Java 魔法类 Unsafe 详解
- Java SPI 机制详解
- Java 语法糖详解
集合
知识点/面试题总结:
- Java 集合常见知识点&面试题总结(上) (必看 :+1:)
- Java 集合常见知识点&面试题总结(下) (必看 :+1:)
- Java 容器使用注意事项总结
源码分析:
- ArrayList 核心源码+扩容机制分析
- LinkedList 核心源码分析
- HashMap 核心源码+底层数据结构分析
- ConcurrentHashMap 核心源码+底层数据结构分析
- LinkedHashMap 核心源码分析
- CopyOnWriteArrayList 核心源码分析
- ArrayBlockingQueue 核心源码分析
- PriorityQueue 核心源码分析
- DelayQueue 核心源码分析
IO
并发
知识点/面试题总结(必看 :+1:)
重要知识点详解:
- Java 锁详解
- 乐观锁和悲观锁详解
- CAS 详解
- JMM(Java 内存模型)详解
- 线程池:Java 线程池详解、Java 线程池最佳实践
- ThreadLocal 详解
- Java 并发容器总结
- Atomic 原子类总结
- AQS 详解
- CompletableFuture 详解
JVM(必看 :+1:)
JVM 这部分内容主要参考 JVM 虚拟机规范-Java8 和周志明老师的《深入理解 Java 虚拟机(第 3 版)》 (强烈建议阅读多遍!)。
新特性
- Java 8:Java 8 新特性总结(翻译)、Java8 常用新特性总结
- Java 9 新特性概览
- Java 10 新特性概览
- Java 11 新特性概览
- Java 12 & 13 新特性概览
- Java 14 & 15 新特性概览
- Java 16 新特性概览
- Java 17 新特性概览
- Java 18 新特性概览
- Java 19 新特性概览
- Java 20 新特性概览
- Java 21 新特性概览
- Java 22 & 23 新特性概览
- Java 24 新特性概览
- Java 25 新特性概览
计算机基础
操作系统
- 操作系统常见知识点&面试题总结(上)
- 操作系统常见知识点&面试题总结(下)
- 进程与线程详解:区别、状态、通信、上下文切换与虚拟线程
- 进程间通信(IPC)详解:管道、消息队列、共享内存、Socket 与 Binder
- 死锁详解:四个必要条件、Java 死锁排查与数据库死锁处理
- 操作系统内存管理详解:分页、分段、页面置换、Swap 与 OOM
- 虚拟内存详解:地址转换、TLB、缺页中断与页面置换
- 操作系统文件系统详解:inode、VFS、Page Cache 与日志机制
- I/O 多路复用详解:select、poll、epoll 原理与区别
- 零拷贝详解:mmap、sendfile 与 splice
- Linux:
网络
知识点/面试题总结:
重要知识点详解:
- OSI 和 TCP/IP 网络分层模型详解(基础)
- 应用层常见协议总结(应用层)
- HTTP vs HTTPS(应用层)
- HTTP 1.0 vs HTTP 1.1(应用层)
- HTTP 常见状态码(应用层)
- DNS 域名系统详解(应用层)
- TCP 三次握手和四次挥手(传输层)
- TCP Keepalive 和 HTTP Keep-Alive 有什么区别?(传输层)
- TCP 传输可靠性保障(传输层)
- 能 Ping 通,TCP 就一定能连通吗?(传输层)
- TCP 和 UDP 可以使用同一个端口吗?(传输层)
- 一台主机最多能保持多少个 TCP 连接?(传输层)
- ARP 协议详解(网络层)
- NAT 协议详解(网络层)
- 网络攻击常见手段总结(安全)
数据结构
图解数据结构:
- 线性数据结构 :数组、链表、栈、队列
- 图
- 堆
- 树:重点关注红黑树、B-,B+,B*树、LSM 树
其他常用数据结构:
算法
算法这部分内容非常重要,如果你不知道如何学习算法的话,可以看下我写的:
常见算法问题总结:
另外,GeeksforGeeks 这个网站总结了常见的算法,比较全面系统。
数据库
基础
MySQL
知识点/面试题总结:
- MySQL 常见知识点&面试题总结 (必看 :+1:)
- MySQL 高性能优化规范建议总结
重要知识点:
- MySQL 索引详解
- MySQL 索引失效场景总结
- MySQL 事务隔离级别图文详解)
- MySQL 三大日志(binlog、redo log 和 undo log)详解
- MySQL 备份与恢复详解
- InnoDB 存储引擎对 MVCC 的实现
- SQL 语句在 MySQL 中的执行过程
- MySQL 查询缓存详解
- MySQL 执行计划分析
- MySQL 自增主键一定是连续的吗
- MySQL 时间类型数据存储建议
- MySQL 隐式转换造成索引失效
Redis
知识点/面试题总结(必看:+1:):
重要知识点:
- 3 种常用的缓存读写策略详解
- Redis 能做消息队列吗?怎么实现?
- Redis 5 种基本数据结构详解
- Redis 3 种特殊数据结构详解
- Redis 持久化机制详解
- Redis 内存碎片详解
- Redis 常见阻塞原因总结
- Redis 集群详解
MongoDB
搜索引擎
开发工具
Maven
Gradle
Gradle 核心概念总结(可选,目前国内还是使用 Maven 普遍一些)
Docker
Git
系统设计
基础
常用框架
Spring/SpringBoot(必看 :+1:)
知识点/面试题总结 :
重要知识点详解:
MyBatis
安全
认证授权
数据安全
定时任务
Web 实时消息推送
分布式
理论&算法&协议
RPC
ZooKeeper
这两篇文章可能有内容重合部分,推荐都看一遍。
API 网关
分布式 ID
分布式锁
分布式事务
分布式配置中心
高性能
数据库优化
负载均衡
CDN
消息队列
高可用
冗余设计
限流
降级&熔断
超时&重试
集群
相同的服务部署多份,避免单点故障。
灾备设计和异地多活
灾备 = 容灾 + 备份。
- 备份:将系统所产生的所有重要数据多备份几份。
- 容灾:在异地建立两个完全相同的系统。当某个地方的系统突然挂掉,整个应用系统可以切换到另一个,这样系统就可以正常提供服务了。
异地多活 描述的是将服务部署在异地并且服务同时对外提供服务。和传统的灾备设计的最主要区别在于“多活”,即所有站点都是同时在对外提供服务的。异地多活是为了应对突发状况比如火灾、地震等自然或者人为灾害。
Star 趋势
公众号
如果大家想要实时关注我更新的文章以及分享的干货的话,可以关注我的公众号。
Overview (`docs/README.md`)
home: true
icon: "mdi:home-outline"
title: JavaGuide(Java 面试 & 后端通用知识体系)
heroImage: /logo.svg
heroText: JavaGuide
tagline: GitHub 156K+ Star 的 Java 面试与后端知识体系,覆盖计算机基础、数据库、分布式、高并发、系统设计与 AI 应用开发
sitemap:
changefreq: weekly
priority: 0.9
head:
- meta
- name: keywords
content: JavaGuide,Java面试,Java面试指南,Java八股文,后端面试,后端开发,数据库面试,MySQL面试,Redis面试,分布式,高并发,高性能,高可用,系统设计,消息队列,缓存,计算机网络,Linux,AI面试,AI应用开发,Agent,RAG,MCP,LLM,AI编程
- meta
- property: og:image
content: https://javaguide.cn/logo.png
actions:
- text: 开始阅读
link: /home.md
type: primary - text: 知识星球
link: /about-the-author/zhishixingqiu-two-years.md
type: default
footer: |-
鄂ICP备2020015769号-1 | 主题: VuePress Theme Hope
核心入口
- 后端面试主线:后端面试指南(⭐网站核心):系统整理 Java 面试八股文和后端高频面试题,覆盖 Java 基础、集合、并发、JVM、Spring、MySQL、Redis、分布式、高并发、高可用和系统设计。
- 计算机基础:计算机基础面试指南:系统梳理计算机网络、操作系统、数据结构与算法等后端面试底层基础,适合补齐基础短板。
- AI 应用开发:AI 应用开发面试指南(⭐新增):面向后端开发者梳理大模型基础、Prompt、Agent、RAG、MCP、LLM API 工程和 AI 系统设计等高频知识;如果想系统学习,可以配合 AI 应用开发与 Agent 学习路线(2026 最新版) 和 后端转 AI Agent 学习建议(2026 最新版)。
- AI 编程实战:AI 编程实践指南(⭐新增):聚焦 Claude Code、Codex、AI IDE、CLI Agent、上下文管理和 AI 辅助开发工作流,帮助你把 AI 真正用进日常编码。
- 学习路线:学习路线合集(2026 最新版):整理 Java 后端、AI 应用开发、AI Agent 和全栈开发等方向的系统学习建议。
- 延伸资料:
- 《Java 面试指北》:四年打磨,和 JavaGuide 开源版的内容互补,带你从零开始系统准备后端面试!
- 《后端面试高频系统设计&场景题》:30+ 道高频系统设计和场景面试,助你应对当下中大厂面试趋势。
- ⭐AI 智能面试辅助平台 + RAG 知识库:基于 Spring Boot 4.0 + Java 21 + Spring AI 2.0 的大模型实战项目,适合作为学习和简历项目。
精选文章
- 后端面试路径:Java 后端面试通关计划、Java 学习路线(2026 最新版)、Java 后端面试重点总结。不知道从哪里开始复习时,优先看这一组。
- Java 与数据库高频题:Java 基础、Java 集合、Java 并发、JVM、MySQL、Redis。适合集中刷语言、运行时和数据存储相关的核心问题。
- 架构与中间件高频题:分布式、微服务、消息队列、高性能、高可用、系统设计。适合准备社招、中高级岗位和项目场景追问。
- 计算机基础补强:计算机网络、操作系统、进程和线程、数据结构与算法。适合补齐校招、社招和大厂面试都绕不开的基础能力。
- AI 应用开发进阶:AI 应用开发与 Agent 学习路线(2026 最新版)、后端转 AI Agent 学习建议(2026 最新版)、AI 应用开发知识体系、LLM API 工程实践、RAG 基础概念、AI 应用系统设计。适合后端开发者先明确学习路径,再从模型调用走向可上线的 AI 应用。
- AI 编程效率提升:AI 编程实战指南、Claude Code 使用指南、Codex 使用指南、AI IDE 选型与实践。适合把 AI 编程工具真正接入日常开发、重构和排障流程。
关于 JavaGuide
JavaGuide 是一份面向 Java 和后端开发者的开源知识库,已在 GitHub 获得 156K+ Star。项目从 Java 面试复习出发,逐步扩展为覆盖后端核心技术、工程实践和 AI 应用开发的系统化学习指南。
JavaGuide 自 2018 年开源以来持续维护,累计提交 6200+ commit ,共有 640+ 多位贡献者共同参与维护和完善。
网站内容覆盖:
- 后端面试:Java 基础、集合、并发、JVM、MySQL、Redis、分布式、系统设计等核心知识。
- AI 应用开发:大模型(LLM)基础、Agent 智能体、RAG 检索增强生成、MCP 协议等前沿技术。
真心希望能够把这个项目做好,真正能够帮助到有需要的朋友!
如果觉得 JavaGuide 的内容对你有帮助的话,还请点个免费的 Star(绝不强制点 Star,觉得内容不错有收获再点赞就好),这是对我最大的鼓励,感谢各位一路同行,共勉!传送门:GitHub | Gitee。
PDF 版本 & 微信联系
- 如果你更喜欢 PDF(比如通勤/离线阅读/打印学习),扫描下方二维码,后台回复“PDF”即可获取最新版(持续更新,详细介绍见:2026 最新后端面试 PDF 资料)。
- 如果你想加我的微信,可以扫描下方二维码,后台回复“微信”。我会在朋友圈分享一些优质技术内容、学习资料和项目更新。
Overview (`docs/about-the-author/README.md`)
title: 个人介绍 Q&A category: 走近作者
这篇文章我会通过 Q&A 的形式简单介绍一下我自己。
我是什么时候毕业的?
很多老读者应该比较清楚,我是 19 年本科毕业的,刚毕业就去了某家外企“养老”。
我的学校背景是比较差的,高考失利,勉强过了一本线 20 来分,去了荆州的一所很普通的双非一本。不过,还好我没有因为学校而放弃自己,反倒是比身边的同学都要更努力,整个大学还算过的比较充实。
下面这张是当时拍的毕业照(后排最中间的就是我):
我坚持写了多久博客?
时间真快啊!我自己是从大二开始写博客的。那时候就是随意地在博客平台上发发自己的学习笔记和自己写的程序。就比如 谢希仁老师的《计算机网络》内容总结 这篇文章就是我在大二学习计算机网络这门课的时候对照着教材总结的。
身边也有很多小伙伴经常问我:“我现在写博客还晚么?”
我觉得哈!如果你想做什么事情,尽量少问迟不迟,多问自己值不值得,只要你觉得有意义,就尽快开始做吧!人生很奇妙,我们每一步的重大决定,都会对自己未来的人生轨迹产生影响。是好还是坏,也只有我们自己知道了!
对我自己来说,坚持写博客这一项决定对我人生轨迹产生的影响是非常正面的!所以,我也推荐大家养成坚持写博客的习惯。
我在大学期间赚了多少钱?
在校期间,我还通过办培训班、接私活、技术培训、编程竞赛等方式变现 20w+,成功实现“经济独立”。我用自己赚的钱去了重庆、三亚、恩施、青岛等地旅游,还给家里补贴了很多,减轻了父母的负担。
下面这张是我大一下学期办补习班的时候拍的(离开前的最后一顿饭):
下面这张是我大三去三亚的时候拍的:
其实,我在大学就这么努力地开始赚钱,也主要是因为家庭条件太一般,父母赚钱都太辛苦了!也正是因为我自己迫切地想要减轻父母的负担,所以才会去尝试这么多赚钱的方法。
我发现做咱们程序员这行的,很多人的家庭条件都挺一般的,选择这个行业的很大原因不是因为自己喜欢,而是为了多赚点钱。
如果你也想通过接私活变现的话,可以在我的公众号后台回复“接私活”来了解一些我的个人经验分享。
::: center
:::
为什么自称 Guide?
可能是因为我的项目名字叫做 JavaGuide , 所以导致有很多人称呼我为 Guide 哥。
后面,为了读者更方便称呼,我就将自己的笔名改成了 Guide。
我早期写文章用的笔名是 SnailClimb 。很多人不知道这个名字是啥意思,给大家拆解一下就清楚了。SnailClimb=Snail(蜗牛)+Climb(攀登)。我从小就非常喜欢听周杰伦的歌曲,特别是他的《蜗牛》🐌 这首歌曲,另外,当年我高考发挥的算是比较失常,上了大学之后还算是比较“奋青”,所以,我就给自己起的笔名叫做 SnailClimb ,寓意自己要不断向上攀登,嘿嘿 😁
后记
凡心所向,素履所往,生如逆旅,一苇以航。
生活本就是有苦有甜。共勉!
Overview (`docs/ai-coding/README.md`)
title: AI 编程实战指南:Claude Code、Cursor、Codex、Trae 使用技巧与面试题
category: AI 编程
tag:
- AI 编程
- AI 辅助开发
- 开发效率
- 后端面试
icon: mdi:code-tags
head: - meta
- name: keywords
content: AI编程,AI辅助编程,AI编程实战,AI编程技巧,AI编程面试题,AI编程工具,Claude Code,Claude Code教程,Claude Code使用技巧,Claude Code原理,Claude Code上下文管理,AutoCompact,CLAUDE.md,Claude Code Hooks,Auto Memory,Multi-Agent,Cursor,OpenAI Codex,Codex最佳实践,oh-my-pi,Trae,CLI vs IDE,Spec Coding,AI Skills,AI代码审查,drawio-chart,draw.io技术图
- meta
- property: og:title
content: AI 编程实战指南:Claude Code、Cursor、Codex、Trae 使用技巧与面试题
- meta
- property: og:description
content: 梳理 Claude Code、Cursor、OpenAI Codex、Trae、oh-my-pi 等 AI 编程工具的使用边界、上下文管理、原理机制、规则文件、代码审查和真实项目落地经验。
AI 编程工具好不好用,真不全看模型。很多时候,差别反而出在你怎么给上下文、怎么拆任务、怎么看 diff。
当然,这不是说模型不重要。模型质量是底座,但同一个模型放在不同的人手里,最后出来的效果可能差很多。
别把 AI 编程想成“我把需求丢进去,代码自己就写好了”。真实项目里没这么轻松。更常见的是:AI 写到一半方向歪了,你得接回来;一次改太多文件,你得拆小;测试没过,你还得顺着错误往回追;它一本正经瞎编的时候,你得能看出来。
所以这个专题不会只聊“哪个工具最强”。Claude Code、Cursor、OpenAI Codex、Trae 都有能帮上忙的地方,关键是你得知道:什么时候让 AI 写代码,什么时候让它查资料、读代码,什么时候该自己上手。还有更重要的一点:出问题以后怎么回滚,怎么把影响控制住。
本专栏属于 AIGuide 项目,对标 JavaGuide 质量,免费开源,欢迎 Star 支持:
适合谁看
- 已经在用 Claude Code、Cursor、Codex、Trae,但总觉得“能用,就是不太稳”。
- 想把 AI 编程工具用到真实项目里,而不是只拿来写几个 Demo。
- 正在纠结 CLI 和 IDE 怎么选,不知道什么时候该开多 Agent 并行。
- 想把
CLAUDE.md、Skills、Spec、上下文压缩这些机制真正用起来。 - 想理解 Claude Code Memory、Skills、Hooks 和 Multi-Agent 这些机制背后的分工。
- 准备 AI 编程、AI IDE、AI 辅助开发相关面试,想把工具经验讲得更像真实项目经历。
- 带团队,想知道 AI 生成的代码怎么审、怎么测、怎么控制提交粒度。
几个容易想错的地方
CLI 和 IDE 没有谁一定比谁强,主要看当前任务是什么。跨文件重构、批量修改、长任务自动化,用 CLI 会更顺手;局部补全、边看边改、随时调整,IDE 体验通常更好。把这条线分清楚,选工具就没那么纠结。
上下文不是越多越好。项目规则、相关文件、报错日志、验收标准都很重要,但一股脑塞给 AI,只会让关键约束被稀释。该写进 CLAUDE.md 的写进去,该放文档链接的放链接,该临时提供的就别变成永久规则。
多模型协同也不是把所有任务都丢给最贵的模型。写代码、看架构、审 diff、排查问题,需要的能力不一样。分工清楚,多模型能放大效率;分工不清楚,它也会把错误一起放大。
AI 生成的代码,一定要过测试、审查和可回滚的提交管理。“看起来能跑”只是第一步。真正麻烦的不是它某一行写错了,而是一次改了几百行,最后出问题时你根本不知道从哪儿查。
面试里如果被问到“AI 对开发效率的影响”,也别只说“提升了多少多少”。更好的回答是讲清楚:它在哪些环节确实省时间,哪些环节反而增加了审查成本,以及你是怎么兜住风险的。
建议阅读顺序
- AI 编程开放性面试题:先看面试会怎么问,也顺便校准自己到底会不会用。
- AI 编程选 CLI 还是 IDE?:把工具路线分清楚,别一上来就陷入工具名之争。
- Ghostty 安装、配置和常见技巧、Claude Code 使用指南、Claude Code 核心命令详解、高颜值 Claude Code 替代 OMP:如果你主用 CLI Agent,先把终端工作台调顺,再看 Claude Code 和 oh-my-pi 这类终端代理的操作思路。
- CLAUDE.md 最佳实践、Claude Code 上下文管理、Claude Code 记忆系统、Claude Code Skills 原理、Claude Code Hooks 原理、Claude Code 多 Agent 机制:把规则、上下文治理、记忆、按需加载、生命周期钩子和任务拆分串起来。
- AI 编程必备 Skills 推荐、强模型时代,AI 编程 Skills 还有必要装吗?、mattpocock/skills 深度使用指南、draw.io 绘图 Skill、OpenAI Codex 最佳实践指南、Spec Coding 规范驱动编程、Vibe Coding 实用技巧总结:把提示词、权限、Spec、Git、多 Agent、Skill 选型和技术配图工作流串起来。
- 工具栈确定后,再按需看 Qoder、Trae、DeepSeek V4 + Claude Code、MiniMax M3 + Claude Code、Kimi K3、Claude Code 接入第三方模型等实战案例。
核心文章
工具选型与方法论
- AI 编程开放性面试题:把 Cursor、Claude Code 等工具怎么用、AI 对后端开发有什么影响这些问题放在一起讲。
- AI 编程选 CLI 还是 IDE?:对比 Claude Code、Cursor、Kiro、Trae 等工具,重点看 CLI 和 IDE 到底适合什么活。
- Ghostty 安装、配置和常见技巧:从安装、字体主题、分屏快捷键到 Shell Integration,搭好 Claude Code 和 Codex CLI 的终端工作台。
- Spec Coding 规范驱动编程:系统梳理 Vibe Coding 和 Spec Coding 的区别,从四步落地到多代理协作的完整实战指南。
- Vibe Coding 实用技巧总结:涵盖 Git 版本控制、Spec 范围管理、Skill 沉淀、多模型分工、上下文管理、多 Agent 协作和权限控制等实战经验。
Claude Code 与 Codex 实战
- Claude Code 使用指南:从配置、能力扩展到常用工作流,适合刚开始认真用 Claude Code 的读者。
- CLAUDE.md 最佳实践:讲清
CLAUDE.md该写什么、不该写什么,项目变大后怎么和.claude/rules/、Auto Memory 配合。 - Claude Code 核心命令详解:专门讲
/simplify、/review、/loop、/batch这些命令怎么用。 - AI 编程必备 Skills 推荐:按任务场景整理 TDD、代码审查、UI 设计、网页自动化和 Skill 开发工作流,并说明哪些情况不值得安装。
- 强模型时代,AI 编程 Skills 还有必要装吗?:从 Superpowers 和
grilling的实际使用出发,讨论哪些 Skill 值得保留,以及怎么给 Skill 列表做减法。 - mattpocock/skills 深度使用指南:结合真实案例拆解
grilling、research、diagnosing-bugs和code-review的适用场景。 - drawio-chart Skill 开源:分享如何用 Agent 自动生成可编辑的 draw.io 技术图,适合流程图、架构图和模块关系图。
- OpenAI Codex 最佳实践指南:讲 Codex 云端智能体和 CLI 怎么配提示词、工具权限和安全策略。
- 高颜值 Claude Code 替代 OMP:介绍这个高颜值 Claude Code 替代工具的 Hashline、LSP/DAP、内置工具、多模型路由和上手配置。
- Claude Code Agent View 多会话管理:多 Agent 并行时,最怕状态乱、权限确认乱,这篇主要解决这个问题。
Claude Code 原理进阶
- Claude Code 上下文管理:从窗口预算、Context Rot 和渐进式压缩讲到 Sub-agent、handoff 与长任务状态外化。
- Claude Code 记忆系统:拆解
CLAUDE.md、.claude/rules/、Auto Memory、Subagent Memory 和第三方记忆插件的分工。 - Claude Code Skills 原理:讲清 Skills 的文件结构、发现加载、动态上下文、安全限制,以及和 Subagent 的配合方式。
- Claude Code Hooks 原理:围绕生命周期节点讲 Hooks 的触发时机、输入输出、风险拦截和自动化工作流。
- Claude Code 多 Agent 机制:对比 Subagent、Fork Subagent 和 Agent Teams,理解任务拆分、上下文隔离和协作边界。
真实项目案例
- IDEA 搭配 Qoder 插件实战:看 AI 怎么在 JetBrains IDE 里做接口优化和代码重构。
- Trae + MiniMax 多场景实战:用 Redis 故障排查、跨语言重构这些场景,看 AI 辅助编程能做到哪一步。
- Claude Code 接入第三方模型实战:通过 GLM-5.1 做 JVM 智能诊断助手和慢查询治理。
- DeepSeek V4 + Claude Code 实战:实测代码审计、Flyway 集成、多模型协同这些更贴近项目的任务。
- MiniMax M3 + Claude Code 实战:用线上 Redis SCAN 故障排查、SCAN 游标算法跨语言复刻、监控面板搭建三个案例实测 M3。
- Kimi K3 多场景实战:通过热点追踪系统、Java 项目改造和 3A 游戏 Demo,实测 K3 处理长程任务、多模态和复杂工程交付的表现。
- IDEA + CC GUI 插件实战:想在 IDEA 里用 GUI 管 Claude Code 和 Codex,可以看这个开源插件案例。
高频问题
- AI 编程工具到底适合做代码生成、代码审查、重构、排错还是文档整理?
- Claude Code、Cursor、Codex、Trae、Qoder 分别适合什么场景?
- CLI 和 IDE 的核心差异是什么?为什么长任务更依赖上下文管理?
CLAUDE.md、.claude/rules/、Skills 和 Auto Memory 应该怎么分工?- Claude Code Hooks、Skills、Subagent 和 Agent Teams 分别解决什么问题?
- 如何给 AI 提供足够但不过量的上下文?
- AI 修改大仓库时,如何控制变更范围,避免越改越乱?
- 多模型协同什么时候有价值?如何避免模型之间互相放大错误?
- AI 生成代码应该如何验收?测试、Diff、代码审查和提交粒度怎么配合?
- AI 编程会削弱程序员能力吗?后端开发者应该保留哪些判断力和工程基本功?
相关专题
Overview (`docs/ai/README.md`)
title: AI 应用开发知识体系:大模型、Agent、RAG、MCP、Prompt 工程与系统设计
category: AI
tag:
- AI
- 大模型
- AI 应用开发
- 后端面试
icon: mdi:robot-outline
sitemap:
changefreq: weekly
priority: 1
head: - meta
- name: keywords
content: AI应用开发,AI应用开发面试,AI工程师面试,大模型,大模型面试,LLM,LLM面试,Agent,Agent面试,RAG,RAG面试,MCP,Prompt工程,向量数据库,AI系统设计,AI编程面试
- meta
- property: og:title
content: AI 应用开发知识体系:大模型、Agent、RAG、MCP、Prompt 工程与系统设计
- meta
- property: og:description
content: 从大模型调用、Agent、RAG、MCP、Prompt 工程到评测和系统设计,梳理后端开发者进入 AI 应用开发需要补齐的关键知识。
做 AI 应用不是把 Prompt 塞进接口就结束了。真到项目里,马上会遇到上下文长度、结构化输出、RAG 召回、工具权限、评测回归、成本和稳定性这些问题。
这些问题没法各解各的。大模型基础、Agent、RAG、工具调用、系统设计必须连起来理解——只懂调用 API,到了架构评审会卡住;只熟 RAG 论文,到了知识库维护还是不知道怎么处理增量更新和版本去重。
如果时间有限,先看 AI 应用开发面试指南,把大模型、Agent、RAG、Skills、MCP 和 AI 系统设计里最容易被追问的问题过一遍;如果你还没确定学习顺序,或者正从后端开发转向 AI 应用开发,可以先看 Java/Go 开发者 AI 应用开发与 Agent 学习路线(2026 最新版) 和 后端开发者转型 AI Agent 学习建议(2026 最新版);如果想补得扎实一些,再按下面的阅读顺序推进。
专题内容按大模型基础、Agent、RAG 和系统设计组织,文章之间通过阅读顺序和相关链接串联:
本专栏内容同时收录在开源 AIGuide 项目中:
文章会随 API、框架和模型能力变化持续校订,涉及版本、价格和产品能力时请同时核对对应官方文档。
适合谁看
- 正在从后端开发转向 AI 应用开发,想补齐大模型、Agent、RAG 和系统设计主线的工程师。
- 准备 AI 工程师、AI 应用开发、后端转 AI 相关岗位面试的同学。
- 做过 Prompt Demo,但对模型调用链路、结构化输出、RAG 检索优化和评测闭环还不够熟的开发者。
- 想把 MCP、Function Calling、Tool Calling、向量数据库、模型网关这些概念放到真实项目里理解的读者。
- 已经在项目中接入大模型,但开始遇到稳定性、成本、安全治理和质量回归问题的团队成员。
几个容易踩坑的地方
大模型真不能只当成一个黑盒 API 来调。Token 被截断、采样参数一变输出就飘、说好返回 JSON 结果还是乱了,这些问题靠 Prompt 很难彻底兜住。你在提示词里加一句“请严格按照 JSON 输出”,只能算第一层约束,真正上线时还是得在调用链路里做格式校验、重试、兜底和异常处理。
Agent 也不是能自动调工具就完事了。真正难的是 Memory 和 Context Engineering。上下文没管好,Agent 跑几轮之后就容易偏题,前面说过什么、当前任务做到哪一步、哪些工具结果还能用,全都可能乱掉。长任务里更明显,有时候它不是不会做,而是循环几次之后自己把自己绕进去了,一直跑到 token 快耗完才停。
RAG 答非所问,很多时候也别急着怪模型。大部分问题其实出在召回阶段:Chunk 切得太粗、Query 没改写、关键词检索和向量检索没结合、重排没做好。这个时候一项一项排查召回链路,往往比直接换一个更贵的模型有用。
MCP、Function Calling、Tool Calling 这些东西,解决的是工具怎么接进来的问题。协议统一之后,接工具确实方便了,但真到生产环境,麻烦的地方反而在后面:谁能调用这个工具、能操作哪些数据、调用记录怎么审计、失败了怎么回滚。这些如果没设计好,协议再标准也不够用。
AI 应用一旦上线,稳定性、可观测、成本控制、质量回归这些问题都会冒出来。Demo 阶段通常感受不到,因为调用量小、场景也干净。等真正接到业务流量里,第一次做生产级 AI 应用的团队,基本都会被这些问题教育一次。
建议阅读顺序
AI 核心概念总览:先把 LLM、Token、Agent、RAG、MCP、Skills、ReAct 这些概念放到同一条链路里。
AI 应用开发面试指南:建立高频问题清单,知道面试和项目复盘最常被追问哪些点。
LLM 运行机制、大模型 API 调用工程实践:理解模型调用链路、上下文和结构化返回。
AI Agent 核心概念、大模型提示词工程、上下文工程:建立 Agent 和 Prompt/Context 的基础认知。
多 Agent 协作系统设计:继续学习任务拆分、状态共享、冲突处理和失败恢复。
Memory、MCP、Skills、Harness、Workflow、Loop 的完整顺序见 Agent 专题 README。
RAG 基础概念、RAG 文档处理与切分策略、RAG 检索优化:补齐企业知识库问答主线。
AI 应用系统设计、LLM/Agent 安全实战、大模型网关详解、AI 应用评测体系:把 Demo 放进真实后端系统里,补齐权限、安全、网关、评测和治理。
核心文章
面试与复习路线
- Java/Go 开发者 AI 应用开发与 Agent 学习路线(2026 最新版):按大模型基础、LLM API、Prompt、RAG、Agent、工程化和项目实战拆解学习路径。
- 后端开发者转型 AI Agent 学习建议(2026 最新版):先判断是否适合转型,再看 Java AI 与 Python AI 怎么选、能投什么岗位、应该如何学习。
- AI 核心概念总览:按大模型基础、Agent 和 RAG 三条主线串联 LLM、Token、MCP、Skills、ReAct、Embedding、GraphRAG 等核心概念。
- AI 应用开发面试题专题:按大模型基础、AI Agent、RAG 和 AI 系统设计组织复习路线。
- AI 应用开发面试指南:把 AI 应用开发常见追问放到一条复习路线里,适合先看。
- 大模型基础面试题总结:覆盖 Token、上下文窗口、采样参数、API 调用、结构化输出和评测体系。
- AI Agent 面试题总结:覆盖 Agent Loop、Memory、Prompt、Context、MCP、Skills、Harness Engineering 和工作流。
- Agent 项目面试怎么讲?:从系统架构、技术选型、效果证据讲到 Badcase 复盘,适合把真实项目整理成完整面试回答。
- RAG 面试题总结:覆盖 RAG 基础、向量数据库、文档处理、检索优化、GraphRAG、知识库更新和评测。
- AI 系统设计面试题总结:覆盖生产级 AI 应用架构、模型网关、可观测、评测、安全治理和实时语音 Agent。
大模型基础
- 大模型基础专题:从模型运行机制、API 调用、结构化输出到 AI 应用评测,先把调用链路看明白。
- LLM 运行机制:Token、上下文窗口与采样参数怎么影响输出:把 Token、上下文窗口、Temperature 等概念还原为清晰、可控的工程参数。
- 大模型 API 调用工程实践:拆解 Prompt 组装、模型网关、流式响应、重试限流和结构化返回。
- 大模型结构化输出详解:讲清 JSON Schema、Function Calling、Tool Calling 与 MCP 的底层链路。
- AI 应用评测体系:覆盖 Golden Set、LLM-as-Judge、RAG/Agent 指标、Trace 回放和线上灰度闭环。
AI Agent
- AI Agent 专题:从 Agent 基础概念、Memory、Prompt、Context 到 MCP、Skills 和 Harness Engineering。
- AI Agent 核心概念:理解 Agent 和传统编程、Workflow 的区别,以及 Agent Loop、Tools 注册等核心概念。
- AI Agent 记忆系统:深入理解短期记忆、长期记忆、记忆生命周期和生产级优化策略。
- 多 Agent 协作系统设计:讲清什么时候需要多 Agent,以及任务拆分、状态共享、冲突处理和失败恢复如何落地。
- 大模型提示词工程:掌握 Prompt 四要素、常见技巧和 Prompt 注入防护。
- 上下文工程:理解静态规则编排、动态信息挂载、Token 预算降级和上下文持久化。
- 万字拆解 MCP 协议:理解 MCP 的分层架构、核心能力和 MCP Server 生产实践。
- 万字详解 Agent Skills:理解 Skills 与 Prompt、MCP、Function Calling 的本质区别。
- Harness Engineering:六层检查框架、上下文管理与工程实践:拆解 Model + Harness 的工程化架构和团队实践。
- AI 工作流中的 Workflow、Graph 与 Loop:理解 AI 工作流的节点、边、状态、安全边界和实现方式。
- Loop Engineering 是什么?为什么说它是新瓶装旧酒?:说明代码 Agent 外层循环的触发、上下文、验证、状态和停止条件。
RAG 检索增强生成
- RAG 专题:围绕企业知识库问答,梳理文档处理、向量数据库、GraphRAG、检索优化和知识库更新。
- RAG 基础概念:理解 RAG 是什么、为什么需要它、核心优势和局限性。
- RAG 文档处理与切分策略:覆盖文档解析、清洗、结构化、Chunking 和多模态内容处理。
- RAG 向量索引算法和向量数据库:掌握 HNSW、IVFFLAT 等索引算法和向量数据库选型。
- RAG 检索优化:覆盖 Chunk 策略、Hybrid Search、Query Rewrite、Rerank 和上下文压缩。
- GraphRAG:理解实体、关系、社区发现、全局检索与局部检索。
- RAG 知识库文档更新策略:掌握增量更新、版本控制、去重和全量重建。
AI 系统设计
- AI 系统设计专题:把 Prompt Demo 放进真实后端系统里看,重点关注架构、模型网关、语音链路、可观测、评测和安全治理。
- AI 应用系统设计:把 Prompt Demo 放进生产链路,覆盖 Prompt 管理、模型网关、RAG、Memory、Tool 调用、可观测、评测和安全合规。
- LLM/Agent 安全实战:覆盖直接与间接 Prompt Injection、工具越权、MCP 授权、敏感数据、代码沙箱、供应链和安全回归。
- 大模型网关详解:理解 LLM Gateway 的多模型路由、fallback、限流配额、成本归因、观测审计和缓存策略。
- AI 语音技术详解:拆解 VAD、ASR、LLM、TTS、流式播放、打断处理和端云混合选型。
高频问题
- 大模型的 Token、上下文窗口、Temperature、Top P 分别会影响什么?
- 为什么结构化输出不能只依赖 Prompt?JSON Schema、Function Calling 和服务端校验分别解决什么问题?
- Agent 和 Workflow 有什么区别?Agent Loop 中观察、规划、行动、反思如何协作?
- Prompt Engineering 和 Context Engineering 有什么区别?
- MCP 解决了什么问题?它和 Function Calling、Tool Calling 是什么关系?
- RAG 为什么会答非所问?应该从召回、排序、上下文压缩还是生成阶段排查?
- 向量数据库如何选型?HNSW、IVFFLAT 这些索引适合什么场景?
- AI 应用怎么评测?Golden Set、LLM-as-Judge、线上灰度和 Trace 回放如何串起来?
- 生产级 AI 应用为什么需要模型网关?如何做限流、fallback、成本控制和审计?
相关专题
Overview (`docs/books/README.md`)
title: 技术书籍精选:Java、数据库、分布式系统、计算机基础与软件质量
category: 计算机书籍
sitemap:
changefreq: weekly
priority: 0.9
head:
- meta
- name: keywords
content: 技术书籍推荐,计算机书籍,Java书籍,数据库书籍,分布式系统书籍,计算机基础书籍,软件质量书籍,程序员书单,后端书单
这份 技术书籍精选 面向程序员系统学习和长期成长,整理 Java、数据库、分布式系统、计算机基础、搜索引擎、软件质量等方向的优质书单。
书单来自开源项目 CodingDocs/awesome-cs,会持续更新。欢迎在项目 issues 区 推荐你认可的技术书籍,一起维护一个更高质量的中文技术书单。
适合谁看
- 想系统补基础,但不知道该读哪些书的程序员。
- 准备校招、社招、中大厂后端面试的同学。
- 希望从碎片化文章过渡到体系化学习的后端开发者。
- 想提升架构、数据库、代码质量、工程协作能力的工程师。
学习重点
- 书单不是越长越好,优先选择和当前阶段最匹配的一两本精读。
- 计算机基础、Java、数据库是后端开发最常用的底层能力。
- 分布式系统和搜索引擎适合在有一定工程经验后深入学习。
- 软件质量类书籍适合用来提升代码可维护性、测试意识和团队协作能力。
- 读书要结合项目、面试题和源码实践,否则容易停留在概念层面。
建议阅读顺序
- 计算机基础书籍推荐:先补操作系统、网络、数据结构与算法等通用基础。
- Java 书籍推荐:再系统学习 Java 基础、并发、JVM、框架和性能优化。
- 数据库书籍推荐:掌握 MySQL、Redis、NoSQL 和数据工程相关知识。
- 分布式系统书籍推荐:有项目经验后再深入分布式理论、架构和中间件。
- 软件质量书籍推荐:用重构、测试、工程化和协作能力提升长期产出质量。
核心文章
- 计算机基础书籍推荐:操作系统、计算机网络、数据结构与算法等基础书单,适合打底和面试复习。
- Java 书籍推荐:覆盖 Java 基础、并发、JVM、框架、性能优化等方向的经典书籍。
- 数据库书籍推荐:整理 MySQL、Redis、NoSQL、数据工程等后端常用数据库方向书籍。
- 分布式系统书籍推荐:覆盖分布式理论、系统架构、中间件和工程实践。
- 搜索引擎书籍推荐:整理信息检索、搜索架构、Elasticsearch 等相关书籍与资料。
- 软件质量书籍推荐:覆盖代码质量、重构、测试、工程化和团队协作相关书籍。
相关专题
Overview (`docs/cs-basics/README.md`)
title: 计算机基础知识体系:计算机网络、操作系统、数据结构与算法
icon: "mdi:desktop-classic"
sitemap:
changefreq: weekly
priority: 0.95
head:
- meta
- name: keywords
content: 计算机基础,计算机基础知识总结,计算机基础面试题,计算机网络,计算机网络面试题,操作系统,操作系统面试题,数据结构,数据结构面试题,算法,算法面试题,Linux,TCP/IP,HTTP,DNS,后端面试,Java面试,八股文
- meta
- property: og:title
content: 计算机基础知识体系:计算机网络、操作系统、数据结构与算法
- meta
- property: og:description
content: 梳理计算机网络、操作系统、数据结构与算法等计算机基础知识,适合后端开发者校招、社招复习。
这份 计算机基础知识体系 面向后端学习和面试复习,按“计算机网络 -> 操作系统 -> 数据结构 -> 算法”的顺序整理本站计算机基础相关文章。
如果你时间有限,建议先看 计算机网络常见面试题总结 和 操作系统常见面试题总结,快速建立高频问题清单;如果你想系统补基础,可以按下面的专题顺序推进。
整站配有 300+ 张技术配图,用图解的方式把抽象概念讲清楚,不是干巴巴的文字堆砌。
适合谁看
- 正在系统补齐计算机基础的后端开发者。
- 准备校招、社招、中大厂后端面试的同学。
- 想把网络、操作系统、数据结构和算法串成完整知识体系的读者。
- 已经写过业务代码,但对 TCP/IP、HTTP、进程线程、内存管理、树图、排序等基础不够扎实的工程师。
学习重点
- 计算机网络重点理解分层模型、TCP/UDP、HTTP/HTTPS、DNS、ARP、NAT 和常见网络安全问题。
- 操作系统重点理解进程线程、锁与同步、内存管理、虚拟内存、零拷贝、I/O 多路复用、文件系统、Linux 基础和 Shell 使用。
- 数据结构重点理解数组、链表、栈、队列、哈希表、树、图、堆、Trie、并查集、跳表、红黑树、布隆过滤器和 LRU 的特点与适用场景。
- 算法重点理解复杂度分析、二分、双指针、滑动窗口、DFS/BFS、回溯、动态规划、贪心、Top K、排序、字符串、链表和 LeetCode 高频题。
- 面试中要能把“概念 -> 原理 -> 对比 -> 场景 -> 常见问题”串成完整回答。
建议阅读顺序
- 计算机网络专题:先从分层模型、HTTP、TCP、DNS 和常见网络面试题入手,建立网络通信的整体认知。
- 操作系统专题:理解进程线程、内存、文件系统、Linux 和 Shell,为并发编程、JVM、数据库打基础。
- 数据结构专题:掌握线性表、哈希表、树、图、堆、Trie、并查集、跳表、红黑树、布隆过滤器、LRU 等常见结构。
- 算法专题:结合复杂度分析、核心算法模板和 LeetCode 高频题进行练习。
- 回到面试题做查缺补漏:重点复盘网络和操作系统高频问题,再把数据结构与算法题按类型刷一遍。
如果你的目标公司比较重算法,建议把第 3 步和第 4 步合在一起复习:先看一个数据结构,再刷对应题型。例如,看完 哈希表 就刷两数之和、前缀和;看完 堆 就刷 Top K;看完 树 和 图 后,再集中练 DFS 与 BFS、回溯 和 动态规划。
核心文章
计算机网络
- 计算机网络专题:按协议层梳理计算机网络核心知识和面试高频题。
- 计算机网络常见面试题总结(上):覆盖 OSI/TCP-IP 模型、HTTP、HTTPS、DNS 等基础问题。
- 计算机网络常见面试题总结(下):继续补充 TCP、UDP、网络安全、Socket 等常见问题。
- OSI 七层模型与 TCP/IP 四层模型详解:建立网络分层和协议职责认知。
- 从输入 URL 到页面展示到底发生了什么?:用一次完整请求串联 DNS、TCP、HTTP、浏览器渲染等知识点。
- HTTP vs HTTPS、HTTP 1.0 vs HTTP 1.1、HTTP 常见状态码总结:集中理解 HTTP 相关高频考点。
- TCP 三次握手和四次挥手、TCP 传输可靠性保障:掌握 TCP 最核心的连接管理和可靠传输机制。
操作系统
- 操作系统专题:从操作系统基础讲到 Linux 常见问题。
- 操作系统常见面试题总结(上):覆盖操作系统基础、进程线程、死锁、内存管理等问题。
- 操作系统常见面试题总结(下):继续整理文件系统、I/O、Linux 等面试考点。
- 进程与线程详解:区别、状态、通信、上下文切换与虚拟线程:讲清进程和线程的资源边界、状态转换、上下文切换和 Java 虚拟线程。
- 进程间通信(IPC)详解:管道、消息队列、共享内存、Socket 与 Binder:对比管道、消息队列、共享内存、Socket、Binder 等 IPC 机制。
- 操作系统锁与同步机制详解:mutex、semaphore、condition variable、spinlock 与 futex:讲清临界区、互斥锁、信号量、条件变量、自旋锁和 futex。
- 操作系统内存管理详解:分页、分段、页面置换、Swap 与 OOM:讲清内存分配、内存碎片、页表、TLB、页面置换、Swap 和 OOM。
- 虚拟内存详解:地址转换、TLB、缺页异常与页面置换:讲清分页、页表、TLB、缺页异常和页面置换。
- 操作系统文件系统详解:inode、VFS、Page Cache 与日志机制:讲清 inode、dentry、文件描述符、VFS、Page Cache 和日志机制。
- I/O 多路复用详解:select、poll、epoll 原理与区别:讲清 select、poll、epoll 的实现原理、性能差异和适用场景。
- 零拷贝详解:mmap、sendfile 与 splice:讲清传统 I/O、mmap、sendfile、splice 的拷贝路径和工程应用。
- Linux 基础知识总结:掌握 Linux 目录、文件权限、常用命令和系统基础。
- Shell 编程基础知识总结:补齐脚本编写、变量、流程控制和常用命令能力。
数据结构
- 数据结构专题:按结构类型整理常见数据结构及图解。
- 线性数据结构详解:理解数组、链表、栈、队列的存储特点和操作复杂度。
- 哈希表面试题总结:理解哈希函数、哈希冲突、扩容和 Java
HashMap关联。 - 树结构详解:掌握二叉树、二叉搜索树、AVL、B 树、B+ 树等常见树结构。
- 图详解:理解图的表示、DFS、BFS 和最短路径等基础算法。
- 堆详解、红黑树详解、布隆过滤器详解:补齐高频工程结构和面试考点。
- Trie 前缀树面试题总结、并查集面试题总结、跳表面试题总结、LRU 缓存面试题总结:补齐字符串集合、连通性、Redis ZSet 和缓存淘汰等高频场景。
复习数据结构时,可以同步回看 Java 和数据库专题:数组/链表/哈希表对应 Java 集合,B+ 树对应 MySQL 索引,跳表对应 Redis 跳表,LRU 和布隆过滤器对应缓存场景。
算法
- 算法专题:整理常见算法思想、LeetCode 高频题和经典排序。
- 时间复杂度和空间复杂度面试指南:掌握 Big O、递归复杂度和常见复杂度误判。
- 二分查找面试题总结、双指针与滑动窗口面试题总结:掌握数组、字符串、链表题里最常见的手写模板。
- DFS 与 BFS 面试题总结、回溯算法面试题总结:掌握树、图、矩阵搜索、排列组合和路径枚举。
- 动态规划面试题总结、贪心算法面试题总结、Top K 问题面试题总结:补齐最优值、区间贪心、堆和数据流相关题型。
- 经典算法思想总结:覆盖二分、双指针、滑动窗口、回溯、动态规划等常见思想。
- 常见数据结构经典 LeetCode 题目推荐:按数据结构类型整理刷题路线。
- 几道常见的字符串算法题、几道常见的链表算法题:集中练习高频题型。
- 剑指 Offer 部分编程题、十大经典排序算法总结:适合面试前复盘。
算法刷题建议先把每类模板写稳,再做题单:经典算法思想总结 适合按题型刷,常见数据结构经典 LeetCode 题目推荐 适合按结构刷。
高频问题
- OSI 七层模型和 TCP/IP 四层模型分别是什么?每层解决什么问题?
- 从输入 URL 到页面展示,中间经历了哪些步骤?
- HTTP 和 HTTPS 有什么区别?HTTPS 为什么更安全?
- TCP 三次握手、四次挥手分别解决什么问题?TIME_WAIT 为什么存在?
- TCP 如何保证可靠传输?TCP 和 UDP 如何选型?
- 进程和线程有什么区别?什么是死锁,如何避免?
- 操作系统内存管理、虚拟内存、分页和分段分别是什么?
- 数组、链表、栈、队列、树、图、堆分别适合什么场景?
- 哈希表、红黑树、B+ 树、跳表、布隆过滤器、LRU 在工程中常用在哪里?
- 刷算法题时如何按题型建立解题模板?
相关专题
Overview (`docs/database/README.md`)
title: 数据库知识体系:SQL、MySQL、Redis、MongoDB 与 Elasticsearch
category: 数据库
tag:
- 数据库
- MySQL
- Redis
sitemap:
changefreq: weekly
priority: 0.95
head: - meta
- name: keywords
content: 数据库,数据库面试题,SQL,MySQL,Redis,MongoDB,Elasticsearch,MySQL索引,MySQL事务,MySQL日志,MVCC,Redis缓存,Redis持久化,Redis集群,后端面试
这份 数据库知识体系 面向后端学习、工程实践和面试复习,按“数据库基础 -> SQL -> MySQL -> Redis -> NoSQL 与搜索”的顺序整理本站数据库相关文章。
如果你时间有限,建议先看 数据库基础常见面试题总结、SQL 语法基础知识总结、MySQL 常见面试题总结 和 Redis 常见面试题总结(上),快速建立高频问题清单。
适合谁看
- 正在系统学习数据库基础、SQL、MySQL 和 Redis 的后端开发者。
- 准备校招、社招、中大厂后端面试的同学。
- 想补齐索引、事务、日志、执行计划、缓存一致性、Redis 持久化和集群能力的工程师。
- 已经写过业务 CRUD,但对数据库底层原理、性能优化和 NoSQL 选型不够熟的读者。
学习重点
- 关系型数据库和 NoSQL 分别适合解决什么问题,常见选型边界在哪里?
- SQL 的查询、聚合、连接、子查询和事务语义应该如何掌握?
- MySQL 索引、事务隔离、MVCC、三大日志和执行计划如何串成一条主线?
- Redis 为什么快,常用数据结构、缓存策略、持久化、阻塞问题和集群机制如何理解?
- MongoDB、Elasticsearch 这类 NoSQL/搜索系统在后端面试和工程选型中常考哪些点?
建议阅读顺序
- 数据库基础常见面试题总结 和 NoSQL 基础常见面试题总结:先理解数据库分类、事务、范式、NoSQL 类型和典型应用场景。
- SQL 语法基础知识总结:补齐查询、过滤、排序、聚合、连接、子查询、插入、更新和删除等 SQL 基本功。
- MySQL 专题:重点学习索引、事务隔离、MVCC、三大日志、执行过程和执行计划。
- Redis 专题:重点学习缓存基础、数据结构、缓存读写策略、持久化、阻塞问题、内存碎片和集群。
- MongoDB 专题 和 Elasticsearch 常见面试题总结:根据岗位要求补充文档数据库和搜索引擎相关知识。
核心文章
数据库基础与 SQL
这部分适合先建立数据库通用认知,重点理解数据库类型、事务语义、字符集、SQL 基础和常见查询题。
- 数据库基础常见面试题总结:梳理数据库基础概念、事务特性、并发控制、范式和常见面试问题。
- NoSQL 基础常见面试题总结:理解键值、文档、列族、图数据库等 NoSQL 类型及适用场景。
- 字符集详解:字符集是什么?怎么用?:理解字符集、编码、乱码原因以及 MySQL 字符集设置。
- SQL 专题:从 SQL 语法基础讲到常见 SQL 面试题。
- SQL 语法基础知识总结:覆盖查询、过滤、排序、聚合、分组、连接、子查询和数据修改。
MySQL
MySQL 是后端开发最核心的关系型数据库考点之一,学习时建议把“索引 -> 执行计划 -> 事务 -> MVCC -> 日志 -> 性能优化”连起来。
- MySQL 专题:串联 MySQL 索引、事务、MVCC、日志、执行计划和性能优化。
- MySQL 常见面试题总结:快速建立 MySQL 高频问题清单。
- MySQL 索引详解:理解索引数据结构、最左前缀、覆盖索引、回表和索引设计原则。
- MySQL 事务隔离级别详解:理解脏读、不可重复读、幻读和不同隔离级别的权衡。
- InnoDB 存储引擎对 MVCC 的实现:理解 Read View、隐藏字段、undo log 和快照读。
- MySQL 三大日志详解:理解 binlog、redo log、undo log 的作用和关系。
- MySQL 数据同步到 Elasticsearch 详解:对比应用层双写、定时同步、Canal、Debezium 和 Flink CDC,理解全量、增量和数据一致性问题。
- MySQL 执行计划分析:掌握 EXPLAIN 常见字段和慢 SQL 分析入口。
Redis
Redis 既是缓存,也是高频中间件考点。学习时不要只背命令,更要理解缓存策略、数据结构、持久化、阻塞原因和集群机制。
- Redis 专题:围绕缓存、数据结构、持久化、集群、阻塞问题和工程实践展开。
- 缓存基础常见面试题总结:理解缓存使用场景、缓存穿透、击穿、雪崩和一致性问题。
- Redis 常见面试题总结(上) 和 Redis 常见面试题总结(下):快速建立 Redis 高频问题清单。
- Redis 5 种基本数据类型详解:理解 String、List、Hash、Set、Sorted Set 的应用场景。
- Redis 持久化机制详解:理解 RDB、AOF、AOF 重写和混合持久化。
- Redis 集群详解:理解主从复制、哨兵、Cluster、槽位和故障转移。
NoSQL 与搜索
这部分适合在掌握关系型数据库和缓存之后补充,用来理解文档数据库、搜索引擎和非关系型存储的选型边界。
- MongoDB 专题:整理 MongoDB 文档模型、索引、副本集、分片、事务和常见面试问题。
- MongoDB 常见面试题总结(上) 和 MongoDB 常见面试题总结(下):理解 MongoDB 核心概念和工程实践。
- Elasticsearch 常见面试题总结:理解倒排索引、分片、副本、写入查询流程和搜索场景。
高频问题
- 关系型数据库和 NoSQL 有什么区别?分别适合哪些场景?
- 数据库事务 ACID 是什么?隔离级别分别解决什么问题?
- SQL 中 WHERE、GROUP BY、HAVING、ORDER BY 的执行顺序如何理解?
- MySQL 索引为什么能加速查询?什么情况下会索引失效?
- InnoDB 如何通过 MVCC 实现非锁定一致性读?
- binlog、redo log、undo log 分别解决什么问题?
- 如何通过 EXPLAIN 分析 SQL 执行计划?
- Redis 为什么快?缓存穿透、击穿、雪崩如何处理?
- Redis 持久化、主从复制、哨兵和 Cluster 分别解决什么问题?
- MongoDB 和 Elasticsearch 分别适合什么业务场景?
相关专题
Overview (`docs/distributed-system/README.md`)
title: 分布式系统知识体系:入门、理论协议、RPC、网关、锁、事务与 ID
category: 分布式
tag:
- 分布式
- 系统设计
- 后端面试
sitemap:
changefreq: weekly
priority: 0.95
head: - meta
- name: keywords
content: 分布式系统,分布式系统入门,分布式系统面试题,中心化,去中心化,CAP,BASE,拜占庭将军问题,Paxos,Raft,ZAB,Gossip,RPC,Dubbo,API网关,分布式ID,分布式锁,分布式事务,配置中心,ZooKeeper,后端面试
这份 分布式系统知识体系 面向后端学习、系统设计和面试复习,按“分布式入门 -> 通信调用 -> 服务治理 -> 一致性与协调 -> 工程实践”的顺序整理本站分布式相关文章。
如果你刚开始学分布式,建议先看 分布式系统入门,建立整体认知;如果你时间有限,建议先看 分布式系统面试题总结,快速建立高频问题清单。岗位明确考察微服务时,可以再看 微服务面试题总结,串联服务拆分、通信、数据一致性和稳定性。
适合谁看
- 正在系统学习分布式系统的后端开发者。
- 准备校招、社招、中大厂后端面试的同学。
- 想补齐分布式理论、工程实践和方案选型能力的工程师。
- 已经写过业务代码,但对 RPC、分布式锁、分布式事务、配置中心等底层原理不够熟的读者。
学习重点
- 分布式系统为什么需要在一致性、可用性、性能和复杂度之间做取舍?
- CAP、BASE、Paxos、Raft、ZAB、Gossip、一致性哈希分别解决什么问题?
- RPC、API 网关、配置中心、注册中心在微服务体系里分别承担什么职责?
- 分布式 ID、分布式锁、分布式事务在真实业务里有哪些常见方案和坑?
- 面试中如何把“概念、原理、场景、方案对比、落地经验”串成完整回答?
这组文章怎么串起来?
这组文章可以按 4 条线一起看。
第一条是理论线:分布式系统入门 先解释多节点系统为什么会变复杂,CAP 定理与 BASE 理论详解 解释分区发生时一致性和可用性怎么取舍,分布式协调详解 再把 Leader、Quorum、Lease、Fencing Token、Gossip 放到同一条线上。
第二条是调用线:RPC 远程过程调用专题 解决服务之间怎么互相调用,API 网关详解 解决外部流量怎么进入系统,Spring Cloud Gateway 面试题总结 则展开 Spring Cloud Gateway 的路由、断言、过滤器和限流。
第三条是数据一致性线:分布式 ID 生成方案详解 解决全局唯一标识问题,分布式 ID 设计实战 把 ID 放到订单号、优惠券、短网址这些业务里,分布式锁 和 分布式事务 继续处理跨节点互斥和跨服务写入一致性。
第四条是协调组件线:分布式配置中心详解 讲配置发布和客户端容灾,ZooKeeper 专题 讲协调组件的概念、ZAB、会话、Watcher 和 Curator 实战。
建议阅读顺序
- 分布式系统入门:先理解什么是分布式系统,以及为什么拆成多节点后会引入通信、故障和一致性问题。
- 分布式系统面试题总结:建立高频问题清单,知道面试最常考哪些点。
- 微服务面试题总结:理解服务拆分、服务通信、数据一致性、稳定性和迁移问题。
- 分布式协调详解:理解 Leader、Quorum、Gossip、Lease、脑裂和 Fencing Token 之间的关系。
- CAP 定理与 BASE 理论详解:理解分布式系统最核心的取舍逻辑。
- 拜占庭将军问题详解:理解共识问题里的恶意节点、故障假设和 BFT 容错边界。
- RPC 远程过程调用详解:掌握服务之间如何通信,以及 RPC 框架解决了哪些工程问题。
- 分布式 ID 生成方案详解、分布式锁入门、分布式事务解决方案详解:补齐高频工程实践。
- ZooKeeper 入门指南 和 分布式配置中心详解:理解分布式协调和配置治理。
核心文章
分布式基础与理论协议
这部分适合先建立分布式系统的底层认知,重点理解一致性、可用性、分区容错、共识算法和数据分布。
- 分布式系统入门:理解分布式系统的定义、架构演进、典型特征、常见系统和学习路线。
- 微服务面试题总结:串联服务拆分、RPC 与消息、数据归属、分布式事务、容错、观测和迁移。
- 分布式理论、算法与协议专题:把 CAP、BASE、Paxos、Raft、ZAB、Gossip 和一致性哈希放在同一条学习线上。
- 分布式协调详解:串起 Leader/Quorum、脑裂、Lease、Fencing Token 和 Gossip,理解“谁来做决定、状态怎么传播”这两个问题。
- CAP 定理与 BASE 理论详解:理解一致性、可用性、分区容错和最终一致性。
- 拜占庭将军问题详解:理解恶意节点场景下的共识难点、
3m + 1节点要求和 BFT 容错。 - Raft 算法详解:用更易理解的共识算法入门 Leader 选举和日志复制。
- Paxos 算法详解:补齐经典共识算法的角色、流程和难点。
- ZAB 协议详解:理解 ZooKeeper 的原子广播、崩溃恢复和事务日志机制。
- Gossip 协议详解:理解大规模节点之间的信息传播和最终一致性。
- 一致性哈希算法详解:理解分布式缓存、负载均衡和分库分表中的数据分布问题。
RPC 与服务调用
RPC 解决的是远程服务调用的工程复杂度,包括序列化、网络传输、服务发现、负载均衡、超时重试和服务治理。
- RPC 专题:从 RPC 基础、Dubbo 到 HTTP 与 RPC 的关系,建立服务调用完整认知。
- RPC 远程过程调用详解:理解 RPC 调用流程、动态代理、序列化、网络传输和框架选型。
- Dubbo 面试题总结:串联 Dubbo 架构、服务暴露与引用、SPI、负载均衡和集群容错。
- 有了 HTTP 协议,为什么还要 RPC?:厘清 HTTP 和 RPC 的层次关系与选型边界。
API 网关与流量入口
API 网关负责统一接入、路由转发、认证鉴权、限流熔断、灰度发布和跨域处理,是微服务体系里的重要入口层。
- API 网关详解:理解请求路由、认证鉴权、限流熔断、负载均衡和双层网关架构。
- Spring Cloud Gateway 面试题总结:掌握 Predicate、GatewayFilter、GlobalFilter、限流熔断和常见生产问题。
分布式 ID、锁与事务
这部分偏工程落地,常见于订单、支付、秒杀、库存扣减、数据一致性和跨服务协作场景。
- 分布式 ID 生成方案详解:对比 UUID、数据库自增、号段模式、Redis、Snowflake、Leaf、Tinyid 等方案。
- 分布式 ID 设计实战:结合订单号、支付码、优惠券、一码付等业务场景理解业务 ID 设计。
- 分布式锁入门:理解互斥语义、锁粒度、安全释放、超时续约和 Fencing Token。
- 分布式锁实现方案详解:对比 Redis、Redisson、Redlock、ZooKeeper 和 Curator 分布式锁。
- 分布式事务解决方案详解:系统理解 XA、AT、TCC、Saga、本地消息表、事务消息和最大努力通知。
配置中心与 ZooKeeper
配置中心和 ZooKeeper 主要解决服务配置、注册发现、分布式协调和集群一致性问题。
- 分布式配置中心详解:对比 Apollo、Nacos、Spring Cloud Config 和 Kubernetes ConfigMap。
- ZooKeeper 专题:从 ZooKeeper 核心概念讲到 ZAB、Leader 选举、Curator 和分布式锁实践。
- ZooKeeper 入门指南:掌握 ZNode、Watcher、ACL 和典型应用场景。
- ZooKeeper 进阶详解:理解 ZAB 协议、Leader 选举、集群部署和会话管理。
- ZooKeeper 实战教程:通过 Docker、zkCli、四字命令和 Curator 完成实践。
高频问题
- 分布式系统为什么不能同时完美满足一致性、可用性和分区容错?
- CAP 和 BASE 是什么关系?最终一致性适合哪些场景?
- 分布式系统为什么需要协调?中心化和去中心化分别适合哪些场景?
- Paxos、Raft、ZAB 有什么区别,分别用在什么地方?
- RPC 和 HTTP 有什么区别?为什么内部服务调用常用 RPC?
- 分布式 ID 如何保证全局唯一、趋势递增和业务可读?
- Redis 分布式锁有哪些坑?Redlock 是否一定可靠?
- 分布式事务有哪些方案?TCC、Saga、本地消息表分别适合什么场景?
- 配置中心和注册中心有什么区别?Apollo、Nacos、Spring Cloud Config 如何选型?
- 微服务应该怎样拆分?什么情况下拆分会变成分布式单体?
- 微服务之间怎样处理通信失败、跨服务数据一致性和灰度发布?
相关专题
Overview (`docs/high-availability/README.md`)
title: 高可用系统知识体系:SLA、限流、熔断、降级、重试、幂等、冗余与压测
category: 高可用
tag:
- 高可用
- 系统设计
- 后端面试
sitemap:
changefreq: weekly
priority: 0.95
head: - meta
- name: keywords
content: 高可用系统,高可用系统设计,高可用面试题,SLA,可用性指标,限流,熔断,降级,超时重试,接口幂等,冗余设计,容灾,性能测试,故障演练,后端面试
这份 高可用系统知识体系 面向后端学习、系统设计和面试复习,围绕“减少故障、隔离故障、快速恢复、降低重复请求和异常流量影响”整理本站高可用相关文章。
如果你时间有限,建议先看 高可用系统设计面试题总结,快速建立高频问题清单;如果你想系统补基础,可以按下面的阅读顺序推进。
适合谁看
- 正在系统学习高可用系统设计的后端开发者。
- 准备校招、社招、中大厂后端面试的同学。
- 想补齐限流、降级、熔断、重试、幂等、容灾和压测能力的工程师。
- 已经遇到接口超时、重复请求、下游故障、流量突增、系统雪崩等问题,但缺少系统解法的读者。
学习重点
- 高可用不是“不出故障”,而是故障发生时尽量可控、可隔离、可恢复。
- SLA、可用性指标、RTO、RPO 是衡量高可用设计的重要语言。
- 限流、降级、熔断、超时重试分别解决不同层面的稳定性问题。
- 幂等设计是应对重复请求、自动重试、消息重复消费和支付回调的基础能力。
- 冗余容灾、性能压测、故障演练和灰度发布决定系统是否能扛住真实生产风险。
建议阅读顺序
- 高可用系统设计面试题总结:先建立 SLA、限流、熔断、重试、幂等、容灾和压测的高频问题清单。
- 高可用系统设计详解:系统理解高可用架构的整体设计方法。
- 服务限流详解、服务降级与熔断详解、超时和重试机制详解:掌握稳定性治理三件套。
- 接口幂等性设计详解:理解重复请求、业务幂等和支付回调等场景。
- 冗余设计详解 和 性能测试入门:补齐容灾、压测、容量评估和故障验证。
核心文章
总览与面试路线
- 高可用系统设计面试题总结:串联 SLA、单点故障、限流降级熔断、超时重试、接口幂等、冗余容灾和性能压测。
- 高可用系统设计详解:讲解 SLA、多少个 9、单点故障治理、缓存高可用、异步削峰、灰度发布和故障恢复。
稳定性治理
- 服务限流详解:理解固定窗口、滑动窗口、令牌桶、漏桶、Sentinel、Redis Lua 和 Redisson 限流。
- 服务降级与熔断详解:理解 Fallback、降级开关、熔断器状态机、雪崩效应、隔离策略和框架选型。
- 超时和重试机制详解:理解连接超时、读取超时、指数退避、随机抖动、重试风暴和幂等性。
幂等、容灾与验证
- 接口幂等性设计详解:讲解幂等键、Token、唯一索引、去重表、乐观锁、悲观锁、分布式锁和支付回调。
- 冗余设计详解:讲解硬件冗余、服务冗余、数据冗余、地域冗余、同城灾备、异地灾备和多活架构。
- 性能测试入门:理解 QPS、TPS、RT、P90/P99/P999、容量评估、压力测试、稳定性测试和压测工具。
高频问题
- 高可用系统如何定义可用性?几个 9 分别意味着什么?
- RTO 和 RPO 有什么区别?
- 限流、降级、熔断分别解决什么问题?
- 令牌桶和漏桶有什么区别?分布式限流如何实现?
- 超时和重试为什么可能导致雪崩?
- 接口幂等有哪些实现方案?支付回调如何保证幂等?
- 冗余、容灾、同城多活、异地多活分别适合什么场景?
- 性能测试、压力测试、稳定性测试有什么区别?
- 如何通过故障演练验证高可用设计是否有效?
相关专题
Overview (`docs/high-performance/README.md`)
title: 高性能系统知识体系:CDN、负载均衡、数据库优化、缓存与消息队列
category: 高性能
tag:
- 高性能
- 系统设计
- 后端面试
sitemap:
changefreq: weekly
priority: 0.95
head: - meta
- name: keywords
content: 高性能系统,高性能系统设计,高性能面试题,CDN,负载均衡,读写分离,分库分表,冷热分离,深度分页,SQL优化,消息队列,Kafka,RocketMQ,RabbitMQ,Disruptor,后端面试
这份 高性能系统知识体系 面向后端学习、系统设计和面试复习,围绕“减少延迟、提升吞吐、削峰填谷、降低数据库压力、优化数据访问路径”整理本站高性能相关文章。
如果你时间有限,建议先看 高性能系统设计面试题总结,快速建立高频问题清单;如果你想系统补基础,可以按下面的阅读顺序推进。
学习这部分内容时,不建议只记“加缓存、加 MQ、分库分表”这些方案名。高性能优化更像一条链路分析题:请求从用户侧进来,经过 CDN、负载均衡、应用服务、缓存、数据库、消息队列,每一段都有可能成为瓶颈。能讲清楚瓶颈在哪里、为什么选这个方案、会引入什么新问题,才算真的掌握。
适合谁看
- 正在系统学习高性能系统设计的后端开发者。
- 准备校招、社招、中大厂后端面试的同学。
- 想补齐 CDN、负载均衡、数据库优化、消息队列等工程能力的工程师。
- 已经遇到慢 SQL、深分页、热点流量、消息积压、数据库压力等问题,但缺少系统解法的读者。
学习重点
- 高性能系统到底是在优化延迟、吞吐、资源利用率,还是在优化用户感知?
- CDN、负载均衡、缓存、数据库优化、消息队列分别解决链路上的哪些瓶颈?
- 读写分离、分库分表、冷热分离、深度分页优化分别适合什么场景?
- Kafka、RocketMQ、RabbitMQ、Disruptor 的定位和选型差异是什么?
- 面试中如何从“瓶颈定位 -> 方案选择 -> 取舍分析 -> 落地风险”回答高性能问题?
面试回答主线
回答高性能系统设计题时,可以按下面这条线展开:
- 先确认目标:QPS、RT、P99、数据量、读写比例、一致性要求、成本约束。
- 再定位瓶颈:入口带宽、应用线程池、慢 SQL、锁竞争、缓存命中率、MQ 积压、下游依赖。
- 然后选方案:入口层用 CDN/负载均衡,应用层用缓存/限流/异步化,数据层用索引/读写分离/分库分表/冷热分离,削峰层用 MQ。
- 最后讲取舍:方案带来的复杂度、一致性风险、运维成本、回滚方案和监控指标。
面试里最忌讳一上来就堆技术名词。比如“订单查询慢”不一定要分库分表,可能只是缺索引、深分页、历史数据太多或者热点商家查询集中。先把场景问清楚,再给方案,答案会稳很多。
建议阅读顺序
- 高性能系统设计面试题总结:先建立缓存、数据库、消息队列、负载均衡等高频问题清单。
- CDN 工作原理详解 和 负载均衡原理及算法详解:理解流量入口和请求分发。
- 读写分离和分库分表详解、常见 SQL 优化手段总结、深度分页介绍及优化建议:补齐数据库性能优化主线。
- 消息队列面试题总结:建立可靠性、幂等、顺序、积压和选型的问题清单。
- 消息队列基础知识总结:理解异步处理、解耦、削峰、消息可靠性、顺序性和幂等。
- 再根据技术栈深入 Kafka 常见问题总结、RocketMQ 常见问题总结、RabbitMQ 常见问题总结。
核心文章
流量入口与请求分发
- CDN 工作原理详解:理解 GSLB 调度、缓存策略、预热刷新、命中率优化和防盗链。
- 负载均衡原理及算法详解:理解四层/七层负载均衡、服务端/客户端负载均衡和常见调度算法。
数据库与数据访问优化
- 读写分离和分库分表详解:理解主从复制、读写分离、垂直拆分、水平拆分和分库分表后的问题。
- 数据冷热分离详解:理解冷热数据判定、分层存储、数据迁移一致性和冷数据查询优化。
- 常见 SQL 优化手段总结:梳理慢 SQL 定位、索引优化、查询重写和分页优化等实战方法。
- 深度分页介绍及优化建议:理解深分页性能问题,以及范围查询、子查询优化、延迟关联和覆盖索引方案。
消息队列与异步削峰
- 消息队列专题:从消息队列基础讲到 Kafka、RocketMQ、RabbitMQ 和 Disruptor 的使用边界。
- 消息队列面试题总结:按消息生命周期复习可靠性、消费语义、主流产品和项目追问。
- 消息队列基础知识总结:理解应用场景、消息模型、消息可靠性、顺序性、幂等和积压处理。
- Kafka 常见问题总结:掌握 Kafka 架构、高性能原理、消息可靠性、顺序性和 Rebalance。
- RocketMQ 常见问题总结:理解 RocketMQ 架构、消息类型、存储机制、可靠性和 5.x 新特性。
- RabbitMQ 常见问题总结:理解 AMQP、Exchange 类型、确认机制、死信队列、延迟队列、Quorum Queue 和 Streams。
- Disruptor 常见问题总结:理解 RingBuffer、Sequencer、WaitStrategy、无锁设计和缓存行填充。
高频问题
- 高性能系统优化时,应该先定位哪些指标?
- CDN 和负载均衡分别解决什么问题?
- 四层负载均衡和七层负载均衡有什么区别?
- 读写分离会带来哪些一致性问题?如何处理主从延迟?
- 分库分表后如何处理分布式 ID、跨库 JOIN 和分布式事务?
- 深度分页为什么慢?有哪些优化方案?
- 消息队列如何保证消息不丢、不重复、不乱序?
- Kafka、RocketMQ、RabbitMQ 如何选型?
- 消息积压应该如何定位和处理?
相关专题
Overview (`docs/high-quality-technical-articles/README.md`)
title: 高质量技术文章精选:程序员成长、工作经验、面试复盘与职业思考
category: 程序人生
sitemap:
changefreq: weekly
priority: 0.9
head:
- meta
- name: keywords
content: 高质量技术文章,程序员成长,程序员职业发展,程序员工作经验,程序员面试经验,技术成长,职场经验,程序人生
这份 高质量技术文章精选 收录程序员成长、工作经验、面试复盘、职业选择和技术学习方法相关内容。它不是具体技术点清单,更适合在学习、求职、入职、转岗、晋升等阶段反复阅读。
如果你时间有限,建议先看 程序员如何快速学习新技术 和 从面试官和候选者的角度谈如何准备技术初试,一个解决学习方法,一个解决面试准备。
适合谁看
- 正在从学生转向工程师、需要建立职业认知的同学。
- 准备面试,希望理解面试官视角和真实候选人经验的读者。
- 工作 1 到 5 年,想提升学习效率、业务理解和技术成长速度的程序员。
- 面临入职适应、绩效考核、职业选择、成长瓶颈的工程师。
学习重点
- 技术成长不只是多学框架,更包括学习方法、业务理解、表达能力和工程判断。
- 面试准备要同时站在候选人和面试官视角,既要能答题,也要能呈现真实项目能力。
- 个人经历类文章适合用来理解不同阶段的选择、试错和复盘方式。
- 工作经验类文章重点看“如何快速进入状态、如何和团队协作、如何面对绩效与反馈”。
- 职业发展需要长期复盘,不要把短期 offer、职级或绩效当成唯一目标。
建议阅读顺序
- 程序员如何快速学习新技术:先建立学习新技术的方法。
- 程序员职业方向怎么选?:在入行、转岗或换方向前,先理解几个常见方向的工作内容和门槛。
- 程序员的技术成长战略 和 给想成长为高级别开发同学的七条建议:理解技术成长的长期路线。
- 从面试官和候选者的角度谈如何准备技术初试:用面试官视角校准准备方向。
- 斩获 20+ 大厂 offer 的面试经验分享 和 普通人的春招总结:参考真实求职复盘。
- 新入职一家公司如何快速进入工作状态:拿到机会后继续关注入职、协作和长期成长。
核心文章
练级攻略
- 程序员如何快速学习新技术:讲学习路径、资料选择、实践验证和避免低效学习。
- 程序员的技术成长战略:从长期视角理解技术成长和能力沉淀。
- 十年大厂成长之路:复盘长期职业发展中的选择与积累。
- 美团三年,总结的 10 条血泪教训:从真实经历里看工程师成长常见坑。
- 给想成长为高级别开发同学的七条建议:聚焦高级别工程师需要的能力。
- 糟糕程序员的 20 个坏习惯:帮助识别影响成长和协作的坏习惯。
- 工作五年之后,对技术和业务的思考:理解技术、业务和个人发展的关系。
个人经历
- 从校招入职腾讯的四年工作总结:复盘大厂工作、成长和选择。
- 我在滴滴和头条的两年后端研发工作经验分享:了解不同团队和业务环境下的后端研发经验。
- 一个中科大差生的 8 年程序员工作总结:从较长周期看程序员成长路径。
- 华为 OD 275 天后,我进了腾讯!:参考真实求职和职业转换经历。
程序员
- 程序员职业方向怎么选?:对比后端、前端、全栈、AI 应用开发、数据库内核、算法、中间件、测开、平台工程和架构师等方向。
- 程序员最该拿的几种高含金量证书:了解软考、PMP、云认证等证书的适用场景。
- 程序员怎样出版一本技术书:了解技术书出版流程和注意事项。
- 高效出书指南:程序员如何从 0 到 1 写一本技术书:补充技术写作和出版实践。
面试与工作
- 从面试官和候选者的角度谈如何准备技术初试:理解技术初试到底考什么。
- 斩获 20+ 大厂 offer 的面试经验分享:参考完整求职准备和复盘方式。
- 包装严重的 IT 行业,作为面试官,我是如何甄别应聘者的包装程度:理解面试官如何识别项目真实性。
- 新入职一家公司如何快速进入工作状态:帮助入职后快速熟悉业务、代码和团队节奏。
- 32 条总结教你提升职场经验:补充沟通、协作、汇报和成长细节。
- 聊聊大厂的绩效考核:理解绩效机制和工作反馈。
高频问题
- 程序员如何快速学习一门新技术,而不是只收藏资料?
- 工作几年后,如何判断自己是在成长还是重复劳动?
- 准备技术面试时,应该如何平衡八股文、项目和算法?
- 面试官如何判断项目是不是候选人真实做过?
- 新入职一家公司,前几周应该重点做什么?
- 如何面对绩效、晋升、业务压力和职业焦虑?
- 技术能力和业务理解哪个更重要?不同阶段如何取舍?
相关专题
Overview (`docs/interview-preparation/README.md`)
title: 面试准备知识体系:复习计划、简历、项目经验、学习路线、面试重点与心态调整
category: 面试准备
tag:
- 面试准备
- Java面试
- 后端面试
sitemap:
changefreq: weekly
priority: 0.95
head: - meta
- name: keywords
content: 面试准备,Java面试,后端面试,校招,社招,简历,项目经验,Java学习路线,面试重点,面经,JavaGuide
这份 面试准备知识体系 面向 Java 后端校招和社招复习,围绕“制定计划 -> 完善简历 -> 梳理项目 -> 补齐技术重点 -> 自测复盘 -> 调整心态”的顺序整理本站面试准备相关文章。
面试准备不是简单背题,更像一次面向岗位要求的系统工程:技术知识要扎实,简历和项目要经得起深挖,表达和复盘也要跟上。
适合谁看
- 准备 Java 后端校招、春招、秋招的同学。
- 准备跳槽或转岗,需要重新梳理 Java 后端知识体系的开发者。
- 简历、项目经历、面试表达比较薄弱,不知道从哪里开始准备的读者。
- 已经刷过不少面试题,但缺少整体复习节奏和查漏补缺方法的同学。
学习重点
- 面试准备要先明确目标岗位和时间周期,再拆成可执行的复习计划。
- 简历是面试提问地图,项目经历、技术栈和成果描述都要能被追问。
- 技术复习要按优先级推进,Java、MySQL、Redis、Spring、JVM、计算机基础、分布式和系统设计都要结合目标岗位取舍。
- 项目准备要能讲清业务背景、个人职责、技术难点、方案取舍和最终结果。
- 面经、自测和模拟面试的价值在于暴露盲区,面后复盘要持续沉淀问题清单。
建议阅读顺序
- 2026 最新版 Java 后端面试通关计划:先建立 4-8 周复习路线和阶段目标。
- 如何高效准备 Java 面试?:理解求职导向学习、技能清单和复习方法。
- 程序员简历编写指南、项目经验指南 和 后端项目面试怎么讲?:先把简历和项目经历打磨到可深挖,再准备项目介绍、技术选型、难点和故障复盘。
- 2026 最新版 Java 后端面试重点总结:按高频考点集中补技术短板。
- 常见面试题自测、优质面经汇总:通过自测和面经做查漏补缺。
- 面试太紧张怎么办?:面试前补齐心态、表达和临场应对。
核心文章
复习计划与学习路线
- 2026 最新版 Java 后端面试通关计划:按项目经历、Java/MySQL/Redis、框架、系统设计、计算机基础、分布式和 JVM 编排复习节奏。
- 如何高效准备 Java 面试?:讲清面试准备方法、求职导向学习、技能清单和冲刺策略。
- 2026 最新版 Java 学习路线:从 Java 基础到数据库、缓存、中间件、框架和面试重点,适合长期补基础。
简历与项目经验
- 程序员简历编写指南:从简历筛选逻辑出发,讲清简历结构、技能描述、项目经历和模板选择。
- 项目经验指南:帮助没有项目或项目平淡的同学补强项目亮点,学会复盘和表达。
- 后端项目面试怎么讲?:准备 30 秒和 3 分钟项目介绍,并围绕个人职责、核心链路、技术选型、量化指标和线上故障应对项目深挖。
- 校招没有实习经历怎么办?实习经历怎么写?:讲清实习缺失时如何通过项目、简历和技术面试弥补短板。
技术重点与资料
- 2026 最新版 Java 后端面试重点总结:梳理 Java 基础、集合、并发、MySQL、Redis、Spring、JVM 和项目经验等高频重点。
- 2026 最新 Java 面试 + 后端面试 PDF 资料:整理 JavaGuide 后端面试 PDF 资料,适合集中复习和离线查阅。
- 常见面试题自测:按面试提问方式整理高频问题,适合面前自查掌握程度。
面经复盘与临场状态
高频问题
- Java 后端面试应该按什么顺序准备?
- 校招和社招的准备重点有什么区别?
- 简历应该怎么写,才能让面试官更容易看到亮点?
- 项目经历比较普通,如何提炼技术难点和个人贡献?
- 后端项目应该怎样介绍,如何应对技术选型、性能优化和线上故障追问?
- 没有实习经历会不会影响校招?应该如何弥补?
- Java、MySQL、Redis、Spring、JVM 哪些知识点最值得优先复习?
- 面经应该怎么用,如何避免只背答案?
- 面试紧张、表达卡壳、遇到不会的问题时应该怎么办?
- 面试结束后如何复盘,才能让下一次表现更好?
相关专题
Overview (`docs/java/README.md`)
title: Java 知识体系:基础、集合、并发、JVM、IO 与新特性
category: Java
tag:
- Java
- Java基础
- Java面试
sitemap:
changefreq: weekly
priority: 0.95
head: - meta
- name: keywords
content: Java,Java基础,Java集合,Java并发,JVM,Java IO,Java NIO,Java新特性,Java面试题,Java后端面试
这份 Java 知识体系 面向 Java 后端学习和面试复习,按“基础语法 -> 集合容器 -> 并发编程 -> IO/NIO -> JVM -> 新特性”的顺序整理本站 Java 相关文章。
如果你时间有限,建议先看 Java 基础、集合、并发和 JVM 的面试题总结,快速建立高频问题清单;如果你想系统补基础,可以按下面的专题顺序阅读。
适合谁看
- 正在系统学习 Java 的后端开发者。
- 准备校招、社招、中大厂 Java 后端面试的同学。
- 想把 Java 基础、集合、并发、JVM、IO 和新特性串起来复习的读者。
- 已经写过 Java 项目,但对底层原理、源码设计和工程实践理解不够系统的工程师。
学习重点
- Java 基础语法、面向对象、异常、泛型、反射、代理、序列化等核心机制。
- List、Map、Queue、并发容器的使用边界、源码实现和常见面试题。
- Java 线程、锁、JMM、CAS、AQS、线程池、CompletableFuture 和虚拟线程。
- JVM 内存区域、类加载、垃圾回收、参数配置、监控工具和线上问题排查。
- BIO、NIO、AIO、IO 模型,以及装饰器、适配器等 IO 相关设计模式。
- Java 8 到 Java 26 的重要新特性,以及哪些特性真正影响日常开发。
建议阅读顺序
- Java 基础专题:先掌握语法、面向对象、泛型、反射、代理、序列化等基础能力。
- Java 集合专题:理解 ArrayList、LinkedList、HashMap、ConcurrentHashMap 等常用容器的使用和源码。
- Java 并发编程专题:系统学习线程、锁、JMM、CAS、AQS、线程池和并发工具类。
- JVM 专题:理解内存区域、类加载、垃圾回收、JVM 参数和线上排查。
- Java IO 专题:补齐 BIO、NIO、AIO、Reactor、多路复用和 IO 设计模式。
- Java 新特性专题:按版本梳理 Lambda、Stream、模块化、var、Record、虚拟线程等关键特性。
核心文章
Java 基础
- Java 基础专题:从基础语法讲到核心机制和常见 Java 面试题。
- Java基础常见面试题总结(上):覆盖 Java 语言特点、基础语法、面向对象和常用类。
- Java基础常见面试题总结(中):继续梳理异常、泛型、反射、注解和常见细节。
- Java基础常见面试题总结(下):补齐高级基础知识和常见易错点。
- Java 值传递详解:厘清值传递、引用变量和对象修改之间的关系。
- Java 序列化详解:理解序列化机制、serialVersionUID、安全风险和替代方案。
- Java 反射机制详解 和 Java 代理模式详解:掌握框架底层常见机制。
Java 集合
- Java 集合专题:串联集合框架、使用注意事项和常见源码分析。
- Java集合常见面试题总结(上) 和 Java集合常见面试题总结(下):覆盖 List、Set、Map、Queue 和并发集合高频问题。
- Java集合使用注意事项总结:总结集合判空、遍历、扩容、线程安全和性能相关注意点。
- ArrayList 源码分析、HashMap 源码分析、ConcurrentHashMap 源码分析:从源码理解常用容器的设计取舍。
Java 并发
- Java 并发编程专题:围绕线程、锁、内存模型、线程池和并发工具展开。
- Java并发常见面试题总结(上)、Java并发常见面试题总结(中)、Java并发常见面试题总结(下):建立并发面试问题清单。
- JMM(Java 内存模型)详解:理解可见性、原子性、有序性和 happens-before。
- CAS 详解、AQS 详解、Java 线程池详解:掌握并发底层高频考点。
- 虚拟线程常见问题总结:理解 Project Loom 对并发模型的影响。
JVM 与 IO
- JVM 专题:围绕内存、类加载、GC、参数、工具和线上排查展开。
- Java内存区域详解(重点):理解程序计数器、虚拟机栈、本地方法栈、堆和方法区。
- JVM垃圾回收详解(重点):理解对象存活判断、垃圾收集算法和主流垃圾收集器。
- 类加载过程详解 和 类加载器详解(重点):掌握类生命周期和双亲委派模型。
- Java IO 专题:从 BIO、NIO、AIO 讲到 IO 模型和 IO 设计模式。
- Java IO 基础知识总结、Java NIO 核心知识总结、Java IO 模型详解:补齐网络编程和中间件学习前置知识。
Java 新特性
- Java 新特性专题:按版本梳理 Java 8 之后的重要语言、标准库和 JVM 特性。
- Java8 新特性实战:掌握 Lambda、Stream、Optional、接口默认方法和新日期 API。
- Java 11 新特性概览(重要)、Java 17 新特性概览(重要)、Java 21 新特性概览(重要):优先关注 LTS 版本中的长期可用特性。
高频问题
- Java 为什么是值传递?对象引用作为参数传递时到底发生了什么?
String、StringBuilder、StringBuffer有什么区别?equals()和hashCode()有什么关系?ArrayList和LinkedList如何选择?HashMap为什么线程不安全?ConcurrentHashMap在 JDK 7 和 JDK 8 中有什么变化?synchronized和ReentrantLock有什么区别?- JMM 如何保证可见性、有序性和原子性?
- 线程池核心参数如何配置?为什么不建议直接使用
Executors? - JVM 内存区域如何划分?哪些区域可能发生 OOM?
- G1、ZGC、Shenandoah 分别适合什么场景?
- BIO、NIO、AIO 有什么区别?Reactor 模型解决什么问题?
- Java 8、11、17、21 中哪些新特性最值得掌握?
相关专题
Overview (`docs/open-source-project/README.md`)
title: Java 开源项目精选:学习、实战与工具选型
category: 开源项目
sitemap:
changefreq: weekly
priority: 0.9
head:
- meta
- name: keywords
content: Java开源项目,Java实战项目,Java项目推荐,Java技术教程,Java系统设计项目,Java工具库,开源项目推荐,后端项目,简历项目
这份 Java 开源项目精选 面向 Java 后端学习、项目实战和工具选型,整理 GitHub 和 Gitee 上仍在维护的 Java/JVM 项目。
项目数量不是越多越好。这里更看重代码活跃度、文档质量和可运行性,也会尽量说明项目适合解决什么问题。欢迎在 JavaGuide Issues 区 推荐你认可的项目。
目录导航
| 目录 | 收录范围 | 适合用来做什么 |
|---|---|---|
| 技术教程 | 知识库、课程、开源书籍和代码示例 | 补基础、查缺补漏 |
| 实战项目 | 具备完整业务功能的 Java/JVM 应用 | 练手、二次开发、准备项目经历 |
| AI 项目 | Java AI 框架、模型推理工具和 AI 应用 | 开发 RAG、Agent 和模型服务 |
| 框架与基础设施 | Web 框架、数据库、中间件、可观测性等组件 | 技术选型、源码学习、系统设计 |
| 工具类库 | 通过 Maven/Gradle 引入项目的 Java 库 | 解决编码中的具体问题 |
| 开发工具 | 独立运行的 GUI、CLI、检查平台和运维工具 | 完善本地开发与排障工具链 |
| 大数据项目 | 计算、存储、数据集成和湖仓项目 | 学习或搭建大数据平台 |
收录与维护规则
- 本轮核验日期为 2026 年 8 月 11 日,最近一次更新早于 2026 年 5 月 11 日 的项目不予收录。
- GitHub 项目以默认分支最近一次提交为准;Gitee 项目参考仓库的最近推送时间。
- 仓库迁移、归档或停止维护时,会更新链接或移出清单。一次性的版本号、README 修改不能完全代表项目质量,后续还会结合发布记录和 Issue 处理情况复核。
- Star 只作为参考。缺少文档、无法构建、许可证不清晰,或者技术栈明显过时的项目,即使热度较高也可能不收录。
怎么使用这份清单
- 初学阶段先看技术教程,再从 Spring Petclinic 这类边界清楚的项目开始,不要一上来就啃大型微服务系统。
- 准备项目经历时,优先选择能在本地跑起来、业务流程完整、自己确实能改造的实战项目。克隆和部署不等于项目经验。
- 做技术选型时,先确定需要的是代码依赖、独立工具还是基础设施,再进入对应目录,避免拿不同层次的项目直接比较。
- 阅读源码前先看项目文档、模块划分和测试,再带着一个具体问题追调用链,效率通常比从入口逐行阅读高。
高频问题
- 找 Java 练手项目时,应该优先看业务项目还是基础设施项目?
- 如何判断一个开源项目是否适合写进简历?
- Star 数高的项目一定值得学习吗?
- 克隆项目跑起来之后,应该从哪些地方开始改造?
- 面试官问项目难点时,如何避免只说 CRUD?
- 工具库应该如何选型?什么时候不应该引入新依赖?
- 想提升源码阅读能力,应该从哪些开源项目开始?
相关专题
Overview (`docs/roadmap/README.md`)
title: 学习路线合集(2026 最新版):Java 后端、AI Agent、AI 应用开发、全栈与测开
category: 学习路线
head:
- meta
- name: keywords
content: 学习路线,2026学习路线,Java学习路线,Java后端学习路线,AI学习路线,Agent学习路线,全栈学习路线,测开学习路线,测试开发学习路线,AI应用开发,AI编程
这里统一整理 JavaGuide 中适合按路线学习的内容,帮助你根据目标选择 2026 最新版学习路径,而不是在大量文章里来回跳。
如果你正在准备后端面试,优先看 Java 后端路线;如果你已经有 Java/Go 后端基础,想进入 AI 应用开发或 Agent 方向,可以从 AI 应用开发路线开始;如果你希望提升独立交付能力,可以看全栈学习建议;如果你想准备测试开发、自动化测试或 AI 测试方向,可以看测开学习路线。
后端方向
- Java 后端学习路线(2026 最新版):从 Java 基础、集合、并发、JVM、Spring、数据库、分布式到项目实践,适合 Java 后端系统学习和求职准备。
AI 方向
- Java/Go 开发者 AI 应用开发与 Agent 学习路线(2026 最新版):面向后端开发者,按大模型基础、LLM API、Prompt、RAG、Agent、工程化和项目实战拆解学习路径。
- 后端开发者转型 AI Agent 学习建议(2026 最新版):先判断是否适合转型,再看 Java AI 与 Python AI 怎么选、能投什么岗位、应该如何学习。
全栈方向
- 后端开发者全栈学习路线(2026 最新版):结合 AI 编程工具,讲清后端开发者如何补齐前端能力,逐步具备独立交付完整功能的能力。
测试开发方向
- 测试开发学习路线(2026 最新版):面向测开、自动化测试和质量工程方向,覆盖测试理论、接口/UI/性能自动化、CI/CD、测试平台、AI 辅助测试和 AI 应用评测。
Overview (`docs/system-design/README.md`)
title: 系统设计知识体系:设计模式、工程基础、认证授权、数据安全与常用框架
category: 系统设计
tag:
- 系统设计
- 设计模式
- 后端面试
sitemap:
changefreq: weekly
priority: 0.95
head: - meta
- name: keywords
content: 系统设计,系统设计面试题,设计模式,RESTful API,软件工程,代码重构,单元测试,认证授权,JWT,SSO,权限系统,数据安全,Spring,Spring Boot,MyBatis,Netty,定时任务,实时消息推送,后端面试
这份 系统设计知识体系 面向后端学习、工程实践和面试复习,围绕“把业务需求拆成稳定、可维护、可扩展的工程方案”整理本站系统设计相关文章。
系统设计不是只背秒杀、短链、Feed 流这类场景题。真正落到后端开发里,还需要理解 API 设计、代码质量、设计模式、框架原理、认证授权、数据安全、定时任务和实时消息推送等基础能力。
如果你时间有限,建议先看 系统设计常见面试题总结,快速建立高频问题清单;如果想系统补基础,可以按下面的阅读顺序推进。
适合谁看
- 正在系统学习后端系统设计的开发者。
- 准备校招、社招、中大厂后端面试的同学。
- 想把“会写业务代码”进一步提升到“能设计模块和方案”的工程师。
- 已经接触 Spring、MyBatis、权限系统、定时任务等技术,但缺少系统化梳理的读者。
学习重点
- 系统设计面试中,如何从需求澄清、核心流程、数据模型、接口设计、扩展性和风险点组织答案?
- RESTful API、命名、重构、单元测试这些工程基础,如何影响长期维护成本?
- 设计模式不是背 UML 图,而是理解常见变化点如何被封装。
- Spring、MyBatis、Netty 等框架分别解决了哪些工程复杂度?
- 认证授权、JWT、SSO、权限系统和数据安全分别覆盖安全链路的哪些部分?
- 定时任务和实时消息推送在生产环境里有哪些选型和稳定性问题?
建议阅读顺序
- 系统设计常见面试题总结:先建立系统设计场景题和高频问题清单。
- 系统设计基础专题:补齐 RESTful API、软件工程、代码命名、代码重构和单元测试。
- 设计模式常见面试题总结:理解常见设计模式适合解决哪些变化点。
- Spring & Spring Boot 专题:掌握 IoC、AOP、事务、自动装配、常用注解和框架设计模式。
- 认证授权与数据安全专题:系统理解认证授权、JWT、SSO、权限系统、加密、脱敏和数据校验。
- 再根据项目需要阅读 Java 定时任务详解 和 Web 实时消息推送详解。
核心文章
系统设计与工程基础
- 系统设计常见面试题总结:覆盖短链系统、秒杀系统、海量数据处理等系统设计场景题。
- 系统设计基础专题:从 RESTful API、软件工程讲到代码命名、代码重构和单元测试。
- RestFul API 简明教程:理解资源建模、HTTP 方法、状态码和接口设计规范。
- 代码重构指南:理解代码坏味道、重构原则和常见重构手法。
- 单元测试到底是什么?应该怎么做?:理解单元测试、Mock、Stub、测试金字塔和 JUnit 基础。
- 设计模式常见面试题总结:梳理单例、工厂、代理、责任链、策略、观察者等常见设计模式。
- 状态机与流程编排:理解条件转移、并行汇聚、任务重试、幂等和流程版本管理。
常用框架
- Spring & Spring Boot 专题:串联 Spring、Spring MVC、Spring Boot 的常见问题。
- Spring常见面试题总结:覆盖 IoC、AOP、Bean 生命周期、依赖注入等核心问题。
- SpringBoot常见面试题总结:覆盖自动配置、Starter、配置加载和 Actuator 等问题。
- MyBatis常见面试题总结:理解
#{}、${}、动态 SQL、缓存、分页插件和 Mapper 映射。 - Netty常见面试题总结:了解高性能网络编程、Reactor 模型、事件循环和 ChannelPipeline。
认证授权与数据安全
- 认证授权与数据安全专题:围绕认证授权、JWT、SSO、权限系统、加密、脱敏和数据校验展开。
- 认证授权基础概念详解:理解认证、授权、Session、Token、OAuth2 等基础概念。
- JWT 基础概念详解:理解 JWT 组成、签名、工作流程和登录鉴权场景。
- 权限系统设计详解:理解 RBAC 权限模型和权限系统设计。
- 常见加密算法总结:理解对称加密、非对称加密、哈希算法和常见应用场景。
- 数据脱敏方案总结:理解常见敏感数据脱敏规则和工程实现。
任务调度与消息推送
- Java 定时任务详解:梳理 Timer、ScheduledThreadPoolExecutor、DelayQueue、时间轮、Spring @Scheduled 和分布式任务调度框架。
- Web 实时消息推送详解:理解短轮询、长轮询、SSE、WebSocket 和消息推送选型。
- J2EE 基础知识:回顾 Servlet、请求转发与重定向、Session 和 Cookie 等 Java Web 基础。
高频问题
- 系统设计题应该按什么结构回答?
- RESTful API 设计里,资源路径、HTTP 方法和状态码应该如何使用?
- 代码命名、重构和单元测试为什么会影响系统可维护性?
- 常见设计模式分别解决什么问题?在 Spring 里有哪些应用?
- 状态机与流程编排分别适合什么场景?并行任务如何可靠汇聚?
- Spring IoC、AOP、Bean 生命周期和事务机制怎么理解?
- Spring Boot 自动装配的核心流程是什么?
- MyBatis 的
#{}和${}有什么区别?一级缓存和二级缓存如何工作? - 认证和授权有什么区别?Session、Token、JWT、OAuth2 分别适合什么场景?
- RBAC 权限系统应该如何设计?
- 数据脱敏、数据校验和加密算法分别解决什么安全问题?
- 定时任务框架应该如何选型?分布式任务调度要注意哪些问题?
- Web 实时消息推送有哪些方案?短轮询、SSE 和 WebSocket 如何取舍?
相关专题
Overview (`docs/tools/README.md`)
title: 开发工具知识体系:Maven、Gradle、Git、GitHub、Docker 与 IDEA
category: 开发工具
tag:
- 开发工具
- Maven
- Git
sitemap:
changefreq: weekly
priority: 0.9
head: - meta
- name: keywords
content: 开发工具,Maven,Gradle,Git,GitHub,Docker,IDEA,依赖管理,项目构建,版本控制,容器化部署,后端开发
这份 开发工具知识体系 面向后端学习和日常开发,围绕“项目构建 -> 依赖管理 -> 版本控制 -> 协作提效 -> 容器化交付”的顺序整理本站开发工具相关文章。
开发工具不只是会敲几个命令,更重要的是理解它们在团队协作、工程规范、环境一致性和交付效率中的作用。
适合谁看
- 正在学习后端开发,需要补齐常用工程工具的同学。
- 准备校招、社招,想把 Maven、Git、Docker 等工具问题答得更扎实的读者。
- 已经能写业务代码,但对依赖冲突、Git 分支协作、Docker 镜像和容器管理不够熟的开发者。
- 想提升项目构建、代码协作、环境交付和日常开发效率的工程师。
学习重点
- Maven 和 Gradle 解决的是项目构建、依赖管理、生命周期、Wrapper 和插件扩展问题。
- Git 是团队协作的基础能力,重点不是背命令,而是理解工作区、暂存区、提交、分支、合并和冲突处理。
- GitHub 不只是代码托管平台,也承载开源协作、个人展示、代码阅读、Actions 自动化和项目管理。
- Docker 主要解决环境一致性、部署隔离、镜像分发、本地快速搭建依赖服务和多容器应用编排的问题。
- 工具类知识最好结合真实项目练习,单独看概念容易会看不会用。
建议阅读顺序
- Git 核心概念总结:先掌握版本控制、提交、分支、合并和协作流程。
- Maven 核心概念总结:理解 Java 项目构建、POM、坐标、仓库、依赖和生命周期。
- Maven 最佳实践:补齐依赖版本管理、BOM、Maven Wrapper、CI 和日常使用规范。
- Docker 核心概念总结:建立镜像、容器、仓库和 Docker 引擎的基本认知。
- Docker 实战:通过命令和场景练习容器管理、镜像构建、数据卷和常见排查。
- Gradle 核心概念总结 和 GitHub 实用小技巧总结:按项目需要补充 Gradle Wrapper、GitHub Actions 与代码阅读技巧。
核心文章
项目构建与依赖管理
- Maven 专题:讲清 Maven 核心概念和最佳实践,是 Java 后端项目构建最常用的工具专题。
- Maven 核心概念总结:理解 POM、坐标、仓库、依赖范围、生命周期、插件和多模块项目。
- Maven 最佳实践:整理标准目录结构、编译版本、依赖管理、Maven Wrapper、CI 和常用实践建议。
- Gradle 核心概念总结:了解 Gradle、Groovy/Kotlin DSL、Gradle Wrapper、插件和 Task 等核心概念。
版本控制与代码协作
- Git 专题:围绕 Git 核心概念、工作流和 GitHub 提效技巧展开。
- Git 核心概念总结:理解版本控制、工作区、暂存区、提交、分支、合并、冲突和远程仓库。
- GitHub 实用小技巧总结:整理个人主页、项目徽章、代码阅读、Actions、Explore/Trending 和开源协作相关技巧。
容器化与本地环境
- Docker 专题:从核心概念到实战操作,帮助理解容器化交付和环境一致性。
- Docker 核心概念总结:理解容器、镜像、仓库、Docker 引擎以及容器和虚拟机的区别。
- Docker 实战:通过镜像、容器、网络、数据卷、日志和排查命令完成 Docker 入门实践。
IDE 与效率工具
- IDEA 教程:整理 IntelliJ IDEA 常用配置、插件、快捷键和提效技巧。
高频问题
- Maven 的 POM、坐标、仓库、依赖范围分别是什么?
- Maven 生命周期和插件是什么关系?
- Maven 多模块项目如何通过 BOM、
dependencyManagement和 Maven Wrapper 管理公共依赖与构建版本? - Gradle 和 Maven 有什么区别?什么时候需要了解 Gradle?
- Git 工作区、暂存区、本地仓库和远程仓库分别是什么?
- Git merge 和 rebase 有什么区别?冲突应该如何处理?
- GitHub 除了托管代码,还能通过 Profile README、Actions、Codespaces、Explore/Trending 帮开发者做哪些事?
- Docker 镜像和容器是什么关系?容器和虚拟机有什么区别?
- Docker 为什么能解决开发、测试、部署环境不一致的问题?Compose 适合解决什么问题?
相关专题
Overview (`docs/zhuanlan/README.md`)
title: 星球专属优质专栏:Java 面试、系统设计、手写 RPC、源码阅读与实战项目
category: 知识星球
sitemap:
changefreq: weekly
priority: 0.9
head:
- meta
- name: keywords
content: JavaGuide知识星球,Java面试指北,后端系统设计,手写RPC框架,Java源码阅读,Java实战项目,Java面试资料,知识星球专栏
这份 星球专属优质专栏 汇总 JavaGuide 知识星球里的系统学习资料,覆盖 Java 面试、系统设计与场景题、手写 RPC、源码阅读和实战项目。
如果你正在准备 Java 后端面试,建议先看 《Java 面试指北》 和 《后端面试高频系统设计&场景题》;如果你想补项目和源码能力,可以继续看 AI 智能面试辅助平台 + RAG 知识库、《手写 RPC 框架》 和 《Java 必读源码系列》。
适合谁看
- 正在准备 Java 后端校招、社招、中大厂面试的同学。
- 想用系统资料替代碎片化搜索,提高复习效率的读者。
- 需要补齐系统设计、场景题、项目实战和源码阅读能力的后端开发者。
- 希望在 JavaGuide 开源内容之外获得更完整学习路线和资料支持的读者。
学习重点
- Java 面试复习要同时覆盖基础知识、项目经验、系统设计、场景题和表达方式。
- 系统设计与场景题重点看问题拆解、容量估算、核心链路、数据一致性和可用性设计。
- 手写 RPC 适合把网络通信、序列化、注册中心、动态代理和服务治理串起来。
- 源码阅读要带着问题看,重点理解框架设计思路和可迁移的工程经验。
- 实战项目要能跑起来、讲清楚、改得动,才真正能转化为面试竞争力。
建议阅读顺序
- 《Java 面试指北》:先建立 Java 后端面试复习主线。
- 《后端面试高频系统设计&场景题》:补齐短链、秒杀、海量数据去重、第三方授权登录等高频场景。
- AI 智能面试辅助平台 + RAG 知识库:用完整实战项目补简历亮点和工程经验。
- 《手写 RPC 框架》:通过从零实现 RPC 框架理解分布式服务调用。
- 《Java 必读源码系列》:在有基础后阅读 Dubbo、Netty、Spring Boot 等框架源码。
核心文章
面试资料
- 《Java 面试指北》:与 JavaGuide 开源版内容互补,面向 Java 后端面试系统复习。
- 《后端面试高频系统设计&场景题》:覆盖短链系统、秒杀系统、海量数据去重、第三方授权登录等高频问题。
- 《Java 必读源码系列》:整理 Dubbo 2.6.x、Netty 4.x、Spring Boot 2.1 等框架和中间件源码阅读资料。
实战项目
- AI 智能面试辅助平台 + RAG 知识库:基于 Spring Boot 4.0、Java 21、Spring AI 2.0 开发,适合作为学习和简历项目。
- 《手写 RPC 框架》:从零开始基于 Netty、Kryo、ZooKeeper 实现一个简易 RPC 框架。
高频问题
- Java 后端面试复习应该先看开源内容,还是先看星球专栏?
- 系统设计题应该怎么拆解,如何避免只背固定答案?
- 手写 RPC 框架适合什么基础的读者学习?
- 源码阅读应该从 Dubbo、Netty、Spring Boot 哪个开始?
- 实战项目写进简历时,如何讲清楚技术难点和个人贡献?
- 星球内容如何和 JavaGuide、项目实战、面试题一起使用?
相关专题
Overview (`docs/ai/agent/README.md`)
title: AI Agent 专题:Agent Loop、Memory、Prompt、Context、MCP 与 Skills
category: AI
tag:
- AI Agent
- 大模型
- AI 应用开发
sidebar: false
Agent 不是“会调用工具的聊天机器人”。一旦任务变长,它就要处理状态、记忆、权限、失败重试、上下文裁剪和执行边界。
这份 AI Agent 专题 面向想理解和落地 Agent 应用的开发者,把 Agent Loop、Memory、Prompt、Context、Tools、MCP、Skills、Harness Engineering 和 Workflow 放到同一条工程主线里看。
适合谁看
- 想理解 AI Agent 原理和工程落地方式的开发者。
- 正在做工具调用、自动化任务、多轮推理、长任务执行相关 AI 应用的工程师。
- 准备 Agent、MCP、Prompt、Context、Skills、工作流相关面试题的同学。
学习重点
- Agent 和 Workflow 的区别不在于有没有调用大模型,而在于是否具备观察、规划、行动和反馈闭环。
- Memory 解决跨轮、跨任务的信息保留问题,但必须设计生命周期、存储边界和隐私治理。
- Prompt Engineering 更关注指令表达,Context Engineering 更关注把正确的信息在正确时机放进上下文。
- MCP、Skills、Harness Engineering 决定 Agent 能不能稳定、安全、可扩展地接入真实工具和环境。
建议阅读顺序
- AI Agent 核心概念:先建立 Agent 的整体认知。
- 大模型提示词工程、上下文工程:理解指令和上下文如何共同影响输出。
- AI Agent 记忆系统:补齐短期记忆、长期记忆和记忆生命周期。
- 万字拆解 MCP 协议、万字详解 Agent Skills:理解工具接入和能力扩展。
- 多 Agent 协作系统设计:理解任务拆分、状态共享、并发冲突和失败恢复。
- Harness Engineering:六层检查框架、上下文管理与工程实践、AI 工作流中的 Workflow、Graph 与 Loop、Loop Engineering 是什么:进入生产级 Agent 工程化。
阅读时注意区分三种循环:
| 层次 | 负责什么 | 对应文章 |
|---|---|---|
| 内层 Agent Loop | 一次任务中反复推理、调用工具、读取结果,直到完成或触发停止条件。 | Agent 核心概念 |
| 工作流图上的回边 | 按状态和条件返回先前节点,让局部流程继续迭代。 | Workflow、Graph 与 Loop |
| 外层 Loop Engineering | 把已有循环接到 CI、定时任务和停止条件上,决定何时启动下一轮任务。 | Loop Engineering |
核心文章
- AI Agent 核心概念:梳理 AI Agent 的演进脉络,讲清 Agent Loop、Context Engineering、Tools 注册等基础概念。
- AI Agent 记忆系统:深入理解短期记忆与长期记忆设计,掌握记忆存储形式、生命周期操作与生产级工程优化策略。
- 多 Agent 协作系统设计:从任务契约、共享状态和冲突处理讲到检查点、接管、补偿与人工介入。
- 大模型提示词工程:从 Prompt 四要素、常见技巧讲到企业级安全实践。
- 上下文工程:掌握静态规则编排、动态信息挂载、Token 预算降级等关键技术。
- 万字详解 Agent Skills:理解 Skills 的设计理念,以及 Skills 与 Prompt、MCP、Function Calling 的本质区别。
- 万字拆解 MCP 协议:理解 MCP 协议的核心概念、架构设计和生产级最佳实践。
- Harness Engineering:六层检查框架、上下文管理与工程实践:拆解 OpenAI、Anthropic、Stripe 等团队在 Agent 工程化上的实践思路。
- AI 工作流中的 Workflow、Graph 与 Loop:对比传统工作流与 AI 工作流的差异,覆盖 Spring AI Alibaba 和 LangGraph 实现。
- Loop Engineering 是什么?为什么说它是新瓶装旧酒?:把 Loop Engineering 放回 Agent Loop、Context、Harness、Skills、MCP 和验证闭环里,理解它到底新在哪里。
工具调用和评测的完整内容在已有文章中:
- 结构化输出与 Function Calling:覆盖工具调用完整链路、权限、二次确认、幂等、审计、超时和 Java 示例。
- Agent 应用怎么评测:覆盖任务完成率、工具调用、执行轨迹、错误恢复和多次运行一致性等指标。
高频问题
- Agent 和 Workflow、Chatbot、普通工具调用有什么区别?
- Agent Loop 中观察、规划、行动、反思分别负责什么?
- Memory 应该存什么、不存什么?如何避免污染和隐私风险?
- Prompt Engineering 和 Context Engineering 为什么不能混为一谈?
- MCP、Skills、Function Calling 的边界分别在哪里?
- 长任务 Agent 如何控制上下文、权限、失败重试和可观测性?
相关专题
总结
Agent 的工程问题会随着任务变长逐步显现:模型要在循环中根据状态做决策,历史信息需要存储和检索,外部工具需要明确的接入、权限与审计规则,失败还要能够回放和处理。
阅读时可以先分清 Agent、Workflow 与普通工具调用的职责,再沿着 Prompt、Context、Memory、MCP、Skills 和 Harness 这条链路补齐实现细节。做具体项目时,从可验证的最小闭环开始,根据实际出现的上下文、成本、权限或稳定性问题增加机制即可。
Overview (`docs/ai/interview-questions/README.md`)
title: AI 应用开发面试题专题
category: AI
tag:
- AI
- AI 面试
- 后端面试
sidebar: false
AI 应用开发面试很少只问“概念是什么”。更常见的是顺着一个项目往下追:为什么这样设计,出了问题怎么排查,上线后怎么评测,成本和安全怎么管。
这份 AI 应用开发面试题专题 面向 AI 工程师、AI 应用开发和后端转 AI 岗位复习,把“大模型基础、AI Agent、RAG、AI 系统设计”这些问题串成一条复习路线。
适合谁看
- 准备 AI 应用开发、AI 工程师、后端转 AI 相关岗位面试的同学。
- 已经看过一些 AI 概念,但回答面试题时容易说散、说浅,或者只停留在 Demo 层面的读者。
- 想把项目经历整理成“问题 -> 原理 -> 方案 -> 取舍 -> 落地”表达方式的开发者。
学习重点
- 大模型基础题重点讲清 Token、上下文、采样参数、结构化输出、模型调用和评测。
- Agent 题重点讲清 Agent Loop、Memory、Prompt、Context、MCP、Skills 和工作流。
- RAG 题重点讲清文档处理、向量检索、混合检索、Rerank、GraphRAG、知识库更新和评测。
- 系统设计题重点讲清模型网关、可观测、成本、安全、灰度和实时语音 Agent。
建议阅读顺序
- AI 应用开发面试指南:先看总入口,建立整体复习地图。
- 大模型基础面试题总结:补齐 LLM 基础概念和 API 调用链路。
- AI Agent 面试题总结:掌握 Agent 相关高频概念和工程化问题。
- Agent 项目面试怎么讲?:把概念组织成项目背景、系统架构、技术选型、效果证据和 Badcase 复盘。
- RAG 面试题总结:围绕企业知识库问答,复习召回、排序、更新和评测问题。
- AI 系统设计面试题总结:把前面的模块串成生产级系统设计表达。
核心文章
- AI 应用开发面试指南:AI 应用开发面试题总入口,按大模型基础、AI Agent、RAG、AI 系统设计组织复习路线。
- 大模型基础面试题总结:覆盖 Token、上下文窗口、采样参数、API 调用、结构化输出、Function Calling、MCP 与 AI 应用评测。
- AI Agent 面试题总结:覆盖 Agent 核心概念、Memory、Prompt Engineering、Context Engineering、MCP、Agent Skills、Harness Engineering 与 AI 工作流。
- Agent 项目面试怎么讲?:用一条可核验的证据链讲清 Agent 项目的架构、选型、指标和 Badcase,避免只堆技术名词。
- RAG 面试题总结:覆盖 RAG 基础、Embedding、向量数据库、Chunk 策略、文档处理、检索优化、GraphRAG、知识库更新与 RAG 评测。
- AI 系统设计面试题总结:覆盖生产级架构、模型网关、Prompt 管理、可观测、评测、安全治理与实时语音 Agent。
高频问题
- 面试官问“你做过 AI 应用吗”,如何从业务场景、架构、效果评测和上线治理回答?
- 大模型 API 调用为什么需要重试、限流、fallback 和结构化校验?
- Agent 和普通工作流的区别是什么?
- RAG 检索不到、检索错、生成错分别怎么排查?
- AI 应用系统设计如何体现稳定性、可观测性、成本控制和安全治理?
相关专题
Overview (`docs/ai/llm-basis/README.md`)
title: 大模型基础专题:运行机制、API 调用、结构化输出与评测
category: AI
tag:
- 大模型
- LLM
- AI 应用开发
sidebar: false
大模型 API 看起来只是一段请求和一段返回,实际落地时问题都藏在细节里:Token 怎么花掉、上下文为什么塞不下、采样参数会不会让答案飘、结构化输出为什么偶发失败、上线前怎么证明效果真的变好了。
这份 大模型基础专题 面向 AI 应用开发入门和工程落地,先把这些基础问题讲清楚,再进入 Agent、RAG 和系统设计会顺很多。
适合谁看
- 正在学习 LLM 基础概念和大模型 API 调用的开发者。
- 做过 Prompt Demo,但对生产级调用链路、结构化输出和评测不熟的工程师。
- 准备大模型基础、Function Calling、Tool Calling、AI 应用评测相关面试题的同学。
学习重点
- Token、上下文窗口、Temperature、Top P、停止词等参数如何影响模型输出。
- 生产级大模型 API 调用需要处理流式响应、超时、重试、限流、fallback、日志和审计。
- 结构化输出不能只靠 Prompt,还要结合 JSON Schema、Function Calling、Tool Calling 和服务端校验;即便这样,也要准备失败兜底。
- AI 应用评测要区分离线 Golden Set、LLM-as-Judge、线上灰度、Trace 回放和持续回归。
建议阅读顺序
- LLM 运行机制:Token、上下文窗口与采样参数怎么影响输出:先理解 Token、上下文窗口和采样参数。
- 大模型 API 调用工程实践:再看大模型调用如何接入真实后端链路。
- 大模型结构化输出详解:补齐结构化返回和工具调用基础。
- AI 应用评测体系:最后建立质量评估和上线回归方法。
核心文章
- LLM 运行机制:Token、上下文窗口与采样参数怎么影响输出:把 Token、上下文窗口、Temperature 等概念还原为可观察、可调试的工程参数。
- 大模型 API 调用工程实践:拆解 AI 应用调用大模型 API 的生产链路,覆盖流式输出、重试、限流、结构化返回与后端工程落地。
- 大模型结构化输出详解:讲清 JSON Schema、Function Calling、Tool Calling 与 MCP 在一次工具调用里分别负责什么。
- AI 应用评测体系:从 Golden Set、LLM-as-Judge、RAG/Agent 指标、Trace 回放到 CI 回归,说明 AI 应用该怎么验收。
高频问题
- 为什么同一个 Prompt 每次输出不一样?
- 上下文窗口变大是不是就一定更好?
- 为什么结构化输出会偶发失败?如何做兜底和校验?
- Function Calling、Tool Calling、MCP 分别解决什么问题?
- AI 应用上线前如何证明“效果变好了”而不是只凭感觉?
相关专题
Overview (`docs/ai/rag/README.md`)
title: RAG 专题:文档处理、向量数据库、GraphRAG、检索优化与知识库更新
category: AI
tag:
- RAG
- 向量数据库
- AI 应用开发
sidebar: false
RAG 不只有“文档切块 + 向量检索”。解析、权限、排序、生成、更新和评测都会影响最终答案。
这份 RAG 专题 面向企业知识库问答、智能客服、文档助手和内部搜索等场景,按文档进入系统后的真实链路展开:解析、清洗、切分、向量化、索引、召回、重排、生成、更新和评测。
适合谁看
- 正在学习或落地 RAG 知识库问答的开发者。
- 做过“文档切块 + 向量检索”Demo,但对召回质量、文档更新、一致性和评测不熟的工程师。
- 准备 RAG、向量数据库、GraphRAG、企业知识库相关面试题的同学。
学习重点
- RAG 的效果问题要分段排查:文档处理、Chunk、Embedding、召回、Rerank、上下文压缩和生成。
- 向量数据库选型要结合数据规模、过滤条件、更新频率、延迟要求和运维成本。
- GraphRAG 更适合实体关系强、全局问题多、需要跨文档推理的场景。
- 知识库更新不是简单覆盖文件,还要考虑版本、去重、增量索引、回滚和灰度。
- RAG 评测要同时看检索指标和生成指标,不能只凭最终回答是否“像那么回事”来判断。
建议阅读顺序
- RAG 基础概念:检索、生成与工程取舍:先理解 RAG 的核心流程、优势和局限。
- RAG 文档处理与切分策略:理解文档进入索引前的处理链路。
- RAG 向量索引算法和向量数据库:补齐向量索引和数据库选型基础。
- RAG 优化:从召回、重排到上下文工程:掌握召回、重排、改写和上下文压缩。
- GraphRAG:用图结构补充向量检索、RAG 知识库文档更新策略:进一步理解复杂知识组织和持续更新。
核心文章
- RAG 基础概念:检索、生成与工程取舍:理解 RAG 的工作流程、适用场景和局限性。
- RAG 文档处理与切分策略:涵盖文件解析、清洗、结构化、Chunking 策略与多模态内容处理。
- RAG 向量索引算法和向量数据库:掌握 HNSW、IVFFLAT 等索引算法原理,学会选择合适的向量数据库。
- RAG 优化:从召回、重排到上下文工程:围绕 Chunk 策略、Hybrid Search、Query Rewrite、Rerank、上下文压缩排查召回问题。
- GraphRAG:用图结构补充向量检索:理解知识图谱驱动的 RAG,掌握实体、关系、社区发现、全局检索与局部检索。
- RAG 知识库文档更新策略:涵盖增量更新、版本回滚、去重与灰度发布。
高频问题
- RAG 为什么还会幻觉?应该从哪些环节排查?
- Chunk 切大还是切小?如何处理标题、表格、代码块和多模态内容?
- 向量检索、关键词检索、混合检索分别适合什么场景?
- Rerank 的作用是什么?什么时候值得引入?
- GraphRAG 和普通 RAG 有什么区别?
- 知识库更新如何保证一致性、可回滚和不停机?
- RAG 应用如何评测召回质量和最终回答质量?
相关专题
Overview (`docs/ai/system-design/README.md`)
title: AI 系统设计专题:从生产级架构到安全、可观测与语音 Agent
category: AI
tag:
- AI 系统设计
- 大模型
- AI 应用开发
- AI 可观测
sidebar: false
Prompt Demo 能跑起来,只说明模型在某个样例里给过一个可用回答。放到生产环境,还要继续回答几个工程问题:模型怎样路由,失败时怎么兜底,Token 成本如何归因,一次 Agent 请求如何还原,敏感工具由谁授权和审计。
这份 AI 系统设计专题 面向已经做过 Demo、准备把 AI 能力接进真实业务的开发者。内容按后端系统的视角展开:架构分层、模型网关、RAG、Memory、Tool 调用、可观测、评测、安全治理和实时语音链路。
适合谁看
- 做过 Prompt 或 RAG Demo,想知道上线前还差哪些工程环节的开发者。
- 需要在项目中设计模型网关、多模型路由、fallback、限流、缓存和成本控制的工程师。
- 准备 AI 系统设计、模型网关、AI 可观测、实时语音 Agent 相关面试题的同学。
学习重点
- 生产级 AI 应用关注可持续运行:输出质量、延迟、失败兜底、成本和审计都要能解释、能复盘。
- 模型网关把模型服务和业务调用方隔开,统一处理多模型路由、fallback、限流、缓存、Token 预算、成本归因、观测审计和安全策略。
- Prompt 管理、RAG、Memory、Tool 调用、异步任务、评测和可观测要放在同一条链路里设计;这里很难套通用模板,通常要按业务风险、调用量和成本约束取舍。
- AI 可观测要能从接口请求继续下钻到模型、检索、工具和 Agent,并处理跨线程上下文传播、采样、敏感数据和线上 Badcase 回流。
- 实时语音 Agent 除了文本推理,还要处理 VAD、ASR、LLM、TTS、流式播放、打断处理和端云混合选型,对端到端延迟更敏感。
建议阅读顺序
- AI 应用系统设计:先看 Prompt Demo 进入生产链路时,需要补哪些后端能力。
- LLM/Agent 安全实战:沿一次工具调用理解直接与间接 Prompt Injection、资源鉴权、审批、MCP 授权、隔离和安全回归。
- 大模型网关详解:再看模型调用治理,多模型路由、fallback、限流和成本归因怎么放在网关层处理。
- AI 可观测性与 Trace:沿一次错误回答还原模型、RAG、工具和 Agent 的执行过程,再看上下文传播、采样和评测回流。
- AI 语音技术详解:最后看语音 Agent,从 VAD、ASR 到 LLM、TTS、播放和打断处理。
核心文章
- AI 应用系统设计:从 Prompt 管理讲到模型网关、RAG、Memory、Tool 调用、异步任务、可观测、评测和安全合规,适合作为系统设计主线。
- LLM/Agent 安全实战:通过售后退款 Agent 串联间接 Prompt Injection、工具越权、MCP 授权、敏感数据、代码隔离、供应链和安全评测。
- 大模型网关详解:说明 LLM Gateway 在模型路由、fallback、限流配额、Token 预算、成本归因、观测审计和缓存策略中的职责。
- AI 可观测性与 Trace:讲清 Session、Run、Trace、Span 和 Attempt,并覆盖 Agent/RAG/Tool Span、上下文传播、采样、敏感数据与 Spring AI 接入。
- AI 语音技术详解:沿语音系统链路展开 VAD、ASR、LLM、TTS、流式播放、打断处理与端云混合选型。
高频问题
- Prompt Demo 上线前还缺哪些工程能力?
- Agent 读取外部文档并调用写工具时,如何防止注入、越权和数据外传?
- AI 应用为什么常把模型调用收敛到网关层?
- 多模型路由、fallback、限流和缓存分别解决什么问题?
- 一次 Agent 请求应该怎样拆 Span,跨线程和跨服务时如何保持 Trace?
- 线上失败 Trace 如何回流到评测集和回归检查?
- 实时语音 Agent 的延迟、打断和端云选型为什么更难处理?
总结
生产级 AI 系统的难点不在于把一次模型调用跑通,而在于把质量、延迟、成本、权限和故障处理放进同一条可追踪的链路。可以先从应用架构了解这些能力如何分层,再用模型网关收口调用治理,通过 Trace 还原 RAG、工具和 Agent 的执行过程,最后结合语音 Agent 理解实时场景中的流式、打断和端云取舍。具体方案仍要按业务风险、调用规模和合规要求选择。
相关专题
Overview (`docs/cs-basics/algorithms/README.md`)
title: 算法专题:面试刷题路线、核心模板与 LeetCode 高频题
category: 计算机基础
tag:
- 算法
- LeetCode
- 面试
sidebar: false
sitemap:
changefreq: weekly
priority: 0.9
head: - meta
- name: keywords
content: 算法,算法面试题,LeetCode,刷题路线,二分查找,双指针,滑动窗口,DFS,BFS,回溯,动态规划,贪心,TopK,排序算法,字符串算法,链表算法,后端面试
这份 算法专题 不是按教材顺序堆知识点,而是按面试刷题的真实路径整理:先搞清复杂度,再掌握二分、双指针、滑动窗口、DFS/BFS、回溯、动态规划、贪心、Top K 这些高频模板,最后用字符串、链表、排序和 LeetCode 题单做复盘。
算法题准备到后面,很容易陷入一个状态:题刷了不少,但换个条件就卡住。原因通常不是题量不够,而是没有把题目归到模板里。面试时真正有用的是:看到题目后能判断它像哪类问题,先写出可工作的版本,再解释复杂度和边界处理。
适合谁看
- 正在准备校招、社招算法题,希望按题型系统刷 LeetCode 的同学。
- 已经刷过一些题,但复盘时说不清“这题为什么这么做”的读者。
- 数据结构基础还可以,但缺少算法模板和边界处理经验的后端开发者。
- 面试前只有 7 到 30 天,需要快速找回手感的工程师。
算法面试考什么
算法面试一般不只是看你能不能 AC 一道题,更多是在看 4 件事:
| 考察点 | 面试里的具体表现 | 复习时要做什么 |
|---|---|---|
| 题型识别 | 这题是二分、滑动窗口、回溯还是 DP | 按题型刷,不要完全随机刷 |
| 代码稳定性 | 边界、空指针、下标、循环条件是否可靠 | 每个模板准备 2 到 3 个边界样例 |
| 复杂度表达 | 能否说清时间复杂度和空间复杂度 | 每做完一题都写复杂度 |
| 迁移能力 | 条件变化后能否改模板 | 一类题至少刷基础题和变体题 |
如果只能记一句话:先按题型建模板,再用代表题练迁移。
建议阅读顺序
- 时间复杂度和空间复杂度面试指南:先把 Big O、递归复杂度和常见误判讲清楚。
- 二分查找面试题总结:练基础二分、左右边界和答案二分。
- 双指针与滑动窗口面试题总结:解决数组、字符串、链表里的高频题。
- DFS 与 BFS 面试题总结:掌握树、图、矩阵搜索和层序遍历。
- 回溯算法面试题总结:集中处理组合、排列、子集和棋盘问题。
- 动态规划面试题总结:从状态定义和转移方程入手,不靠背题。
- 贪心算法面试题总结 和 Top K 问题面试题总结:补齐排序贪心、堆、快排分区和桶计数。
- 几道常见的字符串算法题、几道常见的链表算法题、十大经典排序算法总结:按专题做面试前复盘。
核心模板
| 模板 | 识别信号 | 重点文章 |
|---|---|---|
| 二分查找 | 有序、单调、最小可行值、最大可行值 | 二分查找面试题总结 |
| 双指针 | 原地修改、两端收缩、快慢追赶、链表定位 | 双指针与滑动窗口面试题总结 |
| 滑动窗口 | 连续子数组、连续子串、最长/最短窗口 | 双指针与滑动窗口面试题总结 |
| DFS/BFS | 树遍历、图遍历、矩阵连通块、层序最短步数 | DFS 与 BFS 面试题总结 |
| 回溯 | 枚举所有方案、路径选择、组合排列、棋盘约束 | 回溯算法面试题总结 |
| 动态规划 | 最优值、计数、能否到达、子序列、背包 | 动态规划面试题总结 |
| 贪心 | 每一步选择当前最合适的对象,常和排序搭配 | 贪心算法面试题总结 |
| Top K | 第 K 大、前 K 高频、数据流、优先级 | Top K 问题面试题总结 |
7 天速刷路线
时间很紧时,不建议从难题开始。7 天路线的目标是恢复模板和手写稳定性:
| 天数 | 重点 | 建议动作 |
|---|---|---|
| 第 1 天 | 复杂度 + 排序 | 复盘 Big O、快排、归并、堆排序和稳定性 |
| 第 2 天 | 二分 + 双指针 | 写左右边界模板、两数之和、三数之和、删除重复项 |
| 第 3 天 | 滑动窗口 + 字符串 | 写最长无重复子串、最小覆盖子串、回文相关题 |
| 第 4 天 | 链表 | 写反转链表、环形链表、删除倒数第 N 个节点 |
| 第 5 天 | 树和 BFS | 写前中后序遍历、层序遍历、最近公共祖先 |
| 第 6 天 | 回溯 + DP | 写子集、组合、零钱兑换、最长递增子序列 |
| 第 7 天 | Top K + 复盘 | 写第 K 大、前 K 高频,整理错题和边界样例 |
30 天系统路线
30 天路线不用追求每天刷很多题。更靠谱的节奏是:每天 1 到 3 道代表题,题后写 5 行复盘。
| 阶段 | 时间 | 目标 |
|---|---|---|
| 第一阶段 | 第 1 到 5 天 | 复杂度、数组、链表、栈、队列,保证基础模板能手写 |
| 第二阶段 | 第 6 到 12 天 | 二分、双指针、滑动窗口、字符串,重点练边界 |
| 第三阶段 | 第 13 到 18 天 | 树、图、DFS/BFS、并查集,建立搜索题框架 |
| 第四阶段 | 第 19 到 24 天 | 回溯、动态规划、贪心,重点练状态定义和剪枝 |
| 第五阶段 | 第 25 到 30 天 | Top K、排序、综合题和错题复盘,准备面试讲解 |
高频问题自测
- 时间复杂度为什么要看最高阶?递归复杂度怎么算?
- 二分查找的
left < right和left <= right怎么选? - 双指针和滑动窗口有什么区别?
- DFS 和 BFS 分别适合什么问题?什么时候需要
visited? - 回溯和 DFS 是什么关系?剪枝应该放在哪里?
- 动态规划为什么难?状态定义和遍历顺序怎么确定?
- 贪心为什么需要证明?面试中答到什么程度够用?
- Top K 用堆、快排分区还是桶计数,怎么选?
- 排序算法的稳定性、原地排序、最好/最坏复杂度分别是什么?
相关专题
Overview (`docs/cs-basics/data-structure/README.md`)
title: 数据结构知识体系:数组、链表、哈希表、树、图、堆与面试
category: 计算机基础
tag:
- 数据结构
- 算法
- 面试
sitemap:
changefreq: weekly
priority: 0.9
head: - meta
- name: keywords
content: 数据结构,数据结构面试题,数据结构复习路线,数组,链表,栈,队列,哈希表,HashMap,树,图,堆,Trie,并查集,跳表,红黑树,布隆过滤器,LRU,Java集合,Redis,MySQL索引,后端面试
这份 数据结构知识体系 按面试和 Java 后端场景组织内容:先理解数据怎么存,再看常见操作复杂度,最后把结构和 Java 集合、MySQL 索引、Redis、缓存、消息队列这些工程问题连起来。
面试里问数据结构,很少只停在“数组是什么”。更常见的是追问:数组和链表为什么一个查询快、一个插入删除方便?HashMap 为什么需要扩容?B+ 树为什么适合索引?布隆过滤器为什么会误判?这些问题背后都在考同一件事:你是否理解结构选择带来的复杂度和场景取舍。
准备数据结构时,不建议只背定义。更有效的方式是把每个结构拆成 4 个问题:怎么存、怎么查、怎么改、适合什么场景。能把这 4 个问题讲清楚,再去刷对应的算法题,效率会高很多。
适合谁看
- 正在补数据结构基础,准备校招或社招后端面试的同学。
- 刷算法题时经常卡在数组、链表、树、图、堆等结构上的读者。
- 想把数据结构和 Java 集合、Redis、MySQL、缓存系统联系起来的工程师。
- 已经看过概念,但回答面试题时容易停在定义层面的开发者。
数据结构面试考什么
| 考察点 | 常见问法 | 复习重点 |
|---|---|---|
| 存储方式 | 顺序存储和链式存储有什么区别? | 内存连续性、指针、缓存友好性 |
| 操作复杂度 | 为什么数组查询是 O(1),链表查询是 O(n)? | 查询、插入、删除、遍历复杂度 |
| 结构对比 | 红黑树和 AVL 树怎么选?B 树和 B+ 树有什么区别? | 对比表 + 适用场景 |
| 工程关联 | HashMap、TreeMap、PriorityQueue、Redis ZSet 用了什么结构? | Java/数据库/缓存中的真实应用 |
| 算法承接 | 树遍历、图搜索、Top K、LRU 怎么写? | 和算法模板一起复习 |
面试回答框架
数据结构题的回答不要只停在“是什么”。面试里更稳的表达方式是按下面这条线展开:
定义 -> 存储方式 -> 常见操作复杂度 -> 优缺点 -> 适用场景 -> Java/Redis/MySQL 中的应用以哈希表为例,一个完整回答可以这样组织:
- 哈希表通过哈希函数把 key 映射到数组下标。
- 查询、插入、删除平均是
O(1),但冲突严重时会退化。 - 冲突可以用拉链法、开放寻址法等方式处理。
- Java
HashMap使用数组 + 链表 + 红黑树,扩容用于控制负载因子。 - 它适合快速查找、计数、去重、缓存索引等场景,但会消耗额外空间。
这种回答比“哈希表查询是 O(1)”更耐追问,因为它同时交代了原理、复杂度和工程落点。
建议阅读顺序
- 线性数据结构详解:先掌握数组、链表、栈、队列,理解顺序存储和链式存储。
- 哈希表面试题总结:理解哈希函数、冲突、扩容,并和
HashMap连起来。 - 树结构详解:掌握二叉树、二叉搜索树、AVL、B 树、B+ 树,以及 MySQL 索引关联。
- 堆详解:理解优先队列、Top K、堆排序和
PriorityQueue。 - 图详解:理解图的存储、DFS、BFS、拓扑排序和最短路径入口。
- Trie 前缀树面试题总结、并查集面试题总结:补齐字符串集合和连通性问题。
- 跳表面试题总结、红黑树详解、布隆过滤器详解、LRU 缓存面试题总结:面向 Java 集合、Redis、缓存和数据库场景复盘。
核心文章
| 文章 | 重点 | 常见关联 |
|---|---|---|
| 线性数据结构详解 | 数组、链表、栈、队列 | ArrayList、LinkedList、消息队列 |
| 哈希表面试题总结 | 哈希函数、冲突、扩容 | HashMap、缓存、去重 |
| 树结构详解 | 二叉树、BST、AVL、B 树、B+ 树 | MySQL 索引、表达式树 |
| 图详解 | 邻接表、邻接矩阵、DFS、BFS | 依赖关系、路由、推荐关系 |
| 堆详解 | 最大堆、最小堆、堆排序 | PriorityQueue、Top K、延迟队列 |
| 红黑树详解 | 近似平衡、旋转、变色 | TreeMap、HashMap 树化 |
| 布隆过滤器详解 | 位数组、哈希、误判 | 缓存穿透、去重、黑名单 |
| 跳表面试题总结 | 多级索引、范围查询 | Redis ZSet |
| LRU 缓存面试题总结 | 哈希表 + 双向链表 | 本地缓存、页面置换 |
结构选型速查
很多数据结构问题,实际是在问“这个场景为什么选它,而不是另一个结构”。下面这张表适合面试前快速复盘:
| 场景 | 优先考虑 | 取舍点 |
|---|---|---|
| 按下标频繁随机访问 | 数组、ArrayList |
查询快,插入删除中间元素成本高 |
| 频繁在两端插入删除 | 双端队列、链表 | 指针操作灵活,但随机访问慢 |
| 快速判断元素是否存在 | 哈希表、布隆过滤器 | 哈希表准确但占空间,布隆过滤器省空间但可能误判 |
| 维护有序集合和范围查询 | 红黑树、跳表、B+ 树 | 红黑树适合内存有序集合,跳表适合范围查询和工程实现 |
| 处理最大值、最小值、Top K | 堆、优先队列 | 只关心局部最值,不适合全量有序遍历 |
| 判断连通性和分组 | 并查集 | 合并和查询很快,但不适合频繁删除关系 |
| 前缀匹配、搜索提示 | Trie | 查询与字符串长度相关,但节点数量可能较多 |
| 缓存淘汰 | LRU、LFU | LRU 看最近访问,LFU 看访问频率 |
复习时可以反过来问自己:如果不用这个结构,会慢在哪里?会多占多少空间?边界条件是什么?这几个问题想清楚,面试追问通常就能接住。
7 天复习路线
| 天数 | 重点 | 建议动作 |
|---|---|---|
| 第 1 天 | 数组、链表 | 写复杂度表,手写反转链表和删除节点 |
| 第 2 天 | 栈、队列、哈希表 | 写括号匹配、用栈实现队列、两数之和 |
| 第 3 天 | 树 | 写二叉树遍历、最近公共祖先,复盘 B+ 树 |
| 第 4 天 | 堆 | 写 Top K、前 K 高频元素,理解 PriorityQueue |
| 第 5 天 | 图 | 写 DFS/BFS、岛屿数量、课程表 |
| 第 6 天 | 红黑树、跳表、布隆过滤器 | 重点准备工程场景追问 |
| 第 7 天 | LRU 和综合复盘 | 手写 LRU,整理所有结构的复杂度和应用场景 |
30 天复习路线
| 阶段 | 时间 | 目标 |
|---|---|---|
| 第一阶段 | 第 1 到 6 天 | 线性结构和哈希表,能说清复杂度和 Java 集合关联 |
| 第二阶段 | 第 7 到 13 天 | 树、堆、图,配合 DFS/BFS 和 Top K 刷题 |
| 第三阶段 | 第 14 到 20 天 | Trie、并查集、跳表、红黑树,补齐进阶结构 |
| 第四阶段 | 第 21 到 25 天 | 布隆过滤器、LRU、工程场景题,连接 Redis/MySQL/缓存 |
| 第五阶段 | 第 26 到 30 天 | 做错题复盘和面试口述练习,每个结构准备 2 个追问 |
高频问题自测
- 数组和链表的内存布局有什么区别?为什么数组随机访问快?
ArrayList和LinkedList在 Java 里怎么选?- 栈和队列分别适合哪些场景?单调栈、单调队列解决什么问题?
- 哈希冲突有哪些解决方式?
HashMap为什么要扩容? - 二叉搜索树、AVL 树、红黑树有什么区别?
- B 树和 B+ 树为什么适合数据库索引?
- 堆和普通二叉树有什么区别?Top K 为什么常用堆?
- 图的邻接表和邻接矩阵怎么选?DFS 和 BFS 复杂度是多少?
- 跳表为什么适合范围查询?Redis 为什么使用跳表?
- 布隆过滤器为什么会误判?为什么删除困难?
- LRU 为什么常用哈希表加双向链表实现?
相关专题
Overview (`docs/cs-basics/network/README.md`)
title: 计算机网络专题:分层模型、HTTP、HTTPS、DNS、TCP、UDP、ARP 与 NAT
category: 计算机基础
tag:
- 计算机网络
- TCP/IP
- HTTP
sidebar: false
sitemap:
changefreq: weekly
priority: 0.9
head: - meta
- name: keywords
content: 计算机网络,计算机网络面试题,OSI七层模型,TCP/IP,HTTP,HTTPS,DNS,TCP,UDP,ARP,NAT,后端面试
这份 计算机网络专题 面向后端学习和面试复习,按“分层模型 -> 应用层 -> 传输层 -> 网络层 -> 安全”的顺序整理本站网络相关文章。
适合谁看
- 正在系统学习计算机网络的后端开发者。
- 准备校招、社招、中大厂网络面试题的同学。
- 对 HTTP、HTTPS、TCP、DNS、Socket 等知识点只会零散背诵的读者。
- 想把网络知识和 RPC、网关、负载均衡、系统设计联系起来的工程师。
学习重点
- 网络分层的核心价值是拆分复杂通信问题,每一层只解决自己的职责。
- HTTP、HTTPS、DNS 是后端开发最常用的应用层知识。
- TCP 高频考点集中在连接管理、可靠传输、拥塞控制、TIME_WAIT 和 Keepalive。
- ARP、NAT 等网络层知识能帮助理解局域网通信、内外网访问和排障。
- 面试中要能结合一次完整请求,把协议、连接、加密、解析和传输串起来。
建议阅读顺序
- 计算机网络常见面试题总结(上) 和 计算机网络常见面试题总结(下):先建立高频问题清单。
- OSI 七层模型与 TCP/IP 四层模型详解:理解网络分层和各层职责。
- 从输入 URL 到页面展示到底发生了什么?:用完整链路串联 DNS、TCP、HTTP 和浏览器处理。
- HTTP vs HTTPS、HTTPS 握手里的 RSA 和 ECDHE、HTTP 常见状态码总结:补齐应用层高频问题。
- TCP 三次握手和四次挥手、TCP 传输可靠性保障、TCP TIME_WAIT 详解:重点攻克 TCP。
核心文章
总览与基础
- 计算机网络常见面试题总结(上):覆盖网络模型、HTTP、HTTPS、DNS 等基础问题。
- 计算机网络常见面试题总结(下):继续整理 TCP、UDP、Socket、网络安全等高频问题。
- OSI 七层模型与 TCP/IP 四层模型详解:理解网络模型、协议分层和数据封装过程。
- 从输入 URL 到页面展示到底发生了什么?:用一次请求串联常见网络知识点。
应用层
- 常见应用层协议总结:梳理 HTTP、WebSocket、SMTP、FTP、SSH、DNS 等协议。
- HTTP vs HTTPS:理解 HTTPS 加密、证书、身份认证和完整性保护。
- HTTPS 握手里的 RSA 和 ECDHE:区分不同密钥交换方式和前向安全性。
- HTTP 1.0 vs HTTP 1.1:理解长连接、缓存、Host 头等差异。
- HTTP 常见状态码总结:掌握 1xx 到 5xx 状态码语义和使用场景。
- DNS 域名系统详解:理解域名解析、递归查询、迭代查询和缓存。
- 有了 HTTP,为什么还要 RPC?:厘清 HTTP 和 RPC 的层次关系。
传输层、网络层与安全
- TCP 三次握手和四次挥手:掌握连接建立、断开和关键状态。
- TCP 传输可靠性保障:理解序列号、确认应答、重传、流量控制和拥塞控制。
- TCP TIME_WAIT 详解:理解 TIME_WAIT 的作用、影响和优化边界。
- TCP Keepalive 和 HTTP Keep-Alive 有什么区别?:区分传输层保活和应用层长连接。
- 为什么 TCP 是面向字节流,UDP 是面向报文?:理解 TCP/UDP 数据边界差异。
- ARP 协议详解、NAT 协议详解、网络攻击常见手段总结:补齐网络层和安全常识。
高频问题
- OSI 七层模型和 TCP/IP 四层模型有什么区别?
- 从输入 URL 到页面展示,网络部分发生了什么?
- HTTP 和 HTTPS 有什么区别?HTTPS 握手过程是怎样的?
- HTTP 1.0、1.1、2.0 的核心差异是什么?
- 常见 HTTP 状态码分别表示什么?
- TCP 三次握手、四次挥手为什么不能少?
- TCP 如何保证可靠传输?拥塞控制和流量控制有什么区别?
- TIME_WAIT 为什么存在?大量 TIME_WAIT 如何排查?
- TCP Keepalive 和 HTTP Keep-Alive 有什么区别?
- DNS、ARP、NAT 分别解决什么问题?
相关专题
Overview (`docs/cs-basics/operating-system/README.md`)
title: 操作系统专题:进程线程、内存管理、文件系统、I/O 多路复用、Linux 与 Shell
category: 计算机基础
tag:
- 操作系统
- Linux
- Shell
sidebar: false
sitemap:
changefreq: weekly
priority: 0.9
head: - meta
- name: keywords
content: 操作系统,操作系统面试题,进程,线程,进程间通信,IPC,锁与同步,互斥锁,信号量,条件变量,futex,死锁,内存管理,虚拟内存,零拷贝,I/O多路复用,select,poll,epoll,文件系统,Linux,Shell,后端面试
这份 操作系统专题 面向后端学习和面试复习,整理操作系统基础、进程线程、进程间通信、锁与同步、内存管理、虚拟内存、零拷贝、I/O 多路复用、文件系统、Linux 和 Shell 相关内容。
适合谁看
- 正在系统学习操作系统基础的后端开发者。
- 准备校招、社招、中大厂操作系统面试题的同学。
- 对进程线程、死锁、内存管理、Linux 命令只会零散背诵的读者。
- 想为 Java 并发、JVM、数据库、网络编程打底的工程师。
学习重点
- 操作系统负责管理 CPU、内存、文件、I/O 和进程,是理解上层软件运行机制的基础。
- 进程、线程和进程间通信是并发编程、服务端性能和问题排查的基础概念。
- 锁与同步、死锁、上下文切换、调度是面试高频点。
- 内存管理、虚拟内存、分页、页面置换能帮助理解 JVM、数据库和缓存。
- 零拷贝、I/O 多路复用能帮助理解 Kafka、RocketMQ、Redis、Nginx、Netty 等高性能组件。
- Linux 和 Shell 是后端开发、部署、排障、自动化脚本的常用能力。
建议阅读顺序
- 操作系统常见面试题总结(上):先建立操作系统基础、进程线程、死锁、内存管理的高频问题清单。
- 操作系统常见面试题总结(下):继续补齐文件系统、I/O、Linux 等问题。
- 进程与线程详解:区别、状态、通信、上下文切换与虚拟线程:系统理解进程、线程、PCB/TCB、fork/exec/wait、线程模型和上下文切换。
- 中断、异常与系统调用详解:从内核入口到缺页异常:以
read()为线索串起硬件中断、同步异常、系统调用、信号、缺页异常和线程切换。 - CPU 调度与系统负载详解:理解调度算法、CFS/EEVDF、load average、CPU 使用率及线上排查思路。
- 进程间通信(IPC)详解:管道、消息队列、共享内存、Socket 与 Binder:对比管道、消息队列、共享内存、信号量、Socket、Binder 等 IPC 方案。
- 操作系统锁与同步机制详解:mutex、semaphore、condition variable、spinlock 与 futex:理解临界区、互斥锁、信号量、条件变量、自旋锁和 futex 的职责边界。
- 死锁详解:四个必要条件、Java 死锁排查与数据库死锁处理:讲清死锁等待环、四个必要条件、Java 线程死锁排查和数据库事务重试。
- 操作系统内存管理详解:分页、分段、页面置换、Swap 与 OOM:理解内存分配、碎片、页表、页面回收和 OOM。
- 虚拟内存详解:地址转换、TLB、缺页异常与页面置换:把分页、页表、TLB、缺页异常和页面置换串起来。
- 操作系统文件系统详解:inode、VFS、Page Cache 与日志机制:理解文件、目录、inode、VFS、Page Cache 和日志恢复。
- I/O 多路复用详解:select、poll、epoll 原理与区别:理解一个线程处理海量连接背后的内核机制。
- 零拷贝详解:mmap、sendfile 与 splice:搞清传统 I/O、mmap、sendfile、splice 的拷贝路径和适用场景。
- Linux 基础知识总结:掌握目录结构、文件权限、常用命令和基础排障能力。
- Shell 编程基础知识总结:学习变量、条件、循环、函数和常用脚本写法。
核心文章
- 操作系统常见面试题总结(上):覆盖操作系统基础、进程线程、死锁、内存管理等高频问题。
- 操作系统常见面试题总结(下):继续整理文件系统、I/O、多路复用、Linux 等知识点。
- 进程与线程详解:区别、状态、通信、上下文切换与虚拟线程:讲清进程和线程的资源边界、状态转换、Linux 创建机制和 Java 虚拟线程。
- 中断、异常与系统调用详解:从内核入口到缺页异常:讲清硬件中断、同步异常、系统调用、信号和缺页异常之间的关系。
- CPU 调度与系统负载详解:讲清任务调度、CFS/EEVDF、load average、CPU 使用率和常用排查命令。
- 进程间通信(IPC)详解:管道、消息队列、共享内存、Socket 与 Binder:讲清常见 IPC 方式的原理、优缺点和选型思路。
- 操作系统锁与同步机制详解:mutex、semaphore、condition variable、spinlock 与 futex:讲清临界区、互斥锁、信号量、条件变量、自旋锁、futex、内存顺序和内核锁上下文。
- 死锁详解:四个必要条件、Java 死锁排查与数据库死锁处理:讲清死锁形成条件、资源分配图、Java 排查工具、数据库死锁检测和应用层重试策略。
- 操作系统内存管理详解:分页、分段、页面置换、Swap 与 OOM:讲清 VSZ/RSS/PSS、连续分配、内存碎片、伙伴系统、页表、TLB、缺页异常、页面回收和 OOM。
- 虚拟内存详解:地址转换、TLB、缺页异常与页面置换:讲清虚拟地址、物理地址、分页、多级页表、TLB、缺页异常和页面置换算法。
- 操作系统文件系统详解:inode、VFS、Page Cache 与日志机制:讲清文件、目录、inode、dentry、文件描述符、VFS、Page Cache、fsync 和日志机制。
- I/O 多路复用详解:select、poll、epoll 原理与区别:讲清网络 I/O 的两个阶段、五种 I/O 模型,以及 select、poll、epoll 的区别。
- 零拷贝详解:mmap、sendfile 与 splice:讲清零拷贝到底省掉了什么,以及 Java NIO、Kafka、RocketMQ 中的典型应用。
- Linux 基础知识总结:讲解 Linux 目录树、文件权限、常用命令、用户和进程管理。
- Shell 编程基础知识总结:讲解 Shell 变量、条件判断、循环、函数、文本处理和脚本实践。
高频问题
- 进程和线程有什么区别?线程之间共享哪些资源?
- 进程间通信有哪些方式?各自适合什么场景?
- 什么是上下文切换?频繁上下文切换有什么影响?
- mutex、semaphore、condition variable、spinlock 和 futex 分别解决什么问题?
- 死锁产生的必要条件是什么?Java 和数据库里如何排查死锁?
- 虚拟内存是什么?分页和分段有什么区别?
- TLB、缺页异常和页面置换分别解决什么问题?
- 页面置换算法有哪些?缺页异常是怎么回事?
- 零拷贝为什么快?mmap、sendfile、splice 有什么区别?
- 文件系统 inode、硬链接、软链接分别是什么?
- select、poll、epoll 有什么区别?
- Linux 文件权限如何理解?常用排障命令有哪些?
- Shell 脚本适合解决哪些自动化问题?
相关专题
Overview (`docs/database/mongodb/README.md`)
title: MongoDB 专题:文档模型、索引、副本集、分片、事务与常见面试题
category: 数据库
tag:
- MongoDB
- NoSQL
- 后端面试
sitemap:
changefreq: weekly
priority: 0.85
head: - meta
- name: keywords
content: MongoDB,MongoDB面试题,NoSQL,文档数据库,MongoDB索引,副本集,分片,聚合,事务,后端面试
MongoDB 是典型的文档数据库,适合半结构化数据、灵活字段模型和快速迭代的业务场景。学习 MongoDB 时,建议重点理解文档模型、索引、副本集、分片、聚合和事务能力,而不是把它简单看成“可以存 JSON 的数据库”。
适合谁看
- 想了解 MongoDB 和文档数据库核心概念的后端开发者。
- 准备 MongoDB、NoSQL、文档模型相关面试题的同学。
- 需要在关系型数据库和文档数据库之间做选型的工程师。
- 已经接触过 MongoDB,但对索引、副本集、分片和事务机制不够熟的读者。
学习重点
- MongoDB 的数据库、集合、文档和关系型数据库中的库、表、行有什么差异?
- 文档模型适合哪些数据结构,什么时候不适合用 MongoDB?
- MongoDB 索引、聚合管道、副本集和分片分别解决什么问题?
- read concern、write concern、事务和一致性语义如何理解?
- 面试中如何从“模型、查询、索引、高可用、扩展性、适用场景”回答 MongoDB 问题?
建议阅读顺序
- NoSQL 基础常见面试题总结:先理解 NoSQL 分类、适用场景和与关系型数据库的差异。
- MongoDB 常见面试题总结(上):学习 MongoDB 基础概念、文档模型、索引和查询。
- MongoDB 常见面试题总结(下):继续理解副本集、分片、事务、聚合和生产实践。
- 再回到 数据库知识体系,把 MongoDB 与 MySQL、Redis、Elasticsearch 的定位放在一起比较。
核心文章
- MongoDB 常见面试题总结(上):覆盖 MongoDB 基础概念、文档模型、集合、查询、索引和常见使用问题。
- MongoDB 常见面试题总结(下):覆盖副本集、分片、事务、聚合、读写关注、存储引擎和生产实践。
- NoSQL 基础常见面试题总结:帮助理解 MongoDB 在 NoSQL 体系中的定位,以及与键值、列族、图数据库的区别。
高频问题
- MongoDB 和 MySQL 有什么区别?
- MongoDB 的文档模型适合哪些业务场景?
- MongoDB 索引有哪些类型?如何设计索引?
- MongoDB 副本集如何保证高可用?
- MongoDB 分片解决什么问题?分片键如何选择?
- MongoDB 支持事务吗?适合复杂事务场景吗?
- MongoDB 的聚合管道适合解决哪些问题?
- read concern 和 write concern 分别控制什么?
- 什么场景不适合使用 MongoDB?
- MongoDB、Redis、Elasticsearch 的定位有什么区别?
相关专题
Overview (`docs/database/mysql/README.md`)
title: MySQL 专题:索引、事务、日志、备份恢复、MVCC 与性能优化
category: 数据库
tag:
- MySQL
- 数据库
- 后端面试
sitemap:
changefreq: weekly
priority: 0.9
head: - meta
- name: keywords
content: MySQL,MySQL面试题,MySQL索引,索引失效,事务隔离级别,MVCC,binlog,redo log,undo log,MySQL备份,MySQL恢复,MySQL同步ES,Canal,Flink CDC,执行计划,SQL执行过程,MySQL性能优化,后端面试
MySQL 是后端开发最常用的关系型数据库之一,也是数据库面试中最容易追问到底的专题。学习 MySQL 时,建议围绕“索引怎么让查询变快、事务怎么保证一致性、日志怎么保证恢复和复制、数据怎么同步到搜索等异构系统、执行计划怎么定位慢 SQL”这几条主线展开。
适合谁看
- 想系统学习 MySQL 原理和性能优化的后端开发者。
- 准备 MySQL 索引、事务、MVCC、日志、执行计划相关面试题的同学。
- 已经能写常规 SQL,但对慢 SQL 分析、索引设计和事务问题不够熟的读者。
- 需要在项目中处理 MySQL 性能、数据一致性、异构数据同步和字段设计问题的工程师。
学习重点
- B+ 树索引、聚簇索引、二级索引、覆盖索引和回表分别是什么?
- 哪些 SQL 写法会导致索引失效,如何通过执行计划验证?
- MySQL 事务隔离级别如何影响脏读、不可重复读和幻读?
- InnoDB 如何通过 MVCC、undo log 和 Read View 实现快照读?
- binlog、redo log、undo log 分别解决复制、崩溃恢复和事务回滚中的哪些问题?
- MySQL 如何通过全量备份、binlog 和 PITR 恢复到指定时间点?
- MySQL 数据同步到 Elasticsearch 有哪些方案?如何处理全量、增量、乱序和数据对账?
- 慢 SQL 优化应该如何从建表、索引、SQL 写法和执行计划逐层定位?
建议阅读顺序
- MySQL 常见面试题总结:先建立 MySQL 高频问题清单。
- MySQL 索引详解、MySQL 索引失效场景总结:理解索引原理和常见失效场景。
- MySQL 事务隔离级别详解、InnoDB 存储引擎对 MVCC 的实现:掌握事务和一致性读。
- MySQL 三大日志详解、MySQL备份与恢复详解、MySQL 数据同步到 Elasticsearch 详解:理解 binlog 在提交、恢复、复制和异构数据同步中的作用。
- SQL 语句在 MySQL 中的执行过程:理解连接器、分析器、优化器、执行器和存储引擎如何协作。
- MySQL 执行计划分析、MySQL 高性能优化规范建议总结:把原理落到慢 SQL 和工程规范上。
核心文章
总览与规范
- MySQL 常见面试题总结:串联索引、事务、锁、日志、存储引擎和 SQL 优化等高频考点。
- MySQL 高性能优化规范建议总结:从建表、字段、索引、SQL、事务和开发规范角度总结优化建议。
- 一千行 MySQL 学习笔记:适合用来查漏补缺,快速回顾 MySQL 常见知识点。
索引与执行计划
- MySQL 索引详解:理解索引数据结构、聚簇索引、二级索引、覆盖索引、最左前缀和索引设计。
- MySQL 索引失效场景总结:整理常见索引失效写法和排查思路。
- MySQL 隐式转换造成索引失效:聚焦隐式类型转换导致的索引失效问题。
- MySQL 执行计划分析:掌握 EXPLAIN 的 type、key、rows、Extra 等关键字段。
事务、MVCC、日志与数据同步
- MySQL 事务隔离级别详解:理解读未提交、读已提交、可重复读、串行化以及并发读异常。
- InnoDB 存储引擎对 MVCC 的实现:理解隐藏字段、undo log、Read View 和可见性判断。
- MySQL 三大日志详解:理解 binlog、redo log、undo log 的作用、写入时机和两阶段提交。
- MySQL备份与恢复详解:理解 mysqldump、XtraBackup、binlog、PITR、RTO/RPO 和恢复演练。
- MySQL 数据同步到 Elasticsearch 详解:对比应用层双写、定时同步、Canal、Debezium 和 Flink CDC,并理解全量、增量与最终一致性问题。
执行过程与工程细节
- SQL 语句在 MySQL 中的执行过程:理解连接器、查询缓存、分析器、优化器、执行器和存储引擎的协作。
- MySQL 查询缓存详解:理解查询缓存的工作方式、失效原因和被移除的背景。
- MySQL 自增主键一定是连续的吗?:理解自增值分配、回滚、批量插入和主键连续性的关系。
- MySQL 日期类型选择建议:对比 DATE、DATETIME、TIMESTAMP 等类型的适用场景。
高频问题
- MySQL 为什么推荐使用 B+ 树索引?
- 聚簇索引和二级索引有什么区别?什么是回表和覆盖索引?
- 最左前缀原则是什么?哪些场景会导致索引失效?
- 如何通过 EXPLAIN 判断一条 SQL 是否走了合适的索引?
- MySQL 的四种事务隔离级别分别解决什么问题?
- MVCC 是如何实现的?Read View 里有哪些关键字段?
- binlog、redo log、undo log 有什么区别?两阶段提交解决什么问题?
- MySQL 如何通过全量备份和 binlog 做按时间点恢复?
- MySQL 数据同步到 Elasticsearch 有哪些方案?如何保证幂等并处理消息乱序?
- 慢 SQL 优化应该从哪些维度入手?
- 自增主键为什么不一定连续?
- DATETIME 和 TIMESTAMP 应该如何选择?
相关专题
Overview (`docs/database/redis/README.md`)
title: Redis 专题:缓存、数据结构、持久化、集群、阻塞与工程实践
category: 数据库
tag:
- Redis
- 缓存
- 后端面试
sitemap:
changefreq: weekly
priority: 0.9
head: - meta
- name: keywords
content: Redis,Redis面试题,缓存,缓存穿透,缓存击穿,缓存雪崩,Redis数据结构,Redis持久化,Redis集群,Redis阻塞,Redis跳表,Redis延时任务,Redis消息队列,后端面试
Redis 是后端开发最常用的缓存和高性能内存数据存储之一。学习 Redis 时,不能只停留在命令和数据类型上,还要理解缓存读写策略、底层数据结构、持久化、阻塞原因、内存管理、复制与集群等工程问题。
适合谁看
- 想系统学习 Redis 原理、缓存设计和工程实践的后端开发者。
- 准备 Redis 数据结构、持久化、集群、高可用、缓存一致性相关面试题的同学。
- 已经在项目中使用 Redis,但对缓存异常、阻塞、内存碎片和集群机制不够熟的读者。
- 需要基于 Redis 实现延时任务、消息队列、排行榜、购物车等能力的工程师。
学习重点
- Redis 常用数据结构分别适合哪些业务场景,底层编码如何影响性能?
- 缓存穿透、击穿、雪崩和缓存一致性问题应该如何设计方案?
- Redis 持久化中 RDB、AOF、AOF 重写和混合持久化有什么区别?
- Redis 为什么可能阻塞,如何定位慢命令、大 Key、热 Key 和持久化影响?
- 主从复制、哨兵和 Cluster 分别解决什么问题,故障转移有哪些关键流程?
- 基于 Redis 做延时任务、消息队列时有哪些能力边界和可靠性风险?
建议阅读顺序
- 缓存基础常见面试题总结:先理解缓存使用场景、缓存异常和一致性问题。
- Redis 常见面试题总结(上)、Redis 常见面试题总结(下):建立 Redis 高频问题清单。
- Redis 5 种基本数据类型详解、Redis 3 种特殊数据类型详解:系统掌握数据结构和应用场景。
- 3 种常用的缓存读写策略详解、Redis 持久化机制详解:补齐缓存一致性和数据恢复能力。
- Redis 常见阻塞原因总结、Redis 集群详解:把 Redis 放到生产环境里理解。
核心文章
缓存基础与读写策略
- 缓存基础常见面试题总结:讲解缓存应用场景、缓存穿透、缓存击穿、缓存雪崩、缓存一致性和缓存淘汰。
- 3 种常用的缓存读写策略详解:对比 Cache Aside、Read/Write Through、Write Behind 等常见策略。
- Redis 常见面试题总结(上) 和 Redis 常见面试题总结(下):串联 Redis 基础、线程模型、数据结构、持久化、集群和生产问题。
数据结构与典型应用
- Redis 5 种基本数据类型详解:理解 String、List、Hash、Set、Sorted Set 的底层结构和业务场景。
- Redis 3 种特殊数据类型详解:理解 Bitmap、HyperLogLog、Geospatial 的用法和适用场景。
- Redis 为什么用跳表实现有序集合:理解跳表结构、查询复杂度和 Sorted Set 的实现选择。
- 如何基于 Redis 实现延时任务?:对比过期事件、Sorted Set、Stream 等实现方式。
- 如何基于 Redis 实现消息队列?:理解 List、Pub/Sub、Stream 做消息队列的差异。
持久化、内存与集群
- Redis 持久化机制详解:系统讲解 RDB、AOF、AOF 重写和混合持久化。
- Redis 内存碎片详解:理解内存碎片产生原因、指标观察和清理策略。
- Redis 常见阻塞原因总结:整理慢命令、大 Key、持久化、主从同步、CPU 和网络等阻塞来源。
- Redis 集群详解:理解主从复制、哨兵、Cluster、槽位迁移和故障转移。
高频问题
- Redis 为什么快?单线程为什么还能支撑高并发?
- Redis 常见数据类型分别适合哪些业务场景?
- Sorted Set 为什么使用跳表?
- 缓存穿透、击穿、雪崩有什么区别,如何处理?
- 缓存和数据库如何保证一致性?
- RDB 和 AOF 有什么区别?AOF 重写解决什么问题?
- Redis 常见阻塞原因有哪些?如何排查大 Key 和慢命令?
- Redis 主从复制、哨兵和 Cluster 有什么区别?
- Redis 如何实现延时任务?可靠性风险在哪里?
- Redis Stream 和传统消息队列相比有哪些边界?
相关专题
Overview (`docs/database/sql/README.md`)
title: SQL 专题:语法基础、查询、聚合、连接、子查询与常见面试题
category: 数据库
tag:
- SQL
- 数据库
- 后端面试
sitemap:
changefreq: weekly
priority: 0.9
head: - meta
- name: keywords
content: SQL,SQL面试题,SQL语法,SQL查询,SQL聚合,SQL连接,SQL子查询,数据库基础,后端面试
SQL 是后端开发绕不开的数据库基本功。无论后续学习 MySQL 索引、执行计划,还是做慢 SQL 优化,都需要先把查询、过滤、聚合、连接、子查询和数据修改这些基础语义掌握扎实。
适合谁看
- 正在学习数据库基础和 SQL 语法的后端开发者。
- 准备 SQL 基础、SQL 查询题、数据库 CRUD 相关面试题的同学。
- 写过简单 SQL,但对 JOIN、GROUP BY、HAVING、子查询和执行顺序不够熟的读者。
- 想在学习 MySQL 索引和 SQL 优化前补齐 SQL 基本功的工程师。
学习重点
- SELECT、WHERE、ORDER BY、LIMIT、GROUP BY、HAVING 的职责和执行顺序如何理解?
- INNER JOIN、LEFT JOIN、RIGHT JOIN、UNION、子查询分别适合哪些场景?
- 聚合函数、分组统计和条件过滤如何组合使用?
- INSERT、UPDATE、DELETE 写法中有哪些容易忽略的边界?
- 面试中的 SQL 题应该如何从表关系、过滤条件、聚合维度和排序分页拆解?
建议阅读顺序
- SQL 语法基础知识总结:先系统掌握 SQL 的基本语法和常见操作。
- SQL 常见面试题总结(1)、SQL 常见面试题总结(2):练习基础查询、排序、聚合和常见函数。
- SQL 常见面试题总结(3):继续补充连接、子查询和复杂查询思路。
- SQL 常见面试题总结(4)、SQL 常见面试题总结(5):通过更多题目巩固查询拆解能力。
- 学完 SQL 基础后,建议继续阅读 MySQL 专题,把 SQL 写法和索引、执行计划结合起来。
核心文章
- SQL 语法基础知识总结:覆盖查询、过滤、排序、聚合、分组、连接、子查询、插入、更新、删除和约束等基础语法。
- SQL 常见面试题总结(1):通过基础题目熟悉查询、排序和简单过滤。
- SQL 常见面试题总结(2):继续练习函数、字符串处理、日期处理和常见查询写法。
- SQL 常见面试题总结(3):适合用来训练多表查询、分组统计和子查询拆解。
- SQL 常见面试题总结(4):补充更多常见 SQL 面试题和解题思路。
- SQL 常见面试题总结(5):进一步巩固 SQL 查询题的综合应用能力。
高频问题
- SQL 查询语句的逻辑执行顺序是什么?
- WHERE 和 HAVING 有什么区别?
- INNER JOIN 和 LEFT JOIN 有什么区别?
- UNION 和 UNION ALL 有什么区别?
- COUNT(*)、COUNT(1)、COUNT(列名) 有什么区别?
- 子查询和 JOIN 应该如何选择?
- GROUP BY 后为什么只能选择分组列或聚合结果?
- 分页查询有哪些常见写法?
- UPDATE 和 DELETE 为什么一定要谨慎带过滤条件?
- SQL 题应该如何根据表关系拆解?
相关专题
Overview (`docs/distributed-system/protocol/README.md`)
title: 分布式理论、算法与协议:CAP、BASE、中心化与去中心化、Paxos、Raft 与一致性哈希
category: 分布式
tag:
- 分布式理论
- 分布式协议与算法
- 共识算法
sitemap:
changefreq: weekly
priority: 0.9
head: - meta
- name: keywords
content: 分布式理论,分布式算法,分布式协议,中心化,去中心化,CAP,BASE,拜占庭将军问题,Paxos,Raft,ZAB,Gossip,一致性哈希,共识算法,拜占庭容错,BFT,最终一致性,分布式系统面试题
分布式理论、算法与协议是理解分布式系统的基础。学习这部分内容时,不建议只背结论,更重要的是理解不同方案在一致性、可用性、容错、共识、性能和工程复杂度之间的取舍。
适合谁看
- 想系统理解分布式一致性、共识算法和数据分布的后端开发者。
- 准备分布式系统、系统设计、后端架构面试的同学。
- 对 CAP、BASE、Paxos、Raft、ZAB、Gossip 等概念只停留在“背过”的读者。
- 需要理解 ZooKeeper、Redis Cluster、分布式缓存和服务发现底层机制的工程师。
学习重点
- CAP 和 BASE 不是口号,它们背后对应哪些真实工程取舍?
- 中心化和去中心化是在解决什么问题?Leader、多数派和 Gossip 分别适合哪些场景?
- Paxos、Raft、ZAB 都在解决共识问题,为什么工程复杂度和使用场景不同?
- Gossip 为什么适合大规模节点传播状态,和强一致协议有什么区别?
- 一致性哈希如何降低节点扩缩容时的数据迁移成本?
- 面试中如何用“问题背景 -> 核心思想 -> 流程 -> 优缺点 -> 使用场景”讲清一个协议?
这组文章怎么互补?
CAP 定理与 BASE 理论详解 负责解释分布式系统里“一致性、可用性、分区容错”这组取舍。分布式协调详解 继续往工程场景里推进,讨论谁来做决定、旧 Leader 为什么会造成问题、什么时候应该用 Gossip。
Paxos、Raft、ZAB 是共识协议线。建议先读 Raft 算法详解,再读 Paxos 算法详解 和 ZAB 协议详解。Raft 更适合入门,Paxos 更接近经典理论,ZAB 则对应 ZooKeeper 的原子广播和崩溃恢复。
Gossip 协议详解 和 一致性哈希算法详解 不负责强一致写入顺序,它们分别解决状态传播和数据映射问题。把这两篇放在共识协议之后看,可以避免把“传播状态”和“达成共识”混在一起。
建议阅读顺序
- CAP 定理与 BASE 理论详解:先建立一致性、可用性、分区容错的取舍视角。
- 分布式协调详解:把 Leader、Quorum、脑裂、Lease、Fencing Token 和 Gossip 放到同一条主线里。
- 拜占庭将军问题详解:理解恶意节点、矛盾消息、
3m + 1节点要求和 BFT 容错边界。 - Raft 算法详解:用相对易懂的 Leader 选举和日志复制入门共识算法。
- Paxos 算法详解:理解经典共识算法的角色、阶段和难点。
- ZAB 协议详解:把共识算法落到 ZooKeeper 的消息广播和崩溃恢复场景。
- Gossip 协议详解 和 一致性哈希算法详解:理解大规模系统里的状态传播和数据分布。
核心文章
一致性与分布式理论
- CAP 定理与 BASE 理论详解:理解一致性、可用性、分区容错的取舍,以及 BASE 理论和最终一致性的工程含义。
- 分布式协调详解:理解分布式系统为什么需要协调,以及 Leader/Quorum、Gossip、Lease 和 Fencing Token 如何分别处理协调问题。
- 拜占庭将军问题详解:理解存在恶意节点或异常节点时,正常节点如何对同一个结果达成一致。
共识算法
- Paxos 算法详解:理解 Proposer、Acceptor、Learner 角色、两阶段流程和 Multi-Paxos 优化。
- Raft 算法详解:理解 Leader 选举、日志复制、安全性约束、成员变更和与 Paxos 的差异。
- ZAB 协议详解:理解 ZooKeeper Atomic Broadcast、消息广播、崩溃恢复、ZXID 和事务日志。
数据传播与数据分布
- Gossip 协议详解:理解反熵、谣言传播、Push/Pull 模式、SWIM 协议和最终一致性。
- 一致性哈希算法详解:理解哈希环、虚拟节点、节点扩缩容、数据倾斜和分布式缓存应用。
高频问题
- CAP 中的 C、A、P 分别指什么?为什么分区容错通常不能舍弃?
- BASE 理论和最终一致性解决了什么问题?
- 中心化设计和去中心化设计的区别是什么?为什么有 Leader 也不一定是单点?
- Paxos 为什么难理解?Basic Paxos 和 Multi-Paxos 有什么区别?
- Raft 为什么更容易工程实现?Leader 选举和日志复制如何保证安全性?
- ZAB 和 Raft 有哪些相似点和差异?
- Gossip 协议适合哪些场景?为什么它通常不提供强一致?
- 一致性哈希为什么需要虚拟节点?
相关专题
Overview (`docs/distributed-system/rpc/README.md`)
title: RPC 远程过程调用专题:原理、调用流程、序列化、服务发现、Dubbo 与 gRPC
category: 分布式
tag:
- RPC
- Dubbo
- 分布式
sitemap:
changefreq: weekly
priority: 0.9
head: - meta
- name: keywords
content: RPC,RPC原理,RPC框架,Dubbo,gRPC,Thrift,远程过程调用,服务发现,动态代理,序列化协议,负载均衡,HTTP和RPC区别,后端面试
RPC(Remote Procedure Call,远程过程调用)是分布式系统里最常见的服务调用方式之一。它希望让调用远程服务像调用本地方法一样自然,同时封装网络通信、序列化、服务发现、负载均衡、超时重试和容错治理等复杂细节。
适合谁看
- 想理解 RPC 框架工作原理的后端开发者。
- 正在准备 RPC、Dubbo、微服务调用相关面试题的同学。
- 已经用过 Feign、Dubbo、gRPC,但对底层调用流程和服务治理不够熟的读者。
- 需要判断内部服务调用到底该用 HTTP 还是 RPC 的工程师。
学习重点
- RPC 是远程调用模型,不是某一个具体协议。
- 一次 RPC 调用通常会经过动态代理、序列化、网络传输、服务发现、负载均衡、结果反序列化等步骤。
- Dubbo、gRPC、Thrift 等框架的侧重点不同,不能只用“性能更好”概括 RPC。
- HTTP 和 RPC 不是互斥关系,gRPC 就是基于 HTTP/2 承载 RPC 调用的典型例子。
- 面试中要能从“为什么需要 RPC、RPC 怎么工作、框架如何治理服务、如何选型”几个层次回答。
和其他分布式文章的关系
RPC 主要解决服务之间如何调用。它和 API 网关 经常一起出现,但两者位置不同:网关负责外部流量入口和统一治理,RPC 更常用于内部服务之间的调用。
RPC 框架通常还会依赖注册发现、负载均衡、超时重试和集群容错。想把这条链路补完整,可以把本专题和 分布式配置中心详解、ZooKeeper 专题 放在一起看。
建议阅读顺序
- 有了 HTTP 协议,为什么还要 RPC?:先厘清 HTTP 与 RPC 的关系。
- RPC 远程过程调用详解:再系统理解 RPC 调用流程、核心组件和框架选型。
- Dubbo 面试题总结:最后把 RPC 放到 Dubbo 这种成熟框架里,看服务治理和生产实践。
核心文章
- RPC 远程过程调用详解:从调用流程、动态代理、序列化协议、网络传输和服务发现等角度理解 RPC 的基本原理。
- Dubbo 面试题总结:串联 Dubbo 架构原理、服务暴露与引用、SPI 扩展机制、负载均衡、集群容错、注册中心和生产实践问题。
- 有了 HTTP 协议,为什么还要 RPC?:解释 HTTP 与 RPC 的关系,避免把 RPC 简单理解成“比 HTTP 更高级的协议”。
高频问题
- RPC 调用一次完整流程是什么?
- RPC 框架为什么通常会用动态代理?
- RPC 常见序列化协议有哪些,如何选型?
- Dubbo 的服务暴露和服务引用过程是怎样的?
- Dubbo 的 SPI、负载均衡和集群容错机制怎么实现?
- HTTP 和 RPC 有什么区别?为什么服务内部调用常用 RPC?
- gRPC 为什么说是 RPC,又为什么基于 HTTP/2?
- 内部服务调用用 HTTP 还是 RPC,应该从哪些维度判断?
相关专题
Overview (`docs/high-performance/message-queue/README.md`)
title: 消息队列专题:异步处理、削峰填谷、可靠性、顺序性、Kafka、RocketMQ 与 RabbitMQ
category: 高性能
tag:
- 消息队列
- Kafka
- RocketMQ
sitemap:
changefreq: weekly
priority: 0.9
head: - meta
- name: keywords
content: 消息队列,消息队列面试题,Kafka,RocketMQ,RabbitMQ,Disruptor,消息可靠性,消息幂等,消息顺序性,消息积压,异步处理,削峰填谷,后端面试
消息队列是高性能和高可用系统里都非常常见的中间件,主要用于异步处理、应用解耦、削峰填谷和流量缓冲。学习消息队列时,不能只背 Kafka、RocketMQ、RabbitMQ 的特性,更要理解消息可靠性、顺序性、幂等性、积压处理和技术选型。
如果你准备面试,建议先看 消息队列面试题总结,把 MQ 问题放进生产、存储、消费、确认和补偿链路中复习,再根据项目使用的中间件深入对应专题文章。
适合谁看
- 想系统学习消息队列的后端开发者。
- 准备 Kafka、RocketMQ、RabbitMQ、消息可靠性相关面试题的同学。
- 已经在项目中使用 MQ,但对消息丢失、重复消费、顺序消费、消息积压处理不够熟的读者。
- 需要在 Kafka、RocketMQ、RabbitMQ、Disruptor 之间做技术选型的工程师。
学习重点
- 消息队列解决的是异步、解耦、削峰和缓冲问题,不是所有链路都应该引 MQ。
- 消息可靠性要分别看生产者、Broker、消费者和业务幂等。
- 消息顺序性通常需要从 Topic、队列、分区、消费线程和业务 Key 一起设计。
- 消息积压不是单点问题,可能来自消费能力、下游依赖、限流策略和数据倾斜。
- Kafka、RocketMQ、RabbitMQ、Disruptor 的定位不同,选型时要结合吞吐、延迟、消息模型、生态和运维成本。
建议阅读顺序
- 消息队列面试题总结:先建立可靠性、幂等、顺序、积压和选型的问题清单。
- 消息队列基础知识总结:理解 MQ 的通用模型、应用场景和常见问题。
- Kafka 常见问题总结:理解高吞吐日志流、分区、副本、Consumer Group 和 Rebalance。
- RocketMQ 常见问题总结:理解业务消息场景、事务消息、定时消息、顺序消息和消息存储。
- RabbitMQ 常见问题总结:理解 AMQP、Exchange、消息确认、死信队列和延迟队列。
- Disruptor 常见问题总结:理解高性能内存队列、无锁设计和低延迟场景。
核心文章
- 消息队列面试题总结:按消息生命周期串联通用高频题、主流中间件原理、技术选型和项目追问。
- 消息队列基础知识总结:系统讲解应用场景、消息模型、消息可靠性、幂等性、顺序性、积压处理和技术选型。
- Kafka 常见问题总结:覆盖 Broker、Topic、Partition、Consumer Group、零拷贝、顺序写、ACK、ISR 和 Rebalance。
- RocketMQ 常见问题总结:覆盖 NameServer、Broker、Proxy、普通消息、顺序消息、事务消息、定时消息和存储机制。
- RabbitMQ 常见问题总结:覆盖 AMQP、Exchange 类型、确认机制、死信队列、延迟队列、优先级队列和高可用集群。
- Disruptor 常见问题总结:覆盖 RingBuffer、Sequencer、WaitStrategy、无锁设计、缓存行填充和预分配内存。
高频问题
- 为什么要使用消息队列?哪些场景不适合引入 MQ?
- 如何保证消息不丢失?
- 如何处理重复消费和业务幂等?
- 如何保证消息顺序性?
- 消息积压如何排查和处理?
- Kafka 为什么吞吐高?零拷贝和顺序写分别起什么作用?
- RocketMQ 的事务消息如何工作?
- RabbitMQ 的 Exchange 有哪些类型?各自适合什么场景?
- Kafka、RocketMQ、RabbitMQ 应该如何选型?
选型速查
| 场景 | 常见选择 | 重点关注 |
|---|---|---|
| 日志、埋点、流式处理 | Kafka、Pulsar | 吞吐、分区扩展、生态 |
| 订单、交易、业务事件 | RocketMQ | 事务消息、延时消息、顺序消息 |
| 灵活路由、轻量接入 | RabbitMQ | Exchange、确认机制、队列类型 |
| 进程内低延迟事件处理 | Disruptor | RingBuffer、WaitStrategy、无锁设计 |
选型时不要只比较吞吐量。团队运维经验、已有技术栈、消息语义、延迟要求、消息保留时间、是否需要重放,都会影响最终选择。
相关专题
Overview (`docs/java/basis/README.md`)
title: Java 基础专题:语法、面向对象、泛型、反射、代理与序列化
category: Java
tag:
- Java
- Java基础
- Java面试
sitemap:
changefreq: weekly
priority: 0.9
head: - meta
- name: keywords
content: Java基础,Java基础面试题,Java关键字,Java值传递,Java泛型,Java反射,Java代理,Java序列化,Java SPI,Java Unsafe,Java语法糖
Java 基础是后续学习集合、并发、JVM、Spring 和各类中间件的前置能力。这部分内容不只是背语法,更重要的是理解 Java 的对象模型、参数传递、泛型擦除、反射调用、动态代理、序列化边界和框架扩展机制。
适合谁看
- 刚开始系统学习 Java,想把基础语法和核心机制串起来的读者。
- 准备 Java 基础面试题,希望快速查漏补缺的同学。
- 已经写过 Java 项目,但对反射、代理、泛型、SPI、序列化等机制理解不深的开发者。
- 想继续学习集合、并发、JVM、Spring 源码前,需要补齐前置知识的工程师。
学习重点
- Java 基本语法、面向对象、异常、常用类、关键字和编码细节。
- 值传递、引用变量、对象可变性和方法调用之间的关系。
- 泛型、通配符、类型擦除以及它们对集合、API 设计和反射的影响。
- 反射、动态代理、SPI 等框架底层常见机制。
- 序列化、
BigDecimal、Unsafe、语法糖等容易在项目和面试中踩坑的知识点。
建议阅读顺序
- Java基础常见面试题总结(上):先过一遍 Java 基础语法、面向对象和常用类。
- Java基础常见面试题总结(中) 和 Java基础常见面试题总结(下):补齐异常、泛型、反射、注解和常见易错点。
- Java 关键字总结 和 Java 值传递详解:厘清基础概念中的高频误区。
- 泛型&通配符详解、Java 反射机制详解、Java 代理模式详解:理解框架底层常见能力。
- Java 序列化详解、Java SPI 机制详解、Java 魔法类 Unsafe 详解:继续补齐工程实践和源码阅读中的扩展知识。
核心文章
基础面试题
- Java基础常见面试题总结(上):覆盖 Java 语言特点、基础语法、面向对象、常用类和常见易错点。
- Java基础常见面试题总结(中):继续梳理异常、泛型、反射、注解等基础能力。
- Java基础常见面试题总结(下):补齐更偏细节和进阶的基础面试题。
语言机制
- Java 关键字总结:讲清
final、static、this、super等关键字。 - Java 值传递详解:解释 Java 为什么只有值传递,以及引用变量传参时的真实语义。
- 泛型&通配符详解:理解泛型语法、上下界通配符、类型擦除和常见使用场景。
- Java 语法糖详解:了解编译器如何处理增强 for、自动装箱拆箱、枚举、Lambda 等语法糖。
框架底层机制
- Java 反射机制详解:理解 Class 对象、反射调用、性能开销和使用场景。
- Java 代理模式详解:掌握静态代理、JDK 动态代理和 CGLIB 代理。
- Java SPI 机制详解:理解服务发现和插件化扩展机制。
- Java 序列化详解:理解序列化流程、serialVersionUID、安全风险和替代方案。
实用细节
- BigDecimal 详解:掌握金额计算、精度、舍入模式和构造方式的注意点。
- Java 金额用 long 还是 BigDecimal?:区分金额存储与计算场景,掌握最小单位、舍入、溢出和数据库字段设计。
- Java 魔法类 Unsafe 详解:了解堆外内存、CAS、对象字段偏移和源码中的底层能力。
高频问题
- Java 是值传递还是引用传递?对象作为参数传递时为什么能修改字段?
==和equals()有什么区别?为什么重写equals()必须重写hashCode()?String、StringBuilder、StringBuffer如何选择?final、static、this、super分别有什么作用?- 泛型擦除是什么?
List<String>和List在运行时有什么区别? - 反射为什么慢?有哪些典型使用场景?
- JDK 动态代理和 CGLIB 动态代理有什么区别?
- 为什么不建议直接使用 Java 原生序列化?
BigDecimal为什么推荐用字符串构造?- 金额应该用
long存最小单位,还是用BigDecimal?
相关专题
Overview (`docs/java/collection/README.md`)
title: Java 集合专题:List、Map、Queue、并发集合与源码分析
category: Java
tag:
- Java
- Java集合
- Java面试
sitemap:
changefreq: weekly
priority: 0.9
head: - meta
- name: keywords
content: Java集合,Java集合面试题,ArrayList,LinkedList,HashMap,ConcurrentHashMap,CopyOnWriteArrayList,ArrayBlockingQueue,PriorityQueue,DelayQueue,集合源码
Java 集合是业务开发中使用频率最高的基础库之一,也是 Java 面试最常考的模块。学习集合时,既要知道每个容器适合什么场景,也要理解扩容、哈希冲突、迭代器、线程安全和并发容器背后的设计取舍。
适合谁看
- 想系统掌握 Java 集合框架的后端开发者。
- 准备 List、Map、Queue、并发集合和源码分析相关面试题的同学。
- 平时经常使用集合,但对扩容、哈希冲突、fail-fast、线程安全等细节不熟的读者。
- 想阅读 JDK 源码,从常用集合类开始建立源码分析能力的工程师。
学习重点
- List、Set、Map、Queue 的接口体系和常见实现类定位。
ArrayList、LinkedList、HashMap、LinkedHashMap的底层数据结构和扩容机制。ConcurrentHashMap、CopyOnWriteArrayList、ArrayBlockingQueue等并发容器的线程安全思路。- 哈希冲突、红黑树化、fail-fast、迭代器删除、集合判空和容量预估等常见细节。
- 源码分析时如何从数据结构、关键字段、核心方法和并发控制四个角度入手。
建议阅读顺序
- Java集合常见面试题总结(上):先建立集合框架和常见容器的问题清单。
- Java集合常见面试题总结(下):继续补齐 Map、Queue、并发集合和源码细节。
- Java集合使用注意事项总结:掌握项目里真正容易踩坑的使用方式。
- ArrayList 源码分析、LinkedList 源码分析、HashMap 源码分析:从最常用容器开始读源码。
- ConcurrentHashMap 源码分析、CopyOnWriteArrayList 源码分析、ArrayBlockingQueue 源码分析:再进入并发集合和阻塞队列。
核心文章
集合面试与使用规范
- Java集合常见面试题总结(上):覆盖集合框架、List、Set、Map、Queue 的基础问题。
- Java集合常见面试题总结(下):继续梳理哈希表、并发集合、集合源码和常见易错点。
- Java集合使用注意事项总结:总结集合初始化、判空、遍历删除、线程安全和性能相关注意事项。
List 与 Map 源码
- ArrayList 源码分析:理解动态数组、扩容、随机访问和迭代器。
- LinkedList 源码分析:理解双向链表、头尾操作和适用场景。
- HashMap 源码分析:理解数组、链表、红黑树、扰动函数、扩容和树化。
- LinkedHashMap 源码分析:理解访问顺序、插入顺序和 LRU 场景。
如果对底层结构还不熟,可以先看 线性数据结构详解、哈希表面试题总结、红黑树详解 和 LRU 缓存面试题总结,再回来看集合源码会顺很多。
并发集合与队列
- ConcurrentHashMap 源码分析:理解分段锁到 CAS + synchronized 的演进。
- CopyOnWriteArrayList 源码分析:理解写时复制和读多写少场景。
- ArrayBlockingQueue 源码分析:理解有界阻塞队列、锁和条件队列。
- PriorityQueue 源码分析(付费):理解堆结构和优先级队列。
- DelayQueue 源码分析:理解延迟队列、优先级队列和定时任务场景。
高频问题
ArrayList和LinkedList有什么区别?为什么很多场景更推荐ArrayList?HashMap的底层数据结构是什么?什么时候会树化?HashMap为什么线程不安全?扩容时可能出现什么问题?HashMap和ConcurrentHashMap有什么区别?ConcurrentHashMap在 JDK 7 和 JDK 8 中的实现有什么变化?CopyOnWriteArrayList为什么适合读多写少?- fail-fast 和 fail-safe 有什么区别?
- 遍历集合时如何安全删除元素?
ArrayBlockingQueue、PriorityQueue、DelayQueue分别适合什么场景?
相关专题
Overview (`docs/java/concurrent/README.md`)
title: Java 并发编程专题:线程、锁、JMM、CAS、AQS、线程池与虚拟线程
category: Java
tag:
- Java
- Java并发
- Java面试
sitemap:
changefreq: weekly
priority: 0.9
head: - meta
- name: keywords
content: Java并发,Java锁,synchronized,ReentrantLock,JMM,CAS,AQS,ThreadLocal,线程池,CompletableFuture,并发容器,Atomic,虚拟线程
Java 并发编程是后端开发和面试中最重要、也最容易混淆的模块之一。学习并发不能只背 API,要把线程生命周期、锁机制、内存模型、原子操作、线程池和并发工具类放在同一条主线上理解。
适合谁看
- 想系统学习 Java 并发编程的后端开发者。
- 准备线程、锁、JMM、CAS、AQS、线程池等面试题的同学。
- 已经在项目中使用多线程,但对死锁、线程池参数、ThreadLocal 泄漏等问题不够熟的读者。
- 想理解并发容器、CompletableFuture、虚拟线程等工程实践能力的工程师。
学习重点
- 线程创建、生命周期、上下文切换、线程安全和常见并发问题。
synchronized、volatile、ReentrantLock、互斥锁、读写锁、乐观锁、悲观锁的适用边界。- JMM、happens-before、指令重排、可见性、原子性和有序性。
- CAS、Atomic 原子类、AQS、并发容器和阻塞队列的底层思路。
- 线程池核心参数、拒绝策略、任务队列、参数配置和生产实践。
- CompletableFuture、ThreadLocal、虚拟线程在真实项目中的使用方式和风险。
建议阅读顺序
- Java并发常见面试题总结(上):先建立线程、锁和线程安全的基础问题清单。
- Java并发常见面试题总结(中) 和 Java并发常见面试题总结(下):继续补齐 JMM、CAS、AQS、线程池和并发工具。
- Java 锁详解、乐观锁和悲观锁详解、CAS 详解、JMM(Java 内存模型)详解:先建立锁体系,再理解并发控制的底层语义。
- AQS 详解、从ReentrantLock的实现看AQS的原理及应用:深入理解 Java 锁和同步器。
- Java 线程池详解 和 Java 线程池最佳实践:掌握生产中最常用的并发基础设施。
核心文章
并发面试题
- Java并发常见面试题总结(上):覆盖线程基础、线程安全、锁和常见并发问题。
- Java并发常见面试题总结(中):继续梳理 JMM、volatile、CAS、AQS 等核心知识。
- Java并发常见面试题总结(下):补齐线程池、并发工具类、CompletableFuture 和虚拟线程等内容。
锁、内存模型与同步器
- Java 锁详解:从互斥锁、读写锁、自旋锁到
synchronized、ReentrantLock、AQS,建立 Java 锁体系。 - 乐观锁和悲观锁详解:理解不同并发冲突处理策略。
- CAS 详解:理解比较并交换、ABA 问题和自旋开销。
- JMM(Java 内存模型)详解:掌握可见性、原子性、有序性和 happens-before。
- AQS 详解:理解同步队列、独占/共享模式和常见同步器底层。
- 从ReentrantLock的实现看AQS的原理及应用:通过 ReentrantLock 深入理解 AQS。
并发工具与工程实践
- Java 线程池详解:理解核心参数、任务队列、拒绝策略和执行流程。
- Java 线程池最佳实践:总结生产环境中线程池隔离、参数配置和监控建议。
- Java 常见并发容器总结:梳理 ConcurrentHashMap、CopyOnWriteArrayList、BlockingQueue 等容器。
- Atomic 原子类总结:理解原子更新基本类型、数组、引用和字段。
- ThreadLocal 详解:理解线程本地变量、ThreadLocalMap 和内存泄漏风险。
- CompletableFuture 详解:掌握异步编排、异常处理和线程池使用。
- 虚拟线程常见问题总结:理解虚拟线程的定位、适用场景和使用限制。
高频问题
- 线程和进程有什么区别?线程有哪些状态?
- 什么是线程安全?如何定位死锁?
- 互斥锁、读写锁、自旋锁有什么区别?
synchronized和ReentrantLock有什么区别?volatile能保证原子性吗?它解决什么问题?- JMM 是什么?happens-before 规则有什么用?
- CAS 有什么优缺点?ABA 问题如何解决?
- AQS 的核心思想是什么?哪些工具类基于 AQS?
- 线程池核心参数如何配置?拒绝策略如何选择?
- 为什么不建议直接使用
Executors创建线程池? ThreadLocal为什么可能内存泄漏?- CompletableFuture 默认线程池有什么风险?
- 虚拟线程适合 CPU 密集型任务吗?
相关专题
Overview (`docs/java/io/README.md`)
title: Java IO 专题:BIO、NIO、AIO、IO 模型与设计模式
category: Java
tag:
- Java
- Java IO
- Java面试
sitemap:
changefreq: weekly
priority: 0.9
head: - meta
- name: keywords
content: Java IO,Java NIO,BIO,NIO,AIO,IO模型,I/O多路复用,Reactor,Selector,Channel,Buffer,Java IO面试题
Java IO 是理解文件读写、网络编程、Netty、RPC 框架和高性能服务端的重要基础。学习 IO 时,建议同时理解 Java API、操作系统 IO 模型和常见设计模式,这样才能把 BIO、NIO、AIO、Selector、Channel、Buffer、Reactor 串起来。
适合谁看
- 想系统学习 Java IO/NIO 的后端开发者。
- 准备 BIO、NIO、AIO、IO 多路复用、Reactor 相关面试题的同学。
- 想继续学习 Netty、RPC、消息队列、数据库驱动等网络通信框架的读者。
- 对阻塞/非阻塞、同步/异步、Selector、Channel、Buffer 等概念容易混淆的工程师。
学习重点
- Java IO 流体系、字节流、字符流、缓冲流和常见文件操作。
- 装饰器模式、适配器模式等设计模式在 IO 中的应用。
- BIO、NIO、AIO 的模型差异、适用场景和优缺点。
- 同步/异步、阻塞/非阻塞、I/O 多路复用、Reactor 和 Proactor。
- Buffer、Channel、Selector 的协作关系,以及它们在网络编程中的作用。
建议阅读顺序
- Java IO 基础知识总结:先掌握 IO 流体系、常用类和文件读写基础。
- Java IO 设计模式总结:理解装饰器模式、适配器模式等设计模式如何落到 IO API 中。
- Java IO 模型详解:厘清 BIO、NIO、AIO、同步/异步、阻塞/非阻塞和多路复用。
- Java NIO 核心知识总结:深入学习 Buffer、Channel、Selector 和 NIO 编程模型。
核心文章
- Java IO 基础知识总结:系统介绍字节流、字符流、缓冲流、随机访问文件和常见 IO 类。
- Java IO 设计模式总结:讲解装饰器模式、适配器模式等设计模式在 IO 中的应用。
- Java IO 模型详解:区分 BIO、NIO、AIO、同步/异步、阻塞/非阻塞和 I/O 多路复用。
- Java NIO 核心知识总结:理解 Buffer、Channel、Selector、SelectionKey 和 NIO 服务端编程。
高频问题
- 字节流和字符流有什么区别?什么时候使用缓冲流?
- Java IO 中为什么大量使用装饰器模式?
- BIO、NIO、AIO 有什么区别?
- 同步和异步、阻塞和非阻塞分别是什么意思?
- I/O 多路复用解决了什么问题?
select、poll、epoll有什么区别?- Reactor 模型是什么?和 Proactor 有什么区别?
- NIO 中 Buffer、Channel、Selector 分别承担什么职责?
- 为什么 Netty 基于 NIO 构建,而不是直接使用传统 BIO?
相关专题
Overview (`docs/java/jvm/README.md`)
title: JVM 专题:内存区域、类加载、垃圾回收、参数调优与线上排查
category: Java
tag:
- Java
- JVM
- Java面试
sitemap:
changefreq: weekly
priority: 0.9
head: - meta
- name: keywords
content: JVM,JVM面试题,Java内存区域,类加载,类加载器,垃圾回收,GC,JVM参数,JDK监控工具,OOM,性能调优
JVM 是 Java 后端绕不开的核心基础。学习 JVM 的目标不是背概念,而是能解释对象如何创建和回收、类如何加载、GC 如何影响应用、参数如何配置,以及线上 OOM、频繁 GC、CPU 飙高等问题应该如何排查。
适合谁看
- 想系统学习 JVM 的 Java 后端开发者。
- 准备 JVM 内存、类加载、GC、参数调优和线上排查相关面试题的同学。
- 已经参与过线上服务维护,但对 GC 日志、堆转储、线程栈和 JDK 工具不熟的读者。
- 想继续深入 Spring、Netty、中间件或性能优化的工程师。
学习重点
- JVM 运行时内存区域、对象创建、对象访问和 OOM 场景。
- 类文件结构、类加载过程、类加载器和双亲委派模型。
- 垃圾回收基础、对象存活判断、垃圾收集算法和主流垃圾收集器。
- JVM 参数、GC 日志、堆转储、线程栈和常见 JDK 监控诊断工具。
- 线上问题排查的基本路径:现象观察、指标采集、工具分析、原因定位和优化验证。
建议阅读顺序
- 准备面试时先看 JVM 常见面试题总结,找出自己答不完整的模块;系统学习可以从下一步开始。
- 大白话带你认识 JVM:先建立 JVM 的整体认知。
- Java内存区域详解(重点):理解运行时数据区和常见 OOM 场景。
- 类文件结构详解、类加载过程详解、类加载器详解(重点):掌握类从
.class到可运行对象的过程。 - JVM垃圾回收详解(重点):系统学习 GC 基础、算法和垃圾收集器。
- 最重要的 JVM 参数总结、JDK 监控和故障处理工具总结、Java 后端线上问题排查:进入参数配置和线上实践。
核心文章
JVM 基础与内存
- JVM 常见面试题总结:按内存、类加载、GC、参数工具和线上排查整理高频问题。
- 大白话带你认识 JVM:用整体视角理解 JVM 的定位和组成。
- Java内存区域详解(重点):讲解程序计数器、虚拟机栈、本地方法栈、堆、方法区和直接内存。
- 类文件结构详解:理解魔数、版本号、常量池、访问标志、字段表、方法表和属性表。
类加载
- 类加载过程详解:梳理加载、验证、准备、解析、初始化、使用和卸载。
- 类加载器详解(重点):理解启动类加载器、扩展类加载器(JDK 8)/平台类加载器(JDK 9+)、应用类加载器和双亲委派模型。
垃圾回收与调优
- JVM垃圾回收详解(重点):理解对象存活判断、引用类型、垃圾收集算法、分代收集和主流垃圾收集器。
- 最重要的JVM参数总结:整理堆大小、GC 日志、OOM 转储、垃圾收集器和诊断相关参数。
- JDK监控和故障处理工具总结:介绍 jps、jstat、jmap、jstack、jcmd、JConsole、VisualVM、JMC 等工具。
- Java 后端线上问题排查:从告警确认、止血留证开始,串起 CPU、内存、GC、线程池、连接池、慢 SQL、Redis 和消息积压的排查过程。
高频问题
- JVM 运行时内存区域如何划分?哪些区域线程私有?
- 堆和方法区分别存放什么?直接内存会不会 OOM?
- 对象是如何创建的?对象访问定位有哪几种方式?
- 类加载过程有哪些阶段?初始化阶段什么时候触发?
- 什么是双亲委派模型?为什么需要它?
- 如何判断对象是否可以被回收?强软弱虚引用有什么区别?
- Minor GC、Major GC、Full GC 有什么区别?
- G1、ZGC、Shenandoah 分别适合什么场景?
- 常用 JVM 参数有哪些?线上如何保留 GC 日志和 OOM dump?
- CPU 飙高、频繁 Full GC、内存泄漏应该如何排查?
相关专题
Overview (`docs/java/new-features/README.md`)
title: Java 新特性专题:Java 8 到 Java 26 重要特性梳理
category: Java
tag:
- Java
- Java新特性
- Java面试
sitemap:
changefreq: weekly
priority: 0.9
head: - meta
- name: keywords
content: Java新特性,Java8新特性,Java11新特性,Java17新特性,Java21新特性,Lambda,Stream,Optional,模块化,var,Record,Switch,虚拟线程,模式匹配
Java 新特性不适合按版本机械背诵,更适合抓住“语言表达能力、标准库增强、并发模型、JVM 改进、长期支持版本”这几条主线。日常开发优先掌握 Java 8、11、17、21 等 LTS 版本中的稳定特性,再按需了解后续版本的预览和孵化特性。
时间比较紧的话,可以先看面试突击版的 Java 新特性常见面试题总结,再回到本专题查看对应版本的完整说明。
适合谁看
- 想系统了解 Java 8 之后版本变化的 Java 开发者。
- 准备 Java 新特性、LTS 版本差异、虚拟线程、Record、模式匹配等面试题的同学。
- 负责 JDK 升级,需要判断哪些特性会影响项目代码和运行时表现的工程师。
- 已经熟悉 Java 8,但对 Java 11、17、21 之后变化不够清楚的读者。
学习重点
- Java 8 的 Lambda、Stream、Optional、接口默认方法和新日期 API。
- Java 9 的模块化,以及后续版本对语言语法和标准库的持续增强。
- Java 11、17、21 等 LTS 版本中更值得优先掌握的稳定能力。
- var、文本块、Record、Switch 表达式、密封类、模式匹配等语言层变化。
- 虚拟线程、结构化并发、分代 ZGC、Foreign Function & Memory API 等运行时和并发相关变化。
- 区分正式特性、预览特性、孵化特性,避免在生产升级中误判风险。
建议阅读顺序
- Java8 新特性实战:先掌握 Lambda、Stream、Optional、接口默认方法和新日期 API。
- Java 9 新特性概览、Java 10 新特性概览:理解模块化和局部变量类型推断等基础变化。
- Java 11 新特性概览(重要):重点关注第一个 8 之后被广泛采用的 LTS 版本。
- Java 17 新特性概览(重要):掌握 Record、密封类、Switch、模式匹配等现代 Java 语法演进。
- Java 21 新特性概览(重要):重点学习虚拟线程、分代 ZGC、模式匹配和字符串模板等变化。
- 再按需阅读 Java 22 & 23 新特性概览、Java 24 新特性概览、Java 25 新特性概览、Java 26 新特性概览。
核心文章
Java 8 基础能力
- Java8 新特性实战:掌握 Lambda、函数式接口、Stream、Optional、接口默认方法和新日期 API。
- 《Java8 指南》中文翻译:通过更系统的教程理解 Java 8 常用特性。
重要 LTS 版本
- Java 11 新特性概览(重要):关注 HTTP Client、字符串 API、集合 API、ZGC 实验特性等变化。
- Java 17 新特性概览(重要):关注 Record、密封类、Switch 表达式、文本块和模式匹配相关能力。
- Java 21 新特性概览(重要):关注虚拟线程、分代 ZGC、Record Pattern、Pattern Matching for switch 等特性。
按版本追踪
- Java 9 新特性概览:理解模块化系统和 JShell。
- Java 10 新特性概览:了解局部变量类型推断和运行时改进。
- Java 12 & 13 新特性概览:了解 Switch 表达式、文本块等变化。
- Java 14 & 15 新特性概览:了解 Record、文本块、隐藏类等特性。
- Java 16 新特性概览:了解 Record 正式转正、Pattern Matching for instanceof 等变化。
- Java 18 新特性概览、Java 19 新特性概览、Java 20 新特性概览:跟进 UTF-8 默认字符集、虚拟线程预览、结构化并发等演进。
- Java 22 & 23 新特性概览、Java 24 新特性概览、Java 25 新特性概览、Java 26 新特性概览:了解较新版本中的预览、孵化和正式特性。
高频问题
- Java 8 为什么重要?Lambda 和 Stream 分别解决什么问题?
Optional适合用在哪些场景?为什么不建议滥用?- Java 9 模块化解决了什么问题?
var是动态类型吗?它适合在哪些场景使用?- Record 和普通 JavaBean 有什么区别?
- Switch 表达式和传统 switch 有什么区别?
- 密封类适合解决什么问题?
- 模式匹配带来了哪些代码简化?
- 虚拟线程适合什么场景?和平台线程有什么区别?
- 生产升级 JDK 时,如何区分正式特性、预览特性和孵化特性?
相关专题
Overview (`docs/system-design/basis/README.md`)
title: 系统设计基础专题:RESTful API、软件工程、代码命名、重构与单元测试
category: 系统设计
tag:
- 系统设计
- 工程基础
- RESTful API
sitemap:
changefreq: weekly
priority: 0.9
head: - meta
- name: keywords
content: 系统设计基础,RESTful API,软件工程,代码命名,代码重构,单元测试,JUnit,后端工程基础,后端面试
系统设计基础专题关注后端开发里最容易被忽略、但长期影响最大的工程能力:接口怎么设计、代码怎么命名、坏味道怎么识别、测试怎么写、需求到交付的过程如何组织。
这些内容看起来不像缓存、消息队列、分库分表那样“硬核”,但它们决定了系统能不能被团队长期维护。
适合谁看
- 想系统补齐后端工程基础的开发者。
- 准备 RESTful API、代码质量、单元测试相关面试题的同学。
- 已经能完成业务开发,但想提升接口设计和代码可维护性的读者。
- 需要给团队制定接口规范、命名规范或测试规范的工程师。
学习重点
- RESTful API 的核心不是固定 URL 模板,而是围绕资源设计清晰、一致、可演进的接口。
- 软件工程关注的是需求、设计、开发、测试、交付和维护的全过程。
- 好命名能降低沟通成本,坏命名会让系统越来越难改。
- 重构不是“重写”,而是在保持外部行为不变的前提下改善内部结构。
- 单元测试要服务于代码质量和变更信心,不能只追求覆盖率数字。
建议阅读顺序
- 软件工程简明教程:先理解软件开发过程、开发模型和工程化基本概念。
- RestFul API 简明教程:学习资源路径、HTTP 方法、状态码和接口设计规范。
- 代码命名指南:建立清晰、一致、可读的命名习惯。
- 代码重构指南:识别代码坏味道,理解常见重构原则和手法。
- 单元测试到底是什么?应该怎么做?:最后用测试提高代码变更的安全感。
核心文章
- 软件工程简明教程:梳理软件危机、软件开发过程模型、瀑布模型、敏捷开发等基础概念。
- RestFul API 简明教程:讲解 REST 架构原则、资源路径设计、HTTP 方法使用和状态码规范。
- 代码命名指南:总结变量、方法、类的命名原则与技巧,提升代码可读性和可维护性。
- 代码重构指南:讲解重构定义、重构原则、代码坏味道识别及常用重构技巧。
- 单元测试到底是什么?应该怎么做?:覆盖单元测试概念、Mock 与 Stub、测试金字塔及 JUnit 基础。
高频问题
- REST 和 RESTful API 有什么区别?
- RESTful API 中资源路径应该如何命名?
- GET、POST、PUT、PATCH、DELETE 分别适合什么场景?
- 软件工程为什么不只是写代码?
- 好的代码命名应该遵循哪些原则?
- 什么是代码坏味道?什么时候应该重构?
- 重构和重写有什么区别?
- 单元测试、集成测试、端到端测试有什么区别?
- Mock 和 Stub 分别解决什么问题?
- 单元测试覆盖率越高越好吗?
相关专题
Overview (`docs/system-design/security/README.md`)
title: 认证授权与数据安全专题:JWT、SSO、权限系统、加密、脱敏与数据校验
category: 系统设计
tag:
- 认证授权
- 数据安全
- 后端面试
sitemap:
changefreq: weekly
priority: 0.9
head: - meta
- name: keywords
content: 认证授权,Authentication,Authorization,Session,Token,OAuth2,JWT,SSO,RBAC,权限系统,加密算法,敏感词过滤,数据脱敏,数据校验,密码安全,后端面试
认证授权与数据安全专题关注后端系统里非常基础、但出错成本很高的一条链路:用户如何登录、身份如何传递、权限如何判断、敏感数据如何保护、输入数据如何校验。
安全不是某一个框架或某一个注解能兜住的事情。它需要从认证、授权、传输、存储、展示、输入校验和审计等多个环节一起设计。
时间比较紧的话,可以先看面试突击版的 认证与授权常见面试题总结 和 Web 安全常见面试题总结,再回到本专题补完整原理和实现方案。
适合谁看
- 正在学习登录鉴权、权限系统和数据安全的后端开发者。
- 准备认证授权、JWT、SSO、RBAC、数据脱敏相关面试题的同学。
- 项目中需要设计后台权限、用户体系、敏感数据展示或数据校验方案的工程师。
- 已经用过 Spring Security、Sa-Token、Shiro 等框架,但想补齐底层概念的读者。
学习重点
- 认证解决“你是谁”,授权解决“你能做什么”,两者不能混为一谈。
- Session、Token、JWT、OAuth2、SSO 适合的场景不同,不能只用“无状态”判断优劣。
- JWT 的优势和问题都很明显,重点在失效控制、续期、泄露风险和服务端治理。
- 权限系统通常要从用户、角色、权限、资源、组织、数据范围等维度建模。
- 数据安全既包括加密存储,也包括脱敏展示、输入校验、敏感词过滤和密码安全。
- 安全方案必须考虑落地成本、用户体验、可审计性和事故处置能力。
建议阅读顺序
- 认证授权基础概念详解:先区分认证、授权、Session、Token、OAuth2 等概念。
- JWT 基础概念详解 和 JWT 身份认证优缺点分析:理解 JWT 的工作方式、优势和局限。
- SSO 单点登录详解:理解统一认证中心、跨系统登录和登录态同步。
- 权限系统设计详解:把认证授权落到 RBAC 权限系统设计。
- 常见加密算法总结、数据脱敏方案总结、为什么前后端都要做数据校验?:补齐数据安全基础。
- 再根据业务场景阅读 敏感词过滤方案总结 和 为什么忘记密码时只能重置,不能告诉你原密码?。
核心文章
认证授权
- 认证授权基础概念详解:讲解 Authentication、Authorization、Session、Token、OAuth2 等核心知识。
- JWT 基础概念详解:讲解 JSON Web Token 的组成结构、签名算法、工作原理及登录鉴权应用。
- JWT 身份认证优缺点分析:分析 JWT 无法主动失效、Token 续期等问题及解决方案。
- SSO 单点登录详解:讲解统一认证中心、CAS 协议、跨域登录实现及登录态同步机制。
- 权限系统设计详解:基于 RBAC 讲解权限系统建模、访问控制和后台管理设计。
数据安全
- 常见加密算法总结:梳理 AES、RSA、MD5、SHA 等算法的原理与应用场景。
- 敏感词过滤方案总结:从暴力匹配到 Trie 树、AC 自动机,讲解敏感词过滤算法演进和工程实践。
- 数据脱敏方案总结:讲解手机号、身份证、银行卡等敏感数据脱敏规则和实现方法。
- 为什么前后端都要做数据校验?:解释参数校验、权限校验的重要性,以及如何防止绕过前端校验。
- 为什么忘记密码时只能重置,不能告诉你原密码?:解释密码哈希、加盐、Bcrypt 和密码传输安全。
高频问题
- 认证和授权有什么区别?
- Session 和 Token 有什么区别?
- JWT 由哪几部分组成?签名解决了什么问题?
- JWT 为什么无法天然主动失效?有哪些解决方案?
- OAuth2 和 JWT 是什么关系?
- SSO 单点登录的核心流程是什么?
- RBAC 权限模型如何设计?用户、角色、权限、资源之间是什么关系?
- 对称加密、非对称加密和哈希算法分别适合什么场景?
- 为什么密码不能明文存储?为什么忘记密码只能重置?
- 数据脱敏应该在存储层、服务层还是展示层做?
- 为什么后端必须做数据校验?
- 敏感词过滤有哪些常见实现方案?
相关专题
Overview (`docs/tools/docker/README.md`)
title: Docker 专题:容器、镜像、仓库、数据卷、网络与容器化部署
category: 开发工具
tag:
- Docker
- 容器
- 部署
sitemap:
changefreq: weekly
priority: 0.85
head: - meta
- name: keywords
content: Docker,容器,镜像,仓库,Docker引擎,数据卷,网络,容器化部署,环境一致性,后端开发
Docker 是后端开发非常常见的容器化工具,常用于本地快速启动 MySQL、Redis、Kafka 等依赖服务,也常用于测试环境和部署交付。学习 Docker 时,要把核心概念和命令实践结合起来。
适合谁看
- 想快速理解 Docker 容器化基础的后端开发者。
- 需要用 Docker 搭建本地开发环境、测试环境或依赖服务的同学。
- 准备面试,需要讲清容器、镜像、数据卷、网络和容器与虚拟机区别的读者。
- 已经会复制 Docker 命令,但不清楚镜像构建、容器生命周期和数据持久化的工程师。
学习重点
- 容器解决的是应用运行环境隔离和一致性问题。
- 镜像是静态模板,容器是镜像运行后的实例,仓库用于分发和复用镜像。
- Docker 常用命令要围绕镜像管理、容器生命周期、日志查看、端口映射和进入容器来掌握。
- 数据卷用于数据持久化和宿主机目录挂载,网络用于容器之间和容器与外部服务通信。
- Docker Compose 用于定义和运行多容器应用,适合本地开发环境和简单服务编排。
- Docker 不是万能部署方案,生产环境还要结合镜像安全、资源限制、日志、监控和编排能力考虑。
建议阅读顺序
- Docker 核心概念总结:先理解容器、镜像、仓库、Docker 引擎以及容器和虚拟机的区别。
- Docker 实战:通过实际命令练习镜像拉取、容器启动、端口映射、数据卷、日志查看和常见服务部署。
- 结合一个 Java 项目练习:把应用依赖的 MySQL、Redis 等服务用 Docker 启动起来,再观察日志、数据目录和端口映射。
核心文章
- Docker 核心概念总结:讲解容器、镜像、仓库、Docker 引擎、Docker 架构、Docker Compose,以及 Docker 和虚拟机的区别。
- Docker 实战:通过常用命令和实践场景理解镜像管理、容器管理、服务部署、本地环境搭建和常见排查。
高频问题
- Docker 主要解决什么问题?
- 容器和虚拟机有什么区别?
- 镜像和容器是什么关系?
- Dockerfile、镜像、容器、仓库分别是什么?
- 为什么容器删除后数据可能丢失?数据卷解决了什么问题?
- 端口映射和容器网络分别解决什么问题?
- 如何查看容器日志、进入容器、停止和删除容器?
docker compose适合解决什么问题?和单独执行docker run有什么区别?- Docker 在开发、测试和部署环境里分别有什么价值?
相关专题
Overview (`docs/tools/git/README.md`)
title: Git 专题:版本控制、分支协作、提交管理、冲突处理与 GitHub 技巧
category: 开发工具
tag:
- Git
- GitHub
- 版本控制
sitemap:
changefreq: weekly
priority: 0.85
head: - meta
- name: keywords
content: Git,GitHub,版本控制,分支,提交,合并,冲突解决,远程仓库,开源协作,代码协作
Git 是开发者必须掌握的基础工具。学习 Git 时,不建议只背命令,更要理解版本控制模型、提交历史、分支协作和冲突处理,这样在团队协作和开源贡献中才不容易慌。
适合谁看
- 刚开始接触 Git 和 GitHub 的后端初学者。
- 经常使用 Git 命令,但对工作区、暂存区、分支和远程仓库理解不清的开发者。
- 准备面试,需要把 Git 常见问题讲清楚的同学。
- 想提升 GitHub 个人主页、项目展示和开源协作效率的读者。
学习重点
- Git 是分布式版本控制系统,核心是记录代码快照和提交历史。
- 工作区、暂存区、本地仓库、远程仓库分别对应不同阶段的代码状态。
- 分支让多人并行开发成为可能,合并和冲突处理是团队协作高频场景。
- Git 命令要结合真实流程理解,比如 clone、add、commit、branch、merge、pull、push。
- GitHub 可以用于代码托管、项目展示、开源协作、代码搜索、Actions 自动化和个人影响力建设。
建议阅读顺序
- Git 核心概念总结:先理解版本控制、Git 数据模型、常用命令、分支和远程协作。
- 在本地项目中练习一次完整流程:clone、创建分支、提交、合并、解决冲突、推送远程分支。
- GitHub 实用小技巧总结:补充 GitHub 个人主页、项目徽章、代码阅读、Actions 和 Explore/Trending 技巧。
核心文章
- Git 核心概念总结:系统介绍版本控制、Git 简史、数据存储方式、工作区、暂存区、提交、分支、合并和常用命令。
- GitHub 实用小技巧总结:整理 GitHub 简历、个人主页、项目徽章、开源趋势、代码阅读、Actions 和搜索等实用技巧。
高频问题
- Git 和 SVN 有什么区别?
- Git 为什么说是分布式版本控制系统?
- 工作区、暂存区、本地仓库、远程仓库分别是什么?
git add、git commit、git push分别做了什么?- Git 分支的本质是什么?为什么分支切换很快?
- merge 和 rebase 有什么区别?
- 发生冲突时应该如何定位和解决?
- GitHub Profile README、项目徽章、Codespaces、Actions 和代码搜索有什么实际价值?
相关专题
Overview (`docs/tools/maven/README.md`)
title: Maven 专题:POM、坐标、仓库、依赖管理、生命周期、插件与多模块项目
category: 开发工具
tag:
- Maven
- 项目构建
- 依赖管理
sitemap:
changefreq: weekly
priority: 0.85
head: - meta
- name: keywords
content: Maven,POM,Maven坐标,Maven仓库,依赖管理,依赖范围,Maven生命周期,Maven插件,多模块项目,Java项目构建
Maven 是 Java 后端项目中最常见的构建和依赖管理工具。学习 Maven 时,不要只会复制 pom.xml,还要理解坐标、仓库、依赖传递、生命周期、插件和多模块管理这些基础概念。
适合谁看
- 正在学习 Java 项目构建和依赖管理的同学。
- 使用 Maven 写项目,但遇到依赖冲突、版本不统一、多模块管理、CI 构建时容易卡住的开发者。
- 准备面试,需要讲清 Maven 核心概念和最佳实践的读者。
- 需要维护 Spring Boot、微服务或多模块 Java 项目的工程师。
学习重点
- POM 是 Maven 项目的核心配置,坐标用于唯一标识一个构件。
- Maven 仓库分为本地仓库、私服仓库和中央仓库,依赖解析会按一定顺序查找。
- 依赖范围、依赖传递、依赖排除和版本管理决定项目最终使用哪些 Jar 包。
- 生命周期定义构建阶段,插件负责真正执行编译、测试、打包等任务。
- Maven Wrapper 能固定项目使用的 Maven 版本,适合团队协作和 CI 环境。
- 多模块项目要重点关注父 POM、
dependencyManagement、pluginManagement和模块边界。
建议阅读顺序
- Maven 核心概念总结:先理解 POM、坐标、仓库、依赖、生命周期、插件和多模块项目。
- Maven 最佳实践:再学习标准目录结构、编译版本、BOM、依赖版本管理、Maven Wrapper、CI 和常见实践。
- 结合一个 Spring Boot 项目查看
pom.xml:重点看父工程、依赖范围、插件配置和最终依赖树。
核心文章
- Maven 核心概念总结:系统介绍 Maven 的定位、POM、坐标、仓库、依赖、生命周期、插件和多模块项目。
- Maven 最佳实践:整理标准目录结构、编译版本、依赖版本统一、Maven Wrapper、CI 和日常使用建议。
高频问题
- Maven 是什么?它主要解决哪些问题?
- POM、groupId、artifactId、version 分别是什么?
- 本地仓库、私服仓库、中央仓库有什么区别?
- Maven 依赖传递是什么?依赖冲突如何排查?
dependencyManagement和dependencies有什么区别?- Maven 生命周期和插件是什么关系?
- 为什么团队项目建议提交 Maven Wrapper?
compile、provided、runtime、test等依赖范围有什么区别?- 多模块项目为什么通常需要父 POM?
相关专题
Overview (`docs/distributed-system/distributed-process-coordination/zookeeper/README.md`)
title: ZooKeeper 专题:核心概念、ZNode、Watcher、ZAB、集群部署、Curator 与分布式锁
category: 分布式
tag:
- ZooKeeper
- 分布式协调
- 分布式锁
sitemap:
changefreq: weekly
priority: 0.9
head: - meta
- name: keywords
content: ZooKeeper,ZooKeeper入门,ZooKeeper进阶,ZNode,Watcher,ZAB协议,Leader选举,Curator,分布式协调,分布式锁,注册中心,后端面试
ZooKeeper 是经典的分布式协调组件,常用于注册中心、配置管理、分布式锁、Leader 选举和集群元数据管理。学习 ZooKeeper 时,可以先掌握数据模型和 Watcher,再理解 ZAB 协议、Leader 选举和工程实践。
适合谁看
- 想系统学习 ZooKeeper 的后端开发者。
- 准备 ZooKeeper、注册中心、分布式锁、分布式协调相关面试题的同学。
- 用过 ZooKeeper 或 Curator,但对 ZNode、Watcher、ZAB、Leader 选举不够熟的读者。
- 需要对比 ZooKeeper、Eureka、Nacos 等注册中心和协调组件的工程师。
学习重点
- ZooKeeper 的数据模型为什么是树形 ZNode?
- 临时节点、有序节点、Watcher 机制分别适合解决哪些问题?
- ZooKeeper 如何通过 ZAB 协议处理消息广播、主从同步和崩溃恢复?
- 为什么 ZooKeeper 集群通常建议部署奇数个节点?
- Curator 如何简化原生 ZooKeeper 客户端开发和分布式锁实现?
和其他文章的关系
分布式协调详解 先讲 Leader、Quorum、脑裂和租约这些通用问题;ZooKeeper 专题把这些问题落到 ZNode、Watcher、Session 和 ZAB 上。
ZAB 协议详解 只讲 ZooKeeper 的原子广播和崩溃恢复,不展开 ZNode、Curator 命令和应用场景。想看实际命令和客户端代码,可以继续读 ZooKeeper 实战教程。
建议阅读顺序
- 分布式协调详解:先理解分布式协调、Leader、Quorum、脑裂和租约这些共性问题。
- ZooKeeper 入门指南:再理解 ZNode、Watcher、ACL 和典型应用场景。
- ZooKeeper 进阶详解:继续补齐 ZAB、Leader 选举、集群部署和会话机制。
- ZooKeeper 实战教程:最后通过 Docker、zkCli、四字命令和 Curator 做实践。
核心文章
- ZooKeeper 入门指南:讲解 ZooKeeper 核心概念、ZNode 数据模型、节点类型、Watcher 监听机制、ACL 权限控制和典型应用场景。
- ZooKeeper 进阶详解:深入讲解 ZAB 协议、Leader 选举、集群部署策略、奇数节点原则、会话管理以及与 Eureka、Nacos 等注册中心的对比。
- ZooKeeper 实战教程:覆盖 Docker 安装部署、zkCli 常用命令、四字命令、Curator Java 客户端 CRUD 操作和分布式锁示例。
高频问题
- ZooKeeper 适合解决哪些分布式协调问题?
- ZNode 有哪些类型?临时顺序节点为什么常用于分布式锁?
- Watcher 为什么是一次性触发?使用时有什么注意事项?
- ZAB 协议和 Raft、Paxos 有什么关系?
- ZooKeeper 集群为什么通常部署奇数个节点?
- ZooKeeper 和 Eureka、Nacos 作为注册中心有什么区别?
- Curator 相比原生 ZooKeeper 客户端解决了哪些开发痛点?
相关专题
Overview (`docs/system-design/framework/spring/README.md`)
title: Spring & Spring Boot 专题:IoC、AOP、事务、自动装配、常用注解与源码
category: 框架
tag:
- Spring
- Spring Boot
- 后端面试
sitemap:
changefreq: weekly
priority: 0.9
head: - meta
- name: keywords
content: Spring,Spring Boot,Spring面试题,SpringBoot面试题,IoC,AOP,Bean生命周期,Spring事务,Spring自动装配,Spring常用注解,Spring源码,@Async,Java后端面试
Spring 是 Java 后端最核心的基础设施之一。学习 Spring 不能只背注解,还要理解 IoC、AOP、Bean 生命周期、事务、自动装配、设计模式和常见扩展点。
Spring Boot 则进一步把配置、依赖管理、自动装配和生产可观测能力整合起来,让应用开发更快,但也更容易让人忽略底层原理。
时间比较紧的话,可以先看面试突击版的 Spring 常见面试题总结,再回到本专题补 IoC、AOP、事务和自动装配的完整细节。
适合谁看
- 正在系统学习 Spring、Spring MVC、Spring Boot 的 Java 后端开发者。
- 准备 Spring、Spring Boot 高频面试题的同学。
- 用过 Spring Boot 开发项目,但对 IoC、AOP、事务和自动装配理解不够深的读者。
- 想从框架原理角度理解后端工程基础设施的工程师。
学习重点
- Spring IoC 解决对象创建和依赖管理问题,AOP 解决横切逻辑复用问题。
- Bean 生命周期、作用域、循环依赖和扩展点是理解 Spring 容器的关键。
- Spring 事务要重点掌握传播行为、隔离级别、回滚规则和失效场景。
- Spring Boot 自动装配的核心在于条件装配、配置绑定和 Starter 体系。
- 学注解不能只背用途,还要知道它背后对应的容器能力。
- Spring 源码和设计模式适合用来加深理解,不建议一开始就硬啃源码细节。
建议阅读顺序
- Spring常见面试题总结:先建立 Spring 高频问题清单。
- IoC & AOP详解(快速搞懂):理解 Spring 最核心的两个基础概念。
- Spring&SpringMVC&SpringBoot常用注解总结:把常用注解和容器能力对应起来。
- Spring 事务详解:重点掌握事务传播、隔离级别、回滚规则和失效场景。
- SpringBoot 自动装配原理详解:理解 Spring Boot 为什么能做到开箱即用。
- 再根据需要阅读 Spring 中的设计模式详解、Async 注解原理分析 和 Spring Boot核心源码解读。
核心文章
- Spring常见面试题总结:覆盖 IoC 容器、AOP 原理、Bean 生命周期、依赖注入等 Spring 核心知识点。
- SpringBoot常见面试题总结:覆盖自动配置原理、Starter 机制、配置文件加载及 Actuator 监控等知识点。
- IoC & AOP详解(快速搞懂):讲解控制反转、依赖注入、切面编程和动态代理实现机制。
- Spring&SpringMVC&SpringBoot常用注解总结:梳理
@Autowired、@Component、@RequestMapping等常用注解。 - Spring 事务详解:覆盖
@Transactional、事务传播行为、隔离级别、事务失效场景及回滚规则。 - SpringBoot 自动装配原理详解:解析
@EnableAutoConfiguration、SpringFactories 加载机制和条件注解。 - Spring Boot 的 JAR 为什么可以直接运行?:理解 Manifest、Boot Loader、嵌套依赖加载和可执行 JAR 的打包过程。
- Spring 中的设计模式详解:理解工厂模式、代理模式、单例模式、模板方法等在 Spring 中的应用。
- Async 注解原理分析:理解异步任务配置、线程池设置和
@EnableAsync机制。 - Spring Boot核心源码解读:从源码角度理解启动流程、自动配置机制和 SpringApplication。
高频问题
- 什么是 IoC?什么是 DI?
- Spring AOP 和动态代理是什么关系?
- Spring Bean 的生命周期是怎样的?
- Spring 如何解决循环依赖?哪些循环依赖解决不了?
@Autowired和@Resource有什么区别?- Spring 事务的传播行为有哪些?
@Transactional常见失效场景有哪些?- Spring Boot 自动装配的流程是什么?
- Spring Boot 的 JAR 为什么可以直接运行?Boot Loader 和内嵌 Web 服务器各自负责什么?
- Starter 的作用是什么?如何自定义一个 Starter?
- Spring 中用到了哪些设计模式?
@Async为什么有时不生效?- Spring 源码应该从哪些入口开始看?
相关专题
Distributed System Distributed System Intro (`docs/distributed-system/distributed-system-intro.md`)
title: 分布式系统详解:核心概念、架构演进、典型特征与学习路线
category: 分布式
tag:
- 分布式
- 系统设计
head: - meta
- name: keywords
content: 分布式系统,什么是分布式系统,分布式架构,微服务,集群,远程调用,分布式一致性,数据分片,副本复制,分布式系统学习路线
刚接触分布式系统时,很多人会先被一串名词砸中:CAP、BASE、Paxos、Raft、分布式锁、分布式事务。
这些概念都绕不开,但入门时直接钻进去,容易把分布式系统学成一堆互不相干的术语。更好的切入口是一个更土但更实用的问题:原来一台机器、一个进程、一个数据库就能完成的事情,为什么后来要拆到多台机器上?拆完以后,为什么一个超时、一次重试、一条消息重复投递,都会牵出这么多设计问题?
这篇文章先把“分布式系统是什么”讲清楚。Paxos、Raft、分布式事务不会展开推导,只把它们放回主线里,知道它们大概在解决哪类麻烦。
什么是分布式系统?
《分布式系统概念与设计》这本书对分布式系统的定义是:
分布式系统是这样一种系统:位于联网计算机上的硬件或软件组件,仅通过消息传递进行通信并协调行动。
工程里可以这样理解:分布式系统由多个相对独立的计算单元组成,这些计算单元靠网络通信和协作,对外提供一项完整服务。
这里的“独立”别理解成每个节点都独占 CPU、内存、磁盘和操作系统。节点可以是物理机、虚拟机、容器,也可以只是一个软件进程。同一台机器上的两个进程会共享底层 CPU 和物理内存,容器也常共享宿主机内核。说它独立,主要是说它有自己的执行状态和局部数据,可以单独运行,也可能单独卡住、重启或丢失连接。
麻烦也从这里开始。
节点之间不共享同一个进程地址空间,原来的本地方法调用,拆出去后可能变成一次 RPC、一次 HTTP 请求、一次消息投递,或者一次数据库副本同步。网络有延迟,会丢包,请求可能到了服务端但响应丢了;客户端看到的“超时”,并不能证明服务端没有执行。
多台机器还带来另一个限制:没有哪个节点能在某个瞬间看到整个系统的真实状态。每个节点看到的是本地状态和已经收到的消息。节点时钟也会有偏差,即使用 NTP 做同步,也不能当作完全一致的全局时钟来用。
比如用户点击“提交订单”,页面上只是一次请求,服务端可能已经经过网关、用户服务、商品服务、订单服务、库存服务、优惠券服务、支付服务,还可能写数据库、发消息、更新缓存。用户看到的是一个按钮,后端看到的是一串跨节点协作。
| 维度 | 单机或单进程系统 | 分布式系统 |
|---|---|---|
| 通信方式 | 方法调用、共享内存 | RPC、消息、网络协议 |
| 故障范围 | 往往共享一个故障边界 | 节点可以独立故障 |
| 时间视图 | 主要依赖同一机器时钟 | 多节点时钟存在偏差 |
| 状态观察 | 较容易观察整体状态 | 节点通常只有局部视图 |
| 事务处理 | 本地数据库事务更常见 | 跨服务协调、补偿或共识 |
| 扩容方式 | 纵向升级为主 | 横向增加节点 |
| 问题排查 | 单进程日志和调用栈 | 日志、指标、链路追踪和跨节点状态 |
为什么需要分布式系统?
单体应用并不低级。一个管理后台、一个访问量不大的业务系统,用一个应用加一台数据库,反而更容易开发、部署和排查问题。很多系统真正出问题,不是因为一开始用了单体,而是拆得太早,复杂度先涨上来,收益还没出现。
分布式系统一般是在压力出现后才变得有必要。
先是计算压力。 一台机器的 CPU、内存、磁盘 I/O 都有上限。机器配置可以往上堆,但价格、硬件规格和单点风险都会把纵向扩容拦住。把请求分摊到多台机器上,才是多数业务系统后面会走的路。
存储压力也类似。 一张订单表从 100 万行涨到 10 亿行,查询、备份、索引维护、故障恢复都会变重。所有数据继续压在一台机器上,成本和风险都会升高。数据分片会把不同数据拆到不同节点,比如按用户 ID 或订单 ID 分片;副本复制会把同一份数据保存多份,用来提高可用性、容灾能力,顺便分担一部分读请求。它们能解决容量和故障问题,也会把跨分片查询、副本同步、数据一致性带进来。
可用性也会逼着系统往多节点走。 如果只有一台应用服务器,它挂了服务就停;如果只有一台数据库,磁盘损坏或主机故障都会直接影响业务。多个实例、多个副本、多个可用区,至少能让系统在部分节点出问题时继续服务,或者保住一部分能力。
放到业务里,常见动机大概是这几类:
- 性能撑不住:单机处理不了那么多请求,需要多台机器分担计算。
- 数据放不下:单机存储、索引、备份和恢复成本太高,需要分片或副本。
- 故障扛不住:单点故障影响太大,需要冗余、故障转移和降级。
也有一些系统不是被单机容量逼出来的,而是被地域、组织和安全边界推着走。服务和数据部署到离用户更近的地域,可以降低访问延迟,也方便做跨区域容灾;不同团队、业务域或安全域独立部署,可以避免所有功能共享同一个发布窗口和故障边界。
所以,分布式不是“加机器”这么简单。它解决的是容量、可用性、隔离和协作问题,同时把网络、故障、数据一致性和排障成本一起带进系统。
从单体电商到分布式电商
假设有一个早期电商系统,用户、商品、订单、库存、支付都写在一个 Spring Boot 应用里,数据放在同一个 MySQL 实例中。
业务刚开始时,这种结构很舒服。一次下单就是一条本地调用链:校验用户,查商品,扣库存,创建订单,发起支付。代码在一个进程里,事务在一个库里完成。出了问题,看一个应用日志和一个数据库,基本就能把事情查清。
访问量上来后,压力会先落到几个地方:商品详情页查询量高,订单创建写入量高,库存扣减并发冲突多,支付链路又不能随便失败。继续把所有逻辑塞在一个应用里,任何一个模块变慢,都可能拖住整个系统。
这时很多团队会先横向扩容:部署 3 个甚至更多应用实例,用 Nginx、网关或负载均衡器分发请求。只要应用尽量无状态,多加实例就能分担一部分流量。
再往后,系统可能会继续拆:
- 商品服务负责商品信息和价格展示;
- 订单服务负责订单创建、订单状态流转;
- 库存服务负责库存扣减、库存回滚和库存流水;
- 支付服务负责对接第三方支付渠道;
- 消息队列负责把支付成功、库存变更、物流通知等事件异步传出去。
拆分后的好处很直接。商品服务访问量大,可以单独扩容;支付服务对稳定性要求高,可以单独做限流、重试和熔断;库存服务并发冲突多,可以围绕库存扣减设计专门的数据结构和锁策略。
麻烦也很快出现。
订单服务调用库存服务扣库存,如果请求超时了,订单服务到底该不该重试?上一次扣库存是没发出去,还是已经扣成功但响应丢了?如果库存扣成功了,订单创建失败了,库存怎么补?支付成功消息重复投递,订单状态会不会被重复更新?
这些问题在单体里也可能出现,只是分布式系统会把它们放大。系统不再只有一个进程、一份内存、一个事务上下文,很多原来“顺手就做了”的事情,拆开后都要重新设计。
分布式系统有哪些典型特征?
只看机器数量不够。下面这些特征,才是分布式系统复杂度的来源。
多节点协作
一个请求往往要多个节点一起完成。下单请求可能经过网关、订单服务、库存服务、支付服务、消息队列和数据库。每个节点只负责一小段逻辑,拼起来才是一条完整业务链路。
链路拉长后,延迟和故障都会被放大。某个节点线程池打满、数据库慢查询、网络抖动,用户看到的可能只是“下单转圈”。
节点并发执行,没有瞬时全局视图
节点是并发运行的。每个节点只能直接看到自己的本地状态,以及已经收到的消息,不能在某个瞬间读取整个系统的真实状态。
于是就会出现一些看起来矛盾、但都能解释得通的判断:一个节点已经拿到最新配置,另一个节点还停留在旧版本;一个节点认为 Leader 还活着,另一个节点因为超时已经开始选举。很多分布式问题不一定是代码写错了,而是不同节点在不同时间看到了不同信息。
网络通信
节点之间要靠网络交换数据。网络和本地内存不是一类东西,它不保证请求一定到达,也不保证响应按预期时间返回。
一次远程调用可能出现这些情况:
- 请求尚未离开客户端;
- 请求已经到达服务端,但尚未执行完成;
- 服务端已经执行成功,但响应没有到达客户端;
- 客户端已经超时,服务端仍在继续执行;
- 服务端执行失败,但错误响应也没有成功返回。
超时、重试、幂等、熔断和降级,就是为这些情况准备的。远程调用进入主链路后,这些设计不能等线上报错以后再补。
局部故障
单机系统里,进程挂了,问题边界相对清晰。分布式系统里经常是半边好、半边坏:一部分节点正常,一部分节点异常;一部分请求成功,一部分请求失败;A 服务访问不了 B 服务,但 C 服务还能访问 B 服务。
一个节点没响应,也不一定就是宕机了。网络抖动、GC 暂停、线程池打满、磁盘 I/O 卡住,都可能让它短时间“像死了一样”。系统如果只靠“有没有响应”判断故障,很容易误判。
数据复制和数据分片
数据规模和可用性上来后,系统很容易走到分片和复制。
分片是把不同数据分散到不同节点,常见规则包括用户 ID、订单 ID、地域、哈希值。分片之后,单个节点压力小了,跨分片查询、跨分片事务、分片扩容会变麻烦。
复制是把同一份数据保存多份,比如 MySQL 主从复制、Redis 主从复制、Kafka 分区副本、ZooKeeper 多节点副本。有了副本,节点故障时更容易继续服务,读请求也可能分摊到多个副本上。代价是副本同步有延迟:主节点写成功后,从节点可能还没追上;用户刚写完数据,下一次读请求如果落到旧副本,就可能读到旧值。
没有完美同步的全局时钟
每台机器都有自己的物理时钟,但时钟会有偏差和漂移。NTP、GPS 这类时间同步机制可以缩小误差,不能保证所有节点在任意时刻都有完全一致的时间视图。
分布式系统很少只靠墙上时钟判断事件先后。表达因果关系时,可以使用 Lamport Clock、Vector Clock 等逻辑时钟;做复制、选举和状态变更时,也常用 term、epoch、版本号或单调递增序列。
物理时间依然有用,日志、超时、租约、缓存过期都离不开它。只是依赖物理时间时,要知道自己能接受多大的时钟误差和漂移。逻辑时钟解决事件顺序问题,不能直接替代“锁多久后过期”这类物理时间需求。
常见的分布式系统有哪些?
分布式系统不是某一种中间件,而是一类系统形态。常见类型有这些。
| 类型 | 解决的问题 | 常见例子 |
|---|---|---|
| 分布式协调系统 | 选主、配置管理、服务发现、分布式锁 | ZooKeeper、etcd、Consul |
| 分布式数据库 | 数据分片、副本复制、水平扩展 | TiDB、CockroachDB、Cassandra、HBase |
| 分布式缓存 | 多节点缓存、热点数据加速、缓存容量扩展 | Redis Cluster、Memcached 集群 |
| 分布式消息队列 | 异步解耦、削峰填谷、事件驱动 | Kafka、RocketMQ、Pulsar |
| 分布式文件/对象存储 | 大文件存储、多副本、高吞吐读写 | HDFS、Ceph、MinIO |
| RPC 框架 | 接口定义、序列化、跨服务请求响应 | gRPC、Apache Thrift |
| 服务治理体系 | 注册发现、负载均衡、流量管理、熔断、配置 | Dubbo、Spring Cloud |
这些系统解决的问题不同,但经常一起出现在一个业务架构里。一个订单系统可能用 Redis 做缓存,用 RocketMQ 传递订单事件,用 ZooKeeper 或 Nacos 做注册发现,用 MySQL 分库分表存订单,再用链路追踪系统排查一次请求经过了哪些服务。
学分布式系统时,不要只盯着某个中间件背参数。要问它放在系统里解决了什么问题,又把哪些复杂度留给了业务方。
分布式系统、集群和微服务有什么区别?
这几个词经常混在一起,但指向的不是同一件事。
集群更强调部署形态。多台机器一起提供服务,就可以叫集群。比如 3 个 Nginx 实例、5 个 Redis 节点、3 个应用实例。它们可能做同样的事情,也可能有主从、分片、选主等分工。
分布式系统更强调节点之间的协作。多个节点靠网络通信,共同完成一个任务,对外表现为一个整体。集群可以是分布式系统的一种形态,但分布式系统还会涉及数据复制、一致性、容错、调度和协调。
微服务是一种应用架构风格。它把业务系统拆成多个围绕业务能力组织的服务,每个服务可以独立开发、部署和扩容。微服务系统一般也是分布式系统,因为服务之间要走网络调用。不过,分布式系统不一定是微服务。Kafka、HDFS、ZooKeeper 本身都是分布式系统,但不是业务微服务。
还有一种情况也很常见:一个业务应用还是单体,但它依赖 Redis Cluster、Kafka、Elasticsearch、MySQL 主从。这个业务应用本身没有拆成微服务,但它运行在一组分布式基础设施之上。
分布式系统难在哪里?
分布式系统难,不是因为概念听起来高级,而是失败情况太多。网络会让一次操作的结果变得不确定,独立故障又会让不同节点同时看到不同的系统状态。
本地调用和调用方处在同一个进程或故障范围内,执行结果相对好判断。远程调用多了一层不确定性:超时只说明客户端在指定时间内没有收到响应,不能证明服务端没有执行。请求可能没发出去,也可能已经执行成功但响应丢了。这个差别会直接影响重试策略。
比如订单服务调用库存服务扣库存,客户端设置了 2 秒超时。2 秒后订单服务没收到响应,它有几种选择:
- 直接认为扣库存失败,取消订单;
- 重新调用一次库存服务;
- 查询库存扣减流水,确认上一次请求是否成功;
- 先把订单置为处理中,后续通过消息或定时任务补偿。
每种选择都有代价。直接取消订单可能误判,因为库存服务也许已经扣成功;直接重试可能重复扣库存;查询流水要求库存服务提供幂等号和可查询记录;异步补偿会让用户看到“处理中”状态,产品体验也要配合。
幂等就是在这种场景下变得重要的。只要存在超时和重试,同一个业务请求就可能被处理多次。服务端必须能识别“这是同一次业务操作”,不能因为客户端重试就重复扣款、重复扣库存、重复发券。对有副作用的远程写操作,还要设计业务幂等号、结果查询、有限重试,以及指数退避和随机抖动,避免下游故障时被重试流量继续压垮。
数据一致性也是类似问题。单体应用里,一个数据库事务可以同时更新订单表和库存表;拆成订单服务和库存服务后,订单库和库存库不在同一个事务里。想让它们要么都成功、要么都失败,就需要分布式事务、事务消息、TCC、Saga、本地消息表等方案。
很多跨服务业务会接受短时间状态不一致,再用事务消息、重试、补偿和对账,让订单、库存、支付等状态最终满足业务约束。工程里也常把这种方案叫“最终一致性”。它和副本一致性模型里的 eventual consistency 不是同一个语境:后者强调不再发生新写入时,多个数据副本最终收敛;前者更偏向跨服务业务流程的异步协调。
排查问题也会变慢。一次请求经过 6 个服务,任何一个服务日志不规范、链路追踪缺失、错误码设计混乱,定位都会很费劲。生产环境里缺少观测能力时,很难判断请求卡在哪个节点,错误最早从哪里冒出来。
业务型分布式系统常见的基础能力
下面这些能力常见于微服务和在线业务系统,属于工程配套,不是分布式系统定义的一部分。服务成员关系也不一定非要靠独立注册中心维护,DNS、静态配置、Gossip 或集群协议都可能用得上。
服务发现和负载均衡:服务实例会扩容、缩容、重启,调用方不能把服务地址写死。注册中心记录服务实例,负载均衡从可用实例里选一个进行调用。
超时、重试和幂等:远程调用必须设置超时。重试要谨慎,只适合可重试且有幂等保护的操作。支付、扣库存、发券这类操作,一定要有业务唯一号或幂等表兜底。
熔断、限流和降级:下游服务变慢或失败时,上游不能无限等待和重试,否则故障会沿着调用链扩散。熔断用于快速失败,限流用于控制入口压力,降级用于保住主链路。
配置管理和动态变更:服务数量多了以后,配置不能只靠本地文件手动改。配置中心可以统一管理配置,并支持灰度发布、动态刷新和回滚。
日志、指标和链路追踪:日志回答“发生了什么”,指标回答“现在健康吗”,链路追踪回答“一次请求经过了哪里”。这 3 类数据放在一起,排查分布式问题才有抓手。
数据一致性和补偿机制:跨节点写数据时,要提前设计失败后的处理方式。是强一致、最终一致,还是允许短时间不一致?失败后靠重试、人工处理、对账修复,还是业务回滚?这些问题不能等线上出错后再补。
分布式系统怎么学?
学习分布式系统,不建议一上来背算法名。先从工程问题往理论走,会顺很多。
第一步看网络通信。HTTP、RPC、TCP、超时、重试、连接池、序列化,这些内容决定服务之间怎么说话。没有这部分基础,后面看服务治理会很虚。
第二步看服务拆分和服务治理。服务为什么要拆,拆完以后怎么注册发现、负载均衡、限流熔断、链路追踪,怎么处理版本兼容和灰度发布。微服务的大部分日常问题都在这一层。
第三步补数据层:复制、分片、缓存、消息队列、分布式 ID、分布式锁、分布式事务。这里要多问异常场景,比如消息重复投递怎么办、缓存和数据库不一致怎么办、锁过期但业务还没执行完怎么办。
最后再看 CAP、BASE、中心化与去中心化、Paxos、Raft、ZAB、Gossip、一致性哈希这些理论和协议。学习这些内容,重点是理解 ZooKeeper、etcd、Kafka、Redis Cluster、分布式数据库这些系统为什么这样设计。
这里建议先读 分布式协调详解。它把 Leader、Quorum、脑裂、Lease、Fencing Token 和 Gossip 放在同一条主线里,能帮你在进入 Raft、ZAB、Gossip 细节之前,先明白“谁来做决定、状态怎么传播、错了会怎样”。
还要分清共识和分布式事务。Paxos、Raft、ZAB 主要解决一组副本如何对日志顺序、Leader 或状态变更达成一致;TCC、Saga、事务消息主要解决多个业务参与方之间如何协调提交和补偿。它们可能出现在同一个系统里,但处理的问题不同。
入门阶段先把这 5 个问题讲清楚,比背一串术语更有用:
- 为什么单机系统要拆成多节点?
- 远程调用和本地调用有什么差别?
- 为什么分布式系统里超时不能简单等同于失败?
- 为什么多副本会引入一致性问题?
- 为什么跨服务写数据通常要考虑幂等、补偿和最终一致性?
- 为什么有些系统需要 Leader,有些系统更适合用 Gossip 传播状态?
参考资料
- 什么是分布式系统,如何学习分布式系统
- 现在主流开源分布式系统架构都有哪些?
- 常见分布式系统设计图解(汇总)
- A brief introduction to distributed systems
- MIT 6.824 Distributed Systems
- Time, Clocks, and the Ordering of Events in a Distributed System
- Timeouts, retries and backoff with jitter
- Eventually Consistent - Revisited
- Raft Consensus Algorithm
- 《Designing Data-Intensive Applications》
- 《分布式系统概念与设计》
Javaguide Intro (`docs/javaguide/intro.md`)
title: 项目介绍 category: 走近项目 icon: "mdi:information-outline"
我是 19 年大学毕业的,在大三准备面试的时候,我开源了 JavaGuide 。我把自己准备面试过程中的一些总结都毫不保留地通过 JavaGuide 分享了出来。
开源 JavaGuide 初始想法源于自己的个人那一段比较迷茫的学习经历,主要目的是为了通过这个开源平台来帮助一些在学习 Java 或者面试过程中遇到问题的小伙伴。
- 对于 Java 初学者来说: 本文档倾向于给你提供一个比较详细的学习路径,目录清晰,让你对于 Java 整体的知识体系有一个清晰认识。你可以跟着视频或者书籍学习完某个知识点之后,然后来这里找对应的总结,帮助你更好地掌握对应的知识点。
- 对于非 Java 初学者来说: 本文档更适合回顾知识,准备面试,让你搞清面试应该把重心放在哪些问题上。
由于本网站的内容完全开源,由大家共同维护完善,因此内容质量比较高。 如果你也想要参与 JavaGuide 的维护建设工作,请查阅贡献指南。
相比于其他通过 JavaGuide 学到东西或者说助力获得 offer 的朋友来说 , JavaGuide 对我的意义更加重大。不夸张的说,有时候真的感觉像是自己的孩子一点一点长大一样,我一直用心呵护着它。虽然,我花了很长时间来维护它,但是,我觉得非常值得!非常有意义!
不过,希望大家对面试不要抱有侥幸的心理,打铁还需自身硬! 我希望这个文档是为你学习 Java 指明方向,而不仅仅是用来应付面试用的。
另外,JavaGuide 不可能把面试中的所有内容都给涵盖住,尤其是阿里、美团这种挖的比较深入的面试。你可以根据你的目标公司进行针对性的深入学习,多看一些目标公司的面经进行查漏补缺,没事就自测一下,多多思考总结。
加油!奥利给!
官方知识星球
对于准备面试的同学来说,强烈推荐我创建的一个纯粹的Java 面试知识星球,干货非常多,学习氛围也很不错!
下面是星球提供的部分服务(点击下方图片即可获取知识星球的详细介绍):
项目说明
- 在线阅读网站基于 Vuepress 搭建,使用的是主题是 vuepress-theme-hope(很 Nice 的一个主题)。
- 项目的 Markdown 格式参考:GitHub Markdown 格式,表情素材来自:EMOJI CHEAT SHEET。
- Logo 下的小图标是使用Shields.IO 生成的。
贡献者
你可以点此链接查看 JavaGuide 的所有贡献者。 感谢你们让 JavaGuide 变得更好!如果你们来到武汉一定要找我,我请你们吃饭玩耍。
Cs Basics Operating System Linux Intro (`docs/cs-basics/operating-system/linux-intro.md`)
title: Linux 基础知识总结
category: 计算机基础
tag:
- 操作系统
- Linux
head: - meta
- name: keywords
content: Linux,基础命令,发行版,文件系统,权限,进程,网络
简单介绍一下 Java 程序员必知的 Linux 的一些概念以及常见命令。
初探 Linux
Linux 简介
通过以下三点可以概括 Linux 到底是什么:
- 类 Unix 系统:Linux 是一种自由、开放源码的类似 Unix 的操作系统。
- Linux 本质是指 Linux 内核:严格来讲,Linux 这个词本身只表示 Linux 内核,单独的 Linux 内核并不能成为一个可以正常工作的操作系统。所以,就有了各种 Linux 发行版。
- Linux 之父(林纳斯·本纳第克特·托瓦兹 Linus Benedict Torvalds):一个编程领域的传奇式人物,真大佬!我辈崇拜敬仰之楷模。他是 Linux 内核 的最早作者,随后发起了这个开源项目,担任 Linux 内核的首要架构师。他还发起了 Git 这个开源项目,并为主要的开发者。
Linux 诞生
1989 年,Linus Torvalds 进入芬兰陆军新地区旅,服 11 个月的国家义务兵役,军衔为少尉,主要服务于计算机部门,任务是弹道计算。服役期间,购买了安德鲁·斯图尔特·塔能鲍姆所著的教科书及 minix 源代码,开始研究操作系统。1990 年,他退伍后回到大学,开始接触 Unix。
Minix 是一个迷你版本的类 Unix 操作系统,由塔能鲍姆教授为了教学之用而创作,采用微核心设计。它启发了 Linux 内核的创作。
1991 年,Linus Torvalds 开源了 Linux 内核。Linux 以一只可爱的企鹅作为标志,象征着敢作敢为、热爱生活。
常见的 Linux 发行版本
Linus Torvalds 开源的只是 Linux 内核,我们上面也提到了操作系统内核的作用。一些组织或厂商将 Linux 内核与各种软件和文档包装起来,并提供系统安装界面和系统配置、设定与管理工具,就构成了 Linux 的发行版本。
内核主要负责系统的内存管理,硬件设备的管理,文件系统的管理以及应用程序的管理。
Linux 的发行版本可以大体分为两类:
- 商业公司维护的发行版本:比如 Red Hat 公司维护支持的 Red Hat Enterprise Linux(RHEL)。
- 社区组织维护的发行版本:比如基于 Red Hat Enterprise Linux(RHEL)的 CentOS、基于 Debian 的 Ubuntu。
对于初学者学习 Linux,不建议再无条件选择 CentOS。CentOS Linux 8 已在 2021 年底停止维护,CentOS Linux 7 也已在 2024 年 6 月结束生命周期;现在的 CentOS Stream 是 RHEL 的上游持续交付分支,定位和过去“稳定的 RHEL 兼容重构版”不一样。
更稳妥的选择是:
- 想学习企业服务器环境、RHEL 生态:优先选择 Rocky Linux 或 AlmaLinux。
- 想快速上手、资料多、桌面和服务器都常见:选择 Ubuntu LTS。
- 想要稳定、轻量、贴近社区发行版:选择 Debian。
如果你的公司环境仍在使用 CentOS,可以按实际环境学习对应版本;但新装学习环境时,更推荐选择仍在维护的发行版。
Linux 文件系统
Linux 文件系统简介
在 Linux 操作系统中,一切被操作系统管理的资源,如网络接口卡、磁盘驱动器、打印机、输入输出设备、普通文件或目录等,都被视为文件。这是 Linux 系统中一个重要的概念,即“一切都是文件”。
这种概念源自 UNIX 哲学,即将所有资源都抽象为文件的方式来进行管理和访问。Linux 的文件系统也借鉴了 UNIX 文件系统的设计理念。这种设计使得 Linux 系统可以通过统一的文件接口来管理和操作不同类型的资源,从而实现了一种统一的文件操作方式。例如,可以使用类似于读写文件的方式来对待网络接口、磁盘驱动器、设备文件等,使得操作和管理这些资源更加统一和简便。
这种文件为中心的设计理念为 Linux 系统带来了灵活性和可扩展性,使得 Linux 成为一种强大的操作系统。同时,这也是 Linux 系统的一大特点,深受广大用户和开发者的喜欢和推崇。
inode 介绍
inode 是 Linux/Unix 文件系统的基础。那 inode 到底是什么?有什么作用呢?
通过以下五点可以概括 inode 到底是什么:
- 硬盘以扇区(Sector)为最小物理存储单位,而操作系统和文件系统通常以块(Block)为单位进行读写,块由多个扇区组成。传统磁盘扇区常见大小是 512 字节,现代磁盘也常见 4 KB 物理扇区(例如 512e/4Kn 设备);文件系统块大小也常见 4 KB,但两者不是一个概念。文件元信息(例如权限、大小、修改时间以及数据块或 extent 的映射)通常记录在 inode(索引节点)中。inode 号只保证在同一个文件系统内唯一,多个硬链接目录项可以指向同一个 inode。固态硬盘(SSD)虽然没有传统机械磁盘意义上的物理扇区,但仍然对外暴露逻辑块接口。
- 在 ext2/ext3/ext4 这类文件系统中,磁盘上的 inode 记录大小会在创建文件系统时确定;其他 Linux 文件系统的元数据布局可能不同。
- inode 的访问速度非常快,因为系统可以直接通过 inode 号码定位到文件的元数据信息,无需遍历整个文件系统。
- ext2/ext3/ext4 等文件系统会在创建文件系统时确定可用 inode 数量,inode 用完后,即使还有数据块空间也无法创建新文件。并非所有 Linux 文件系统都采用固定 inode 表,排查时要结合具体文件系统实现。
- 可以使用
stat命令查看文件的 inode 信息,包括文件的 inode 号、文件类型、权限、所有者、文件大小、修改时间。
简单来说:inode 就是用来维护某个文件被分成几块、每一块在的地址、文件拥有者、创建时间、权限、大小等信息。
再总结一下 inode 和 block:
- inode:记录文件的属性信息,可以使用
stat命令查看 inode 信息。 - block/extent:用于保存文件内容或描述连续的数据块范围。具体分配和共享方式取决于文件系统,不能概括成“一个块永远只属于一个文件”。
Linux/Unix 文件系统使用 inode 标识文件系统对象。同一文件系统内重命名文件时,通常只是修改目录项,inode 号不会改变;删除最后一个硬链接后,如果也没有进程继续打开该文件,inode 会被释放,其编号以后可能被复用。通过路径访问文件时仍要先解析目录项,不能绕过路径查找直接把 inode 号当成稳定的全局文件标识。
不过,使用 inode 号码也使得文件系统在用户和应用程序层面更加抽象和复杂,需要通过系统命令或文件系统接口来访问和管理文件的 inode 信息。
硬链接和软链接
在 Linux/类 Unix 系统上,硬链接和符号链接的实现并不相同:硬链接是另一个指向同一 inode 的目录项,符号链接则是具有独立 inode 的特殊文件。
1、硬链接(Hard Link)
- 在 Linux/类 Unix 文件系统中,每个文件和目录都有一个唯一的索引节点(inode)号,用来标识该文件或目录。硬链接通过 inode 节点号建立连接,硬链接和源文件的 inode 节点号相同,两者对文件系统来说是完全平等的(可以看作是互为硬链接,源头是同一份文件),删除其中任何一个对另外一个没有影响,可以通过给文件设置硬链接文件来防止重要文件被误删。
- 只有删除了源文件和所有对应的硬链接文件,该文件才会被真正删除。
- 硬链接具有一些限制,不能对目录以及不存在的文件创建硬链接,并且,硬链接也不能跨越文件系统。
ln命令用于创建硬链接。
2、软链接(Symbolic Link 或 Symlink)
- 软链接和源文件的 inode 节点号不同,而是指向一个文件路径。
- 源文件删除后,软链接依然存在,但是指向的是一个无效的文件路径。
- 软链接类似于 Windows 系统中的快捷方式。
- 不同于硬链接,可以对目录或者不存在的文件创建软链接,并且,软链接可以跨越文件系统。
ln -s命令用于创建软链接。
硬链接为什么不能跨文件系统?
我们之前提到过,硬链接是通过 inode 节点号建立连接的,而硬链接和源文件共享相同的 inode 节点号。
每个文件系统都有独立的 inode 命名空间,目录项只能引用本文件系统内的 inode,无法直接指向另一个文件系统中的 inode。因此,硬链接不能跨越文件系统边界,这不是简单的 inode 号冲突问题。
Linux 文件类型
Linux 支持很多文件类型,其中非常重要的文件类型有:普通文件、目录文件、链接文件、设备文件、管道文件、Socket 套接字文件 等。
- 普通文件(-):用于存储信息和数据,Linux 用户可以根据访问权限对普通文件进行查看、更改和删除。比如:图片、声音、PDF、text、视频、源代码等等。
- 目录文件(d,directory file):目录也是文件的一种,用于表示和管理系统中的文件,目录文件中包含一些文件名和子目录名。打开目录事实上就是打开目录文件。
- 符号链接文件(l,symbolic link):保存目标路径字符串。访问符号链接时,内核会按这个路径重新解析目标。
- 字符设备(c,char):用来访问字符设备比如键盘。
- 设备文件(b,block):用来访问块设备比如硬盘、软盘。
- 管道文件(p,pipe):一种特殊类型的文件,用于进程之间的通信。
- 套接字文件(s,socket):用于进程间的网络通信,也可以用于本机之间的非网络通信。
每种文件类型都有不同的用途和属性,可以通过命令如 ls、file 等来查看文件的类型信息。
# 普通文件(-)
-rw-r--r-- 1 user group 1024 Apr 14 10:00 file.txt
# 目录文件(d,directory file)
drwxr-xr-x 2 user group 4096 Apr 14 10:00 directory/
# 套接字文件(s,socket)
srwxrwxrwx 1 user group 0 Apr 14 10:00 socketLinux 目录树
Linux 使用一种称为目录树的层次结构来组织文件和目录。目录树由根目录(/)作为起始点,向下延伸,形成一系列的目录和子目录。每个目录可以包含文件和其他子目录。结构层次鲜明,就像一棵倒立的树。
常见目录说明:
- /bin: 存放二进制可执行文件(ls、cat、mkdir 等),常用命令一般都在这里。
- /etc: 存放系统管理和配置文件。
- /home: 存放所有用户文件的根目录,是用户主目录的基点,比如用户 user 的主目录就是 /home/user,可以用 ~user 表示。
- /usr: 用于存放系统应用程序。
- /opt: 额外安装的可选应用程序包所放置的位置。一般情况下,我们可以把 tomcat 等都安装到这里。
- /proc: 虚拟文件系统目录,是系统内存的映射。可直接访问这个目录来获取系统信息。
- /root: 超级用户(系统管理员)的主目录(特权阶级^o^)。
- /sbin: 存放二进制可执行文件,只有 root 才能访问。这里存放的是系统管理员使用的系统级别的管理命令和程序。如 ifconfig 等。
- /dev: 用于存放设备文件。
- /mnt: 系统管理员安装临时文件系统的安装点,系统提供这个目录是让用户临时挂载其他的文件系统。
- /boot: 存放用于系统引导时使用的各种文件。
- /lib 和 /lib64: 存放着和系统运行相关的库文件。
- /tmp: 用于存放各种临时文件,是公用的临时文件存储点。
- /var: 用于存放运行时需要改变数据的文件,也是某些大文件的溢出区,比方说各种服务的日志文件(系统启动日志等)等。
- /lost+found: 这个目录平时是空的,系统非正常关机而留下“无家可归”的文件(Windows 下叫什么 .chk)就在这里。
Linux 常用命令
下面只是给出了一些比较常用的命令。
推荐一个 Linux 命令快查网站,非常不错,大家如果遗忘某些命令或者对某些命令不理解都可以在这里得到解决。Linux 命令在线速查手册:https://wangchujiang.com/linux-command/。
另外,shell.how 这个网站可以用来解释常见命令的意思,对你学习 Linux 基本命令以及其他常用命令(如 Git、NPM)。
目录切换
cd usr:切换到该目录下 usr 目录。cd ..(或 cd../):切换到上一层目录。cd /:切换到系统根目录。cd ~:切换到用户主目录。cd -: 切换到上一个操作所在目录。
目录操作
ls:显示目录中的文件和子目录的列表。例如:ls /home,显示/home目录下的文件和子目录列表。ll:ll是ls -l的别名,ll 命令可以看到该目录下的所有目录和文件的详细信息。mkdir [选项] 目录名:创建新目录(增)。例如:mkdir -m 755 my_directory,创建一个名为my_directory的新目录,并将其权限设置为 755,其中所有者拥有读、写、执行权限,所属组和其他用户只有读、执行权限,无法修改目录内容(如创建或删除文件)。如果希望所有用户(包括所属组和其他用户)对目录都拥有读、写、执行权限,则应设置权限为777,即:mkdir -m 777 my_directory。find [路径] [表达式]:在指定目录及其子目录中搜索文件或目录(查),非常强大灵活。例如:① 列出当前目录及子目录下所有文件和文件夹:find .;② 在/home目录下查找以.txt结尾的文件名:find /home -name "*.txt",忽略大小写:find /home -iname "*.txt";③ 当前目录及子目录下查找所有以.txt和.pdf结尾的文件:find . \( -name "*.txt" -o -name "*.pdf" \)或find . -name "*.txt" -o -name "*.pdf"。pwd:显示当前工作目录的路径。rmdir [选项] 目录名:删除空目录(删)。例如:rmdir -p my_directory,删除名为my_directory的空目录,并且会递归删除my_directory的空父目录,直到遇到非空目录或根目录。rm [选项] 文件或目录名:删除文件/目录(删)。例如:rm -r my_directory,删除名为my_directory的目录,-r(recursive,递归)表示会递归删除指定目录及其所有子目录和文件。cp [选项] 源文件/目录 目标文件/目录:复制文件或目录(移)。例如:cp file.txt /home/file.txt,将file.txt文件复制到/home目录下,并重命名为file.txt。cp -r source destination,将source目录及其下的所有子目录和文件复制到destination目录下,并保留源文件的属性和目录结构。mv [选项] 源文件/目录 目标文件/目录:移动文件或目录(移),也可以用于重命名文件或目录。例如:mv file.txt /home/file.txt,将file.txt文件移动到/home目录下,并重命名为file.txt。mv与cp的结果不同,mv好像文件“搬家”,文件个数并未增加。而cp对文件进行复制,文件个数增加了。
文件操作
像 mv、cp、rm 等文件和目录都适用的命令,这里就不重复列举了。
touch [选项] 文件名..:创建新文件或更新已存在文件(增)。例如:touch file1.txt file2.txt file3.txt,创建 3 个文件。ln [选项] <源文件> <硬链接/软链接文件>:创建硬链接/软链接。例如:ln -s file.txt file_link,创建名为file_link的软链接,指向file.txt文件。-s选项代表的就是创建软链接,s 即 symbolic(软链接又名符号链接)。cat/more/less/tail 文件名:文件的查看(查)。命令tail -f 文件可以对某个文件进行动态监控,例如 Tomcat 的日志文件,会随着程序的运行,日志会变化,可以使用tail -f catalina-2016-11-11.log监控文件的变化。vim 文件名:修改文件的内容(改)。vim 编辑器是 Linux 中的强大组件,是 vi 编辑器的加强版,vim 编辑器的命令和快捷方式有很多,但此处不一一阐述,大家也无需研究的很透彻,使用 vim 编辑修改文件的方式基本会使用就可以了。在实际开发中,使用 vim 编辑器主要作用就是修改配置文件,下面是一般步骤:vim 文件------>进入文件----->命令模式------>按 i 进入编辑模式----->编辑文件------->按 Esc 进入底行模式----->输入:wq/q!(输入 wq 代表写入内容并退出,即保存;输入 q! 代表强制退出不保存)。
文件压缩
1)打包并压缩文件:
Linux 中的打包文件一般是以 .tar 结尾的,压缩的命令一般是以 .gz 结尾的。而一般情况下打包和压缩是一起进行的,打包并压缩后的文件的后缀名一般 .tar.gz。
命令:tar -zcvf 打包压缩后的文件名 要打包压缩的文件,其中:
- z:调用 gzip 压缩命令进行压缩。
- c:打包文件。
- v:显示运行过程。
- f:指定文件名。
比如:假如 test 目录下有三个文件分别是:aaa.txt、bbb.txt、ccc.txt,如果我们要打包 test 目录并指定压缩后的压缩包名称为 test.tar.gz 可以使用命令:tar -zcvf test.tar.gz aaa.txt bbb.txt ccc.txt 或 tar -zcvf test.tar.gz /test/。
2)解压压缩包:
命令:tar [-xvf] 压缩文件
其中 x 代表解压。
示例:
- 将
/test下的test.tar.gz解压到当前目录下可以使用命令:tar -xvf test.tar.gz。 - 将 /test 下的 test.tar.gz 解压到根目录 /usr 下:
tar -xvf test.tar.gz -C /usr(-C代表指定解压的位置)。
文件传输
scp [选项] 源文件 远程文件(scp 即 secure copy,安全复制):用于通过 SSH 协议进行安全的文件传输,可以实现从本地到远程主机的上传和从远程主机到本地的下载。例如:scp -r my_directory user@remote:/home/user,将本地目录my_directory上传到远程服务器/home/user目录下。scp -r user@remote:/home/user/my_directory,将远程服务器的/home/user目录下的my_directory目录下载到本地。需要注意的是,scp命令需要在本地和远程系统之间建立 SSH 连接进行文件传输,因此需要确保远程服务器已经配置了 SSH 服务,并且具有正确的权限和认证方式。rsync [选项] 源文件 远程文件:可以在本地和远程系统之间高效地进行文件复制,并且能够智能地处理增量复制,节省带宽和时间。例如:rsync -r my_directory user@remote:/home/user,将本地目录my_directory上传到远程服务器/home/user目录下。ftp(File Transfer Protocol):提供了一种简单的方式来连接到远程 FTP 服务器并进行文件上传、下载、删除等操作。使用之前需要先连接登录远程 FTP 服务器,进入 FTP 命令行界面后,可以使用put命令将本地文件上传到远程主机,可以使用get命令将远程主机的文件下载到本地,可以使用delete命令删除远程主机的文件。这里就不进行演示了。
文件权限
操作系统中每个文件都拥有特定的权限、所属用户和所属组。权限是操作系统用来限制资源访问的机制,在 Linux 中权限一般分为读(readable)、写(writable)和执行(executable),分为三组。分别对应文件的属主(owner)、属组(group)和其他用户(other),通过这样的机制来限制哪些用户、哪些组可以对特定的文件进行什么样的操作。
通过 ls -l 命令我们可以查看某个目录下的文件或目录的权限。
示例:在随意某个目录下 ls -l
第一列的内容的信息解释如下:
下面将详细讲解文件的类型、Linux 中权限以及文件有所有者、所在组、其它组具体是什么?
文件的类型:
- d:代表目录。
- -:代表文件。
- l:代表软链接(可以认为是 window 中的快捷方式)。
Linux 中权限分为以下几种:
- r:代表权限是可读,r 也可以用数字 4 表示。
- w:代表权限是可写,w 也可以用数字 2 表示。
- x:代表权限是可执行,x 也可以用数字 1 表示。
文件和目录权限的区别:
对文件和目录而言,读写执行表示不同的意义。
对于文件:
| 权限名称 | 可执行操作 |
|---|---|
| r | 可以使用 cat 查看文件的内容 |
| w | 可以修改文件的内容 |
| x | 可以将其运行为二进制文件 |
对于目录:
| 权限名称 | 可执行操作 |
|---|---|
| r | 可以查看目录下列表 |
| w | 可以创建和删除目录下文件 |
| x | 可以使用 cd 进入目录 |
传统 root 通常拥有绕过普通文件自主访问控制(DAC)检查所需的 capability,但这不代表它能绕过 capabilities、LSM、挂载选项、immutable 标志等所有限制。权限为 000 的对象也不能简单概括成 root 一定可以执行或访问。
在 Linux 中的每个用户必须属于一个组,不能独立于组外。在 Linux 中每个文件有所有者、所在组、其它组的概念。
- 所有者(u):一般为文件的创建者,谁创建了该文件,就天然的成为该文件的所有者,用
ls ‐ahl命令可以看到文件的所有者,也可以使用 chown 用户名 文件名来修改文件的所有者。 - 文件所在组(g):当某个用户创建了一个文件后,这个文件的所在组就是该用户所在的组,用
ls ‐ahl命令可以看到文件的所有组,也可以使用 chgrp 组名 文件名来修改文件所在的组。 - 其它组(o):除开文件的所有者和所在组的用户外,系统的其它用户都是文件的其它组。
我们再来看看如何修改文件/目录的权限。
修改文件/目录的权限的命令:chmod
示例:修改 /test 下的 aaa.txt 的权限为文件所有者有全部权限,文件所有者所在的组有读写权限,其他用户只有读的权限。
chmod u=rwx,g=rw,o=r aaa.txt 或者 chmod 764 aaa.txt
补充一个比较常用的东西:
假如我们装了一个 zookeeper,我们每次开机到要求其自动启动该怎么办?
现在主流 Linux 发行版基本使用 systemd 管理服务,推荐做法是编写一个 zookeeper.service 单元文件,然后使用下面的命令设置开机自启:
sudo systemctl enable zookeeper
sudo systemctl start zookeeper
sudo systemctl status zookeeper如果修改了 service 文件,需要先执行 sudo systemctl daemon-reload 让 systemd 重新加载配置。chkconfig --add zookeeper、chkconfig --list 属于 SysV init 时代的做法,只有在较老的发行版或兼容环境中才会用到。
用户管理
Linux 系统是一个多用户多任务的分时操作系统,任何一个要使用系统资源的用户,都必须首先向系统管理员申请一个账号,然后以这个账号的身份进入系统。
用户的账号一方面可以帮助系统管理员对使用系统的用户进行跟踪,并控制他们对系统资源的访问;另一方面也可以帮助用户组织文件,并为用户提供安全性保护。
Linux 用户管理相关命令:
useradd [选项] 用户名:创建用户账号。使用useradd指令所建立的帐号,实际上是保存在/etc/passwd文本文件中。userdel [选项] 用户名:删除用户帐号。usermod [选项] 用户名:修改用户账号的属性和配置比如用户名、用户 ID、家目录。passwd [选项] 用户名:设置用户的认证信息,包括用户密码、密码过期时间等。例如:passwd -S 用户名显示账号密码状态;passwd -d 用户名会删除密码,使密码字段为空,是否允许空密码登录取决于 PAM 和登录服务配置;passwd -l 用户名只锁定密码认证,SSH 密钥等其他认证方式仍可能可用;passwd 用户名用于修改密码。su [选项] 用户名(su 即 Switch User,切换用户):在当前登录的用户和其他用户之间切换身份。
用户组管理
每个用户都有一个用户组,系统可以对一个用户组中的所有用户进行集中管理。不同 Linux 系统对用户组的规定有所不同,如 Linux 下的用户属于与它同名的用户组,这个用户组在创建用户时同时创建。
用户组的管理涉及用户组的添加、删除和修改。组的增加、删除和修改实际上就是对 /etc/group 文件的更新。
Linux 系统用户组的管理相关命令:
groupadd [选项] 用户组:增加一个新的用户组。groupdel 用户组:要删除一个已有的用户组。groupmod [选项] 用户组:修改用户组的属性。
系统状态
top [选项]:用于实时查看系统的 CPU 使用率、内存使用率、进程信息等。htop [选项]:类似于top,但提供了更加交互式和友好的界面,可让用户交互式操作,支持颜色主题,可横向或纵向滚动浏览进程列表,并支持鼠标操作。uptime [选项]:用于查看系统总共运行了多长时间、系统的平均负载等信息。vmstat [间隔时间] [重复次数]:vmstat(Virtual Memory Statistics)的含义为显示虚拟内存状态,但是它可以报告关于进程、内存、I/O 等系统整体运行状态。free [选项]:用于查看系统的内存使用情况,包括已用内存、可用内存、缓冲区和缓存等。df [选项] [文件系统]:用于查看系统的磁盘空间使用情况,包括磁盘空间的总量、已使用量和可用量等,可以指定文件系统上。例如:df -a,查看全部文件系统。du [选项] [文件]:用于查看指定目录或文件的磁盘空间使用情况,可以指定不同的选项来控制输出格式和单位。sar [选项] [时间间隔] [重复次数]:用于收集、报告和分析系统的性能统计信息,包括系统的 CPU 使用、内存使用、磁盘 I/O、网络活动等详细信息。它的特点是可以连续对系统取样,获得大量的取样数据。取样数据和分析的结果都可以存入文件,使用它时消耗的系统资源很小。ps [选项]:用于查看系统中的进程信息,包括进程的 ID、状态、资源使用情况等。ps -ef/ps -aux:这两个命令都是查看当前系统正在运行进程,两者的区别是展示格式不同。如果想要查看特定的进程可以使用这样的格式:ps aux|grep redis(查看包括 redis 字符串的进程),也可使用pgrep redis -a。systemctl [命令] [服务名称]:用于管理系统的服务和单元,可以查看系统服务的状态、启动、停止、重启等。journalctl [选项]:用于查看 systemd 日志,排查服务启动失败、系统错误非常常用。例如:journalctl -u nginx -f实时查看 nginx 服务日志,journalctl -xe查看最近的系统错误上下文。
网络通信
ping [选项] 目标主机:测试与目标主机的网络连接。ifconfig或ip:用于查看系统的网络接口信息,包括网络接口的 IP 地址、MAC 地址、状态等。netstat [选项]:用于查看系统的网络连接状态和网络统计信息,可以查看当前的网络连接情况、监听端口、网络协议等。ss [选项]:比netstat更好用,提供了更快速、更详细的网络连接信息。nload:sar和nload都可以监控网络流量,但sar的输出是文本形式的数据,不够直观。nload则是一个专门用于实时监控网络流量的工具,提供图形化的终端界面,更加直观。不过,nload不保存历史数据,所以它不适合用于长期趋势分析。并且,系统并没有默认安装它,需要手动安装。sudo hostnamectl set-hostname 新主机名:更改主机名,并且重启后依然有效。sudo hostname 新主机名也可以更改主机名。不过需要注意的是,使用hostname命令直接更改主机名只是临时生效,系统重启后会恢复为原来的主机名。
其他
sudo + 其他命令:以系统管理者的身份执行指令,也就是说,经由 sudo 所执行的指令就好像是 root 亲自执行。grep [选项] "搜索内容" 文件路径:非常强大且常用的文本搜索命令,它可以根据指定的字符串或正则表达式,在文件或命令输出中进行匹配查找,适用于日志分析、文本过滤、快速定位等多种场景。示例:忽略大小写搜索 syslog 中所有包含 error 的行:grep -i "error" /var/log/syslog,查找所有与 java 相关的进程:ps -ef | grep "java"。kill -9 进程的 pid:杀死进程(-9 表示强制终止),先用 ps 查找进程,然后用 kill 杀掉。shutdown:shutdown -h now:指定现在立即关机;shutdown +5 "System will shutdown after 5 minutes":指定 5 分钟后关机,同时送出警告信息给登入用户。reboot:reboot:重开机。reboot -w:做个重开机的模拟(只有纪录并不会真的重开机)。
Linux 环境变量
在 Linux 系统中,环境变量是用来定义系统运行环境的一些参数,比如每个用户不同的主目录(HOME)。
环境变量分类
按照作用域来分,环境变量可以简单的分成:
- 用户级别环境变量:
~/.bashrc、~/.bash_profile。 - 系统级别环境变量:
/etc/bashrc、/etc/environment、/etc/profile、/etc/profile.d。
环境变量配置文件的加载顺序不是固定一条线,取决于当前 shell 是登录 shell、非登录交互 shell,还是非交互 shell。以 Bash 为例,登录 shell 通常会读取 /etc/profile,再读取用户目录下第一个存在且可读的 ~/.bash_profile、~/.bash_login 或 ~/.profile;交互式非登录 shell 通常读取 ~/.bashrc。很多发行版会在 ~/.bash_profile 中手动加载 ~/.bashrc,所以你实际看到的加载链路还会受发行版默认配置影响。
如果要修改系统级别环境变量文件,需要管理员具备对该文件的写入权限。
建议用户级别环境变量在 ~/.bash_profile 中配置,系统级别环境变量在 /etc/profile.d 中配置。
按照生命周期来分,环境变量可以简单的分成:
- 永久的:需要用户修改相关的配置文件,变量永久生效。
- 临时的:用户利用
export命令,在当前终端下声明环境变量,关闭 shell 终端失效。
读取环境变量
通过 export 命令可以输出当前系统定义的所有环境变量。
# 列出当前的环境变量值
export -p除了 export 命令之外,env 命令也可以列出所有环境变量。
echo 命令可以输出指定环境变量的值。
# 输出当前的PATH环境变量的值
echo $PATH
# 输出当前的HOME环境变量的值
echo $HOME环境变量修改
通过 export 命令可以修改指定的环境变量。不过,这种方式修改环境变量仅仅对当前 shell 终端生效,关闭 shell 终端就会失效。修改完成之后,立即生效。
export JAVA_HOME="/path/to/jdk"
export PATH="$JAVA_HOME/bin:$PATH"通过 vim 命令修改环境变量配置文件。这种方式修改环境变量永久有效。
vim ~/.bash_profile如果修改的是系统级别环境变量则对所有用户生效,如果修改的是用户级别环境变量则仅对当前用户生效。
修改完成之后,需要 source 命令让其生效或者关闭 shell 终端重新登录。
source ~/.bash_profileCs Basics Operating System Shell Intro (`docs/cs-basics/operating-system/shell-intro.md`)
title: Shell 编程基础知识总结
category: 计算机基础
tag:
- 操作系统
- Linux
head: - meta
- name: keywords
content: Shell,脚本,命令,自动化,运维,Linux,基础语法
Shell 编程在我们的日常开发工作中非常实用,目前 Linux 系统下最流行的运维自动化语言就是 Shell 和 Python 了。
这篇文章我会简单总结一下 Shell 编程基础知识,带你入门 Shell 编程!
版本说明
本文示例适用于 bash 4.0+ 版本。不同版本的 bash 在某些特性上可能有差异,特别是:
- 数组:bash 2.0+ 支持,纯 POSIX sh(如 dash)不支持。
- 某些字符串操作:如
${var:offset:length}在较旧版本可能不支持。 - 算术扩展
$((...)):bash 2.0+ 支持。
检查你的 bash 版本:
bash --version
# 或
echo $BASH_VERSION走进 Shell 编程的大门
为什么要学 Shell?
学一个东西,我们大部分情况都是往实用性方向着想。从工作角度来讲,学习 Shell 是为了提高我们自己工作效率,提高产出,让我们在更少的时间完成更多的事情。
很多人会说 Shell 编程属于运维方面的知识了,应该是运维人员来做,我们做后端开发的没必要学。我觉得这种说法大错特错,相比于专门做 Linux 运维的人员来说,我们对 Shell 编程掌握程度的要求要比他们低,但是 Shell 编程也是我们必须要掌握的!
目前 Linux 系统下最流行的运维自动化语言就是 Shell 和 Python 了。
两者之间,Shell 几乎是 IT 企业必须使用的运维自动化编程语言,特别是在运维工作中的服务监控、业务快速部署、服务启动停止、数据备份及处理、日志分析等环节里,Shell 是不可缺的。Python 更适合处理复杂的业务逻辑,以及开发复杂的运维软件工具,实现通过 web 访问等。Shell 是一个命令解释器,解释执行用户所输入的命令和程序。一输入命令,就立即回应的交互的对话方式。
另外,了解 Shell 编程也是大部分互联网公司招聘后端开发人员的要求。下图是我截取的一些知名互联网公司对于 Shell 编程的要求。
什么是 Shell?
Shell 是 Linux/Unix 系统的命令解释器,它充当用户和操作系统内核之间的桥梁,负责接收用户输入的命令并调用相应的程序。
Shell 编程是通过 Shell 解释器(如 bash)将命令、控制结构(if/for/while)、变量和函数组合成自动化脚本的过程。Shell 既是命令解释器,也是一门完整的编程语言(支持变量、数组、函数、流程控制、管道、重定向等)。
常见的 Shell 类型:
- bash(Bourne Again Shell):Linux 系统默认 Shell,最常用。
- sh(Bourne Shell):Unix 传统 Shell,POSIX 标准。
- zsh:功能强大的交互式 Shell。
- dash:轻量级 Shell,Ubuntu 的 /bin/sh 默认指向它。
- csh/tcsh:C 风格的 Shell。
Shell 编程的 Hello World
学习任何一门编程语言第一件事就是输出 HelloWorld 了!下面我会从新建文件到 Shell 代码编写来说下 Shell 编程如何输出 Hello World。
(1)新建一个文件 helloworld.sh:touch helloworld.sh,扩展名为 sh(sh 代表 Shell)(扩展名并不影响脚本执行,见名知意就好,如果你用 php 写 Shell 脚本,扩展名就用 php 好了)。
(2)使脚本具有执行权限:chmod +x helloworld.sh
(3)使用 vim 命令修改 helloworld.sh 文件:vim helloworld.sh(vim 文件------>进入文件----->命令模式------>按 i 进入编辑模式----->编辑文件------->按 Esc 进入底行模式----->输入:wq/q!(输入 wq 代表写入内容并退出,即保存;输入 q! 代表强制退出不保存。))
helloworld.sh 内容如下:
#!/bin/bash
set -euo pipefail # 严格模式:遇错退出、未定义变量报错、管道失败报错
# 第一个 shell 小程序,echo 是 Linux 中的输出命令
echo "helloworld!"Shell 中 # 符号表示注释。Shell 的第一行比较特殊,一般都会以 #! 开始来指定使用的 Shell 类型。在 Linux 中,除了 bash Shell 以外,还有很多版本的 Shell,例如 zsh、dash 等等...不过 bash Shell 还是我们使用最多的。
(4)运行脚本:./helloworld.sh。(注意,一定要写成 ./helloworld.sh,而不是 helloworld.sh,运行其它二进制的程序也一样,直接写 helloworld.sh,Linux 系统会去 PATH 里寻找有没有叫 helloworld.sh 的,而只有 /bin、/sbin、/usr/bin、/usr/sbin 等在 PATH 里,你的当前目录通常不在 PATH 里,所以写成 helloworld.sh 是会找不到命令的,要用 ./helloworld.sh 告诉系统说,就在当前目录找。)
Shell 变量
Shell 编程中的变量介绍
Shell 编程中一般分为三种变量:
- 自定义变量(局部变量):默认仅在当前 Shell 进程内有效,子进程无法访问。若需传递给子进程,需使用
export声明为环境变量。 - 环境变量:例如
PATH、HOME等,可被子进程继承。使用env命令可以查看所有环境变量,set命令可以查看所有变量(包括环境变量和局部变量)。 - Shell 特殊变量:由 Shell 设置的特殊变量(如
$?、$$、$!等),用于保存进程状态、参数等信息。
常用的环境变量:
PATH 决定了 Shell 将到哪些目录中寻找命令或程序。
HOME 当前用户主目录。
HISTSIZE 历史记录数。
LOGNAME 当前用户的登录名。
HOSTNAME 指主机的名称。
SHELL 当前用户 Shell 类型。
LANGUAGE 语言相关的环境变量,多语言可以修改此环境变量。
MAIL 当前用户的邮件存放目录。
PS1 基本提示符,对于 root 用户是 #,对于普通用户是 $。
使用 Linux 已定义的环境变量:
比如我们要看当前用户目录可以使用:echo $HOME 命令;如果我们要看当前用户 Shell 类型可以使用 echo $SHELL 命令。可以看出,使用方法非常简单。
使用自己定义的变量:
#!/bin/bash
#自定义变量hello
hello="hello world"
echo $hello
echo "helloworld!"Shell 编程中的变量名的命名的注意事项:
- 命名只能使用英文字母、数字和下划线,首个字符不能以数字开头,但是可以使用下划线(_)开头。
- 中间不能有空格,可以使用下划线(_)。
- 不能使用标点符号。
- 不能使用 bash 里的关键字(可用 help 命令查看保留关键字)。
Shell 字符串入门
字符串是 Shell 编程中最常用最有用的数据类型(除了数字和字符串,也没啥其它类型好用了),字符串可以用单引号,也可以用双引号。这点和 Java 中有所不同。
在单引号中,所有特殊字符(如 $、反引号、\ 等)都失去特殊含义,被视为字面量。
在双引号中,以下字符保留特殊含义:
$:变量扩展(如$var)和命令替换(如$(cmd)或`cmd`)\:转义字符`或$():命令替换(推荐使用$()语法)!:历史扩展(仅在交互式 Shell 中默认开启)${}:参数扩展
注意:单引号中的字符串是完全字面量,双引号中的字符串会进行变量和命令替换。
单引号字符串:
#!/bin/bash
name='SnailClimb'
hello='Hello, I am $name!'
echo $hello输出内容:
Hello, I am $name!双引号字符串:
#!/bin/bash
name='SnailClimb'
hello="Hello, I am $name!"
echo $hello输出内容:
Hello, I am SnailClimb!Shell 字符串常见操作
拼接字符串:
#!/bin/bash
name="SnailClimb"
# 使用双引号拼接
greeting="hello, "$name" !"
greeting_1="hello, ${name} !"
echo $greeting $greeting_1
# 使用单引号拼接
greeting_2='hello, '$name' !'
greeting_3='hello, ${name} !'
echo $greeting_2 $greeting_3输出结果:
获取字符串长度:
#!/bin/bash
# 获取字符串长度
name="SnailClimb"
# 第一种方式(推荐):bash 内置
echo ${#name} # 输出 10
# 第二种方式:外部命令(性能较差)
expr length "$name"输出结果:
10
10说明:
- 推荐使用
${#var}语法,这是 bash 内置功能,性能更好。 expr是外部命令,需要 fork 进程,性能较差。expr length是 GNU 扩展,非 POSIX 标准。在 macOS 的 BSD expr 或其他系统上可能不支持。- 如需可移植性,推荐使用
${#var}或expr "$var" : '.*'(POSIX 兼容)。
使用 expr 命令时,表达式中的运算符左右必须包含空格:
expr 5+6 # 直接输出 5+6(无空格)
expr 5 + 6 # 输出 11(有空格)
# 更推荐使用 bash 算术扩展:
echo $((5 + 6)) # 输出 11对于某些运算符,还需要我们使用符号 \ 进行转义:
expr 5 * 6 # 输出错误(未转义)
expr 5 \* 6 # 输出 30(正确转义)截取子字符串:
简单的字符串截取:
#从字符串第 0 个字符开始往后截取 10 个字符(索引从 0 开始)
str="SnailClimb is a great man"
echo ${str:0:10} #输出:SnailClimb根据表达式截取:
#!/bin/bash
# author: amau
var="https://www.runoob.com/linux/linux-shell-variable.html"
# %表示删除从后匹配, 最短结果
# %%表示删除从后匹配, 最长匹配结果
# #表示删除从头匹配, 最短结果
# ##表示删除从头匹配, 最长匹配结果
# 注: *为通配符, 意为匹配任意数量的任意字符
s1=${var%%t*} #h
s2=${var%t*} #https://www.runoob.com/linux/linux-shell-variable.h
s3=${var%%.*} #https://www
s4=${var#*/} #/www.runoob.com/linux/linux-shell-variable.html
s5=${var##*/} #linux-shell-variable.htmlShell 数组
bash 2.0+ 支持一维数组(不支持多维数组),并且没有限定数组的大小。
重要提示:数组是 bash 的非 POSIX 扩展特性,纯 POSIX sh(如 dash)不支持数组。若需编写可移植脚本,应避免使用数组。
下面是一个关于数组操作的 Shell 代码示例,通过该示例大家可以知道如何创建数组、获取数组长度、获取/删除特定位置的数组元素、删除整个数组以及遍历数组。
#!/bin/bash
array=(1 2 3 4 5);
# 获取数组长度
length=${#array[@]}
# 或者
length2=${#array[*]}
#输出数组长度
echo $length #输出:5
echo $length2 #输出:5
# 输出数组第三个元素
echo ${array[2]} #输出:3
unset 'array[1]' # 删除下标为 1 的元素,也就是第二个元素
for i in "${array[@]}"; do echo "$i"; done # 遍历数组,输出:1 3 4 5
unset array; # 删除数组中的所有元素
for i in "${array[@]}"; do echo "$i"; done # 遍历数组,数组元素为空,没有任何输出内容重要说明:数组索引空洞:
使用 unset array[1] 删除元素后,数组会产生索引空洞:
#!/bin/bash
array=(1 2 3 4 5)
echo "删除前: ${array[@]}" # 输出: 1 2 3 4 5
echo "索引1的值: ${array[1]}" # 输出: 2
unset array[1] # 删除索引1的元素
echo "删除后: ${array[@]}" # 输出: 1 3 4 5
echo "索引1的值: ${array[1]}" # 输出: (空值)
echo "索引2的值: ${array[2]}" # 输出: 3 (索引2仍在)
# 遍历时索引不连续
for index in "${!array[@]}"; do
echo "索引[$index] = ${array[$index]}"
done
# 输出:
# 索引[0] = 1
# 索引[2] = 3
# 索引[3] = 4
# 索引[4] = 5注意:删除元素后,如果使用 ${array[1]} 访问会得到空值。遍历数组时建议使用 "${!array[@]}" 获取有效索引,或使用 "${array[@]}" 直接遍历值。
Shell 基本运算符
Shell 编程支持下面几种运算符:
- 算数运算符
- 关系运算符
- 布尔运算符
- 字符串运算符
- 文件测试运算符
算数运算符
| 运算符 | 说明 | 举例 |
|---|---|---|
| + | 加法 | expr $a + $b |
| - | 减法 | expr $a - $b |
| * | 乘法 | expr $a \* $b(注意星号需要转义) |
| / | 除法 | expr $b / $a |
| % | 取余 | expr $b % $a |
| = | 赋值 | a=$b 将变量 b 的值赋给 a |
| == | 相等 | [ "$a" == "$b" ] 用于字符串比较,相同返回 true |
| != | 不相等 | [ "$a" != "$b" ] 用于字符串比较,不同返回 true |
推荐使用 bash 内置算术扩展:
#!/bin/bash
a=3; b=3
val=$((a + b)) # bash 算术扩展(推荐)
# 输出:Total value: 6
echo "Total value: $val"说明:
$((...))是 bash 内置功能,无需 fork 外部进程,性能更好。- 不推荐使用
expr命令(需 fork 进程,且运算符两边必须有空格)。 - 不推荐使用反引号
`...`(已过时),应使用$(...)语法。
如果需要兼容 POSIX sh,可以使用:
val=$(expr "$a" + "$b") # POSIX 兼容,但性能较差关系运算符
关系运算符只支持数字,不支持字符串,除非字符串的值是数字。
| 运算符 | 说明 | 对应英文 |
|---|---|---|
| -eq | 检测两个数是否相等 | equal |
| -ne | 检测两个数是否不相等 | not equal |
| -gt | 检测左边的数是否大于右边的 | greater than |
| -lt | 检测左边的数是否小于右边的 | less than |
| -ge | 检测左边的数是否大于等于右边的 | greater equal |
| -le | 检测左边的数是否小于等于右边的 | less equal |
通过一个简单的示例演示关系运算符的使用,下面 Shell 程序的作用是当 score=100 的时候输出 A 否则输出 B。
#!/bin/bash
score=90;
maxscore=100;
if [[ $score -eq $maxscore ]]
then
echo "A"
else
echo "B"
fi输出结果:
B逻辑运算符
| 运算符 | 说明 | 举例 |
|---|---|---|
| && | 逻辑的 AND | [[ $a -lt 100 && $b -gt 100 ]](全真才为真) |
| || | 逻辑的 OR | [[ $a -lt 100 || $b -gt 100 ]](一真即为真) |
算术扩展中的逻辑运算:
#!/bin/bash
a=$(( 1 && 0))
# 输出:0;逻辑与运算只有相与的两边都是1,与的结果才是1;否则与的结果是0
echo $a;命令短路执行(生产环境常用):
在运维自动化和 CI/CD 管道中,经常使用 && 和 || 来控制命令链路的执行流程,这称为短路执行:
#!/bin/bash
set -euo pipefail
# &&:前一个命令成功(返回 0)时才执行后一个命令
mkdir -p "/tmp/app_data" && echo "目录就绪"
# ||:前一个命令失败(返回非 0)时才执行后一个命令
mkdir -p "/tmp/app_data" || echo "目录创建失败"
# 组合使用:生产环境典型的防御姿势
mkdir -p "/tmp/app_data" && echo "目录就绪" || exit 1
# 实际场景示例
# 1. 检查文件存在后再删除
[ -f "/tmp/old_file.log" ] && rm "/tmp/old_file.log"
# 2. 命令失败时输出错误信息并退出
cd /app/config || { echo "无法进入配置目录"; exit 1; }
# 3. 条件执行命令
command1 && command2 || command3
# ⚠️ 注意:此写法有陷阱!
# - 当 command1 成功时,执行 command2
# - 当 command1 失败时,执行 command3
# - 但如果 command1 成功但 command2 失败,command3 仍会执行!
#
# ✅ 更安全的写法(推荐):
if command1; then
command2
else
command3
fi
#
# 或明确知道 command2 不会失败时才使用 && || 组合重要提示:
- 短路执行依赖命令的退出码(Exit Code):成功返回 0,失败返回非 0。
- 这与
[[ ]]内部的&&和||不同,后者用于条件测试。 command1 && command2 || command3存在陷阱:若 command1 成功但 command2 失败,command3 仍会执行。- 生产环境中强烈建议使用 if-then-else 结构,确保逻辑清晰。
布尔运算符
| 运算符 | 说明 | 举例 |
|---|---|---|
| ! | 将表达式的结果取反。如果表达式为 true,则返回 false;否则返回 true。 | [ ! false ] 返回 false,因为 false 是非空字符串。 |
| -o | 有一个表达式为 true,则返回 true。 | [ "$a" -lt 20 -o "$b" -gt 100 ] 返回 true。 |
| -a | 两个表达式都为 true 才会返回 true。 | [ "$a" -lt 20 -a "$b" -gt 100 ] 返回 false。 |
字符串运算符
| 运算符 | 说明 | 举例 |
|---|---|---|
| = | 检测两个字符串是否相等 | [ "$a" = "$b" ] |
| != | 检测两个字符串是否不相等 | [ "$a" != "$b" ] |
| -z | 检测字符串长度是否为 0 (zero) | [ -z "$a" ] 为空返回 true |
| -n | 检测字符串长度是否不为 0 | [ -n "$a" ] 不为空返回 true |
| str | 直接检测字符串是否为空 | [ "$a" ] 不为空返回 true |
简单示例:
#!/bin/bash
a="abc";
b="efg";
if [[ "$a" = "$b" ]]
then
echo "a 等于 b"
else
echo "a 不等于 b"
fi输出:
a 不等于 b文件相关运算符
用于检测 Unix/Linux 文件的各种属性(如权限、类型等)。
- 存在与类型检测:
- -e file:检测文件(包括目录)是否存在。
- -f file:检测是否为普通文件(既不是目录也不是设备文件)。
- -d file:检测是否为目录。
- -s file:检测文件是否非空(文件大小大于 0 返回 true)。
- -b/-c/-p:分别检测是否为块设备、字符设备、有名管道。
- 权限检测:
- -r file:检测文件是否可读。
- -w file:检测文件是否可写。
- -x file:检测文件是否可执行。
- 特殊标识检测:
- -u / -g / -k:分别检测文件是否设置了 SUID、SGID 或粘着位(Sticky Bit)。
使用方式很简单,比如我们定义好了一个文件路径 file="/usr/learnshell/test.sh",如果我们想判断这个文件是否可读,可以这样 if [ -r $file ];如果想判断这个文件是否可写,可以这样 -w $file,是不是很简单。
Shell 流程控制
if 条件语句
简单的 if else-if else 的条件语句示例:
#!/bin/bash
a=3;
b=9;
if [[ $a -eq $b ]]
then
echo "a 等于 b"
elif [[ $a -gt $b ]]
then
echo "a 大于 b"
else
echo "a 小于 b"
fi输出结果:
a 小于 b相信大家通过上面的示例就已经掌握了 Shell 编程中的 if 条件语句。
空语句的处理:Shell 中空语句可以使用 :(冒号命令)或 true 命令实现:
if [[ condition ]]; then
: # 空语句(什么都不做)
fi
# 或
if [[ condition ]]; then
true # 空语句
fi这在某些场景下很有用,例如在 while 循环中作为占位符。
for 循环语句
通过下面三个简单的示例认识 for 循环语句最基本的使用,实际上 for 循环语句的功能比下面你看到的示例展现的要大得多。
输出当前列表中的数据:
for loop in 1 2 3 4 5
do
echo "The value is: $loop"
done产生 10 个随机数:
#!/bin/bash
for i in {0..9};
do
echo $RANDOM;
done输出 1 到 5:
通常情况下 Shell 变量调用需要加 $,但是 for 的 (()) 中不需要,下面来看一个例子:
#!/bin/bash
length=5
for((i=1;i<=length;i++));do
echo $i;
done;while 语句
基本的 while 循环语句:
#!/bin/bash
int=1
while (( int <= 5 )) # 算术上下文内变量无需 $
do
echo $int
(( int++ )) # 推荐使用 (( )) 替代 let
donewhile 循环可用于读取键盘信息:
echo '按下 <CTRL-D> 退出'
echo -n '输入你最喜欢的电影: '
while read -r FILM # -r 选项禁止反斜杠转义,提高安全性
do
echo "是的!$FILM 是一个好电影"
done输出内容:
按下 <CTRL-D> 退出
输入你最喜欢的电影: 变形金刚
是的!变形金刚 是一个好电影无限循环:
while true
do
command
doneShell 函数
不带参数没有返回值的函数
#!/bin/bash
hello(){
echo "这是我的第一个 shell 函数!"
}
echo "-----函数开始执行-----"
hello
echo "-----函数执行完毕-----"输出结果:
-----函数开始执行-----
这是我的第一个 shell 函数!
-----函数执行完毕-----有返回值的函数
输入两个数字之后相加并输出结果:
#!/bin/bash
set -euo pipefail
funWithReturn(){
local aNum
local anotherNum
echo "输入第一个数字: "
read -r aNum
echo "输入第二个数字: "
read -r anotherNum
echo "两个数字分别为 $aNum 和 $anotherNum !"
result=$((aNum + anotherNum))
}
result=0
funWithReturn
echo "输入的两个数字之和为 $result"重要说明:
local关键字:将变量限制在函数作用域内,避免污染全局命名空间。read -r:-r选项禁止反斜杠转义,提高安全性。- 函数返回值:
return设置的是 0-255 的退出状态,不适合传递计算结果。需要传递数据时,可以使用标准输出或变量。
为什么使用 local?
- 在复杂脚本或引入多个外部脚本时,非 local 变量可能被意外覆盖。
- 全局变量污染会导致难以排查的配置漂移或逻辑越权。
- 使用
local是函数编程的最佳实践,类似于其他编程语言的局部变量概念。
输出结果:
输入第一个数字:
1
输入第二个数字:
2
两个数字分别为 1 和 2 !
输入的两个数字之和为 3带参数的函数
#!/bin/bash
funWithParam(){
echo "第一个参数为 $1"
echo "第二个参数为 $2"
echo "脚本名称为 $0"
echo "第十个参数为 ${10}" # 注意:参数 ≥ 10 时必须用 ${n}
echo "第十一个参数为 ${11}"
echo "参数总数有 $# 个"
echo "所有参数为 $*" # 作为单个字符串输出
echo "所有参数为 $@" # 作为独立的参数输出(推荐)
}
funWithParam 1 2 3 4 5 6 7 8 9 34 73输出结果:
第一个参数为 1
第二个参数为 2
脚本名称为 ./script.sh
第十个参数为 34
第十一个参数为 73
参数总数有 11 个
所有参数为 1 2 3 4 5 6 7 8 9 34 73
所有参数为 1 2 3 4 5 6 7 8 9 34 73重要提示:
- 位置参数
$n当n >= 10时必须使用${n}语法。 - 例如:
$10会被解析为$1和字面量0的拼接,而非第十个参数。 $0表示脚本本身的名称。$#表示参数总数。
$* 与 $@ 的核心区别:
| 表达式 | 未引用 | 双引号包裹 |
|---|---|---|
$* |
展开为所有参数 | 展开为单个字符串(所有参数合并) |
$@ |
展开为所有参数 | 展开为独立的参数(每个参数保持独立) |
示例对比:
#!/bin/bash
test_args() {
echo "--- 使用 \$* (无引号)---"
for arg in $*; do
echo "参数: [$arg]"
done
echo -e "\n--- 使用 \$@ (无引号)---"
for arg in $@; do
echo "参数: [$arg]"
done
echo -e "\n--- 使用 \"\$*\" (双引号)---"
for arg in "$*"; do
echo "参数: [$arg]"
done
echo -e "\n--- 使用 \"\$@\" (双引号,推荐)---"
for arg in "$@"; do
echo "参数: [$arg]"
done
}
# 调用函数,传递包含空格的参数
test_args "hello world" "foo bar"输出结果:
--- 使用 $* (无引号)---
参数: [hello]
参数: [world]
参数: [foo]
参数: [bar]
--- 使用 $@ (无引号)---
参数: [hello]
参数: [world]
参数: [foo]
参数: [bar]
--- 使用 "$*" (双引号)---
参数: [hello world foo bar] # 所有参数合并为一个字符串
--- 使用 "$@" (双引号,推荐)---
参数: [hello world] # 每个参数保持独立
参数: [foo bar]结论:在传递参数时,始终使用 "$@" 以确保每个参数的独立性(特别是当参数包含空格时)。
Shell 编程最佳实践
在掌握了 Shell 编程的基础知识后,了解一些最佳实践能帮助你编写更安全、更高效的脚本。
脚本基础规范
1. Shebang 规范:
#!/usr/bin/env bash
# 通过 PATH 查找 bash
set -euo pipefailShebang 两种写法:
#!/bin/bash:直接指定 bash 路径,适用于你知道 bash 位置的固定环境。#!/usr/bin/env bash:通过 env 查找 bash,更可移植,适合不同系统(如 macOS / Linux)。
本文示例选择:
- 教程示例使用
#!/bin/bash:简洁明了,适合初学者理解。 - 生产级示例使用
#!/usr/bin/env bash:强调可移植性。
2. 变量引用:
# 始终用双引号包裹变量
echo "$var" # 推荐
echo $var # 可能导致 word splitting 和 globbing 问题3. 使用 shellcheck:
shellcheck your_script.sh # 静态分析,发现常见问题4. 推荐语法:
- 使用
[[ ]]而非[ ](更安全、支持模式匹配)。 - 使用
$((...))而非expr(性能更好)。 - 使用
$(...)而非反引号(可嵌套、更清晰)。 - 使用
${n}访问位置参数 n >= 10。
pipefail 工作原理
默认情况下,管道命令的返回值只取决于最后一个命令。启用 pipefail 后,管道的返回值将是最后一个失败命令的返回值,这能避免隐藏中间步骤的错误。
示例对比:
# 默认模式(危险)
cat huge_file.txt | grep "pattern" | head -n 10
# 即使 cat 失败(文件不存在),只要 head 成功,返回码就是 0
# pipefail 模式(安全)
set -o pipefail
cat huge_file.txt | grep "pattern" | head -n 10
# cat 失败会立即返回错误码,不会被忽略把 Shell 脚本放进生产环境前
基础语法写对只是第一步。脚本进入定时任务、部署流程或线上机器后,还要处理失败退出、临时文件、网络超时和后台任务退出码。下面用几个完整的小例子说明。
set -u 和 set -o pipefail 的行为相对明确;set -e 的例外较多,在条件判断、函数、子 Shell 和命令替换中的行为都可能让人意外。可以把 set -euo pipefail 当作新脚本的起点,但不能用它代替显式的 if ! command; then ... fi 错误处理。变量展开仍应按场景加双引号,函数内部变量用 local 限定作用域。
给网络请求设置总预算
一次请求要同时限制连接阶段和整个传输时间。重试只能放在一层:如果外层循环和 curl --retry 同时启用,真实请求次数会相乘,总耗时也很难估算。下面的函数由外层统一控制尝试次数,并在每次失败后加入 0~2 秒的整数随机延迟:
#!/usr/bin/env bash
retry_request() {
local url="$1"
local max_attempts=5
local attempt=1
local delay
local jitter
while (( attempt <= max_attempts )); do
if curl --fail --silent --show-error \
--connect-timeout 3 \
--max-time 10 \
"$url"; then
return 0
fi
if (( attempt == max_attempts )); then
break
fi
delay=$((1 << (attempt - 1)))
(( delay > 16 )) && delay=16
jitter=$((RANDOM % 3))
delay=$((delay + jitter))
printf '第 %d 次请求失败,%d 秒后重试\n' "$attempt" "$delay" >&2
sleep "$delay"
((attempt += 1))
done
return 1
}
command -v curl >/dev/null 2>&1 || {
echo "curl 未安装" >&2
exit 1
}
[[ $# -eq 1 ]] || {
echo "用法: $0 <url>" >&2
exit 1
}
retry_request "$1" || {
echo "请求失败" >&2
exit 1
}真实脚本还要根据接口语义决定哪些错误可以重试。非幂等写请求、认证失败和参数错误通常不应直接重放。
临时文件和互斥锁
不要用 /tmp/data_$$ 这类可预测路径。mktemp 会原子创建文件或目录,配合 trap 可以在正常退出和收到常见信号时清理:
#!/usr/bin/env bash
temp_dir=$(mktemp -d "${TMPDIR:-/tmp}/myapp.XXXXXXXX") || {
echo "无法创建临时目录" >&2
exit 1
}
cleanup() {
rm -rf -- "$temp_dir"
}
trap cleanup EXIT
trap 'exit 129' HUP
trap 'exit 130' INT
trap 'exit 143' TERM
temp_file="$temp_dir/result.txt"
printf 'temporary data\n' > "$temp_file"
cat "$temp_file"同一台机器上需要阻止脚本重复运行时,可以对应用自己管理的锁文件使用 flock:
exec 9>/var/lock/myapp.lock || exit 1
flock -n 9 || {
echo "脚本已在运行" >&2
exit 1
}flock 是协作式锁,其他进程可以选择不遵守。Linux 的 NFS 客户端可以把它模拟为整文件 fcntl 锁,但实际行为还受客户端内核、服务端和 local_lock 等挂载选项影响。锁文件放在网络文件系统时,应在目标部署环境用两个独立客户端验证,不能预设一定有效或一定失效。跨机器互斥也不能只写一句 Redis SET NX PX:实现还要处理唯一 token、条件删除、租约续期和故障模型。
收集后台任务的退出码
不带参数的 wait 会等待所有后台任务,但 Bash 返回 0,无法告诉你其中哪个任务失败。下面的脚本并发统计多个文件的字节数,并逐个收集子进程状态:
#!/usr/bin/env bash
set -u
pids=()
for file in "$@"; do
wc -c -- "$file" &
pids+=("$!")
done
exit_code=0
for pid in "${pids[@]}"; do
if ! wait "$pid"; then
echo "后台任务 $pid 执行失败" >&2
exit_code=1
fi
done
exit "$exit_code"直接写 while wait -n; do ...; done 也不完整:某个任务失败时 wait -n 返回非 0,循环会立即结束,剩余任务可能没有被收集。
常见误区
不要把整条命令的标准输出和标准错误都长期重定向到 /dev/null,否则失败时只剩退出码,没有诊断信息。只屏蔽确认不需要的输出,错误信息写入日志或保留在标准错误中。脚本依赖 curl、jq 等外部命令时,启动阶段先用 command -v 检查;管道需要感知中间命令失败时,再启用 set -o pipefail。
上线前怎么验证
验证内容应贴着脚本真实依赖来定。网络脚本至少覆盖连接失败、超时和不可重试的 HTTP 状态;并发脚本检查每个子进程的退出码;会创建临时资源的脚本还要验证正常退出和信号中断后是否完成清理。故障注入命令会修改防火墙、系统时间或挂载状态,不适合当作可直接复制的通用示例,应在隔离测试环境中按实际基础设施单独设计。
总结
Shell 适合把已有命令串成小型自动化流程。先掌握变量引用、条件、循环、函数和退出状态,再根据脚本实际使用的网络、文件和并发资源补上超时、清理与错误处理。
核心知识点回顾
| 知识模块 | 关键要点 |
|---|---|
| 变量 | 区分局部变量、环境变量和特殊变量;使用 local 避免全局污染;始终用双引号包裹变量 |
| 字符串 | 推荐使用双引号;理解单引号和双引号的区别;掌握 ${#var} 获取长度 |
| 数组 | bash 2.0+ 支持数组(非 POSIX);注意删除元素后的索引空洞 |
| 运算符 | 优先使用 $((...)) 进行算术运算;[[ ]] 比 [ ] 更安全 |
| 流程控制 | 使用 [[ ]] 进行条件测试;避免 command1 && command2 || command3 的陷阱 |
| 函数 | 使用 local 限制变量作用域;函数只能返回 0-255 的退出码 |
| 命令替换 | 使用 $(...) 替代反引号;使用 read -r 提高安全性 |
学习建议
从日志筛选、批量改名、文件统计这类短任务开始,写完先用 bash -n 检查语法,再用 ShellCheck 找未引用变量、错误重定向等常见问题。脚本开始管理后台进程或系统服务后,再继续学习信号、作业控制、sed、awk 和 grep。超过几百行、需要复杂数据结构或异常恢复时,Python 等通用语言通常更容易维护。
参考资源
- 官方文档:Bash Reference Manual(GNU)
- 代码检查:ShellCheck - Shell Script Analysis Tool
- 编码规范:Google Shell Style Guide
- 常见陷阱:Bash Pitfalls (http://mywiki.wooledge.org/BashPitfalls)
Database Mysql Mysql Index (`docs/database/mysql/mysql-index.md`)
title: MySQL索引详解
category: 数据库
tag:
- MySQL
head: - meta
- name: keywords
content: MySQL索引,B+树索引,聚簇索引,覆盖索引,联合索引,索引下推,回表查询,索引失效,最左前缀原则
感谢WT-AHA对本文的完善,相关 PR:https://github.com/Snailclimb/JavaGuide/pull/1648 。
但凡经历过几场面试的小伙伴,应该都清楚,数据库索引这个知识点在面试中出现的频率高到离谱。
除了对于准备面试来说非常重要之外,善用索引对 SQL 的性能提升非常明显,是一个性价比较高的 SQL 优化手段。
索引介绍
索引是一种用于快速查询和检索数据的数据结构,其本质可以看成是一种排序好的数据结构。
索引的作用就相当于书的目录。打个比方:我们在查字典的时候,如果没有目录,那我们就只能一页一页地去找我们需要查的那个字,速度很慢;如果有目录了,我们只需要先去目录里查找字的位置,然后直接翻到那一页就行了。
索引底层数据结构存在很多种类型,常见的索引结构有:B 树、 B+ 树 和 Hash、红黑树。在 MySQL 中,无论是 Innodb 还是 MyISAM,都使用了 B+ 树作为索引结构。
索引的优缺点
索引的优点:
- 查询速度起飞 (主要目的):通过索引,数据库可以大幅减少需要扫描的数据量,直接定位到符合条件的记录,从而显著加快数据检索速度,减少磁盘 I/O 次数。
- 保证数据唯一性:通过创建唯一索引 (Unique Index),可以确保表中的某一列(或几列组合)的值是独一无二的,比如用户 ID、邮箱等。主键本身就是一种唯一索引。
- 加速排序和分组:如果查询中的 ORDER BY 或 GROUP BY 子句涉及的列建有索引,数据库往往可以直接利用索引已经排好序的特性,避免额外的排序操作,从而提升性能。
索引的缺点:
- 创建和维护耗时:创建索引本身需要时间,特别是对大表操作时。更重要的是,当对表中的数据进行增、删、改 (DML 操作) 时,不仅要操作数据本身,相关的索引也必须动态更新和维护,这会降低这些 DML 操作的执行效率。
- 占用存储空间:索引本质上也是一种数据结构,需要以物理文件(或内存结构)的形式存储,因此会额外占用一定的磁盘空间。索引越多、越大,占用的空间也就越多。
- 可能被误用或失效:如果索引设计不当,或者查询语句写得不好,数据库优化器可能不会选择使用索引(或者选错索引),反而导致性能下降。
那么,用了索引就一定能提高查询性能吗?
不一定。 大多数情况下,合理使用索引确实比全表扫描快得多。但也有例外:
- 数据量太小:如果表里的数据非常少(比如就几百条),全表扫描可能比通过索引查找更快,因为走索引本身也有开销。
- 查询结果集占比过大:如果要查询的数据占了整张表的大部分(比如超过 20%-30%),优化器可能会认为全表扫描更划算,因为通过索引多次回表(随机 I/O)的成本可能高于一次顺序的全表扫描。
- 索引维护不当或统计信息过时:导致优化器做出错误判断。
索引底层数据结构选型
Hash 表
哈希表是键值对的集合,通过键(key)即可快速取出对应的值(value),因此哈希表可以快速检索数据(接近 O(1))。
为何能够通过 key 快速取出 value 呢? 原因在于 哈希算法(也叫散列算法)。通过哈希算法,我们可以快速找到 key 对应的 index,找到了 index 也就找到了对应的 value。
hash = hashfunc(key)
index = hash % array_size但是!哈希算法有个 Hash 冲突 问题,也就是说多个不同的 key 最后得到的 index 相同。通常情况下,我们常用的解决办法是 链地址法。链地址法就是将哈希冲突数据存放在链表中。就比如 JDK1.8 之前 HashMap 就是通过链地址法来解决哈希冲突的。不过,JDK1.8 以后HashMap为了提高链表过长时的搜索效率,引入了红黑树。
为了减少 Hash 冲突的发生,一个好的哈希函数应该“均匀地”将数据分布在整个可能的哈希值集合中。
MySQL 的 InnoDB 存储引擎不直接支持常规的哈希索引,但是,InnoDB 存储引擎中存在一种特殊的“自适应哈希索引”(Adaptive Hash Index),自适应哈希索引并不是传统意义上的纯哈希索引,而是结合了 B+Tree 和哈希索引的特点,以便更好地适应实际应用中的数据访问模式和性能需求。自适应哈希索引的每个哈希桶实际上是一个小型的 B+Tree 结构。这个 B+Tree 结构可以存储多个键值对,而不仅仅是一个键。这有助于减少哈希冲突链的长度,提高了索引的效率。关于 Adaptive Hash Index 的详细介绍,可以查看 MySQL 各种“Buffer”之 Adaptive Hash Index 这篇文章。
既然哈希表这么快,为什么 MySQL 没有使用其作为索引的数据结构呢? 主要是因为 Hash 索引不支持顺序和范围查询。假如我们要对表中的数据进行排序或者进行范围查询,那 Hash 索引可就不行了。并且,每次 IO 只能取一个。
试想一种情况:
SELECT * FROM tb1 WHERE id < 500;在这种范围查询中,优势非常大,直接遍历比 500 小的叶子节点就够了。而 Hash 索引是根据 hash 算法来定位的,难不成还要把 1 - 499 的数据,每个都进行一次 hash 计算来定位吗?这就是 Hash 最大的缺点了。
二叉查找树(BST)
二叉查找树(Binary Search Tree)是一种基于二叉树的数据结构,它具有以下特点:
- 左子树所有节点的值均小于根节点的值。
- 右子树所有节点的值均大于根节点的值。
- 左右子树也分别为二叉查找树。
当二叉查找树是平衡的时候,也就是树的每个节点的左右子树深度相差不超过 1 的时候,查询的时间复杂度为 O(log2(N)),具有比较高的效率。然而,当二叉查找树不平衡时,例如在最坏情况下(有序插入节点),树会退化成线性链表(也被称为斜树),导致查询效率急剧下降,时间复杂退化为 O(N)。
也就是说,二叉查找树的性能非常依赖于它的平衡程度,这就导致其不适合作为 MySQL 底层索引的数据结构。
为了解决这个问题,并提高查询效率,人们发明了多种在二叉查找树基础上的改进型数据结构,如平衡二叉树、B-Tree、B+Tree 等。
AVL 树
AVL 树是计算机科学中最早被发明的自平衡二叉查找树,它的名称来自于发明者 G.M. Adelson-Velsky 和 E.M. Landis 的名字缩写。AVL 树的特点是保证任何节点的左右子树高度之差不超过 1,因此也被称为高度平衡二叉树,它的查找、插入和删除在平均和最坏情况下的时间复杂度都是 O(logn)。
AVL 树采用了旋转操作来保持平衡。主要有四种旋转操作:LL 旋转、RR 旋转、LR 旋转和 RL 旋转。其中 LL 旋转和 RR 旋转分别用于处理左左和右右失衡,而 LR 旋转和 RL 旋转则用于处理左右和右左失衡。
由于 AVL 树需要频繁地进行旋转操作来保持平衡,因此会有较大的计算开销进而降低了数据库写操作的性能。并且, 在使用 AVL 树时,每个树节点仅存储一个数据,而每次进行磁盘 IO 时只能读取一个节点的数据,如果需要查询的数据分布在多个节点上,那么就需要进行多次磁盘 IO。磁盘 IO 是一项耗时的操作,在设计数据库索引时,我们需要优先考虑如何最大限度地减少磁盘 IO 操作的次数。
实际应用中,AVL 树使用的并不多。
红黑树
红黑树是一种自平衡二叉查找树,通过在插入和删除节点时进行颜色变换和旋转操作,使得树始终保持平衡状态,它具有以下特点:
- 每个节点非红即黑;
- 根节点总是黑色的;
- 每个叶子节点都是黑色的空节点(NIL 节点);
- 如果节点是红色的,则它的子节点必须是黑色的(反之不一定);
- 从任意节点到它的叶子节点或空子节点的每条路径,必须包含相同数目的黑色节点(即相同的黑色高度)。
和 AVL 树不同的是,红黑树并不追求严格的平衡,而是大致的平衡。正因如此,红黑树的查询效率稍有下降,因为红黑树的平衡性相对较弱,可能会导致树的高度较高,这可能会导致一些数据需要进行多次磁盘 IO 操作才能查询到,这也是 MySQL 没有选择红黑树的主要原因。也正因如此,红黑树的插入和删除操作效率大大提高了,因为红黑树在插入和删除节点时只需进行 O(1) 次数的旋转和变色操作,即可保持基本平衡状态,而不需要像 AVL 树一样进行 O(logn) 次数的旋转操作。
红黑树的应用还是比较广泛的,TreeMap、TreeSet 以及 JDK1.8 的 HashMap 底层都用到了红黑树。对于数据在内存中的这种情况来说,红黑树的表现是非常优异的。
关于二叉搜索树、AVL 树、红黑树、B 树和 B+ 树的基础对比,可以先看 树结构详解 和 红黑树详解。
B 树& B+ 树
B 树也称 B- 树,全称为 多路平衡查找树,B+ 树是 B 树的一种变体。B 树和 B+ 树中的 B 是 Balanced(平衡)的意思。
目前大部分数据库系统及文件系统都采用 B-Tree 或其变种 B+Tree 作为索引结构。
B 树& B+ 树两者有何异同呢?
- B 树的所有节点既存放键(key)也存放数据(data),而 B+ 树只有叶子节点存放 key 和 data,其他内节点只存放 key。
- B 树的叶子节点都是独立的;B+ 树的叶子节点有一条引用链指向与它相邻的叶子节点。
- B 树的检索的过程相当于对范围内的每个节点的关键字做二分查找,可能还没有到达叶子节点,检索就结束了。而 B+ 树的检索效率就很稳定了,任何查找都是从根节点到叶子节点的过程,叶子节点的顺序检索很明显。
- 在 B 树中进行范围查询时,首先找到要查找的下限,然后对 B 树进行中序遍历,直到找到查找的上限;而 B+ 树的范围查询,只需要对链表进行遍历即可。
综上,B+ 树与 B 树相比,具备更少的 IO 次数、更稳定的查询效率和更适于范围查询这些优势。
如果只想从数据结构角度快速复盘 B 树和 B+ 树,可以回到 树结构详解 的面试复盘部分。
在 MySQL 中,MyISAM 引擎和 InnoDB 引擎都是使用 B+Tree 作为索引结构,但是,两者的实现方式不太一样。(下面的内容整理自《Java 工程师修炼之道》)
MyISAM 引擎中,B+Tree 叶节点的 data 域存放的是数据记录的地址。在索引检索的时候,首先按照 B+Tree 搜索算法搜索索引,如果指定的 Key 存在,则取出其 data 域的值,然后以 data 域的值为地址读取相应的数据记录。这被称为“非聚簇索引(非聚集索引)”。
InnoDB 引擎中,其数据文件本身就是索引文件。相比 MyISAM,索引文件和数据文件是分离的,其表数据文件本身就是按 B+Tree 组织的一个索引结构,树的叶节点 data 域保存了完整的数据记录。这个索引的 key 是数据表的主键,因此 InnoDB 表数据文件本身就是主索引。这被称为“聚簇索引(聚集索引)”,而其余的索引都作为 辅助索引,辅助索引的 data 域存储相应记录主键的值而不是地址,这也是和 MyISAM 不同的地方。在根据主索引搜索时,直接找到 key 所在的节点即可取出数据;在根据辅助索引查找时,则需要先取出主键的值,再走一遍主索引。 因此,在设计表的时候,不建议使用过长的字段作为主键,也不建议使用非单调的字段作为主键,这样会造成主索引频繁分裂。
索引类型总结
按照数据结构维度划分:
- BTree 索引:MySQL 里默认和最常用的索引类型。只有叶子节点存储 value,非叶子节点只有指针和 key。存储引擎 MyISAM 和 InnoDB 实现 BTree 索引都是使用 B+Tree,但二者实现方式不一样(前面已经介绍了)。
- 哈希索引:类似键值对的形式,一次即可定位。
- RTree 索引:一般不会使用,仅支持 geometry 数据类型,优势在于范围查找,效率较低,通常使用搜索引擎如 ElasticSearch 代替。
- 全文索引:对文本的内容进行分词,进行搜索。目前只有
CHAR、VARCHAR、TEXT列上可以创建全文索引。一般不会使用,效率较低,通常使用搜索引擎如 ElasticSearch 代替。
按照底层存储方式角度划分:
- 聚簇索引(聚集索引):索引结构和数据一起存放的索引,InnoDB 中的主键索引就属于聚簇索引。
- 非聚簇索引(非聚集索引):索引结构和数据分开存放的索引,二级索引(辅助索引)就属于非聚簇索引。MySQL 的 MyISAM 引擎,不管主键还是非主键,使用的都是非聚簇索引。
按照应用维度划分:
- 主键索引:加速查询 + 列值唯一(不可以有 NULL)+ 表中只有一个。
- 普通索引:仅加速查询。
- 唯一索引:加速查询 + 列值唯一(可以有 NULL)。
- 覆盖索引:一个索引包含(或者说覆盖)所有需要查询的字段的值。
- 联合索引:多列值组成一个索引,专门用于组合搜索,其效率大于索引合并。
- 全文索引:对文本的内容进行分词,进行搜索。目前只有
CHAR、VARCHAR、TEXT列上可以创建全文索引。一般不会使用,效率较低,通常使用搜索引擎如 ElasticSearch 代替。 - 前缀索引:对文本的前几个字符创建索引,相比普通索引建立的数据更小,因为只取前几个字符。
MySQL 8.x 中实现的索引新特性:
- 隐藏索引:也称为不可见索引,不会被优化器使用,但是仍然需要维护,通常会软删除和灰度发布的场景中使用。主键不能设置为隐藏(包括显式设置或隐式设置)。
- 降序索引:之前的版本就支持通过 desc 来指定索引为降序,但实际上创建的仍然是常规的升序索引。直到 MySQL 8.x 版本才开始真正支持降序索引。另外,在 MySQL 8.x 版本中,不再对 GROUP BY 语句进行隐式排序。
- 函数索引:从 MySQL 8.0.13 版本开始支持在索引中使用函数或者表达式的值,也就是在索引中可以包含函数或者表达式。
主键索引(Primary Key)
数据表的主键列使用的就是主键索引。
一张数据表有只能有一个主键,并且主键不能为 null,不能重复。
在 MySQL 的 InnoDB 的表中,当没有显示的指定表的主键时,InnoDB 会自动先检查表中是否有唯一索引且不允许存在 null 值的字段,如果有,则选择该字段为默认的主键,否则 InnoDB 将会自动创建一个 6Byte 的自增主键。
二级索引
二级索引(Secondary Index)的叶子节点存储的数据是主键的值,也就是说,通过二级索引可以定位主键的位置,二级索引又称为辅助索引/非主键索引。
唯一索引、普通索引、前缀索引等索引都属于二级索引。
PS:不懂的同学可以暂存疑,慢慢往下看,后面会有答案的,也可以自行搜索。
- 唯一索引(Unique Key):唯一索引也是一种约束。唯一索引的属性列不能出现重复的数据,但是允许数据为 NULL,一张表允许创建多个唯一索引。 建立唯一索引的目的大部分时候都是为了该属性列的数据的唯一性,而不是为了查询效率。
- 普通索引(Index):普通索引的唯一作用就是为了快速查询数据。一张表允许创建多个普通索引,并允许数据重复和 NULL。
- 前缀索引(Prefix):前缀索引只适用于字符串类型的数据。前缀索引是对文本的前几个字符创建索引,相比普通索引建立的数据更小,因为只取前几个字符。
- 全文索引(Full Text):全文索引主要是为了检索大文本数据中的关键字的信息,是目前搜索引擎数据库使用的一种技术。Mysql5.6 之前只有 MyISAM 引擎支持全文索引,5.6 之后 InnoDB 也支持了全文索引。
二级索引:
聚簇索引与非聚簇索引
聚簇索引(聚集索引)
聚簇索引介绍
聚簇索引(Clustered Index)即索引结构和数据一起存放的索引,并不是一种单独的索引类型。InnoDB 中的主键索引就属于聚簇索引。
在 MySQL 中,InnoDB 引擎的表的 .ibd文件就包含了该表的索引和数据,对于 InnoDB 引擎表来说,该表的索引(B+ 树)的每个非叶子节点存储索引,叶子节点存储索引和索引对应的数据。
聚簇索引的优缺点
优点:
- 查询速度非常快:聚簇索引的查询速度非常的快,因为整个 B+ 树本身就是一颗多叉平衡树,叶子节点也都是有序的,定位到索引的节点,就相当于定位到了数据。相比于非聚簇索引, 聚簇索引少了一次读取数据的 IO 操作。
- 对排序查找和范围查找优化:聚簇索引对于主键的排序查找和范围查找速度非常快。
缺点:
- 依赖于有序的数据:因为 B+ 树是多路平衡树,如果索引的数据不是有序的,那么就需要在插入时排序,如果数据是整型还好,否则类似于字符串或 UUID 这种又长又难比较的数据,插入或查找的速度肯定比较慢。
- 更新代价大:如果对索引列的数据被修改时,那么对应的索引也将会被修改,而且聚簇索引的叶子节点还存放着数据,修改代价肯定是较大的,所以对于主键索引来说,主键一般都是不可被修改的。
非聚簇索引(非聚集索引)
非聚簇索引介绍
非聚簇索引(Non-Clustered Index)即索引结构和数据分开存放的索引,并不是一种单独的索引类型。二级索引(辅助索引)就属于非聚簇索引。MySQL 的 MyISAM 引擎,不管主键还是非主键,使用的都是非聚簇索引。
非聚簇索引的叶子节点并不一定存放数据的指针,因为二级索引的叶子节点就存放的是主键,根据主键再回表查数据。
非聚簇索引的优缺点
优点:
更新代价比聚簇索引要小。非聚簇索引的更新代价就没有聚簇索引那么大了,非聚簇索引的叶子节点是不存放数据的。
缺点:
- 依赖于有序的数据:跟聚簇索引一样,非聚簇索引也依赖于有序的数据。
- 可能会二次查询(回表):这应该是非聚簇索引最大的缺点了。当查到索引对应的指针或主键后,可能还需要根据指针或主键再到数据文件或表中查询。
这是 MySQL 的表的文件截图:
聚簇索引和非聚簇索引:
非聚簇索引一定回表查询吗(覆盖索引)?
非聚簇索引不一定回表查询。
试想一种情况,用户准备使用 SQL 查询用户名,而用户名字段正好建立了索引。
SELECT name FROM table WHERE name='guang19';那么这个索引的 key 本身就是 name,查到对应的 name 直接返回就行了,无需回表查询。
即使是 MyISAM 也是这样,虽然 MyISAM 的主键索引确实需要回表,因为它的主键索引的叶子节点存放的是指针。但是!如果 SQL 查的就是主键呢?
SELECT id FROM table WHERE id=1;主键索引本身的 key 就是主键,查到返回就行了。这种情况就称之为覆盖索引了。
覆盖索引和联合索引
覆盖索引
如果一个索引包含(或者说覆盖)所有需要查询的字段的值,我们就称之为 覆盖索引(Covering Index)。
在 InnoDB 存储引擎中,非主键索引的叶子节点包含的是主键的值。这意味着,当使用非主键索引进行查询时,数据库会先找到对应的主键值,然后再通过主键索引来定位和检索完整的行数据。这个过程被称为“回表”。
覆盖索引即需要查询的字段正好是索引的字段,那么直接根据该索引,就可以查到数据了,而无需回表查询。
如主键索引,如果一条 SQL 需要查询主键,那么正好根据主键索引就可以查到主键。再如普通索引,如果一条 SQL 需要查询 name,name 字段正好有索引,
那么直接根据这个索引就可以查到数据,也无需回表。
我们这里简单演示一下覆盖索引的效果。
1、创建一个名为 cus_order 的表,来实际测试一下这种排序方式。为了测试方便,cus_order 这张表只有 id、score、name 这 3 个字段。
CREATE TABLE `cus_order` (
`id` int(11) unsigned NOT NULL AUTO_INCREMENT,
`score` int(11) NOT NULL,
`name` varchar(11) NOT NULL DEFAULT '',
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=100000 DEFAULT CHARSET=utf8mb4;2、定义一个简单的存储过程(PROCEDURE)来插入 100w 测试数据。
DELIMITER ;;
CREATE DEFINER=`root`@`%` PROCEDURE `BatchinsertDataToCusOder`(IN start_num INT,IN max_num INT)
BEGIN
DECLARE i INT default start_num;
WHILE i < max_num DO
insert into `cus_order`(`id`, `score`, `name`)
values (i,RAND() * 1000000,CONCAT('user', i));
SET i = i + 1;
END WHILE;
END;;
DELIMITER ;存储过程定义完成之后,我们执行存储过程即可!
CALL BatchinsertDataToCusOder(1, 1000000); # 插入100w+的随机数据等待一会,100w 的测试数据就插入完成了!
3、创建覆盖索引并使用 EXPLAIN 命令分析。
为了能够对这 100w 数据按照 score 进行排序,我们需要执行下面的 SQL 语句。
#降序排序
SELECT `score`,`name` FROM `cus_order` ORDER BY `score` DESC;使用 EXPLAIN 命令分析这条 SQL 语句,通过 Extra 这一列的 Using filesort,我们发现是没有用到覆盖索引的。
不过这也是理所应当,毕竟我们现在还没有创建索引呢!
我们这里以 score 和 name 两个字段建立联合索引:
ALTER TABLE `cus_order` ADD INDEX id_score_name(score, name);创建完成之后,再用 EXPLAIN 命令分析再次分析这条 SQL 语句。
通过 Extra 这一列的 Using index,说明这条 SQL 语句成功使用了覆盖索引。
关于 EXPLAIN 命令的详细介绍请看:MySQL 执行计划分析这篇文章。
联合索引
使用表中的多个字段创建索引,就是 联合索引,也叫 组合索引 或 复合索引。
以 score 和 name 两个字段建立联合索引:
ALTER TABLE `cus_order` ADD INDEX id_score_name(score, name);最左前缀匹配原则
最左前缀匹配原则指的是在使用联合索引时,MySQL 会根据索引中的字段顺序,从左到右依次匹配查询条件中的字段。如果查询条件与索引中的最左侧字段相匹配,那么 MySQL 就会使用索引来过滤数据,这样可以提高查询效率。
最左匹配原则会一直向右匹配,直到遇到范围查询(如 >、<)为止。对于 >=、<=、BETWEEN 以及前缀匹配 LIKE 的范围查询,不会停止匹配。
假设有一个联合索引 (column1, column2, column3),其从左到右的所有前缀为 (column1)、(column1, column2)、(column1, column2, column3)(创建 1 个联合索引相当于创建了 3 个索引),包含这些列的所有查询都会走索引而不会全表扫描。
我们在使用联合索引时,可以将区分度高的字段放在最左边,这也可以过滤更多数据。
我们这里简单演示一下最左前缀匹配的效果。
1、创建一个名为 student 的表,这张表只有 id、name、class 这 3 个字段。
CREATE TABLE `student` (
`id` int NOT NULL,
`name` varchar(100) DEFAULT NULL,
`class` varchar(100) DEFAULT NULL,
PRIMARY KEY (`id`),
KEY `name_class_idx` (`name`,`class`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;2、下面我们分别测试三条不同的 SQL 语句。
# 可以命中索引
SELECT * FROM student WHERE name = 'Anne Henry';
EXPLAIN SELECT * FROM student WHERE name = 'Anne Henry' AND class = 'lIrm08RYVk';
# 无法命中索引
SELECT * FROM student WHERE class = 'lIrm08RYVk';再来看一个常见的面试题:如果有索引 联合索引(a,b,c),查询 a=1 AND c=1 会走索引么?c=1 呢?b=1 AND c=1 呢? b = 1 AND a = 1 AND c = 1 呢?
先不要往下看答案,给自己 3 分钟时间想一想。
- 查询
a=1 AND c=1:根据最左前缀匹配原则,查询可以使用索引的前缀部分。因此,该查询仅在a=1上使用索引,然后对结果进行c=1的过滤。 - 查询
c=1:由于查询中不包含最左列a,根据最左前缀匹配原则,整个索引都无法被使用。 - 查询
b=1 AND c=1:和第二种一样的情况,整个索引都不会使用。 - 查询
b=1 AND a=1 AND c=1:这个查询是可以用到索引的。查询优化器分析 SQL 语句时,对于联合索引,会对查询条件进行重排序,以便用到索引。会将b=1和a=1的条件进行重排序,变成a=1 AND b=1 AND c=1。
MySQL 8.0.13 版本引入了索引跳跃扫描(Index Skip Scan,简称 ISS),它可以在某些索引查询场景下提高查询效率。在没有 ISS 之前,不满足最左前缀匹配原则的联合索引查询中会执行全表扫描。而 ISS 允许 MySQL 在某些情况下避免全表扫描,即使查询条件不符合最左前缀。不过,这个功能比较鸡肋, 和 Oracle 中的没法比,MySQL 8.0.31 还报告了一个 bug:Bug #109145 Using index for skip scan cause incorrect result(后续版本已经修复)。个人建议知道有这个东西就好,不需要深究,实际项目也不一定能用上。
索引下推
索引下推(Index Condition Pushdown,简称 ICP) 是 MySQL 5.6 版本中提供的一项索引优化功能,它允许存储引擎在索引遍历过程中,执行部分 WHERE 字句的判断条件,直接过滤掉不满足条件的记录,从而减少回表次数,提高查询效率。
假设我们有一个名为 user 的表,其中包含 id、username、zipcode 和 birthdate 4 个字段,创建了联合索引 (zipcode, birthdate)。
CREATE TABLE `user` (
`id` int NOT NULL AUTO_INCREMENT,
`username` varchar(20) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL,
`zipcode` varchar(20) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL,
`birthdate` date NOT NULL,
PRIMARY KEY (`id`),
KEY `idx_zipcode_birthdate` (`zipcode`,`birthdate`) ) ENGINE=InnoDB AUTO_INCREMENT=1001 DEFAULT CHARSET=utf8mb4;
# 查询 zipcode 为 431200 且生日在 3 月的用户
SELECT * FROM user WHERE zipcode = '431200' AND MONTH(birthdate) = 3;- 没有索引下推之前,即使
zipcode字段利用索引可以帮助我们快速定位到zipcode = '431200'的用户,但我们仍然需要对每一个找到的用户进行回表操作,获取完整的用户数据,再去判断MONTH(birthdate) = 3。 - 有了索引下推之后,存储引擎会在使用
zipcode字段索引查找zipcode = '431200'的用户时,同时判断MONTH(birthdate) = 3。这样,只有同时满足条件的记录才会被返回,减少了回表次数。
再来讲讲索引下推的具体原理,先看下面这张 MySQL 简要架构图。
MySQL 可以简单分为 Server 层和存储引擎层这两层。Server 层处理查询解析、分析、优化、缓存以及与客户端的交互等操作,而存储引擎层负责数据的存储和读取,MySQL 支持 InnoDB、MyISAM、Memory 等多种存储引擎。
索引下推的 下推 其实就是指将部分上层(Server 层)负责的事情,交给了下层(存储引擎层)去处理。
我们这里结合索引下推原理再对上面提到的例子进行解释。
没有索引下推之前:
- 存储引擎层先根据
zipcode索引字段找到所有zipcode = '431200'的用户的主键 ID,然后二次回表查询,获取完整的用户数据; - 存储引擎层把所有
zipcode = '431200'的用户数据全部交给 Server 层,Server 层根据MONTH(birthdate) = 3这一条件再进一步做筛选。
有了索引下推之后:
- 存储引擎层先根据
zipcode索引字段找到所有zipcode = '431200'的用户,然后直接判断MONTH(birthdate) = 3,筛选出符合条件的主键 ID; - 二次回表查询,根据符合条件的主键 ID 去获取完整的用户数据;
- 存储引擎层把符合条件的用户数据全部交给 Server 层。
可以看出,除了可以减少回表次数之外,索引下推还可以减少存储引擎层和 Server 层的数据传输量。
最后,总结一下索引下推应用范围:
- 适用于 InnoDB 引擎和 MyISAM 引擎的查询。
- 适用于执行计划是 range、ref、eq_ref、ref_or_null 的范围查询。
- 对于 InnoDB 表,仅用于非聚簇索引。索引下推的目标是减少全行读取次数,从而减少 I/O 操作。对于 InnoDB 聚集索引,完整的记录已经读入 InnoDB 缓冲区。在这种情况下使用索引下推不会减少 I/O。
- 子查询不能使用索引下推,因为子查询通常会创建临时表来处理结果,而这些临时表是没有索引的。
- 存储过程不能使用索引下推,因为存储引擎无法调用存储函数。
正确使用索引的一些建议
选择合适的字段创建索引
- 不为 NULL 的字段:索引字段的数据应该尽量不为 NULL,因为对于数据为 NULL 的字段,数据库较难优化。如果字段频繁被查询,但又避免不了为 NULL,建议使用 0、1、true、false 这样语义较为清晰的短值或短字符作为替代。
- 被频繁查询的字段:我们创建索引的字段应该是查询操作非常频繁的字段。
- 被作为条件查询的字段:被作为 WHERE 条件查询的字段,应该被考虑建立索引。
- 频繁需要排序的字段:索引已经排序,这样查询可以利用索引的排序,加快排序查询时间。
- 被经常频繁用于连接的字段:经常用于连接的字段可能是一些外键列,对于外键列并不一定要建立外键,只是说该列涉及到表与表的关系。对于频繁被连接查询的字段,可以考虑建立索引,提高多表连接查询的效率。
避免索引失效
索引失效也是慢查询的主要原因之一,常见的导致索引失效的情况有下面这两类:
1. SQL 写法与底层逻辑冲突(破坏 B+Tree 有序性)
此类问题最为常见,本质是查询条件让底层的 B+Tree 失去了“二分查找”的快速定位能力。
- 违背最左前缀原则:跳过联合索引前导列,或遇到范围查询(如
>、<、BETWEEN、LIKE "abc%")导致后续列中断精确定位,降级为范围扫描加过滤。 - 对索引列进行加工:在
WHERE左侧对索引列进行数学计算或应用函数,导致原始数据发生逻辑改变,在索引树中呈现无序状态。 - 隐式类型转换(隐蔽且致命):当“字符串类型的列”去比较“数字类型的值”时,MySQL 会默认在列上套用转换函数,直接破坏树的有序性。
- LIKE 模糊查询前置通配符:如
LIKE "%abc",前缀字符的不确定性使得优化器无法锁定扫描区间的起始点。 - ORDER BY 排序陷阱:排序列未命中索引、排序方向与索引结构不一致等触发额外的内存或磁盘排序(
Using filesort)。
2. 优化器的成本决策(基于 I/O 成本妥协)
此类问题并非索引本身不可用,而是 MySQL 优化器经过计算后,认为“不走普通索引”整体开销反而更小。
- 无脑
SELECT \*导致回表成本超载:查询大量非索引覆盖列时,若命中数据量较大(通常超 20%~30%),优化器会判定全表扫描的顺序 I/O 优于频繁回表的随机 I/O,从而主动放弃索引。 OR条件导致全表扫描:只要OR连接的任意一侧条件没有对应索引,就会触发全表扫描。即使两侧都有索引,若 Index Merge(索引合并)的预期成本过高,依然会被放弃。IN列表过长引发估算失真:当IN列表长度超过系统阈值(默认 200)时,优化器会从精准的深入探测(Index Dive)切换为粗略的统计估算,极易因统计信息陈旧而产生执行成本的误判。
详细介绍:MySQL索引失效场景总结。
被频繁更新的字段应该慎重建立索引
虽然索引能带来查询上的效率,但是维护索引的成本也是不小的。 如果一个字段不被经常查询,反而被经常修改,那么就更不应该在这种字段上建立索引了。
限制每张表上的索引数量
索引并不是越多越好,建议单张表索引不超过 5 个!索引可以提高效率,同样可以降低效率。
索引可以增加查询效率,但同样也会降低插入和更新的效率,甚至有些情况下会降低查询效率。
因为 MySQL 优化器在选择如何优化查询时,会根据统计信息,对每一个可以用到的索引来进行评估,以生成出一个最好的执行计划,如果同时有很多个索引都可以用于查询,就会增加 MySQL 优化器生成执行计划的时间,同样会降低查询性能。
尽可能的考虑建立联合索引而不是单列索引
因为索引是需要占用磁盘空间的,可以简单理解为每个索引都对应着一颗 B+ 树。如果一个表的字段过多,索引过多,那么当这个表的数据达到一个体量后,索引占用的空间也是很多的,且修改索引时,耗费的时间也是较多的。如果是联合索引,多个字段在一个索引上,那么将会节约很大磁盘空间,且修改数据的操作效率也会提升。
注意避免冗余索引
冗余索引指的是索引的功能相同,能够命中索引(a, b)就肯定能命中索引(a) ,那么索引(a)就是冗余索引。如(name,city)和(name)这两个索引就是冗余索引,能够命中前者的查询肯定是能够命中后者的。在大多数情况下,都应该尽量扩展已有的索引而不是创建新索引。
字符串类型的字段使用前缀索引代替普通索引
前缀索引仅限于字符串类型,较普通索引会占用更小的空间,所以可以考虑使用前缀索引带替普通索引。
删除长期未使用的索引
删除长期未使用的索引,不用的索引的存在会造成不必要的性能损耗。
MySQL 5.7 可以通过查询 sys 库的 schema_unused_indexes 视图来查询哪些索引从未被使用。
知道如何分析 SQL 语句是否走索引查询
我们可以使用 EXPLAIN 命令来分析 SQL 的 执行计划 ,这样就知道语句是否命中索引了。执行计划是指一条 SQL 语句在经过 MySQL 查询优化器的优化会后,具体的执行方式。
EXPLAIN 并不会真的去执行相关的语句,而是通过 查询优化器 对语句进行分析,找出最优的查询方案,并显示对应的信息。
EXPLAIN 的输出格式如下:
mysql> EXPLAIN SELECT `score`,`name` FROM `cus_order` ORDER BY `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, 1 warning (0.00 sec)各个字段的含义如下:
| 列名 | 含义 |
|---|---|
| id | SELECT 查询的序列标识符 |
| select_type | SELECT 关键字对应的查询类型 |
| table | 用到的表名 |
| partitions | 匹配的分区,对于未分区的表,值为 NULL |
| type | 表的访问方法 |
| possible_keys | 可能用到的索引 |
| key | 实际用到的索引 |
| key_len | 所选索引的长度 |
| ref | 当使用索引等值查询时,与索引作比较的列或常量 |
| rows | 预计要读取的行数 |
| filtered | 按表条件过滤后,留存的记录数的百分比 |
| Extra | 附加信息 |
篇幅问题,我这里只是简单介绍了一下 MySQL 执行计划,详细介绍请看:MySQL 执行计划分析这篇文章。
数据结构延伸阅读
理解 MySQL 索引时,建议回到树结构本身看一遍:
Distributed System Rpc Rpc Intro (`docs/distributed-system/rpc/rpc-intro.md`)
title: RPC 远程过程调用详解:原理、调用流程、序列化协议与框架选型
category: 分布式
tag:
- RPC
head: - meta
- name: keywords
content: RPC,RPC 原理,远程过程调用,动态代理,序列化,服务发现,Dubbo,gRPC,Thrift,微服务通信,RPC 面试题
这篇文章会简单介绍一下 RPC 相关的基础概念。
放到分布式系统里看,RPC 解决的是服务之间如何互相调用。外部请求进入系统通常先经过 API 网关,进入内部服务后,服务之间才会通过 RPC、HTTP Client、消息队列等方式继续协作。如果你想看 Dubbo 这种成熟 RPC 框架的服务治理细节,可以继续读 Dubbo 面试题总结。
RPC 是什么?
RPC(Remote Procedure Call) 即远程过程调用,通过名字我们就能看出 RPC 关注的是远程调用而非本地调用。
为什么要 RPC ? 因为,两个不同的服务器上的服务提供的方法不在一个内存空间,所以,需要通过网络编程才能传递方法调用所需要的参数。并且,方法调用的结果也需要通过网络编程来接收。但是,如果我们自己手动网络编程来实现这个调用过程的话工作量是非常大的,因为,我们需要考虑底层传输方式(TCP 还是 UDP)、序列化方式等等方面。
RPC 能帮助我们做什么呢? 简单来说,通过 RPC 可以帮助我们调用远程计算机上某个服务的方法,这个过程就像调用本地方法一样简单。并且!我们不需要了解底层网络编程的具体细节。
举个例子:两个不同的服务 A、B 部署在两台不同的机器上,服务 A 如果想要调用服务 B 中的某个方法的话就可以通过 RPC 来做。
一言蔽之:RPC 的出现就是为了让你调用远程方法像调用本地方法一样简单。
RPC 的原理是什么?
为了能够帮助小伙伴们理解 RPC 原理,我们可以将整个 RPC 的 核心功能看作是下面 👇 5 个部分实现的:
- 客户端(服务消费端):调用远程方法的一端。
- 客户端 Stub(桩):这其实就是一代理类。代理类主要做的事情很简单,就是把你调用方法、类、方法参数等信息传递到服务端。
- 网络传输:网络传输就是你要把你调用的方法的信息比如说参数啊这些东西传输到服务端,然后服务端执行完之后再把返回结果通过网络传输给你传输回来。网络传输的实现方式有很多种比如最基本的 Socket 或者性能以及封装更加优秀的 Netty(推荐)。
- 服务端 Stub(桩):这个桩就不是代理类了。我觉得理解为桩实际不太好,大家注意一下就好。这里的服务端 Stub 实际指的就是接收到客户端执行方法的请求后,去执行对应的方法然后返回结果给客户端的类。
- 服务端(服务提供端):提供远程方法的一端。
具体原理图如下,后面我会串起来将整个 RPC 的过程给大家说一下。
- 服务消费端(client)以本地调用的方式调用远程服务;
- 客户端 Stub(client stub) 接收到调用后负责将方法、参数等组装成能够进行网络传输的消息体(序列化):
RpcRequest; - 客户端 Stub(client stub) 找到远程服务的地址,并将消息发送到服务提供端;
- 服务端 Stub(桩)收到消息将消息反序列化为 Java 对象:
RpcRequest; - 服务端 Stub(桩)根据
RpcRequest中的类、方法、方法参数等信息调用本地的方法; - 服务端 Stub(桩)得到方法执行结果并将组装成能够进行网络传输的消息体:
RpcResponse(序列化)发送至消费方; - 客户端 Stub(client stub)接收到消息并将消息反序列化为 Java 对象:
RpcResponse,这样也就得到了最终结果。over!
相信小伙伴们看完上面的讲解之后,已经了解了 RPC 的原理。
虽然篇幅不多,但是基本把 RPC 框架的核心原理讲清楚了!另外,对于上面的技术细节,我会在后面的章节介绍到。
最后,对于 RPC 的原理,希望小伙伴不单单要理解,还要能够自己画出来并且能够给别人讲出来。因为,在面试中这个问题在面试官问到 RPC 相关内容的时候基本都会碰到。
有哪些常见的 RPC 框架?
我们这里说的 RPC 框架指的是可以让客户端直接调用服务端方法,就像调用本地方法一样简单的框架,比如我下面介绍的 Dubbo、Motan、gRPC 这些。 如果需要和 HTTP 协议打交道,解析和封装 HTTP 请求和响应。这类框架并不能算是“RPC 框架”,比如 Feign。
Dubbo
Apache Dubbo 是一款微服务框架,为大规模微服务实践提供高性能 RPC 通信、流量治理、可观测性等解决方案,
涵盖 Java、Golang 等多种语言 SDK 实现。
Dubbo 提供了从服务定义、服务发现、服务通信到流量管控等几乎所有的服务治理能力,支持 Triple 协议(基于 HTTP/2 之上定义的下一代 RPC 通信协议)、应用级服务发现、Dubbo Mesh (Dubbo3 赋予了很多云原生友好的新特性)等特性。
Dubbo 是由阿里开源,后来加入了 Apache 。正是由于 Dubbo 的出现,才使得越来越多的公司开始使用以及接受分布式架构。
Dubbo 算的是比较优秀的国产开源项目了,它的源码也是非常值得学习和阅读的!
Motan
Motan 是新浪微博开源的一款 RPC 框架,据说在新浪微博正支撑着千亿次调用。不过笔者倒是很少看到有公司使用,而且网上的资料也比较少。
很多人喜欢拿 Motan 和 Dubbo 作比较,毕竟都是国内大公司开源的。笔者在查阅了很多资料,以及简单查看了其源码之后发现:Motan 更像是一个精简版的 Dubbo,可能是借鉴了 Dubbo 的思想,Motan 的设计更加精简,功能更加纯粹。
不过,我不推荐你在实际项目中使用 Motan。如果你要是公司实际使用的话,还是推荐 Dubbo ,其社区活跃度以及生态都要好很多。
- 从 Motan 看 RPC 框架设计:http://kriszhang.com/motan-rpc-impl/
- Motan 中文文档:https://github.com/weibocom/motan/wiki/zh_overview
gRPC
gRPC 是 Google 开源的一个高性能、通用的开源 RPC 框架。其由主要面向移动应用开发并基于 HTTP/2 协议标准而设计(支持双向流、消息头压缩等功能,更加节省带宽),基于 ProtoBuf 序列化协议开发,并且支持众多开发语言。
何谓 ProtoBuf? ProtoBuf( Protocol Buffer) 是一种更加灵活、高效的数据格式,可用于通讯协议、数据存储等领域,基本支持所有主流编程语言且与平台无关。不过,通过 ProtoBuf 定义接口和数据类型还挺繁琐的,这是一个小问题。
不得不说,gRPC 的通信层的设计还是非常优秀的,Dubbo-go 3.0 的通信层改进主要借鉴了 gRPC。
不过,gRPC 的设计导致其几乎没有服务治理能力。如果你想要解决这个问题的话,就需要依赖其他组件比如腾讯的 PolarisMesh(北极星)了。
- GitHub:https://github.com/grpc/grpc
- 官网:https://grpc.io/
Thrift
Apache Thrift 是 Facebook 开源的跨语言的 RPC 通信框架,目前已经捐献给 Apache 基金会管理,由于其跨语言特性和出色的性能,在很多互联网公司得到应用,有能力的公司甚至会基于 thrift 研发一套分布式服务框架,增加诸如服务注册、服务发现等功能。
Thrift支持多种不同的编程语言,包括C++、Java、Python、PHP、Ruby等(相比于 gRPC 支持的语言更多 )。
- 官网:https://thrift.apache.org/
- Thrift 简单介绍:https://www.jianshu.com/p/8f25d057a5a9
总结
gRPC 和 Thrift 虽然支持跨语言的 RPC 调用,但是它们只提供了最基本的 RPC 框架功能,缺乏一系列配套的服务化组件和服务治理功能的支撑。
Dubbo 不论是从功能完善程度、生态系统还是社区活跃度来说都是最优秀的。而且,Dubbo 在国内有很多成功的案例比如当当网、滴滴等等,是一款经得起生产考验的成熟稳定的 RPC 框架。最重要的是你还能找到非常多的 Dubbo 参考资料,学习成本相对也较低。
下图展示了 Dubbo 的生态系统。
Dubbo 也是 Spring Cloud Alibaba 里面的一个组件。
但是,Dubbo 和 Motan 主要是给 Java 语言使用。虽然,Dubbo 和 Motan 目前也能兼容部分语言,但是不太推荐。如果需要跨多种语言调用的话,可以考虑使用 gRPC。
综上,如果是 Java 后端技术栈,并且你在纠结选择哪一种 RPC 框架的话,我推荐你考虑一下 Dubbo。
如何设计并实现一个 RPC 框架?
《手写 RPC 框架》 是我的知识星球的一个内部小册,我写了 12 篇文章来讲解如何从零开始基于 Netty+Kyro+Zookeeper 实现一个简易的 RPC 框架。
麻雀虽小五脏俱全,项目代码注释详细,结构清晰,并且集成了 Check Style 规范代码结构,非常适合阅读和学习。
内容概览:
既然有了 HTTP 协议,为什么还要有 RPC ?
关于这个问题的详细答案,请看这篇文章:有了 HTTP 协议,为什么还要有 RPC ? 。
Java Concurrent Completablefuture Intro (`docs/java/concurrent/completablefuture-intro.md`)
title: CompletableFuture 详解
category: Java
tag:
- Java并发
head: - meta
- name: keywords
content: CompletableFuture,异步编程,异步编排,Future,thenCompose,thenCombine,allOf,并行任务
实际项目中,一个接口可能需要同时获取多种不同的数据,然后再汇总返回,这种场景还是挺常见的。举个例子:用户请求获取订单信息,可能需要同时获取用户信息、商品详情、物流信息、商品推荐等数据。
如果是串行(按顺序依次执行每个任务)执行的话,接口的响应速度会非常慢。考虑到这些任务之间有大部分都是 无前后顺序关联 的,可以 并行执行,就比如说调用获取商品详情的时候,可以同时调用获取物流信息。通过并行执行多个任务的方式,接口的响应速度会得到大幅优化。
对于存在前后调用顺序关系的任务,可以进行任务编排。
- 获取用户信息之后,才能调用商品详情和物流信息接口。
- 成功获取商品详情和物流信息之后,才能调用商品推荐接口。
可能会用到多线程异步任务编排的场景(这里只是举例,数据不一定是一次返回,可能会对接口进行拆分):
- 首页:例如技术社区的首页可能需要同时获取文章推荐列表、广告栏、文章排行榜、热门话题等信息。
- 详情页:例如技术社区的文章详情页可能需要同时获取作者信息、文章详情、文章评论等信息。
- 统计模块:例如技术社区的后台统计模块可能需要同时获取粉丝数汇总、文章数据(阅读量、评论量、收藏量)汇总等信息。
对于 Java 程序来说,Java 8 才被引入的 CompletableFuture 可以帮助我们来做多个任务的编排,功能非常强大。
这篇文章是 CompletableFuture 的简单入门,带大家看看 CompletableFuture 常用的 API。
Future 介绍
Future 接口是异步思想的典型运用,主要用在一些需要执行耗时任务的场景,避免程序一直原地等待耗时任务执行完成,执行效率太低。具体来说是这样的:当我们执行某一耗时的任务时,可以将这个耗时任务交给一个子线程去异步执行,同时我们可以干点其他事情,不用傻傻等待耗时任务执行完成。等我们的事情干完后,我们再通过 Future 获取到耗时任务的执行结果。这样一来,程序的执行效率就明显提高了。
这其实就是多线程中经典的 Future 模式,你可以将其看作是一种设计模式,核心思想是异步调用,主要用在多线程领域,并非 Java 语言独有。
在 Java 中,Future 是一个泛型接口,位于 java.util.concurrent 包下。它有 5 个经典抽象方法,主要包括下面 4 类功能;JDK 19 起又增加了 resultNow()、exceptionNow() 和 state() 三个默认查询方法。
- 取消任务;
- 判断任务是否被取消;
- 判断任务是否已经执行完成;
- 获取任务执行结果。
// V 代表了Future执行的任务返回值的类型
public interface Future<V> {
// 取消任务执行
// 成功取消返回 true,否则返回 false
boolean cancel(boolean mayInterruptIfRunning);
// 判断任务是否被取消
boolean isCancelled();
// 判断任务是否已经执行完成
boolean isDone();
// 获取任务执行结果
V get() throws InterruptedException, ExecutionException;
// 指定时间内没有返回计算结果就抛出 TimeoutException 异常
V get(long timeout, TimeUnit unit)
throws InterruptedException, ExecutionException, TimeoutException;
}简单理解就是:我有一个任务,提交给了 Future 来处理。任务执行期间我自己可以去做任何想做的事情。并且,在这期间我还可以取消任务以及获取任务的执行状态。一段时间之后,我就可以 Future 那里直接取出任务执行结果。
CompletableFuture 介绍
Future 在实际使用过程中存在一些局限性,比如不支持异步任务的编排组合、获取计算结果的 get() 方法为阻塞调用。
Java 8 才被引入 CompletableFuture 类可以解决 Future 的这些缺陷。CompletableFuture 除了提供了更为好用和强大的 Future 特性之外,还提供了函数式编程、异步任务编排组合(可以将多个异步任务串联起来,组成一个完整的链式调用)等能力。
下面我们来简单看看 CompletableFuture 类的定义。
public class CompletableFuture<T> implements Future<T>, CompletionStage<T> {
}可以看到,CompletableFuture 同时实现了 Future 和 CompletionStage 接口。
CompletableFuture 除了提供了更为好用和强大的 Future 特性之外,还提供了函数式编程的能力。
Future 接口有 5 个方法:
boolean cancel(boolean mayInterruptIfRunning):尝试取消执行任务。boolean isCancelled():判断任务是否被取消。boolean isDone():判断任务是否已经被执行完成。get():等待任务执行完成并获取运算结果。get(long timeout, TimeUnit unit):多了一个超时时间。
CompletionStage 接口描述了一个异步计算的阶段。很多计算可以分成多个阶段或步骤,此时可以通过它将所有步骤组合起来,形成异步计算的流水线。
CompletionStage 接口中的方法比较多,CompletableFuture 的函数式能力就是这个接口赋予的。从这个接口的方法参数你就可以发现其大量使用了 Java8 引入的函数式编程。
由于方法众多,所以这里不能一一讲解,下文中我会介绍大部分常见方法的使用。
CompletableFuture 常见操作
创建 CompletableFuture
常见的创建 CompletableFuture 对象的方法如下:
- 通过 new 关键字。
- 基于
CompletableFuture自带的静态工厂方法:runAsync()、supplyAsync()。
new 关键字
通过 new 关键字创建 CompletableFuture 对象这种使用方式可以看作是将 CompletableFuture 当做 Future 来使用。
我在我的开源项目 guide-rpc-framework 中就是这种方式创建的 CompletableFuture 对象。
下面咱们来看一个简单的案例。
我们通过创建了一个结果值类型为 RpcResponse<Object> 的 CompletableFuture,你可以把 resultFuture 看作是异步运算结果的载体。
CompletableFuture<RpcResponse<Object>> resultFuture = new CompletableFuture<>();假设在未来的某个时刻,我们得到了最终的结果。这时,我们可以调用 complete() 方法为其传入结果,这表示 resultFuture 已经被完成了。
// complete() 方法只能调用一次,后续调用将被忽略。
resultFuture.complete(rpcResponse);你可以通过 isDone() 方法来检查是否已经完成。
public boolean isDone() {
return result != null;
}获取异步计算的结果也非常简单,直接调用 get() 方法即可。调用 get() 方法的线程会阻塞直到 CompletableFuture 完成运算。
rpcResponse = completableFuture.get();如果你已经知道计算的结果的话,可以使用静态方法 completedFuture() 来创建 CompletableFuture。
CompletableFuture<String> future = CompletableFuture.completedFuture("hello!");
assertEquals("hello!", future.get());completedFuture() 方法底层调用的是带参数的 new 方法,只不过,这个方法不对外暴露。
public static <U> CompletableFuture<U> completedFuture(U value) {
return new CompletableFuture<U>((value == null) ? NIL : value);
}静态工厂方法
这两个方法可以帮助我们封装计算逻辑。
static <U> CompletableFuture<U> supplyAsync(Supplier<U> supplier);
// 使用自定义线程池(推荐)
static <U> CompletableFuture<U> supplyAsync(Supplier<U> supplier, Executor executor);
static CompletableFuture<Void> runAsync(Runnable runnable);
// 使用自定义线程池(推荐)
static CompletableFuture<Void> runAsync(Runnable runnable, Executor executor);runAsync() 方法接受的参数是 Runnable,这是一个函数式接口,不允许返回值。当你需要异步操作且不关心返回结果的时候可以使用 runAsync() 方法。
@FunctionalInterface
public interface Runnable {
public abstract void run();
}supplyAsync() 方法接受的参数是 Supplier,这也是一个函数式接口,U 是返回结果值的类型。
@FunctionalInterface
public interface Supplier<T> {
/**
* Gets a result.
*
* @return a result
*/
T get();
}当你需要异步操作且关心返回结果的时候,可以使用 supplyAsync() 方法。
CompletableFuture<Void> future = CompletableFuture.runAsync(() -> System.out.println("hello!"));
future.get();// 输出 "hello!"
CompletableFuture<String> future2 = CompletableFuture.supplyAsync(() -> "hello!");
assertEquals("hello!", future2.get());处理异步结算的结果
当我们获取到异步计算的结果之后,还可以对其进行进一步的处理,比较常用的方法有下面几个:
thenApply()thenAccept()thenRun()whenComplete()
thenApply() 方法接受一个 Function 实例,用它来处理结果。
// 非异步回调:可能由完成上一个阶段的线程执行;如果阶段已经完成,也可能由当前调用线程执行
public <U> CompletableFuture<U> thenApply(
Function<? super T,? extends U> fn) {
return uniApplyStage(null, fn);
}
//使用默认的 ForkJoinPool 线程池(不推荐)
public <U> CompletableFuture<U> thenApplyAsync(
Function<? super T,? extends U> fn) {
return uniApplyStage(defaultExecutor(), fn);
}
// 使用自定义线程池(推荐)
public <U> CompletableFuture<U> thenApplyAsync(
Function<? super T,? extends U> fn, Executor executor) {
return uniApplyStage(screenExecutor(executor), fn);
}thenApply() 方法使用示例如下:
CompletableFuture<String> future = CompletableFuture.completedFuture("hello!")
.thenApply(s -> s + "world!");
assertEquals("hello!world!", future.get());
// 这次调用将被忽略。
future.thenApply(s -> s + "nice!");
assertEquals("hello!world!", future.get());你还可以进行 流式调用:
CompletableFuture<String> future = CompletableFuture.completedFuture("hello!")
.thenApply(s -> s + "world!").thenApply(s -> s + "nice!");
assertEquals("hello!world!nice!", future.get());如果你不需要从回调函数中获取返回结果,可以使用 thenAccept() 或者 thenRun()。这两个方法的区别在于 thenRun() 不能访问异步计算的结果。
thenAccept() 方法的参数是 Consumer<? super T>。
public CompletableFuture<Void> thenAccept(Consumer<? super T> action) {
return uniAcceptStage(null, action);
}
public CompletableFuture<Void> thenAcceptAsync(Consumer<? super T> action) {
return uniAcceptStage(defaultExecutor(), action);
}
public CompletableFuture<Void> thenAcceptAsync(Consumer<? super T> action,
Executor executor) {
return uniAcceptStage(screenExecutor(executor), action);
}顾名思义,Consumer 属于消费型接口,它可以接收 1 个输入对象然后进行“消费”。
@FunctionalInterface
public interface Consumer<T> {
void accept(T t);
default Consumer<T> andThen(Consumer<? super T> after) {
Objects.requireNonNull(after);
return (T t) -> { accept(t); after.accept(t); };
}
}thenRun() 的方法是的参数是 Runnable。
public CompletableFuture<Void> thenRun(Runnable action) {
return uniRunStage(null, action);
}
public CompletableFuture<Void> thenRunAsync(Runnable action) {
return uniRunStage(defaultExecutor(), action);
}
public CompletableFuture<Void> thenRunAsync(Runnable action,
Executor executor) {
return uniRunStage(screenExecutor(executor), action);
}thenAccept() 和 thenRun() 使用示例如下:
CompletableFuture.completedFuture("hello!")
.thenApply(s -> s + "world!").thenApply(s -> s + "nice!").thenAccept(System.out::println);//hello!world!nice!
CompletableFuture.completedFuture("hello!")
.thenApply(s -> s + "world!").thenApply(s -> s + "nice!").thenRun(() -> System.out.println("hello!"));//hello!whenComplete() 的方法的参数是 BiConsumer<? super T, ? super Throwable>。
public CompletableFuture<T> whenComplete(
BiConsumer<? super T, ? super Throwable> action) {
return uniWhenCompleteStage(null, action);
}
public CompletableFuture<T> whenCompleteAsync(
BiConsumer<? super T, ? super Throwable> action) {
return uniWhenCompleteStage(defaultExecutor(), action);
}
// 使用自定义线程池(推荐)
public CompletableFuture<T> whenCompleteAsync(
BiConsumer<? super T, ? super Throwable> action, Executor executor) {
return uniWhenCompleteStage(screenExecutor(executor), action);
}相对于 Consumer, BiConsumer 可以接收 2 个输入对象然后进行“消费”。
@FunctionalInterface
public interface BiConsumer<T, U> {
void accept(T t, U u);
default BiConsumer<T, U> andThen(BiConsumer<? super T, ? super U> after) {
Objects.requireNonNull(after);
return (l, r) -> {
accept(l, r);
after.accept(l, r);
};
}
}whenComplete() 使用示例如下:
CompletableFuture<String> future = CompletableFuture.supplyAsync(() -> "hello!")
.whenComplete((res, ex) -> {
// res 代表返回的结果
// ex 的类型为 Throwable ,代表抛出的异常
System.out.println(res);
// 这里没有抛出异常所有为 null
assertNull(ex);
});
assertEquals("hello!", future.get());异常处理
你可以通过 handle() 方法来处理任务执行过程中可能出现的抛出异常的情况。
public <U> CompletableFuture<U> handle(
BiFunction<? super T, Throwable, ? extends U> fn) {
return uniHandleStage(null, fn);
}
public <U> CompletableFuture<U> handleAsync(
BiFunction<? super T, Throwable, ? extends U> fn) {
return uniHandleStage(defaultExecutor(), fn);
}
public <U> CompletableFuture<U> handleAsync(
BiFunction<? super T, Throwable, ? extends U> fn, Executor executor) {
return uniHandleStage(screenExecutor(executor), fn);
}示例代码如下:
CompletableFuture<String> future
= CompletableFuture.supplyAsync(() -> {
if (true) {
throw new RuntimeException("Computation error!");
}
return "hello!";
}).handle((res, ex) -> {
// res 代表返回的结果
// ex 的类型为 Throwable ,代表抛出的异常
return res != null ? res : "world!";
});
assertEquals("world!", future.get());你还可以通过 exceptionally() 方法来处理异常情况。
CompletableFuture<String> future
= CompletableFuture.supplyAsync(() -> {
if (true) {
throw new RuntimeException("Computation error!");
}
return "hello!";
}).exceptionally(ex -> {
System.out.println(ex.toString());// CompletionException
return "world!";
});
assertEquals("world!", future.get());如果你想让 CompletableFuture 的结果就是异常的话,可以使用 completeExceptionally() 方法为其赋值。
CompletableFuture<String> completableFuture = new CompletableFuture<>();
// ...
completableFuture.completeExceptionally(
new RuntimeException("Calculation failed!"));
// ...
completableFuture.get(); // ExecutionException组合 CompletableFuture
你可以使用 thenCompose() 按顺序链接两个 CompletableFuture 对象,实现异步的任务链。它的作用是将前一个任务的返回结果作为下一个任务的输入参数,从而形成一个依赖关系。
public <U> CompletableFuture<U> thenCompose(
Function<? super T, ? extends CompletionStage<U>> fn) {
return uniComposeStage(null, fn);
}
public <U> CompletableFuture<U> thenComposeAsync(
Function<? super T, ? extends CompletionStage<U>> fn) {
return uniComposeStage(defaultExecutor(), fn);
}
public <U> CompletableFuture<U> thenComposeAsync(
Function<? super T, ? extends CompletionStage<U>> fn,
Executor executor) {
return uniComposeStage(screenExecutor(executor), fn);
}thenCompose() 方法会使用示例如下:
CompletableFuture<String> future
= CompletableFuture.supplyAsync(() -> "hello!")
.thenCompose(s -> CompletableFuture.supplyAsync(() -> s + "world!"));
assertEquals("hello!world!", future.get());在实际开发中,这个方法还是非常有用的。比如说,task1 和 task2 都是异步执行的,但 task1 必须执行完成后才能开始执行 task2(task2 依赖 task1 的执行结果)。
和 thenCompose() 方法类似的还有 thenCombine() 方法, 它同样可以组合两个 CompletableFuture 对象。
CompletableFuture<String> completableFuture
= CompletableFuture.supplyAsync(() -> "hello!")
.thenCombine(CompletableFuture.supplyAsync(
() -> "world!"), (s1, s2) -> s1 + s2)
.thenCompose(s -> CompletableFuture.supplyAsync(() -> s + "nice!"));
assertEquals("hello!world!nice!", completableFuture.get());那 thenCompose() 和 thenCombine() 有什么区别呢?
thenCompose()可以链接两个CompletableFuture对象,并将前一个任务的返回结果作为下一个任务的参数,它们之间存在着先后顺序。thenCombine()会在两个阶段都正常完成后,把它们的结果合并。这两个阶段可以彼此独立,但是否并行执行取决于它们的创建方式和所用执行器,thenCombine()本身不负责启动任务。
除了 thenCompose() 和 thenCombine() 之外, 还有一些其他的组合 CompletableFuture 的方法用于实现不同的效果,满足不同的业务需求。
例如,在 task1 和 task2 都正常完成的场景中,可以使用 acceptEither(),让 task3 接收其中一个先完成任务的结果。需要注意,它不能作为可靠的“第一个成功结果”选择器:只要其中一个阶段异常完成,返回阶段的结果就要遵循 CompletionStage 对 either 组合的异常规则。
public CompletableFuture<Void> acceptEither(
CompletionStage<? extends T> other, Consumer<? super T> action) {
return orAcceptStage(null, other, action);
}
public CompletableFuture<Void> acceptEitherAsync(
CompletionStage<? extends T> other, Consumer<? super T> action) {
return orAcceptStage(asyncPool, other, action);
}简单举一个例子:
CompletableFuture<String> task = CompletableFuture.supplyAsync(() -> {
System.out.println("任务1开始执行,当前时间:" + System.currentTimeMillis());
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("任务1执行完毕,当前时间:" + System.currentTimeMillis());
return "task1";
});
CompletableFuture<String> task2 = CompletableFuture.supplyAsync(() -> {
System.out.println("任务2开始执行,当前时间:" + System.currentTimeMillis());
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("任务2执行完毕,当前时间:" + System.currentTimeMillis());
return "task2";
});
task.acceptEitherAsync(task2, (res) -> {
System.out.println("任务3开始执行,当前时间:" + System.currentTimeMillis());
System.out.println("上一个任务的结果为:" + res);
});
// 增加一些延迟时间,确保异步任务有足够的时间完成
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}输出:
任务1开始执行,当前时间:1695088058520
任务2开始执行,当前时间:1695088058521
任务1执行完毕,当前时间:1695088059023
任务3开始执行,当前时间:1695088059023
上一个任务的结果为:task1
任务2执行完毕,当前时间:1695088059523当两个阶段都正常完成时,acceptEitherAsync() 会使用其中一个已完成阶段的结果异步执行任务 3,通常是先完成者的结果。不过,如果其中一个阶段异常完成,而另一个尚未完成或正常完成,规范并不保证返回阶段最终一定正常完成还是异常完成。因此,不能依赖该方法忽略先发生的异常并继续等待另一个成功结果。
等待多个 CompletableFuture 完成
你可以通过 CompletableFuture 的 allOf() 静态方法等待多个 CompletableFuture 全部完成。allOf() 只组合已有阶段的完成状态,不负责启动这些任务;任务是否并行取决于它们的创建方式和执行器。
实际项目中,我们经常需要并行运行多个互不相关的任务,这些任务之间没有依赖关系,可以互相独立地运行。
比说我们要读取处理 6 个文件,这 6 个任务都是没有执行顺序依赖的任务,但是我们需要返回给用户的时候将这几个文件的处理的结果进行统计整理。像这种情况我们就可以使用并行运行多个 CompletableFuture 来处理。
示例代码如下:
CompletableFuture<Void> task1 =
CompletableFuture.supplyAsync(()->{
//自定义业务操作
});
......
CompletableFuture<Void> task6 =
CompletableFuture.supplyAsync(()->{
//自定义业务操作
});
......
CompletableFuture<Void> headerFuture=CompletableFuture.allOf(task1,.....,task6);
try {
headerFuture.join();
} catch (Exception ex) {
......
}
System.out.println("all done. ");经常和 allOf() 方法拿来对比的是 anyOf() 方法。
allOf() 方法会等到所有的 CompletableFuture 都运行完成之后再返回
Random rand = new Random();
CompletableFuture<String> future1 = CompletableFuture.supplyAsync(() -> {
try {
Thread.sleep(1000 + rand.nextInt(1000));
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
System.out.println("future1 done...");
}
return "abc";
});
CompletableFuture<String> future2 = CompletableFuture.supplyAsync(() -> {
try {
Thread.sleep(1000 + rand.nextInt(1000));
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
System.out.println("future2 done...");
}
return "efg";
});调用 join() 可以让程序等 future1 和 future2 都运行完了之后再继续执行。
CompletableFuture<Void> completableFuture = CompletableFuture.allOf(future1, future2);
completableFuture.join();
assertTrue(completableFuture.isDone());
System.out.println("all futures done...");输出:
future1 done...
future2 done...
all futures done...anyOf() 方法不会等待所有的 CompletableFuture 都运行完成之后再返回,只要有一个执行完成即可!
CompletableFuture<Object> f = CompletableFuture.anyOf(future1, future2);
System.out.println(f.get());输出结果可能是:
future2 done...
efg也可能是:
future1 done...
abcCompletableFuture 使用建议
使用自定义线程池
我们上面的代码示例中,为了方便,都没有选择自定义线程池。实际项目中,这是不可取的。
在 CompletableFuture 的默认实现中,没有显式传入 Executor 的异步方法通常使用全局共享的 ForkJoinPool.commonPool();子类可以通过重写 defaultExecutor() 改变非静态异步方法的默认执行器。这意味着应用程序、多个库或框架若都使用默认实现,相关异步任务通常会共享同一个线程池。
虽然 ForkJoinPool 效率很高,但当同时提交大量任务时,可能会导致资源竞争和线程饥饿,进而影响系统性能。
为避免这些问题,建议为 CompletableFuture 提供自定义线程池,带来以下优势:
- 隔离性:为不同任务分配独立的线程池,避免全局线程池资源争夺。
- 资源控制:根据任务特性调整线程池大小和队列类型,优化性能表现。
- 异常处理:通过自定义
ThreadFactory更好地处理线程中的异常情况。
private ThreadPoolExecutor executor = new ThreadPoolExecutor(10, 10,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>());
CompletableFuture.runAsync(() -> {
//...
}, executor);尽量避免使用 get()
CompletableFuture 的 get() 方法是阻塞的,尽量避免使用。如果必须要使用的话,需要添加超时时间,否则可能会导致主线程一直等待,无法执行其他任务。
CompletableFuture<String> future = CompletableFuture.supplyAsync(() -> {
try {
Thread.sleep(10_000);
} catch (InterruptedException e) {
e.printStackTrace();
}
return "Hello, world!";
});
// 获取异步任务的返回值,设置超时时间为 5 秒
try {
String result = future.get(5, TimeUnit.SECONDS);
System.out.println(result);
} catch (InterruptedException | ExecutionException | TimeoutException e) {
// 处理异常
e.printStackTrace();
}
}上面这段代码在调用 get() 时抛出了 TimeoutException 异常。这样我们就可以在异常处理中进行相应的操作,比如取消任务、重试任务、记录日志等。
正确进行异常处理
使用 CompletableFuture 的时候一定要以正确的方式进行异常处理,避免异常丢失或者出现不可控问题。
下面是一些建议:
whenComplete会在阶段正常或异常完成时执行回调,适合观察结果和记录异常;它默认保留原阶段的结果或异常,不用于把异常转换为正常结果。exceptionally只在阶段异常完成时执行,并用回调的返回值恢复为正常结果;如果需要继续传播异常,可以在回调中显式抛出异常。handle无论阶段正常还是异常完成都会执行,并根据结果和异常生成一个新的结果。CompletableFuture.allOf可以等待多个阶段全部完成;只要其中一个阶段异常完成,返回的CompletableFuture也会异常完成,但仍需分别检查各阶段才能获得每个任务的结果或异常。- ……
合理组合多个异步任务
正确使用 thenCompose()、 thenCombine()、acceptEither()、allOf()、anyOf() 等方法来组合多个异步任务,以满足实际业务的需求,提高程序执行效率。
实际使用中,我们还可以利用或者参考现成的异步任务编排框架,比如京东的 asyncTool。
后记
这篇文章只是简单介绍了 CompletableFuture 的核心概念和比较常用的一些 API。如果想要深入学习的话,还可以多找一些书籍和博客看,比如下面几篇文章就挺不错:
- CompletableFuture 原理与实践-外卖商家端 API 的异步化 - 美团技术团队:这篇文章详细介绍了
CompletableFuture在实际项目中的运用。参考这篇文章,可以对项目中类似的场景进行优化,也算是一个小亮点了。这种性能优化方式比较简单且效果还不错! - 读 RocketMQ 源码,学习并发编程三大神器 - 勇哥 java 实战分享:这篇文章介绍了 RocketMQ 对
CompletableFuture的应用。具体来说,从 RocketMQ 4.7 开始,RocketMQ 引入了CompletableFuture来实现异步消息处理。
另外,建议 G 友们可以看看京东的 asyncTool 这个并发框架,里面大量使用到了 CompletableFuture。
Java Jvm Jvm Intro (`docs/java/jvm/jvm-intro.md`)
title: 大白话带你认识 JVM
category: Java
tag:
- JVM
head: - meta
- name: keywords
content: JVM 基础,类加载,方法区,堆栈,程序计数器,运行时数据区
来自说出你的愿望吧丷投稿,原文地址:https://juejin.im/post/5e1505d0f265da5d5d744050。
前言
如果在文中用词或者理解方面出现问题,欢迎指出。此文旨在提及而不深究,但会尽量效率地把知识点都抛出来
一、JVM 的基本介绍
JVM 是 Java Virtual Machine 的缩写,它是一个虚构出来的计算机,一种规范。通过在实际的计算机上仿真模拟各类计算机功能实现···
好,其实抛开这么专业的句子不说,就知道 JVM 其实就类似于一台小电脑运行在 windows 或者 linux 这些操作系统环境下即可。它直接和操作系统进行交互,与硬件不直接交互,而操作系统可以帮我们完成和硬件进行交互的工作。
1.1 Java 文件是如何被运行的
比如我们现在写了一个 HelloWorld.java 好了,那这个 HelloWorld.java 抛开所有东西不谈,那是不是就类似于一个文本文件,只是这个文本文件它写的都是英文,而且有一定的缩进而已。
那我们的 JVM 是不认识文本文件的,所以它需要一个 编译,让其成为一个它会读二进制文件的 HelloWorld.class
① 类加载器
如果 JVM 想要执行这个 .class 文件,我们需要将其装进一个 类加载器 中,它就像一个搬运工一样,会把所有的 .class 文件全部搬进 JVM 里面来。
② 方法区
方法区 是用于存放类似于元数据信息方面的数据的,比如类信息,常量,静态变量,编译后代码···等
类加载器将 .class 文件搬过来就是先丢到这一块上
③ 堆
堆 主要存放对象实例和数组等数据,它和方法区都属于 线程共享区域。线程共享只表示多个线程可以访问这些区域,并不意味着区域本身“线程不安全”;是否存在数据竞争取决于程序如何访问其中的数据。
④ 栈
栈 这是我们的代码运行空间。我们编写的每一个方法都会放到 栈 里面运行。
我们会听说过本地方法栈或 Java Native Interface(JNI)这两个名词。本地方法由 Java 之外的本地代码实现,常见实现语言是 C 或 C++,具体实现取决于虚拟机和本地库。
⑤ 程序计数器
程序计数器记录当前线程正在执行的 JVM 指令地址;执行分支、循环、异常处理或线程切换后,虚拟机依靠它继续执行。它和栈一样属于 线程私有 区域。执行 native 方法时,规范不规定其取值。
小总结
- Java 文件经过编译后变成 .class 字节码文件
- 字节码文件通过类加载器被搬运到 JVM 虚拟机中
- 虚拟机运行时数据区中,方法区和堆为线程共享区域;虚拟机栈、本地方法栈和程序计数器为线程私有区域。共享与否是内存区域的可见范围,不直接等同于线程安全或不安全
1.2 简单的代码例子
一个简单的学生类
一个 main 方法
执行 main 方法的步骤如下:
- 编译好 App.java 后得到 App.class 后,执行 App.class,系统会启动一个 JVM 进程,从 classpath 路径中找到一个名为 App.class 的二进制文件,将 App 的类信息加载到运行时数据区的方法区内,这个过程叫做 App 类的加载
- JVM 找到 App 的主程序入口,执行 main 方法
- 这个 main 中的第一条语句为 Student student = new Student("tellUrDream"),就是让 JVM 创建一个 Student 对象,但是这个时候方法区中是没有 Student 类的信息的,所以 JVM 马上加载 Student 类,把 Student 类的信息放到方法区中
- 加载完 Student 类后,JVM 在堆中为一个新的 Student 实例分配内存,然后调用构造函数初始化 Student 实例,这个 Student 实例持有 指向方法区中的 Student 类的类型信息 的引用
- 执行 student.sayName();时,JVM 根据 student 的引用找到 student 对象,然后根据 student 对象持有的引用定位到方法区中 student 类的类型信息的方法表,获得 sayName() 的字节码地址。
- 执行 sayName()
其实也不用管太多,只需要知道对象实例初始化时会去方法区中找类信息,完成后再到栈那里去运行方法。找方法就在方法表中找。
二、类加载器的介绍
之前也提到了它是负责加载.class 文件的,它们在文件开头会有特定的文件标示,将 class 文件字节码内容加载到内存中,并将这些内容转换成方法区中的运行时数据结构,并且 ClassLoader 只负责 class 文件的加载,而是否能够运行则由 Execution Engine 来决定
2.1 类加载器的流程
从类被加载到虚拟机内存中开始,到释放内存总共有 7 个步骤:加载,验证,准备,解析,初始化,使用,卸载。其中验证,准备,解析三个部分统称为连接
2.1.1 加载
- 将 class 文件加载到内存
- 将静态数据结构转化成方法区中运行时的数据结构
- 在堆中生成一个代表这个类的 java.lang.Class 对象作为数据访问的入口
2.1.2 链接
- 验证:确保加载的类符合 JVM 规范和安全,保证被校验类的方法在运行时不会做出危害虚拟机的事件,其实就是一个安全检查
- 准备:为类变量(
static字段)分配存储空间并设置默认初始值,例如static int a = 3在准备阶段通常先得到默认值 0,在初始化阶段再赋值为 3。具体存储位置属于虚拟机实现细节 - 解析:虚拟机把运行时常量池中的符号引用转换为直接引用。源码中的
import只是编译期名称解析机制,不是常量池符号引用;直接引用的具体表示也不一定是对象地址
2.1.3 初始化
初始化其实就是执行类构造器方法的 <clinit>() 的过程,而且要保证执行前父类的 <clinit>() 方法执行完毕。这个方法由编译器收集,顺序执行所有类变量(static 修饰的成员变量)显式初始化和静态代码块中语句。此时准备阶段时的那个 static int a 由默认初始化的 0 变成了显式初始化的 3。 由于执行顺序缘故,初始化阶段类变量如果在静态代码块中又进行了更改,会覆盖类变量的显式初始化,最终值会为静态代码块中的赋值。
注意:字节码文件中初始化方法有两种,非静态资源初始化的 `` 和静态资源初始化的
<clinit>,类构造器方法<clinit>()不同于类的构造器,这些方法都是字节码文件中只能给 JVM 识别的特殊方法。
2.1.4 卸载
类卸载是指虚拟机回收不再可达的类元数据及其关联资源,通常要求定义该类的类加载器也不再可达。它不同于普通对象的垃圾回收,而且是否、何时卸载由虚拟机实现和垃圾收集器决定。
2.2 类加载器的加载顺序
以 JDK 8 的 HotSpot 为例,常见类加载器层次如下。JDK 9 模块化后 Extension ClassLoader 被 Platform ClassLoader 取代,rt.jar 也不再存在:
- BootStrap ClassLoader:rt.jar
- Extension ClassLoader: 加载扩展的 jar 包
- App ClassLoader:指定的 classpath 下面的 jar 包
- Custom ClassLoader:自定义的类加载器
2.3 双亲委派机制
当一个类收到了加载请求时,它是不会先自己去尝试加载的,而是委派给父类去完成,比如我现在要 new 一个 Person,这个 Person 是我们自定义的类,如果我们要加载它,就会先委派 App ClassLoader,只有当父类加载器都反馈自己无法完成这个请求(也就是父类加载器都没有找到加载所需的 Class)时,子类加载器才会自行尝试加载。
这样做的好处是,加载位于 rt.jar 包中的类时不管是哪个加载器加载,最终都会委托到 BootStrap ClassLoader 进行加载,这样保证了使用不同的类加载器得到的都是同一个结果。
其实这个也是一个隔离的作用,避免了我们的代码影响了 JDK 的代码,比如我现在自己定义一个 java.lang.String:
package java.lang;
public class String {
public static void main(String[] args) {
System.out.println();
}
}尝试运行当前类的 main 函数的时候,我们的代码肯定会报错。这是因为在加载的时候其实是找到了 rt.jar 中的 java.lang.String,然而发现这个里面并没有 main 方法。
三、运行时数据区
3.1 本地方法栈和程序计数器
比如说我们现在点开 Thread 类的源码,会看到它的 start0 方法带有 native 关键字修饰,而且不存在 Java 方法体。这类方法由 Java 之外的本地代码实现,常见实现语言是 C 或 C++;虚拟机使用本地方法栈支持 native 方法的执行。
程序计数器记录当前线程正在执行的 JVM 指令地址。它也是《Java 虚拟机规范》中唯一没有规定任何 OutOfMemoryError 情形的运行时数据区。字节码解释器通过改变程序计数器的值来选取下一条需要执行的字节码指令。
如果执行的是 native 方法,规范不规定程序计数器的取值。
3.2 方法区
方法区主要的作用是存放类的元数据信息,常量和静态变量···等。当它存储的信息过大时,会在无法满足内存分配时报错。
3.3 虚拟机栈和虚拟机堆
一句话便是:栈管运行,堆管存储。则虚拟机栈负责运行代码,而虚拟机堆负责存储数据。
3.3.1 虚拟机栈的概念
它是 Java 方法执行的内存模型。每次方法调用都会创建栈帧,栈帧中包含局部变量表、操作数栈、动态链接和方法返回地址等信息,并且由线程独享。局部变量表是栈帧的一部分。
public class Person{
int a = 1;
public void doSomething(){
int b = 2;
}
}3.3.2 虚拟机栈存在的异常
如果线程请求的栈的深度大于虚拟机栈的最大深度,就会报 StackOverflowError(这种错误经常出现在递归中)。Java 虚拟机也可以动态扩展,但随着扩展会不断地申请内存,当无法申请足够内存时就会报错 OutOfMemoryError。
3.3.3 虚拟机栈的生命周期
对于栈来说,不存在垃圾回收。只要程序运行结束,栈的空间自然就会释放了。栈的生命周期和所处的线程是一致的。
这里补充一句:方法参数和方法内定义的基本类型变量、对象引用通常保存在当前栈帧的局部变量表中;对象实例通常分配在堆上。方法的字节码属于类元数据,不会作为“实例方法”分配在栈中。
3.3.4 虚拟机栈的执行
栈帧并不是方法本身,而是一次方法调用所对应的运行时数据结构,用于保存局部变量表、操作数栈、动态链接等信息。方法的字节码和元数据不存放在虚拟机栈中。
栈中的数据都是以栈帧的格式存在,它是一个关于方法和运行期数据的数据集。比如我们执行一个方法 a,就会对应产生一个栈帧 A1,然后 A1 会被压入栈中。同理方法 b 会有一个 B1,方法 c 会有一个 C1,等到这个线程执行完毕后,栈会先弹出 C1,后 B1,A1。它是一个先进后出,后进先出原则。
3.3.5 局部变量的复用
局部变量表用于存放方法参数和方法内部所定义的局部变量。它的容量是以 Slot 为最小单位,一个 slot 可以存放 32 位以内的数据类型。
虚拟机通过索引定位局部变量表中的 Slot。若局部变量表容量为 n,有效索引范围是 [0, n);long 和 double 会占用两个连续 Slot。方法参数会按规范规定的顺序排列在局部变量表中。为了节省栈帧空间,当执行位置超出某个局部变量的作用范围后,其 Slot 可以被其他变量复用;局部变量表中仍被视为有效的对象引用会参与 GC Roots 扫描。
3.3.6 虚拟机堆的概念
以 HotSpot 的分代垃圾收集器为例,堆通常划分为年轻代和老年代,年轻代又可以分为 Eden 和两个 Survivor 区。一次复制过程中,一个 Survivor 区作为 from,另一个作为 to。Eden 与单个 Survivor 区的默认比例通常为 8:1:1,但实际大小可能受所用收集器和自适应策略影响。永久代则是 JDK 7 及以前 HotSpot 对方法区的一种实现,并不等同于全部“非堆内存”。
堆内存中主要存放对象,垃圾收集器会判断并回收其中不可达的对象。HotSpot 所称的非堆内存不只包含方法区的实现,还包括 Code Cache 等区域。JDK 8 移除永久代后,类元数据改由 JVM 管理的元空间(Metaspace)保存;元空间使用本地内存,而不是 Java 堆。相关参数包括:
MetaspaceSize:触发元数据 GC 的初始高水位阈值,之后 JVM 会动态调整
MaxMetaspaceSize:限制元空间大小上限移除永久代后,不会再出现由永久代耗尽导致的 java.lang.OutOfMemoryError: PermGen space。但元空间仍可能因类元数据过多或达到 MaxMetaspaceSize 等限制而抛出 OutOfMemoryError: Metaspace。
3.3.7 Eden 年轻代的介绍
当我们 new 一个对象后,会先放到 Eden 划分出来的一块作为存储空间的内存,但是我们知道对堆内存是线程共享的,所以有可能会出现两个对象共用一个内存的情况。这里 JVM 的处理是为每个线程都预先申请好一块连续的内存空间并规定了对象存放的位置,而如果空间不足会再申请多块内存空间。这个操作我们会称作 TLAB,有兴趣可以了解一下。
当 Eden 空间不足以继续分配对象时,通常会触发 Minor GC(即发生在年轻代的 GC),存活对象可能被复制到 Survivor 区或直接晋升到老年代。复制完成后,from 和 to 两个 Survivor 区会交换角色。对象年龄达到晋升阈值后可以进入老年代;在常见 HotSpot 分代收集器中,-XX:MaxTenuringThreshold 的默认值通常是 15,但实际晋升年龄还会受到动态年龄判断、Survivor 空间容量和收集器策略影响,并非所有对象都固定经历 15 次 Minor GC。
🐛 修正:当 Eden 区内存空间满了的时候,就会触发 Minor GC,Survivor0 区满不会触发 Minor GC。
那 Survivor0 区 的对象什么时候垃圾回收呢?
假设 Survivor0 区现在是满的,此时又触发了 Minor GC,发现 Survivor0 区依旧是满的,存不下,此时会将 S0 区与 Eden 区的对象一起进行可达性分析,找出活跃的对象,将它复制到 S1 区并且将 S0 区域和 Eden 区的对象给清空,这样那些不可达的对象进行清除,并且将 S0 区 和 S1 区交换。
老年代主要存储长期存活或直接晋升的对象。老年代空间不足可能触发老年代收集或 Full GC,具体行为取决于垃圾收集器;不同收集器的停顿范围也不相同,不能一概而论为全程停止所有应用线程。
而且当老年区执行了 full gc 之后仍然无法进行对象保存的操作,就会产生 OOM,这时候就是虚拟机中的堆内存不足,原因可能会是堆内存设置的大小过小,这个可以通过参数-Xms、-Xmx 来调整。也可能是代码中创建的对象大且多,而且它们一直在被引用从而长时间垃圾收集无法收集它们。
补充说明:关于-XX:TargetSurvivorRatio 参数的问题。其实也不一定是要满足-XX:MaxTenuringThreshold 才移动到老年代。可以举个例子:如对象年龄 5 的占 30%,年龄 6 的占 36%,年龄 7 的占 34%,加入某个年龄段(如例子中的年龄 6)后,总占用超过 Survivor 空间*TargetSurvivorRatio 的时候,从该年龄段开始及大于的年龄对象就要进入老年代(即例子中的年龄 6 对象,就是年龄 6 和年龄 7 晋升到老年代),这时候无需等到 MaxTenuringThreshold 中要求的 15
3.3.8 如何判断一个对象需要被干掉
图中程序计数器、虚拟机栈、本地方法栈,3 个区域随着线程的生存而生存的。内存分配和回收都是确定的。随着线程的结束内存自然就被回收了,因此不需要考虑垃圾回收的问题。而 Java 堆和方法区则不一样,各线程共享,内存的分配和回收都是动态的。因此垃圾收集器所关注的都是堆和方法这部分内存。
在进行回收前就要判断哪些对象还存活,哪些已经死去。下面介绍两个基础的计算方法
引用计数器计算:给对象添加一个引用计数器,每次引用这个对象时计数器加一,引用失效时减一,计数器等于 0 时就是不会再次使用的。不过这个方法有一种情况就是出现对象的循环引用时 GC 没法回收。
可达性分析计算:从一系列 GC Roots 出发,沿对象之间的引用关系进行搜索,搜索所走过的路径称为引用链。对象之间的引用关系构成的是图,并不是二叉树。当一个对象与 GC Roots 之间不存在任何引用链时,则说明该对象不可达。主流的商用程序语言,例如 Java、C# 等,都会使用这种思路判断对象是否存活。
(了解一下即可)在 Java 语言汇总能作为 GC Roots 的对象分为以下几种:
- 虚拟机栈(栈帧中的本地方法表)中引用的对象(局部变量)
- 方法区中静态变量所引用的对象(静态变量)
- 方法区中常量引用的对象
- 本地方法栈(即 native 修饰的方法)中 JNI 引用的对象(JNI 是 Java 虚拟机调用对应的 C 函数的方式,通过 JNI 函数也可以创建新的 Java 对象。且 JNI 对于对象的局部引用或者全局引用都会把它们指向的对象都标记为不可回收)
- 已启动的且未终止的 Java 线程
这种方法的优点是能够解决循环引用的问题。收集器需要在某些阶段获得一致的对象引用关系,通常会产生 Stop-The-World 停顿;现代并发收集器可将大量标记工作与应用线程并发执行,并不是整个可达性分析过程都要“停止所有进程”。
3.3.9 如何宣告一个对象的真正死亡
首先必须要提到的是一个名叫 finalize() 的方法
finalize() 是 Object 类的一个方法。一个对象的 finalize() 方法至多会被系统自动调用一次;如果对象通过该方法重新建立了可达关系,下一次被判定为不可达时不会再次调用。
补充一句:并不提倡在程序中调用 finalize() 来进行自救。它的执行时间不确定,甚至不保证一定会执行,而且运行代价高昂,无法保证各个对象的调用顺序。finalize() 在 Java 9 中被弃用,并在 Java 18 中被标记为待移除。需要管理堆外资源时,可以根据场景使用 try-with-resources 或 java.lang.ref.Cleaner;Cleaner 本身并不是强、软、弱、幻象引用的统称。
对于重写了 finalize() 方法且该方法尚未执行过的对象,其处理过程通常被概括为两次标记:
- 如果对象经过可达性分析后没有发现与 GC Roots 相连的引用链,它会被第一次标记并接受筛选。对于有必要执行
finalize()方法的对象,HotSpot 可能会将对应的终结引用加入待处理队列,由 Finalizer 线程异步处理。 - 如果对象在
finalize()方法中重新与引用链上的对象建立了关联,后续垃圾收集会将其移出“即将回收”集合;否则,对象仍然可以被回收。这个过程不代表垃圾收集器会同步等待finalize()方法执行,也不保证该方法一定会被调用。
如果确定对象已经死亡,我们又该如何回收这些垃圾呢
3.4 垃圾回收算法
关于常见垃圾回收算法的详细介绍,建议阅读这篇:JVM 垃圾回收详解(重点)。
3.5(了解)各种各样的垃圾回收器
HotSpot VM 中的垃圾回收器,以及适用场景
在常见的 JDK 8 Server HotSpot 配置中,默认组合通常是 Parallel Scavenge 和 Parallel Old;实际默认值仍可能受虚拟机实现、运行模式和平台影响。
JDK 9 将 G1 设为 Server HotSpot 的默认垃圾收集器。不同收集器在吞吐量、延迟、内存占用和 CPU 开销之间各有取舍,不能脱离应用负载和 JVM 配置断言某个收集器的停顿一定最短。
3.6(了解)JVM 的常用参数
JVM 的参数非常之多,这里只列举比较重要的几个,通过各种各样的搜索引擎也可以得知这些信息。
| 参数名称 | 含义 | 说明 |
|---|---|---|
-Xms |
初始堆大小 | 默认值由 HotSpot 的自适应策略根据运行环境计算,不宜写成固定的物理内存比例 |
-Xmx |
最大堆大小 | 默认值由 HotSpot 的自适应策略根据运行环境计算 |
-Xmn |
年轻代大小 | 仅适用于具有固定年轻代概念的收集器;显式设置会限制自适应调整 |
-XX:NewSize |
年轻代初始大小 | 仅对支持该参数的分代收集器有效 |
-XX:MaxNewSize |
年轻代最大值 | 仅对支持该参数的分代收集器有效 |
-XX:PermSize |
永久代初始大小 | 仅适用于 JDK 7 及以前的 HotSpot,JDK 8 已移除永久代 |
-XX:MaxPermSize |
永久代最大值 | 仅适用于 JDK 7 及以前的 HotSpot,JDK 8 已移除永久代 |
-Xss |
每个线程的栈大小 | 默认值依赖平台和 JVM 版本,应结合线程数量、调用深度和测试结果设置 |
-XX:NewRatio |
老年代与年轻代的容量比 | 例如值为 4 时,老年代与年轻代为 4:1;是否生效取决于收集器 |
-XX:SurvivorRatio |
Eden 与单个 Survivor 区的容量比 | 例如值为 8 时,Eden:From:To 通常为 8:1:1;自适应策略可能调整实际大小 |
-XX:+DisableExplicitGC |
忽略 System.gc() 发出的显式 GC 请求 |
不等于关闭 JVM 的自动垃圾回收,使用前需要评估直接内存等场景 |
-XX:PretenureSizeThreshold |
大对象直接进入老年代的阈值 | 是否支持和如何生效取决于垃圾收集器 |
-XX:ParallelGCThreads |
Stop-The-World 并行阶段的 GC 线程数 | 默认值由 JVM 根据可用处理器数量等因素计算 |
-XX:MaxGCPauseMillis |
最大 GC 停顿时间目标 | 这是软目标而不是硬性保证,具体效果取决于所用垃圾收集器 |
其实还有一些打印及 CMS 方面的参数,这里就不以一一列举了
四、关于 JVM 调优的一些方面
根据刚刚涉及的 jvm 的知识点,我们可以尝试对 JVM 进行调优,主要就是堆内存那块
对于采用固定分代布局的收集器,Java 堆可近似看作年轻代与老年代之和;永久代或元空间不属于 Java 堆。在堆总大小固定时,增大年轻代会压缩老年代空间,但具体比例没有适用于所有应用的“官方最优值”,需要根据对象分配、存活情况和所用收集器进行测试。
4.1 调整最大堆内存和最小堆内存
-Xmx 和 -Xms 分别指定 Java 堆的最大值和初始值。未显式设置时,默认值由 HotSpot 根据 JVM 版本、可用内存、容器限制和运行环境自适应计算,不应按固定的物理内存比例估算。
HotSpot 可以在 -Xms 与 -Xmx 之间调整已提交堆空间,MinHeapFreeRatio 和 MaxHeapFreeRatio 是部分收集器用于控制 GC 后空闲比例的参数。实际扩缩容行为和默认比例取决于所用收集器、JDK 版本及自适应策略,不能统一概括为固定的 40% 和 70%。
将 -Xms 与 -Xmx 配置为相同值可以避免运行期间调整已提交堆大小,但会从启动时就保留较大的堆容量,是否采用仍应结合部署环境和负载评估。
我们执行下面的代码
System.out.println("Xmx=" + Runtime.getRuntime().maxMemory() / 1024.0 / 1024 + "M"); //系统的最大空间
System.out.println("free mem=" + Runtime.getRuntime().freeMemory() / 1024.0 / 1024 + "M"); //系统的空闲空间
System.out.println("total mem=" + Runtime.getRuntime().totalMemory() / 1024.0 / 1024 + "M"); //当前可用的总空间注意:此处设置的是 Java 堆大小,也就是新生代大小 + 老年代大小
设置一个 VM options 的参数
-Xmx20m -Xms5m -XX:+PrintGCDetails再次启动 main 方法
这里 GC 弹出了一个 Allocation Failure 分配失败,这个事情发生在 PSYoungGen,也就是年轻代中
这时候申请到的内存为 18M,空闲内存为 4.214195251464844M
我们此时创建一个字节数组看看,执行下面的代码
byte[] b = new byte[1 * 1024 * 1024];
System.out.println("分配了1M空间给数组");
System.out.println("Xmx=" + Runtime.getRuntime().maxMemory() / 1024.0 / 1024 + "M"); //系统的最大空间
System.out.println("free mem=" + Runtime.getRuntime().freeMemory() / 1024.0 / 1024 + "M"); //系统的空闲空间
System.out.println("total mem=" + Runtime.getRuntime().totalMemory() / 1024.0 / 1024 + "M");此时 free memory 就又缩水了,不过 total memory 是没有变化的。Java 会尽可能将 total mem 的值维持在最小堆内存大小
byte[] b = new byte[10 * 1024 * 1024];
System.out.println("分配了10M空间给数组");
System.out.println("Xmx=" + Runtime.getRuntime().maxMemory() / 1024.0 / 1024 + "M"); //系统的最大空间
System.out.println("free mem=" + Runtime.getRuntime().freeMemory() / 1024.0 / 1024 + "M"); //系统的空闲空间
System.out.println("total mem=" + Runtime.getRuntime().totalMemory() / 1024.0 / 1024 + "M"); //当前可用的总空间这时候我们创建了一个 10M 的字节数据,这时候最小堆内存是顶不住的。我们会发现现在的 total memory 已经变成了 15M,这就是已经申请了一次内存的结果。
此时我们再跑一下这个代码
System.gc();
System.out.println("Xmx=" + Runtime.getRuntime().maxMemory() / 1024.0 / 1024 + "M"); //系统的最大空间
System.out.println("free mem=" + Runtime.getRuntime().freeMemory() / 1024.0 / 1024 + "M"); //系统的空闲空间
System.out.println("total mem=" + Runtime.getRuntime().totalMemory() / 1024.0 / 1024 + "M"); //当前可用的总空间此处调用 System.gc() 只是向 JVM 提交一次垃圾收集建议,并不保证一定执行 Full GC;截图中的这次运行确实触发了相应收集并缩小了已提交堆空间,但不同 JVM 参数和收集器下结果可能不同。
4.2 调整新生代和老年代的比值
-XX:NewRatio --- 新生代(eden+2\*Survivor)和老年代(不包含永久区)的比值
例如:-XX:NewRatio=4,表示新生代:老年代=1:4,即新生代占整个堆的 1/5。在 Xms=Xmx 并且设置了 Xmn 的情况下,该参数不需要进行设置。4.3 调整 Survivor 区和 Eden 区的比值
-XX:SurvivorRatio(幸存代)--- 设置两个 Survivor 区和 eden 的比值
例如:8,表示两个 Survivor:eden=2:8,即一个 Survivor 占年轻代的 1/104.4 设置年轻代和老年代的大小
-XX:NewSize --- 设置年轻代大小
-XX:MaxNewSize --- 设置年轻代最大值可以通过设置不同参数测试不同情况。Eden 与两个 Survivor 区的常见初始比例是 8:1:1,但这不是适用于所有应用和收集器的最优解;-Xms 与 -Xmx 不同也不意味着一定会导致多次 GC。
4.5 小总结
应根据实际负载、所用垃圾收集器和 GC 日志调整年轻代与 Survivor 区大小,不存在对所有应用都成立的固定推荐比例。
在 OOM 时,记得 Dump 出堆,确保可以排查现场问题。通过下面命令可以输出一个 .dump 文件,该文件可使用 VisualVM 等工具分析。VisualVM 从 JDK 9 起不再随 Oracle JDK 一起提供,需要单独安装。
-Xmx20m -Xms5m -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=你要输出的日志路径一般我们也可以通过编写脚本的方式来让 OOM 出现时给我们报个信,可以通过发送邮件或者重启程序等来解决。
4.6 永久区的设置(仅适用于 JDK 7 及以前的 HotSpot)
-XX:PermSize -XX:MaxPermSizePermSize 设置永久代的初始大小,MaxPermSize 设置其上限;具体默认值与 JVM 版本和平台有关,不能统一写成固定的物理内存比例。JDK 8 及以后应关注元空间相关参数,而不是这两个永久代参数。
tips:如果堆空间没有用完也抛出了 OOM,有可能是永久区导致的。堆空间实际占用非常少,但是永久区溢出 一样抛出 OOM。
4.7 JVM 的栈参数调优
4.7.1 调整每个线程栈空间的大小
可以通过-Xss:调整每个线程栈空间的大小
线程栈的默认大小取决于 JVM 版本、平台和运行模式,并不是 JDK 5 以后统一为 1 MB。在其他条件相同时,减小线程栈可以为更多平台线程留出地址空间,但线程数量还受操作系统限制,并且栈过小会增加 StackOverflowError 风险。
4.7.2 设置线程栈的大小
-XX:ThreadStackSize=<size>:
设置线程栈的大小(0 means use default stack size)这些参数都是可以通过自己编写程序去简单测试的,这里碍于篇幅问题就不再提供 demo 了
4.8(可以直接跳过了)JVM 其他参数介绍
形形色色的参数很多,就不会说把所有都扯个遍了,因为大家其实也不会说一定要去深究到底。
4.8.1 设置大内存页的大小
-XX:LargePageSizeInBytes=<size>该参数用于设置大内存页大小,是否生效取决于操作系统、JVM 构建和大页配置。
4.8.2 历史参数 `UseFastAccessorMethods`
-XX:+UseFastAccessorMethods该旧版 HotSpot 参数针对反射访问器优化,并非“原始类型的快速优化”;现代 JDK 已不再提供该参数。
4.8.3 设置关闭手动 GC
-XX:+DisableExplicitGC:
设置关闭System.gc()(这个参数需要严格的测试)4.8.4 设置垃圾最大年龄
-XX:MaxTenuringThreshold
设置对象晋升年龄上限。设置为 0 时,支持该参数的分代收集器会让年轻代存活对象不经过 Survivor 区而直接晋升。增大该值可能让对象在 Survivor 区经历更多次复制,但实际晋升还受动态年龄判断、Survivor 容量和收集器策略影响。并不是所有垃圾收集器都使用同样的分代和年龄晋升机制,是否生效应以当前收集器的文档为准。
4.8.5 历史参数 `AggressiveOpts`
-XX:+AggressiveOpts这是旧版 HotSpot 中用于启用实验性性能优化的参数,不能简单理解为“加快编译速度”,并且已在 JDK 12 中移除。
4.8.6 历史参数 `UseBiasedLocking`
-XX:+UseBiasedLocking偏向锁在 JDK 15 中默认关闭并被弃用,相关实现随后已从 HotSpot 移除,不应作为现代 JDK 的通用调优参数。
4.8.7 禁用类卸载
-Xnoclassgc该参数禁用的是类的垃圾回收,而不是对象垃圾回收。
4.8.8 设置堆空间存活时间
-XX:SoftRefLRUPolicyMSPerMB
设置每兆堆空闲空间中SoftReference的存活时间,默认值是1s。4.8.9 设置对象直接分配在老年代
-XX:PretenureSizeThreshold
设置对象超过多大时直接在老年代分配,默认值是0。4.8.10 设置 TLAB 占 eden 区的比例
-XX:TLABWasteTargetPercent
设置TLAB占eden区的百分比,默认值是1% 。finally
真的扯了很久这东西,参考了多方的资料,有极客时间的《深入拆解虚拟机》和《Java 核心技术面试精讲》,也有百度,也有自己在学习的一些线上课程的总结。希望对你有所帮助,谢谢。
Java Jvm Jvm Parameters Intro (`docs/java/jvm/jvm-parameters-intro.md`)
title: 最重要的JVM参数总结
category: Java
tag:
- JVM
head: - meta
- name: keywords
content: JVM 参数,堆大小,栈大小,GC 设置,性能调优,XX 参数
本文由 JavaGuide 翻译自 https://www.baeldung.com/jvm-parameters,并对文章进行了大量的完善补充。
文档参数 https://docs.oracle.com/javase/8/docs/technotes/tools/unix/java.htmlJDK 版本:1.8 为主,也会补充新版本常用参数
在本篇文章中,我们将一起掌握 Java 虚拟机(JVM)中最常用的一些参数配置,帮助你更好地理解和调优 Java 应用的运行环境。
堆内存相关
Java 堆(Java Heap)是 JVM 所管理的内存中最大的一块区域,所有线程共享,在虚拟机启动时创建。此内存区域的唯一目的就是存放对象实例,几乎所有的对象实例以及数组都要在堆上分配内存。
设置堆内存大小(-Xms 和 -Xmx)
根据应用程序的实际需求设置初始和最大堆内存大小,是性能调优中最常见的实践之一。将两者设为相同值,可以减少运行时调整堆容量的开销;如果希望低负载时归还未使用的堆内存,则需要保留伸缩空间。例如,ZGC 在 -Xms 等于 -Xmx 时无法通过 Uncommit 归还堆内存,应根据延迟目标和内存成本选择。
使用以下参数进行设置:
-Xms<heap size>[unit] # 设置 JVM 初始堆大小
-Xmx<heap size>[unit] # 设置 JVM 最大堆大小<heap size>: 指定内存的具体数值。[unit]: 指定内存的单位,如 g (GB)、m (MB)、k (KB)。
示例: 将 JVM 的初始堆和最大堆都设置为 4GB:
-Xms4G -Xmx4G设置新生代内存大小 (Young Generation)
根据Oracle 官方文档,在堆总可用内存配置完成之后,第二大影响因素是 Young Generation 在堆内存所占的比例。新生代的默认大小会受 JVM 实现、平台、垃圾收集器、堆大小和自适应策略影响,并不存在适用于所有环境的固定“最小 1310 MB”;NewSize 和 MaxNewSize 分别用于约束新生代大小的下限和上限。
对于支持固定新生代大小参数的分代收集器,可以通过以下两种方式设置新生代内存大小:
1.通过 -XX:NewSize 和 -XX:MaxNewSize 指定
-XX:NewSize=<young size>[unit] # 设置新生代初始大小
-XX:MaxNewSize=<young size>[unit] # 设置新生代最大大小示例: 设置新生代最小 512MB,最大 1024MB:
-XX:NewSize=512m -XX:MaxNewSize=1024m2.通过 -Xmn<young size>[unit] 指定
示例: 将新生代大小固定为 512MB:
-Xmn512m显式设置新生代大小会限制垃圾收集器的自适应调整能力。Oracle 官方文档明确建议不要为 G1 设置 -Xmn;是否需要调整以及如何调整,应结合具体收集器和 GC 日志判断。
GC 调优策略中很重要的一条经验总结是这样说的:
尽量让新创建的对象在新生代分配内存并被回收,因为 Minor GC 的成本通常远低于 Full GC。通过分析 GC 日志,判断新生代空间分配是否合理。如果大量新对象过早进入老年代(Promotion),可以适当通过
-Xmn或 -XX:NewSize/-XX:MaxNewSize调整新生代大小,目标是最大限度地减少对象直接进入老年代的情况。
另外,你还可以通过 -XX:NewRatio= 参数来设置老年代与整个新生代(包括 Eden 和两个 Survivor 区)的内存大小比例。
例如,-XX:NewRatio=2 表示老年代 : 新生代 = 2 : 1,即新生代占整个堆大小的 1/3。默认值会受 JVM、收集器和平台影响。
-XX:NewRatio=2设置永久代/元空间大小 (PermGen/Metaspace)
从 Java 8 开始,类元数据改用本地内存。如果没有通过 -XX:MaxMetaspaceSize 设置上限,持续增长的类元数据可能消耗大量本地内存;永久代则受 -XX:MaxPermSize 上限约束。
JDK 1.8 之前永久代还没被彻底移除的时候通常通过下面这些参数来调节方法区大小
-XX:PermSize=N #方法区 (永久代) 初始大小
-XX:MaxPermSize=N #方法区 (永久代) 最大大小,超过这个值将会抛出 OutOfMemoryError 异常:java.lang.OutOfMemoryError: PermGen相对而言,垃圾收集行为在这个区域是比较少出现的,但并非数据进入方法区后就“永久存在”了。
JDK 1.8 的时候,方法区(HotSpot 的永久代)被彻底移除了(JDK1.7 就已经开始了),取而代之是元空间,元空间使用的是本地内存。
下面是一些常用参数:
-XX:MetaspaceSize=N #设置 Metaspace 的初始大小(是一个常见的误区,后面会解释)
-XX:MaxMetaspaceSize=N #设置 Metaspace 的最大大小🐛 修正(参见:issue#1947):
1、-XX:MetaspaceSize 并非初始容量: Metaspace 的初始容量并不是 -XX:MetaspaceSize 设置,无论 -XX:MetaspaceSize 配置什么值,对于 64 位 JVM,元空间的初始容量通常是一个固定的较小值(Oracle 文档提到约 12MB 到 20MB 之间,实际观察约 20.8MB)。
可以参考 Oracle 官方文档 Other Considerations 中提到的:
Specify a higher value for the option MetaspaceSize to avoid early garbage collections induced for class metadata. The amount of class metadata allocated for an application is application-dependent and general guidelines do not exist for the selection of MetaspaceSize. The default size of MetaspaceSize is platform-dependent and ranges from 12 MB to about 20 MB.
MetaspaceSize 的默认大小取决于平台,范围从 12 MB 到大约 20 MB。
另外,还可以看一下这个试验:JVM 参数 MetaspaceSize 的误解。
2、扩容与元数据 GC: 当 Metaspace 的已提交空间达到 -XX:MetaspaceSize 对应的高水位阈值时,JVM 会触发一次垃圾回收以尝试卸载类、释放类元数据。在此之后,JVM 会根据释放效果动态调高或调低下一次触发 GC 的阈值;具体采用哪类回收周期取决于垃圾收集器和 JDK 版本,不能一概称为 Full GC。垃圾搜集器内部是根据变量 _capacity_until_GC 来判断 Metaspace 区域是否达到阈值的,初始化代码如下所示:
void MetaspaceGC::initialize() {
// Set the high-water mark to MaxMetapaceSize during VM initialization since
// we can't do a GC during initialization.
_capacity_until_GC = MaxMetaspaceSize;
}**3、-XX:MaxMetaspaceSize 的作用:**如果不显式设置 -XX:MaxMetaspaceSize,元空间默认没有固定上限,持续增长的类元数据可能消耗大量本地内存。是否设置该参数以及设置多大,应结合类元数据使用情况和进程的本地内存预算决定。上限过小会增加元数据 GC 的频率,也可能提前触发 OutOfMemoryError: Metaspace,因此不存在适用于所有应用的推荐值。
相关阅读:issue 更正:MaxMetaspaceSize 如果不指定大小的话,不会耗尽内存 #1204。
垃圾收集相关
选择垃圾回收器
选择合适的垃圾收集器(Garbage Collector, GC)对于应用的吞吐量和响应延迟至关重要。关于垃圾收集算法和收集器的详细介绍,可以看笔者写的这篇:JVM 垃圾回收详解(重点)。
JVM 提供了多种 GC 实现,适用于不同的场景:
- Serial GC(串行垃圾收集器): 单线程执行 GC,适用于客户端模式或单核 CPU 环境。参数:
-XX:+UseSerialGC。 - Parallel GC(并行垃圾收集器): 多线程执行新生代和老年代垃圾收集,关注吞吐量,是 JDK 8 Server VM 的默认 GC;在 JDK 8 中启用
-XX:+UseParallelGC时默认搭配 Parallel Old。参数:-XX:+UseParallelGC。 - CMS GC(Concurrent Mark Sweep 并发标记清除收集器): 以获取最短回收停顿时间为目标,大部分 GC 阶段可与用户线程并发执行。适用于对响应时间要求高的应用。在 JDK 9 中被标记为弃用,JDK 14 中被移除。参数:
-XX:+UseConcMarkSweepGC。 - G1 GC (Garbage-First Garbage Collector): JDK 9 及之后 HotSpot 在典型 Server-class 机器上的默认 GC;其他环境的默认选择可能不同。它将堆划分为多个 Region,兼顾吞吐量和停顿时间,尝试满足用户设置的停顿时间目标。参数:
-XX:+UseG1GC。 - ZGC: 低延迟 GC,需要较新版本的 JDK 支持。参数:
-XX:+UseZGC。 - Shenandoah GC: 另一款低停顿收集器,是否可用取决于具体 JDK 发行版。参数:
-XX:+UseShenandoahGC。
JDK 17 ZGC 参数
JDK 17 的 ZGC 是非分代收集器,使用 -XX:+UseZGC 启用。调优时先根据实际负载确定堆和 CPU 预算,再根据 GC 日志调整参数,不要直接复制其他服务的整套配置。
| 参数 | 含义与使用时需要注意的地方 |
|---|---|
-Xmx、-Xms |
最大堆与初始/最小堆容量。堆需要容纳存活对象,并为并发回收期间的分配预留空间;-Xmx 也不是整个 Java 进程的内存上限。 |
-XX:ConcGCThreads |
并发 GC 线程数。过少可能回收不及时,过多会与业务线程争抢 CPU。先使用 JVM 的自适应选择,再结合分配停顿、CPU 配额和负载验证是否需要手动设置。 |
-XX:ZAllocationSpikeTolerance |
对分配速率突增的容忍系数,JDK 17 默认值为 2.0,用于影响回收时机的预测。它不是堆扩容比例,也不能代替足够的内存余量。 |
-XX:ZCollectionInterval |
GC 周期的最大间隔,单位为秒。默认值 0 表示不通过这个时间间隔触发 GC,不是禁用 GC。不要把它理解为两次 GC 之间必须等待的最短时间。 |
-XX:+ZUncommit、-XX:ZUncommitDelay |
允许归还未使用的堆内存,JDK 17 默认开启,默认延迟为 300 秒。归还不会使堆低于 -Xms;设置 -Xms 等于 -Xmx 时,这项机制实际上不起作用。 |
以上语义可对照 JDK 17 java 命令文档 和 OpenJDK 17 ZGC 参数定义。不同 JDK 版本的参数可能变化,尤其不要给 JDK 17 添加分代 ZGC 参数 -XX:+ZGenerational。
例如,下面的命令同时记录 GC 与安全点日志;堆大小需要另外按应用的资源预算设置:
java -XX:+UseZGC \
'-Xlog:gc*,safepoint:file=gc.log:time,uptime,level,tags:filecount=5,filesize=20M' \
-jar app.jar如果日志中出现 Allocation Stall,应结合分配速率、存活量、可用堆空间和并发 GC 的 CPU 资源定位原因。单纯缩短定时回收间隔不一定有效,还可能增加 CPU 开销。容器的内存预算也要覆盖元空间、线程栈、直接内存和 JVM 本地开销,不能只按 -Xmx 设置容器上限。
GC 日志记录
在生产环境或进行 GC 问题排查时,务必开启 GC 日志记录。详细的 GC 日志是分析和解决 GC 问题的关键依据。
以下是一些 JDK 8 的 GC 日志参数:
# --- 推荐的基础配置 ---
# 打印详细 GC 信息
-XX:+PrintGCDetails
# 打印 GC 发生的时间戳 (相对于 JVM 启动时间)
# -XX:+PrintGCTimeStamps
# 打印 GC 发生的日期和时间 (更常用)
-XX:+PrintGCDateStamps
# 指定 GC 日志文件的输出路径,%t 可以输出日期时间戳
-Xloggc:/path/to/gc-%t.log
# --- 推荐的进阶配置 ---
# 打印对象年龄分布 (有助于判断对象晋升老年代的情况)
-XX:+PrintTenuringDistribution
# 在 GC 前后打印堆信息
-XX:+PrintHeapAtGC
# 打印各种类型引用 (强/软/弱/虚) 的处理信息
-XX:+PrintReferenceGC
# 打印应用暂停时间 (Stop-The-World, STW)
-XX:+PrintGCApplicationStoppedTime
# --- GC 日志文件滚动配置 ---
# 启用 GC 日志文件滚动
-XX:+UseGCLogFileRotation
# 设置滚动日志文件的数量 (例如,保留最近 14 个)
-XX:NumberOfGCLogFiles=14
# 设置每个日志文件的最大大小 (例如,50MB)
-XX:GCLogFileSize=50M
# --- 可选的辅助诊断配置 ---
# 打印安全点 (Safepoint) 统计信息 (有助于分析 STW 原因)
# -XX:+PrintSafepointStatistics
# -XX:PrintSafepointStatisticsCount=1JDK 9 及之后应使用统一 JVM 日志框架 -Xlog。例如,下面的配置记录详细 GC 信息并启用按大小滚动:
-Xlog:gc*:file=/path/to/gc-%t.log:time,uptime,level,tags:filecount=14,filesize=50M旧参数并非都能继续使用:部分参数会被映射、弃用或直接不再识别。例如 PrintGCDetails 对应 -Xlog:gc*,PrintTenuringDistribution 对应 -Xlog:gc+age*=debug,PrintGCApplicationStoppedTime 对应 -Xlog:safepoint。应以目标 JDK 的 java 命令文档和 java -Xlog:help 输出为准。
处理 OOM
对于大型应用程序来说,面对内存不足错误是非常常见的,这反过来会导致应用程序崩溃。这是一个非常关键的场景,很难通过复制来解决这个问题。
这就是为什么 JVM 提供了一些参数,这些参数将堆内存转储到一个物理文件中,以后可以用来查找泄漏:
# Java 堆耗尽并抛出 OOME 时生成堆转储文件
-XX:+HeapDumpOnOutOfMemoryError
# 指定堆转储文件或目录。若指定目录,JVM 会使用默认名称 java_pid<pid>.hprof
-XX:HeapDumpPath=/data/dumps/
# (可选) 在发生 OOM 时执行指定的命令或脚本
# 例如,发送告警通知或尝试重启服务(需谨慎使用)
# -XX:OnOutOfMemoryError="<command> <args>"
# 示例:-XX:OnOutOfMemoryError="sh /path/to/notify.sh"
# (可选) 启用 GC 开销限制检查
# 如果 GC 时间占总时间比例过高(默认 98%)且回收效果甚微(默认小于 2% 堆内存),
# 会提前抛出 OOM,防止应用长时间卡死在 GC 中。
-XX:+UseGCOverheadLimit其他常用参数
-server: 明确启用 Server 模式的 HotSpot VM。(在 64 位 JVM 上通常是默认值)。-XX:+UseStringDeduplication: (JDK 8u20+) 尝试识别内容相同的 String 对象并共享其底层字符数据数组,以减少内存占用。JDK 8 中底层数组是char[],启用 Compact Strings 的现代 JDK 通常是byte[];该选项还要求使用支持字符串去重的收集器,例如 G1。-XX:SurvivorRatio=<ratio>: 设置 Eden 区与单个 Survivor 区的大小比例。例如-XX:SurvivorRatio=8表示 Eden:Survivor = 8:1。-XX:MaxTenuringThreshold=: 设置对象从新生代晋升到老年代的最大年龄阈值(对象每经历一次 Minor GC 且存活,年龄加 1)。默认值与收集器和 JDK 版本有关,例如 JDK 8 文档中 Parallel GC 的默认值为 15,CMS 的默认值为 6。-XX:+DisableExplicitGC: 让 JVM 忽略System.gc()等显式 GC 请求,但 JVM 在需要时仍会自行执行 GC。显式请求并不保证触发 Full GC;是否启用该参数应结合直接内存清理、框架行为和所用收集器评估,不能一概而论。-XX:+UseLargePages:(需要操作系统支持) 尝试使用大内存页(如 2MB 而非 4KB),可能提升内存密集型应用的性能,但需谨慎测试。-XX:MinHeapFreeRatio=/-XX:MaxHeapFreeRatio=: 对支持这组参数的收集器,控制 GC 后期望的堆空闲比例,参与堆容量调整。具体行为取决于收集器,不能用这组参数代替 ZGC 的容量与归还策略。
注意: 以下参数在现代 JVM 版本中可能已弃用、移除或默认开启且无需手动设置:
-XX:+UseLWPSynchronization: 较旧的同步策略选项,现代 JVM 通常有更优化的实现。-XX:LargePageSizeInBytes: 通常由-XX:+UseLargePages自动确定或通过 OS 配置。-XX:+UseStringCache: 已被移除。-XX:+UseCompressedStrings: 旧的实验性选项,已经被移除;JDK 9 引入的 Compact Strings 是另一套实现。-XX:+OptimizeStringConcat: 旧版 HotSpot C2 的字符串拼接优化选项;JDK 9 起字符串拼接主要通过invokedynamic和StringConcatFactory实现,不能把二者视为同一个开关。
总结
本文为 Java 开发者提供了一份实用的 JVM 常用参数配置指南,旨在帮助读者理解和优化 Java 应用的性能与稳定性。文章重点强调了以下几个方面:
- 堆内存配置: 根据部署环境设置初始与最大堆内存(
-Xms、-Xmx),权衡固定堆容量的性能收益和弹性归还内存的需求。新生代大小是否需要显式设置,应结合垃圾收集器和 GC 日志判断;G1 通常不建议设置-Xmn,JDK 17 的非分代 ZGC 则没有新生代。 - 元空间管理(Java 8+):
-XX:MetaspaceSize用于设置触发元数据 GC 的初始高水位阈值,而不是元空间的初始容量。-XX:MaxMetaspaceSize可以限制类元数据使用的本地内存,但具体上限需要根据应用实际情况确定。 - **垃圾收集器选择与日志:**介绍了不同 GC 算法的适用场景,并强调在生产和测试环境中开启详细 GC 日志对于问题排查的必要性;JDK 8 使用传统 GC 日志参数,JDK 9 及之后使用
-Xlog。 - OOM 故障排查: 说明了如何通过
-XX:+HeapDumpOnOutOfMemoryError等参数在发生 OOM 时自动生成堆转储文件,以便进行后续的内存泄漏分析。 - 其他参数: 简要介绍了如字符串去重等其他有用参数,并指出了部分旧参数的现状。
具体的问题排查和调优案例,可以参考笔者整理的这篇文章:JVM 线上问题排查和性能调优案例。
System Design Security Jwt Intro (`docs/system-design/security/jwt-intro.md`)
title: JWT 是什么?JWT 结构、解析与登录鉴权详解
category: 系统设计
tag:
- 安全
head: - meta
- name: keywords
content: JWT是什么,JWT解析,JWT Token,JWT鉴权,JSON Web Token,Token认证,无状态,Header Payload Signature,签名算法,登录鉴权,CSRF
JWT 是什么?
JWT(JSON Web Token)是 RFC 7519 定义的一种紧凑、URL 安全的声明表示格式。登录成功后,服务端可以把用户标识、权限范围和过期时间等声明写入 JWT;客户端在后续请求中携带它,服务端验证签名和相关声明后再决定是否放行。
JWT 可以承载鉴权所需的声明,因此服务端不一定要像传统 Session 方案那样保存会话状态。不过,撤销令牌、权限变更和主动下线等需求仍可能需要服务端状态,不能仅凭“使用 JWT”就认定系统完全无状态。
JWT 的 Header 和 Payload 只是经过 Base64Url 编码,拿到令牌的人都可以解码,不能把密码、身份证号等敏感信息写入 Payload。签名用于校验内容是否被篡改,并不负责加密内容。
如果客户端把 JWT 作为 Bearer Token 显式放入 Authorization Header,浏览器不会像 Cookie 那样自动附带它,因此可以降低传统 CSRF 风险。不过,这取决于凭据的传输和存储方式,而不是 JWT 格式本身;如果把 JWT 放在 Cookie 中,仍然需要 CSRF 防护。
JWT 优缺点分析详细介绍了使用 JWT 做身份认证的优势和限制。
下面是 RFC 7519 对 JWT 的定义。
JSON Web Token (JWT) is a compact, URL-safe means of representing claims to be transferred between two parties. The claims in a JWT are encoded as a JSON object that is used as the payload of a JSON Web Signature (JWS) structure or as the plaintext of a JSON Web Encryption (JWE) structure, enabling the claims to be digitally signed or integrity protected with a Message Authentication Code (MAC) and/or encrypted. ——JSON Web Token (JWT)
JWT 由哪些部分组成?
JWT 通常由三个使用 . 分隔的 Base64Url 编码部分组成:
- Header(头部):描述 JWT 的元数据,包含令牌类型和签名算法。Header 被 Base64Url 编码后成为 JWT 的第一部分。
- Payload(载荷):存放需要传递的声明(Claims),如
sub(subject,主题)、jti(JWT ID)。Payload 被 Base64Url 编码后成为 JWT 的第二部分。 - Signature(签名):根据编码后的 Header、Payload、签名算法和签名密钥计算。HS256 使用共享密钥,RS256、ES256 等非对称算法使用私钥签名、公钥验证。
JWT 通常是这样的:xxxxx.yyyyy.zzzzz。
示例:
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.
eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.
SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c你可以在 jwt.io 对示例 JWT 进行解码,解码后可以看到 Header、Payload、Signature 这三部分。生产环境中的真实令牌可能包含用户标识和权限信息,不要复制到第三方在线工具中。
Header 和 Payload 都是 JSON 数据,Signature 则由编码后的 Header、Payload 和签名密钥计算得到。
JWT 解析和 JWT 验证有什么区别?
JWT 解析只会对 Header 和 Payload 做 Base64Url 解码,不需要密钥。任何拿到令牌的人都能完成解析,因此解析结果不能证明令牌可信。
JWT 验证会使用指定算法和密钥校验 Signature,还应检查 exp、nbf、iss、aud 等声明。只有签名和业务要求的声明全部通过校验,服务端才能信任令牌中的身份与权限信息。
Header
Header 通常由两部分组成:
typ(Type):令牌类型,也就是 JWT。alg(Algorithm):签名算法,比如 HS256。
示例:
{
"alg": "HS256",
"typ": "JWT"
}JSON 形式的 Header 经 Base64Url 编码后成为 JWT 的第一部分。
Payload
Payload 也是 JSON 数据,其中包含 Claims(声明)。
Claims 分为三种类型:
- Registered Claims(注册声明):预定义的一些声明,建议使用,但不是强制性的。
- Public Claims(公有声明):JWT 签发方可以自定义的声明,但是为了避免冲突,应该在 IANA JSON Web Token Registry 中定义它们。
- Private Claims(私有声明):JWT 签发方因为项目需要而自定义的声明,更符合实际项目场景使用。
下面是一些常见的注册声明:
iss(issuer):JWT 签发方。iat(issued at time):JWT 签发时间。sub(subject):JWT 主题。aud(audience):JWT 接收方。exp(expiration time):JWT 的过期时间。nbf(not before time):JWT 生效时间,早于该定义的时间的 JWT 不能被接受处理。jti(JWT ID):JWT 唯一标识。
示例:
{
"uid": "ff1212f5-d8d1-4496-bf41-d2dda73de19a",
"sub": "1234567890",
"name": "John Doe",
"exp": 15323232,
"iat": 1516239022,
"scope": ["admin", "user"]
}Payload 部分默认是不加密的,一定不要将隐私信息存放在 Payload 当中!!!
JSON 形式的 Payload 经 Base64Url 编码后成为 JWT 的第二部分。
Signature
Signature 部分是对前两部分的签名,作用是防止 JWT(主要是 payload) 被篡改。
这个签名的生成需要用到:
- Header + Payload。
- 存放在服务端的签名密钥。使用非对称算法时,签名私钥不能泄露。
- 签名算法。
签名的计算公式如下:
HMACSHA256(
base64UrlEncode(header) + "." +
base64UrlEncode(payload),
secret)算出签名以后,把 Header、Payload、Signature 三个部分拼成一个字符串,每个部分之间用“点”(.)分隔,这个字符串就是 JWT。
如何基于 JWT 进行身份验证?
在基于 JWT 进行身份验证的应用程序中,服务器通过 Payload、Header 和密钥创建 JWT 并将 JWT 发送给客户端。客户端需要根据应用形态和威胁模型安全地保存令牌,以后发出的请求会携带这个令牌。
简化后的步骤如下:
- 用户向服务器发送用户名、密码以及验证码用于登陆系统;
- 如果用户用户名、密码以及验证码校验正确的话,服务端会返回已经签名的 Token,也就是 JWT;
- 客户端收到 Token 后安全保存;浏览器应用可以使用 BFF 把令牌保留在服务端,或者根据场景使用受保护的 Cookie;
- 用户以后每次向后端发请求都在 Header 中带上这个 JWT ;
- 服务端检查 JWT 并从中获取用户相关信息。
两点建议:
- 不要默认把 JWT 存放在
localStorage或sessionStorage中。同源页面中的任意恶意脚本都能读取 Web Storage,一处 XSS 漏洞就可能泄露令牌。使用 Cookie 时,应设置HttpOnly、Secure和合适的SameSite属性,并同时做好 CSRF 防护。 - 非 Cookie 方案携带 JWT 的常见做法是将其放在 HTTP Header 的
Authorization字段中(Authorization: Bearer Token)。
spring-security-jwt-guide 就是一个基于 JWT 来做身份认证的简单案例,感兴趣的可以看看。
如何防止 JWT 被篡改?
有了正确校验的签名之后,即使 JWT 被泄露或者截获,攻击者也无法在不知道签名密钥的情况下修改 Header 或 Payload 并生成有效签名。但签名不提供保密性,也不能阻止攻击者直接重放被盗的有效 JWT。
这是为什么呢?因为服务端拿到 JWT 之后,会解析出其中包含的 Header、Payload 以及 Signature 。服务端会根据 Header、Payload、密钥再次生成一个 Signature。拿新生成的 Signature 和 JWT 中的 Signature 作对比,如果一样就说明 Header 和 Payload 没有被修改。
不过,如果服务端的密钥也被泄露,攻击者就可以修改 Header 和 Payload,再重新生成一个有效的 Signature。
签名密钥必须妥善保管,并建立轮换和吊销机制。
如何加强 JWT 的安全性?
- 使用成熟的开源库,不要自己实现 JWT 加解密和校验逻辑。
- 服务端固定允许的算法集合,不能直接信任 JWT Header 中的
alg选择验证算法;HMAC 密钥要有足够的随机性和长度。 - 验证所有与当前应用有关的声明,包括
iss、aud、exp和nbf,并为允许的时钟偏差设置明确上限。 - 对 ID Token、Access Token 等不同用途的 JWT 使用显式
typ和互斥的校验规则,防止一种令牌被替换到另一种场景。 - 一定不要将隐私信息存放在未加密的 Payload 当中,也不能把收到但尚未验证的 Claim 当作可信输入。
- 根据客户端类型选择安全的令牌存储方式,限制令牌有效期、权限范围和接收方;高风险场景还要考虑撤销、重放检测或发送者约束。
- 密钥必须妥善保管并支持轮换。更完整的安全要求可以参考 RFC 8725:JSON Web Token Best Current Practices。
System Design Security Sso Intro (`docs/system-design/security/sso-intro.md`)
title: SSO 单点登录详解
category: 系统设计
tag:
- 安全
head: - meta
- name: keywords
content: SSO,单点登录,统一认证,登录态,票据,TGT,ST,CAS协议,跨域登录
本文授权转载自:https://ken.io/note/sso-design-implement 作者:ken.io
SSO 介绍
什么是 SSO?
SSO 英文全称 Single Sign On,单点登录。SSO 是在多个应用系统中,用户只需要登录一次就可以访问所有相互信任的应用系统。
例如你登录网易账号中心(https://reg.163.com/ )之后访问以下站点都是登录状态。
- 网易直播 https://v.163.com
- 网易博客 https://blog.163.com
- 网易花田 https://love.163.com
- 网易考拉 https://www.kaola.com
- 网易 Lofter http://www.lofter.com
SSO 有什么好处?
- 用户角度 :用户能够做到一次登录多次使用,无需记录多套用户名和密码,省心。
- 系统管理员角度 : 管理员只需维护好一个统一的账号中心就可以了,方便。
- 新系统开发角度: 新系统开发时只需直接对接统一的账号中心即可,简化开发流程,省时。
SSO 设计与实现
本篇文章也主要是为了探讨如何设计&实现一个 SSO 系统
以下为需要实现的核心功能:
- 单点登录
- 单点登出
- 支持跨域单点登录
- 支持跨域单点登出
核心应用与依赖
| 应用/模块/对象 | 说明 |
|---|---|
| 前台站点 | 需要登录的站点 |
| SSO 站点-登录 | 提供登录的页面 |
| SSO 站点-登出 | 提供注销登录的入口 |
| SSO 服务-登录 | 提供登录服务 |
| SSO 服务-登录状态 | 提供登录状态校验/登录信息查询的服务 |
| SSO 服务-登出 | 提供用户注销登录的服务 |
| 数据库 | 存储用户账户信息 |
| 缓存 | 存储用户的登录信息,通常使用 Redis |
用户登录状态的存储与校验
常见的 Web 框架对于 Session 的实现都是生成一个 SessionId 存储在浏览器 Cookie 中。然后将 Session 内容存储在服务器端内存中,这个 ken.io 在之前Session 工作原理中也提到过。整体也是借鉴这个思路。
用户登录成功后,SSO 站点建立自己的登录会话。浏览器中的会话标识应保存在设置了 Secure、HttpOnly 和合适 SameSite 属性的 Cookie 中;Cookie 尽量只作用于当前主机,不要为了共享登录态而直接扩大到整个父域。手机 App 则应使用系统浏览器完成标准授权流程,并把必要凭据保存在 Keychain、Keystore 等安全存储中。本篇主要探讨基于 Web 站点的 SSO。
用户在浏览需要登录的页面时,客户端将 AuthToken 提交给 SSO 服务校验登录状态/获取用户登录信息
对于登录信息的存储,建议采用 Redis,使用 Redis 集群来存储登录信息,既可以保证高可用,又可以线性扩充。同时也可以让 SSO 服务满足负载均衡/可伸缩的需求。
| 对象 | 说明 |
|---|---|
| AuthToken | 使用密码学安全随机数生成的高熵、不可预测标识,并设置过期、轮换和撤销机制。不要使用可预测的 UUID 版本,也不要自行把 UserName+时间戳加密后当作会话令牌 |
| 登录信息 | 通常是将 UserId,UserName 缓存起来 |
用户登录/登录校验
登录时序图
上图展示的是通过父域 Cookie 在多个子域间共享 AuthToken 的做法。新系统不建议把认证 Cookie 的 Domain 设置为 .test.com:这样会把同一凭据发送给所有匹配的子域,任一薄弱、废弃或被接管的子域都可能扩大攻击面。
更稳妥的做法是让 SSO 站点和各业务站点分别维护 host-only 会话。业务站点需要登录时跳转到 SSO 站点,SSO 站点利用自己的 Cookie 判断用户是否已登录,再通过一次性、短时有效的授权码把认证结果返回业务站点,由业务站点后端换取用户信息并建立本地会话。
登录信息获取/登录状态校验
用户登出
SSO 登出不只是删除一个 Cookie:
- SSO 服务端撤销中央登录会话和相关刷新授权。
- SSO 站点清除自己的会话 Cookie。
- 根据协议和业务风险,通过前通道或后通道通知各业务站点清理本地会话,并处理通知失败、站点离线等情况。
登出时序图
跨域登录、登出
跨域 SSO 不应尝试解决 Cookie 的跨域读写问题,而应通过标准的浏览器重定向和后端令牌交换建立各站点自己的会话。常见选择是 OpenID Connect Authorization Code Flow。
解决跨域的核心思路就是:
- 登录完成后,SSO 服务只向预先登记并严格匹配的回调地址返回一次性、短时有效的授权码。业务站点后端使用该授权码换取认证结果,并建立自己的会话,不在多个域之间复制同一个长期 Bearer Token。
- 授权请求需要校验
state;使用 OpenID Connect 时还要校验 Issuer、Audience 和签名,并在使用nonce时核对其值。公共客户端使用 Authorization Code Flow 时还应使用 PKCE。 - 跨站登出使用协议定义的前通道或后通道通知,并允许业务站点在通知失败时通过会话过期、令牌撤销和重新校验收敛状态。
跨域登录(主域名已登录)
跨域登录(主域名未登录)
跨域登出
上面的时序图来自原转载方案,主要用于帮助理解登录跳转和通知关系,其中直接传递 AuthToken、共享父域 Cookie 等细节不应作为新系统的实现依据。新系统应以所选 OpenID Connect/OAuth 协议的当前安全规范为准。
说明
- 关于方案:这次设计方案更多是提供实现思路。APP 用户登录不应只增加一个自定义“APP 签名”就作为安全方案,推荐使用系统浏览器完成 OpenID Connect/OAuth Authorization Code Flow,并使用 PKCE;APP 不能被当作能够永久保守客户端密钥的可信环境。
- 关于时序图:时序图中并没有包含所有场景,只列举了核心/主要场景,另外对于一些不影响理解思路的消息能省就省了。
参考
Tools Docker Docker Intro (`docs/tools/docker/docker-intro.md`)
title: Docker 核心概念总结
category: 开发工具
tag:
- Docker
head: - meta
- name: keywords
content: Docker,容器,镜像,仓库,引擎,隔离,虚拟机对比,部署
本文主要讲 Docker 的核心概念、运行模型和常见使用场景,不展开安装过程。安装、命令练习和本地服务启动可以看后面的 Docker 实战。
容器介绍
Docker 是常见的软件容器平台。想要搞懂 Docker,先要理解容器解决的到底是什么问题。
什么是容器?
先来看看容器较为官方的解释
一句话概括容器:容器就是将软件打包成标准化单元,以用于开发、交付和部署。
- 容器镜像是轻量的、可执行的独立软件包 ,包含软件运行所需的所有内容:代码、运行时环境、系统工具、系统库和设置。
- 容器化软件适用于基于 Linux 和 Windows 的应用,在任何环境中都能够始终如一地运行。
- 容器赋予了软件独立性,使其免受外在环境差异(例如,开发和预演环境的差异)的影响,从而有助于减少团队间在相同基础设施上运行不同软件时的冲突。
再来看看容器较为通俗的解释
如果需要通俗地描述容器的话,我觉得容器就是一个存放东西的地方,就像书包可以装各种文具、衣柜可以放各种衣服、鞋架可以放各种鞋子一样。我们现在所说的容器存放的东西可能更偏向于应用比如网站、程序甚至是系统环境。
图解物理机、虚拟机与容器
关于虚拟机与容器的对比在后面会详细介绍到,这里只是通过网上的图片加深大家对于物理机、虚拟机与容器这三者的理解(下面的图片来源于网络)。
物理机:
虚拟机:
容器:
通过上面这三张抽象图,我们可以大概通过类比概括出:容器虚拟化的是操作系统而不是硬件,容器之间是共享同一套操作系统资源的。虚拟机技术是虚拟出一套硬件后,在其上运行一个完整操作系统。因此容器的隔离级别会稍低一些。
容器 VS 虚拟机
每当说起容器,我们不得不将其与虚拟机做一个比较。就我而言,对于两者无所谓谁会取代谁,而是两者可以和谐共存。
简单来说:容器和虚拟机具有相似的资源隔离和分配优势,但功能有所不同,因为容器虚拟化的是操作系统,而不是硬件,因此容器更容易移植,效率也更高。
传统虚拟机技术是虚拟出一套硬件后,在其上运行一个完整操作系统,在该系统上再运行所需应用进程;而容器内的应用进程直接运行于宿主的内核,容器内没有自己的内核,而且也没有进行硬件虚拟。因此容器要比传统虚拟机更为轻便。
容器和虚拟机的对比:
容器是一个应用层抽象,用于将代码和依赖资源打包在一起。 多个容器可以在同一台机器上运行,共享操作系统内核,但各自作为独立的进程在用户空间中运行 。与虚拟机相比, 容器占用的空间较少(容器镜像大小通常只有几十兆),瞬间就能完成启动 。
虚拟机 (VM) 是一个物理硬件层抽象,用于将一台服务器变成多台服务器。管理程序允许多个 VM 在一台机器上运行。每个 VM 都包含一整套操作系统、一个或多个应用、必要的二进制文件和库资源,因此 占用大量空间 。而且 VM 启动也十分缓慢 。
通过 Docker 官网,我们知道了这么多 Docker 的优势,但是大家也没有必要完全否定虚拟机技术,因为两者有不同的使用场景。虚拟机更擅长于彻底隔离整个运行环境。例如,云服务提供商通常采用虚拟机技术隔离不同的用户。而 Docker 通常用于隔离不同的应用 ,例如前端,后端以及数据库。
就我而言,对于两者无所谓谁会取代谁,而是两者可以和谐共存。
Docker 介绍
什么是 Docker?
可以从下面几个角度理解 Docker:
- Docker 是一个软件容器平台。
- Docker 使用 Go 语言开发,基于 Linux 内核提供的 cgroups、namespaces,以及 UnionFS 等能力对进程进行封装隔离,属于操作系统层面的虚拟化技术。
- Docker 能够把应用和运行依赖打包到镜像中,减少开发、测试、部署环境不一致带来的问题。
- 用户可以方便地创建和使用容器,把自己的应用放入容器。容器还可以进行版本管理、复制、分享、修改,就像管理普通的代码一样。
Docker 思想:
- 集装箱:就像海运中的集装箱一样,Docker 容器包含了应用程序及其所有依赖项,确保在任何环境中都能以相同的方式运行。
- 标准化:运输方式、存储方式、API 接口。
- 隔离:每个 Docker 容器都在自己的隔离环境中运行,与宿主机和其他容器隔离。
Docker 容器的特点
- 轻量 : 在一台机器上运行的多个 Docker 容器可以共享这台机器的操作系统内核;它们能够迅速启动,只需占用很少的计算和内存资源。镜像是通过文件系统层进行构造的,并共享一些公共文件。这样就能尽量降低磁盘用量,并能更快地下载镜像。
- 标准 : Docker 容器基于开放式标准,能够在所有主流 Linux 版本、Microsoft Windows 以及包括 VM、裸机服务器和云在内的任何基础设施上运行。
- 安全 : Docker 赋予应用的隔离性不仅限于彼此隔离,还独立于底层的基础设施。Docker 默认提供最强的隔离,因此应用出现问题,也只是单个容器的问题,而不会波及到整台机器。
为什么要用 Docker ?
- Docker 的镜像提供了除内核外完整的运行时环境,确保了应用运行环境一致性,从而不会再出现 “这段代码在我机器上没问题啊” 这类问题;——一致的运行环境
- 可以做到秒级、甚至毫秒级的启动时间。大大的节约了开发、测试、部署的时间。——更快速的启动时间
- 避免公用的服务器,资源会容易受到其他用户的影响。——隔离性
- 善于处理集中爆发的服务器使用压力;——弹性伸缩,快速扩展
- 可以很轻易的将在一个平台上运行的应用,迁移到另一个平台上,而不用担心运行环境的变化导致应用无法正常运行的情况。——迁移方便
- 使用 Docker 可以通过定制应用镜像来实现持续集成、持续交付、部署。——持续交付和部署
Docker 基本概念
Docker 中有非常重要的三个基本概念:镜像(Image)、容器(Container)和仓库(Repository)。
理解了这三个概念,就理解了 Docker 的整个生命周期。
镜像(Image):一个特殊的文件系统
操作系统分为内核和用户空间。对于 Linux 而言,内核启动后,会挂载 root 文件系统为其提供用户空间支持。而 Docker 镜像(Image),就相当于是一个 root 文件系统。
Docker 镜像是一个特殊的文件系统,除了提供容器运行时所需的程序、库、资源、配置等文件外,还包含了一些为运行时准备的一些配置参数(如匿名卷、环境变量、用户等)。 镜像不包含任何动态数据,其内容在构建之后也不会被改变。
Docker 设计时,就充分利用 Union FS 的技术,将其设计为分层存储的架构 。镜像实际是由多层文件系统联合组成。
镜像构建时,会一层层构建,前一层是后一层的基础。每一层构建完就不会再发生改变,后一层上的任何改变只发生在自己这一层。 比如,删除前一层文件的操作,实际不是真的删除前一层的文件,而是仅在当前层标记为该文件已删除。在最终容器运行的时候,虽然不会看到这个文件,但是实际上该文件会一直跟随镜像。因此,在构建镜像的时候,需要额外小心,每一层尽量只包含该层需要添加的东西,任何额外的东西应该在该层构建结束前清理掉。
分层存储的特征还使得镜像的复用、定制变的更为容易。甚至可以用之前构建好的镜像作为基础层,然后进一步添加新的层,以定制自己所需的内容,构建新的镜像。
容器(Container):镜像运行时的实体
镜像(Image)和容器(Container)的关系,就像是面向对象程序设计中的 类 和 实例 一样,镜像是静态的定义,容器是镜像运行时的实体。容器可以被创建、启动、停止、删除、暂停等 。
容器的实质是进程,但与直接在宿主执行的进程不同,容器进程运行于属于自己的独立的 命名空间。前面讲过镜像使用的是分层存储,容器也是如此。
容器存储层的生存周期和容器一样,容器消亡时,容器存储层也随之消亡。因此,任何保存于容器存储层的信息都会随容器删除而丢失。
按照 Docker 最佳实践的要求,容器不应该向其存储层内写入业务数据,容器存储层要尽量保持无状态。需要持久化的文件写入,应该使用数据卷(Volume)或者绑定宿主目录,这类读写会绕过容器存储层,直接落到宿主机或网络存储上,性能和稳定性更好。数据卷的生命周期独立于容器,容器删除后,数据卷不会自动删除。
仓库(Repository):集中存放镜像文件的地方
镜像构建完成后,可以很容易的在当前宿主上运行,但是, 如果需要在其它服务器上使用这个镜像,我们就需要一个集中的存储、分发镜像的服务,Docker Registry 就是这样的服务。
一个 Docker Registry 中可以包含多个仓库(Repository);每个仓库可以包含多个标签(Tag);每个标签对应一个镜像。所以说:镜像仓库是 Docker 用来集中存放镜像文件的地方类似于我们之前常用的代码仓库。
通常,一个仓库会包含同一个软件不同版本的镜像,而标签就常用于对应这个软件的各个版本。我们可以通过 <仓库名>:<标签> 的格式指定具体镜像。如果不给出标签,将以 latest 作为默认标签。不过在生产环境中不建议依赖 latest,最好明确指定版本标签,便于回滚和排查问题。
这里补充一下 Docker Registry 公开服务和私有 Docker Registry 的概念:
Docker Registry 公开服务 是开放给用户使用、允许用户管理镜像的 Registry 服务。一般这类公开服务允许用户免费上传、下载公开的镜像,并可能提供收费服务供用户管理私有镜像。
最常使用的 Registry 公开服务是官方的 Docker Hub ,这也是默认的 Registry,并拥有大量的高质量的官方镜像,网址为:https://hub.docker.com/ 。官方是这样介绍 Docker Hub 的:
Docker Hub 是 Docker 官方提供的一项服务,用于与您的团队查找和共享容器镜像。
比如我们想要搜索自己想要的镜像:
在 Docker Hub 的搜索结果中,有几项关键的信息有助于我们选择合适的镜像:
- OFFICIAL Image:代表镜像为 Docker 官方提供和维护,相对来说稳定性和安全性较高。
- Stars:和点赞差不多的意思,类似 GitHub 的 Star。
- Downloads:代表镜像被拉取的次数,基本上能够表示镜像被使用的频度。
当然,除了直接通过 Docker Hub 网站搜索镜像这种方式外,我们还可以通过 docker search 这个命令搜索 Docker Hub 中的镜像,搜索的结果是一致的。
➜ ~ docker search mysql
NAME DESCRIPTION STARS OFFICIAL AUTOMATED
mysql MySQL is a widely used, open-source relation… 8763 [OK]
mariadb MariaDB is a community-developed fork of MyS… 3073 [OK]
mysql/mysql-server Optimized MySQL Server Docker images. Create… 650 [OK]在国内访问 Docker Hub 可能会比较慢,企业项目通常会结合公司内部镜像仓库或云厂商镜像仓库来做镜像缓存和分发。
除了使用公开服务外,用户还可以在 本地搭建私有 Docker Registry 。Docker 官方提供了 Docker Registry 镜像,可以直接使用做为私有 Registry 服务。开源的 Docker Registry 镜像只提供了 Docker Registry API 的服务端实现,足以支持 Docker 命令,不影响使用。但不包含图形界面,以及镜像维护、用户管理、访问控制等高级功能。
Image、Container 和 Repository 的关系
下面这一张图很形象地展示了 Image、Container、Repository 和 Registry/Hub 这四者的关系:
- Dockerfile 是一个文本文件,包含了一系列的指令和参数,用于定义如何构建一个 Docker 镜像。运行
docker build命令并指定一个 Dockerfile 时,Docker 会读取 Dockerfile 中的指令,逐步构建一个新的镜像,并将其保存在本地。 docker pull命令可以从指定的 Registry/Hub 下载一个镜像到本地,默认使用 Docker Hub。docker run命令可以从本地镜像创建一个新的容器并启动它。如果本地没有镜像,Docker 会先尝试从 Registry/Hub 拉取镜像。docker push命令可以将本地的 Docker 镜像上传到指定的 Registry/Hub。
上面涉及到了一些 Docker 基本命令,后面的实战文章会详细介绍。
Build Ship and Run
Docker 的概念基本上已经讲完,我们再来谈谈:Build, Ship, and Run。
如果你搜索 Docker 官网,会发现如下的字样:“Docker - Build, Ship, and Run Any App, Anywhere”。那么 Build, Ship, and Run 到底是在干什么呢?
- Build(构建镜像):镜像就像是集装箱包括文件以及运行环境等等资源。
- Ship(运输镜像):主机和仓库间运输,这里的仓库就像是超级码头一样。
- Run (运行镜像):运行的镜像就是一个容器,容器就是运行程序的地方。
Docker 运行过程也就是去仓库把镜像拉到本地,然后用一条命令把镜像运行起来变成容器。所以,我们也常常将 Docker 称为码头工人或码头装卸工,这和 Docker 的中文翻译搬运工人如出一辙。
Docker 常见命令
基本命令
docker version # 查看 Docker 版本
docker images # 查看所有已下载镜像,等价于:docker image ls 命令
docker container ls # 查看所有容器
docker ps # 查看正在运行的容器
docker image prune # 清理没有被使用的镜像文件。-a/--all 会删除所有未被容器使用的镜像拉取镜像
docker pull 命令默认使用的 Registry 是 Docker Hub。当你执行 docker pull 命令而没有指定任何 Registry 地址时,Docker 会从 Docker Hub 拉取镜像。
docker search mysql # 查看 MySQL 相关镜像
docker pull mysql:8.4 # 拉取 MySQL 镜像
docker image ls # 查看所有已下载镜像构建镜像
运行 docker build命令并指定一个 Dockerfile 时,Docker 会读取 Dockerfile 中的指令,逐步构建一个新的镜像,并将其保存在本地。
# image-name 是镜像名称,1.0.0 是镜像版本号或标签
docker build -t image-name:1.0.0 .需要注意:Dockerfile 的文件名不必须为 Dockerfile,也不一定要放在构建上下文的根目录中。使用 -f 或 --file 选项,可以指定任何位置的任何文件作为 Dockerfile。当然,一般大家习惯性的会使用默认的文件名 Dockerfile,以及会将其置于镜像构建上下文目录中。
删除镜像
比如我们要删除已经下载的 MySQL 镜像。
通过 docker rmi [image](等价于 docker image rm [image])删除镜像之前,首先要确保这个镜像没有被容器引用。可以通过标签名称或者镜像 ID 删除,也可以通过前面讲的 docker ps 命令查看是否有容器正在使用它。
➜ ~ docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
c4cd691d9f80 mysql:5.7 "docker-entrypoint.s…" 7 weeks ago Up 12 days 0.0.0.0:3306->3306/tcp, 33060/tcp mysql可以看到 mysql:5.7 正在被 ID 为 c4cd691d9f80 的容器引用,需要先通过 docker stop c4cd691d9f80 或者 docker stop mysql 暂停这个容器。
然后查看 MySQL 镜像的 ID:
➜ ~ docker images
REPOSITORY TAG IMAGE ID CREATED SIZE
mysql 5.7 f6509bac4980 3 months ago 373MB通过 IMAGE ID 或者 REPOSITORY:TAG 即可删除:
docker rmi f6509bac4980 # 或者 docker rmi mysql:5.7镜像推送
docker push 命令用于将本地的 Docker 镜像上传到指定的 Registry/Hub。
# 将镜像推送到私有镜像仓库 Harbor
# harbor.example.com 是私有镜像仓库的地址,ubuntu 是镜像名称,18.04 是镜像版本标签
docker push harbor.example.com/ubuntu:18.04镜像推送之前,要确保本地已经构建好需要推送的 Docker 镜像。另外,务必先登录到对应的镜像仓库。
Docker 数据管理
在容器中管理数据主要有两种方式:
- 数据卷(Volumes)
- 挂载主机目录 (Bind mounts)
数据卷是由 Docker 管理的数据存储区域,有如下这些特点:
- 可以在容器之间共享和重用。
- 即使容器被删除,数据卷中的数据也不会被自动删除,从而确保数据的持久性。
- 对数据卷的修改会立马生效。
- 对数据卷的更新,不会影响镜像。
# 创建一个数据卷
docker volume create my-vol
# 查看所有的数据卷
docker volume ls
# 查看数据卷的具体信息
docker volume inspect my-vol
# 删除指定的数据卷
docker volume rm my-vol在用 docker run 命令的时候,使用 --mount 标记来将一个或多个数据卷挂载到容器里。
还可以通过 --mount 标记将宿主机上的文件或目录挂载到容器中,这使得容器可以直接访问宿主机的文件系统。Docker 挂载主机目录的默认权限是读写,用户也可以通过增加 readonly 指定为只读。
Docker Compose
什么是 Docker Compose?有什么用?
Docker Compose 是 Docker 官方提供的多容器应用定义和运行工具。通过 Compose,开发者可以使用一个 YAML 文件描述应用依赖的多个服务、网络、端口和数据卷,然后用一条命令启动或停止整组服务。
Docker Compose 是开源项目,地址:https://github.com/docker/compose。
Docker Compose 的核心功能:
- 多容器管理:允许用户在一个 YAML 文件中定义和管理多个容器。
- 服务编排:配置容器间的网络和依赖关系。
- 一键启动与停止:通过
docker compose up和docker compose down等命令,可以轻松启动和停止整个应用。
Docker Compose 简化了多容器应用程序的开发、测试和部署过程,提高了开发团队的生产力,同时降低了应用程序的部署复杂度和管理成本。
Docker Compose 文件基本结构
Docker Compose 文件是 Docker Compose 工具的核心,用于定义和配置多容器 Docker 应用。这个文件通常命名为 compose.yaml 或 docker-compose.yml,采用 YAML(YAML Ain't Markup Language)格式编写。
Docker Compose 文件基本结构如下:
- 服务(services): 定义了应用中的每个容器(服务)。每个服务可以使用不同的镜像、环境设置和依赖关系。
- 镜像(image): 从指定的镜像中启动容器,可以是存储仓库、标签以及镜像 ID。
- 命令(command): 可选,覆盖容器启动后默认执行的命令。在启动服务时运行特定的命令或脚本,常用于启动应用程序、执行初始化脚本等。
- 端口(ports): 可选,映射容器和宿主机的端口。
- 依赖(depends_on): 配置服务之间的启动依赖关系。例如后端服务依赖数据库服务时,可以先启动数据库,再启动后端服务。
- 环境变量(environment): 可选,设置服务运行所需的环境变量。
- 重启(restart): 可选,控制容器的重启策略。在容器退出时,根据指定的策略自动重启容器。
- 服务卷(volumes): 可选,定义服务使用的卷,用于数据持久化或在容器之间共享数据。
- 构建(build): 指定构建镜像的 Dockerfile 上下文路径,或者使用详细配置对象。
- 网络(networks): 定义了容器间的网络连接。
- 卷(volumes): 用于数据持久化和共享的数据卷定义。常用于数据库存储、配置文件、日志等数据的持久化。
services:
service-name-1:
image: nginx:stable
command: ["nginx", "-g", "daemon off;"]
environment:
TZ: Asia/Shanghai
volumes:
- web_data:/usr/share/nginx/html
networks:
- app_net
ports:
- "8080:80"
restart: unless-stopped
depends_on:
- service-name-2
service-name-2:
image: redis:7
networks:
- app_net
volumes:
web_data:
networks:
app_net:Docker Compose 常见命令
启动
docker compose up 会根据 Compose 文件中定义的服务创建并启动容器,并将它们连接到 Compose 创建的网络中。如果文件没有声明自定义网络,Compose 会自动创建默认网络。
# 在当前目录下寻找 compose.yaml 或 docker-compose.yml 文件,并根据其中定义的服务启动应用
docker compose up
# 后台启动
docker compose up -d
# 强制重新创建所有容器,即使它们已经存在
docker compose up --force-recreate
# 重新构建镜像
docker compose up --build
# 指定要启动的服务名称,而不是启动所有服务
# 可以同时指定多个服务,用空格分隔。
docker compose up service-name另外,如果 Compose 文件名称不是 compose.yaml 或 docker-compose.yml,可以通过 -f 参数指定。
docker compose -f compose.prod.yaml up暂停
docker compose down 用于停止并移除通过 docker compose up 启动的容器和网络。
# 在当前目录下寻找 Compose 文件
# 根据其中定义移除启动的容器和网络
docker compose down
# 停止容器但不移除
docker compose stop
# 停止指定服务
docker compose stop service-name同样地,如果 Compose 文件名称不是 compose.yaml 或 docker-compose.yml,可以通过 -f 参数指定。
docker compose -f compose.prod.yaml down查看
docker compose ps 用于查看通过 docker compose up 启动的所有容器的状态信息。
# 查看所有容器的状态信息
docker compose ps
# 只显示服务名称
docker compose ps --services
# 查看指定服务的容器
docker compose ps service-name其他
| 命令 | 介绍 |
|---|---|
docker compose version |
查看版本 |
docker compose images |
列出所有容器使用的镜像 |
docker compose kill |
强制停止服务的容器 |
docker compose exec |
在容器中执行命令 |
docker compose logs |
查看日志 |
docker compose pause |
暂停服务 |
docker compose unpause |
恢复服务 |
docker compose push |
推送服务镜像 |
docker compose start |
启动当前停止的服务 |
docker compose stop |
停止当前运行的服务 |
docker compose rm |
删除已停止的服务容器 |
docker compose top |
查看进程 |
Docker 底层原理
首先,Docker 是基于轻量级虚拟化技术的软件,那什么是虚拟化技术呢?
简单点来说,虚拟化技术可以这样定义:
虚拟化技术是一种资源管理技术,是将计算机的各种实体资源(CPU、内存、磁盘空间、网络适配器等),予以抽象、转换后呈现出来并可供分割、组合为一个或多个电脑配置环境。由此,打破实体结构间的不可切割的障碍,使用户可以比原本的配置更好的方式来应用这些电脑硬件资源。这些资源的新虚拟部分是不受现有资源的架设方式,地域或物理配置所限制。一般所指的虚拟化资源包括计算能力和数据存储。
Docker 技术是基于 LXC(Linux container- Linux 容器)虚拟容器技术的。
LXC,其名称来自 Linux 软件容器(Linux Containers)的缩写,一种操作系统层虚拟化(Operating system–level virtualization)技术,为 Linux 内核容器功能的一个用户空间接口。它将应用软件系统打包成一个软件容器(Container),内含应用软件本身的代码,以及所需要的操作系统核心和库。通过统一的名字空间和共用 API 来分配不同软件容器的可用硬件资源,创造出应用程序的独立沙箱运行环境,使得 Linux 用户可以容易的创建和管理系统或应用容器。
LXC 技术主要是借助 Linux 内核中提供的 CGroup 功能和 namespace 来实现的,通过 LXC 可以为软件提供一个独立的操作系统运行环境。
cgroup 和 namespace 介绍:
namespace 是 Linux 内核用来隔离内核资源的方式。 通过 namespace 可以让一些进程只能看到与自己相关的一部分资源,而另外一些进程也只能看到与它们自己相关的资源,这两拨进程根本就感觉不到对方的存在。具体的实现方式是把一个或多个进程的相关资源指定在同一个 namespace 中。Linux namespaces 是对全局系统资源的一种封装隔离,使得处于不同 namespace 的进程拥有独立的全局系统资源,改变一个 namespace 中的系统资源只会影响当前 namespace 里的进程,对其他 namespace 中的进程没有影响。
(以上关于 namespace 介绍内容来自https://www.cnblogs.com/sparkdev/p/9365405.html ,更多关于 namespace 的内容可以查看这篇文章 )。
CGroup 是 Control Groups 的缩写,是 Linux 内核提供的一种可以限制、记录、隔离进程组 (process groups) 所使用的物理资源 (如 cpu memory i/o 等等) 的机制。
(以上关于 CGroup 介绍内容来自 https://www.ibm.com/developerworks/cn/linux/1506_cgroup/index.html ,更多关于 CGroup 的内容可以查看这篇文章 )。
cgroup 和 namespace 两者对比:
两者都是将进程进行分组,但是两者的作用还是有本质区别。namespace 是为了隔离进程组之间的资源,而 cgroup 是为了对一组进程进行统一的资源监控和限制。
总结
本文主要把 Docker 中的一些常见概念和命令做了详细的阐述。从零到上手实战可以看Docker 从入门到上手干事这篇文章,内容非常详细!
另外,再给大家推荐一本质量非常高的开源书籍《Docker 从入门到实践》 ,这本书的内容非常新,毕竟书籍的内容是开源的,可以随时改进。
参考
- Docker Compose:从零基础到实战应用的全面指南
- Linux Namespace 和 Cgroup
- LXC vs Docker: Why Docker is Better
- CGroup 介绍、应用实例及原理描述
Tools Git Git Intro (`docs/tools/git/git-intro.md`)
title: Git 核心概念总结
category: 开发工具
tag:
- Git
head: - meta
- name: keywords
content: Git,版本控制,分布式,分支,提交,合并,冲突解决,工作流
版本控制
什么是版本控制
版本控制是一种记录一个或若干文件内容变化,以便将来查阅特定版本修订情况的系统。 除了项目源代码,你可以对任何类型的文件进行版本控制。
为什么要版本控制
有了它你就可以将某个文件回溯到之前的状态,甚至将整个项目都回退到过去某个时间点的状态,你可以比较文件的变化细节,查出最后是谁修改了哪个地方,从而找出导致怪异问题出现的原因,又是谁在何时报告了某个功能缺陷等等。
本地版本控制系统
许多人习惯用复制整个项目目录的方式来保存不同的版本,或许还会改名加上备份时间以示区别。 这么做唯一的好处就是简单,但是特别容易犯错。 有时候会混淆所在的工作目录,一不小心会写错文件或者覆盖意想外的文件。
为了解决这个问题,人们很久以前就开发了许多种本地版本控制系统,大多都是采用某种简单的数据库来记录文件的历次更新差异。
集中化的版本控制系统
接下来人们又遇到一个问题,如何让在不同系统上的开发者协同工作? 于是,集中化的版本控制系统(Centralized Version Control Systems,简称 CVCS)应运而生。
集中化的版本控制系统都有一个单一的集中管理的服务器,保存所有文件的修订版本,而协同工作的人们都通过客户端连到这台服务器,取出最新的文件或者提交更新。
这么做虽然解决了本地版本控制系统无法让在不同系统上的开发者协同工作的诟病,但也还是存在下面的问题:
- 单点故障: 中央服务器宕机,则其他人无法使用;如果中心数据库磁盘损坏又没有进行备份,你将丢失所有数据。本地版本控制系统也存在类似问题,只要整个项目的历史记录被保存在单一位置,就有丢失所有历史更新记录的风险。
- 必须联网才能工作: 受网络状况、带宽影响。
分布式版本控制系统
于是分布式版本控制系统(Distributed Version Control System,简称 DVCS)面世了。 Git 就是一个典型的分布式版本控制系统。
这类系统,客户端并不只提取最新版本的文件快照,而是把代码仓库完整地镜像下来。 这么一来,任何一处协同工作用的服务器发生故障,事后都可以用任何一个镜像出来的本地仓库恢复。 因为每一次的克隆操作,实际上都是一次对代码仓库的完整备份。
分布式版本控制系统可以不用联网就可以工作,因为每个人的电脑上都是完整的版本库,当你修改了某个文件后,你只需要将自己的修改推送给别人就可以了。但是,在实际使用分布式版本控制系统的时候,很少会直接进行推送修改,而是使用一台充当“中央服务器”的东西。这个服务器的作用仅仅是用来方便“交换”大家的修改,没有它大家也一样干活,只是交换修改不方便而已。
分布式版本控制系统的优势不单是不必联网这么简单,后面我们还会看到 Git 极其强大的分支管理等功能。
认识 Git
Git 简史
Linux 内核项目组当时使用分布式版本控制系统 BitKeeper 来管理和维护代码。但是,后来开发 BitKeeper 的商业公司同 Linux 内核开源社区的合作关系结束,他们收回了 Linux 内核社区免费使用 BitKeeper 的权力。 Linux 开源社区(特别是 Linux 的缔造者 Linus Torvalds)基于使用 BitKeeper 时的经验教训,开发出自己的版本系统,而且对新的版本控制系统做了很多改进。
Git 与其他版本管理系统的主要区别
Git 在保存和对待各种信息的时候与其它版本控制系统有很大差异,尽管操作起来的命令形式非常相近,理解这些差异将有助于防止你使用中的困惑。
下面我们主要说一个关于 Git 与其他版本管理系统的主要差别:对待数据的方式。
Git 采用的是直接记录快照的方式,而非差异比较。我后面会详细介绍这两种方式的差别。
大部分版本控制系统(CVS、Subversion、Perforce、Bazaar 等等)都是以文件变更列表的方式存储信息,这类系统将它们保存的信息看作是一组基本文件和每个文件随时间逐步累积的差异。
具体原理如下图所示,理解起来其实很简单,每当我们提交更新一个文件之后,系统都会记录这个文件做了哪些更新,以增量符号 Δ(Delta)表示。
我们怎样才能得到一个文件的最终版本呢?
很简单,高中数学的基本知识,我们只需要将这些原文件和这些增加进行相加就行了。
这种方式有什么问题呢?
比如我们的增量特别特别多的话,如果我们要得到最终的文件是不是会耗费时间和性能。
Git 不按照以上方式对待或保存数据。 反之,Git 更像是把数据看作是对小型文件系统的一组快照。 每次你提交更新,或在 Git 中保存项目状态时,它主要对当时的全部文件制作一个快照并保存这个快照的索引。 为了高效,如果文件没有修改,Git 不再重新存储该文件,而是只保留一个链接指向之前存储的文件。 Git 对待数据更像是一个 快照流。
Git 的三种状态
Git 有三种状态,你的文件可能处于其中之一:
- 已提交(committed):数据已经安全的保存在本地数据库中。
- 已修改(modified):已修改表示修改了文件,但还没保存到数据库中。
- 已暂存(staged):表示对一个已修改文件的当前版本做了标记,使之包含在下次提交的快照中。
由此引入 Git 项目的三个工作区域的概念:Git 仓库(.git directory)、工作目录(Working Directory) 以及 暂存区域(Staging Area) 。
基本的 Git 工作流程如下:
- 在工作目录中修改文件。
- 暂存文件,将文件的快照放入暂存区域。
- 提交更新,找到暂存区域的文件,将快照永久性存储到 Git 仓库目录。
Git 使用快速入门
获取 Git 仓库
有两种取得 Git 项目仓库的方法。
- 在现有目录中初始化仓库:进入项目目录运行
git init命令,该命令会创建一个名为.git的子目录。 - 从一个服务器克隆一个现有的 Git 仓库:
git clone [url]。如果想自定义本地目录名,可以使用git clone [url] directoryname。
记录每次更新到仓库
- 检测当前文件状态 :
git status - 提出更改(把它们添加到暂存区):
git add filename(针对特定文件)、git add .(当前目录下所有改动)、git add *.txt(支持通配符,所有.txt文件)。 - 忽略文件:
.gitignore文件 - 提交更新:
git commit -m "代码提交信息"。每次准备提交前,先用git status看一下是否都已暂存。 - 跳过使用暂存区域更新的方式:
git commit -a -m "代码提交信息"。git commit加上-a选项,Git 会自动把所有已经跟踪过的文件暂存起来一并提交,从而跳过git add步骤。 - 移除文件:
git rm filename(从暂存区域移除,然后提交。) - 对文件重命名:
git mv README.md README(这个命令相当于mv README.md README、git rm README.md、git add README这三条命令的集合)
一个好的 Git 提交消息
一个好的 Git 提交消息如下:
标题行:用这一行来描述和解释你的这次提交
主体部分可以是很少的几行,来加入更多的细节来解释提交,最好是能给出一些相关的背景或者解释这个提交能修复和解决什么问题。
主体部分当然也可以有几段,但是一定要注意换行和句子不要太长。因为这样在使用 "git log" 的时候会有缩进比较好看。提交的标题行描述应该尽量的清晰和尽量的一句话概括。这样就方便相关的 Git 日志查看工具显示和其他人的阅读。
推送改动到远程仓库
如果你还没有克隆现有仓库,并想将你的仓库连接到某个远程服务器,可以使用如下命令添加:
git remote add origin <server>。比如要让本地仓库和 GitHub 上创建的仓库关联,可以这样写:git remote add origin https://github.com/Snailclimb/test.git。将这些改动提交到远端仓库:
git push origin main。这里的main可以换成你想要推送的任何分支。很多老项目的默认分支仍然叫master,以实际仓库为准。如此你就能够将你的改动推送到所添加的服务器上去了。
远程仓库的移除与重命名
- 将 test 重命名为 test1:
git remote rename test test1 - 移除远程仓库 test1:
git remote rm test1
查看提交历史
在提交了若干更新,又或者克隆了某个项目之后,你也许想回顾下提交历史。 完成这个任务最简单而又有效的工具是 git log 命令。git log 会按提交时间列出所有的更新,最近的更新排在最上面。
可以添加一些参数来查看自己希望看到的内容:
只看某个人的提交记录:
git log --author=bob撤销操作
有时候我们提交完了才发现漏掉了几个文件没有添加,或者提交信息写错了。 此时,可以运行带有 --amend 选项的提交命令尝试重新提交:
git commit --amend取消暂存的文件:
git restore --staged filename老版本 Git 也常见下面这种写法:
git reset filename撤消对文件的修改:
git restore filename老版本 Git 也常见下面这种写法:
git checkout -- filename假如你想丢弃你在本地的所有改动与提交,可以到服务器上获取最新的版本历史,并将你本地主分支指向它:
git fetch origin
git reset --hard origin/main注意:git reset --hard 会丢弃本地未提交改动,执行前一定要确认没有需要保留的内容。老项目如果默认分支是 master,对应命令改成 git reset --hard origin/master。
分支
分支是用来隔离不同开发任务的。在其他分支上开发功能或修复问题,完成后再合并回主分支。现在很多仓库默认分支叫 main,老项目中也经常能看到 master。
我们通常在开发新功能、修复一个紧急 bug 等等时候会选择创建分支。单分支开发好还是多分支开发好,还是要看具体场景来说。
创建一个名字叫做 test 的分支
git branch test切换当前分支到 test(当你切换分支的时候,Git 会重置你的工作目录,使其看起来像回到了你在那个分支上最后一次提交的样子。Git 会自动添加、删除、修改文件,以确保此时你的工作目录和这个分支最后一次提交时的样子一致)。
git switch test老版本 Git 也常见 git checkout test 这种写法。
你也可以直接这样创建分支并切换过去:
git switch -c feature_x老版本 Git 也常见 git checkout -b feature_x 这种写法。
切换到主分支
git switch main合并分支(可能会有冲突)
git merge test把新建的分支删掉
git branch -d feature_x将分支推送到远端仓库(推送成功后其他人可见):
git push origin feature_x学习资料推荐
在线演示学习工具:
「补充,来自issue729」Learn Git Branching https://oschina.gitee.io/learn-git-branching/ 。该网站可以方便的演示基本的 git 操作,讲解得明明白白。每一个基本命令的作用和结果。
推荐阅读:
- Git 入门图文教程(1.5W 字 40 图):超用心的一篇文章,内容全面且附带详细的图解,强烈推荐!
- Git - 简明指南:涵盖 Git 常见操作,非常清晰。
- 图解 Git:图解 Git 中的最常用命令。如果你稍微理解 git 的工作原理,这篇文章能够让你理解的更透彻。
- 猴子都能懂得 Git 入门:有趣的讲解。
- Pro Git book:国外的一本 Git 书籍,被翻译成多国语言,质量很高。
Distributed System Distributed Process Coordination Zookeeper Zookeeper Intro (`docs/distributed-system/distributed-process-coordination/zookeeper/zookeeper-intro.md`)
title: ZooKeeper 入门指南:核心概念、ZNode、Watcher、ACL 与典型应用场景
category: 分布式
tag:
- ZooKeeper
head: - meta
- name: keywords
content: ZooKeeper,ZooKeeper 入门,ZNode,Watcher,ACL,分布式锁,注册中心,配置中心,分布式协调,ZAB,临时节点,持久节点
相信大家对 ZooKeeper 应该不算陌生。但是你真的了解 ZooKeeper 到底有啥用不?如果别人/面试官让你给他讲讲对于 ZooKeeper 的认识,你能回答到什么地步呢?
拿我自己来说吧!我本人在大学曾经使用 Dubbo 来做分布式项目的时候,使用了 ZooKeeper 作为注册中心。为了保证分布式系统能够同步访问某个资源,我还使用 ZooKeeper 做过分布式锁。另外,我在学习 Kafka 的时候,知道 Kafka 很多功能的实现依赖了 ZooKeeper。
前几天,总结项目经验的时候,我突然问自己 ZooKeeper 到底是个什么东西?想了半天,脑海中只是简单的能浮现出几句话:
- ZooKeeper 可以被用作注册中心、分布式锁;
- ZooKeeper 是 Hadoop 生态系统的一员;
- 构建 ZooKeeper 集群的时候,使用的服务器最好是奇数台。
由此可见,我对于 ZooKeeper 的理解仅仅是停留在了表面。
所以,通过本文,希望带大家稍微详细的了解一下 ZooKeeper 。如果没有学过 ZooKeeper ,那么本文将会是你进入 ZooKeeper 大门的垫脚砖。如果你已经接触过 ZooKeeper ,那么本文将带你回顾一下 ZooKeeper 的一些基础概念。
另外,本文不光会涉及到 ZooKeeper 的一些概念,后面的文章会介绍到 ZooKeeper 常见命令的使用以及使用 Apache Curator 作为 ZooKeeper 的客户端。
如果文章有任何需要改善和完善的地方,欢迎在评论区指出,共同进步!
ZooKeeper 介绍
ZooKeeper 由来
正式介绍 ZooKeeper 之前,我们先来看看 ZooKeeper 的由来,还挺有意思的。
下面这段内容摘自《从 Paxos 到 ZooKeeper》第四章第一节,推荐大家阅读一下:
ZooKeeper 最早起源于雅虎研究院的一个研究小组。在当时,研究人员发现,在雅虎内部很多大型系统基本都需要依赖一个类似的系统来进行分布式协调,但是这些系统往往都存在分布式单点问题。所以,雅虎的开发人员就试图开发一个通用的无单点问题的分布式协调框架,以便让开发人员将精力集中在处理业务逻辑上。
关于“ZooKeeper”这个项目的名字,其实也有一段趣闻。在立项初期,考虑到之前内部很多项目都是使用动物的名字来命名的(例如著名的 Pig 项目),雅虎的工程师希望给这个项目也取一个动物的名字。时任研究院的首席科学家 RaghuRamakrishnan 开玩笑地说:“在这样下去,我们这儿就变成动物园了!”此话一出,大家纷纷表示就叫动物园管理员吧一一一因为各个以动物命名的分布式组件放在一起,雅虎的整个分布式系统看上去就像一个大型的动物园了,而 ZooKeeper 正好要用来进行分布式环境的协调一一于是,ZooKeeper 的名字也就由此诞生了。
ZooKeeper 概览
ZooKeeper 是一个开源的分布式协调服务,它的设计目标是将那些复杂且容易出错的分布式一致性服务封装起来,构成一个高效可靠的原语集,并以一系列简单易用的接口提供给用户使用。
原语: 操作系统或计算机网络用语范畴。是由若干条指令组成的,用于完成一定功能的一个过程。具有不可分割性,即原语的执行必须是连续的,在执行过程中不允许被中断。
ZooKeeper 为我们提供了高可用、高性能、稳定的分布式数据一致性解决方案,通常被用于实现诸如数据发布/订阅、负载均衡、命名服务、分布式协调/通知、集群管理、Master 选举、分布式锁和分布式队列等功能。这些功能的实现主要依赖于 ZooKeeper 提供的 数据存储+事件监听 功能(后文会详细介绍到) 。
ZooKeeper 将数据保存在内存中,性能是不错的。 在“读”多于“写”的应用程序中尤其地高性能,因为“写”会导致所有的服务器间同步状态。(“读”多于“写”是协调服务的典型场景)。
另外,很多顶级的开源项目都用到了 ZooKeeper,比如:
- Kafka : ZooKeeper 主要为 Kafka 提供 Broker 和 Topic 的注册以及多个 Partition 的负载均衡等功能。不过,在 Kafka 2.8 之后,引入了基于 Raft 协议的 KRaft 模式,不再依赖 Zookeeper,大大简化了 Kafka 的架构。
- Hbase : ZooKeeper 为 Hbase 提供确保整个集群只有一个 Master 以及保存和提供 regionserver 状态信息(是否在线)等功能。
- Hadoop : ZooKeeper 为 Namenode 提供高可用支持。
ZooKeeper 特点
- 顺序一致性: 从同一客户端发起的事务请求,最终将会严格地按照顺序被应用到 ZooKeeper 中去。
- 原子性: 所有事务请求的处理结果在整个集群中所有机器上的应用情况是一致的,也就是说,要么整个集群中所有的机器都成功应用了某一个事务,要么都没有应用。
- 单一系统映像: 无论客户端连到哪一个 ZooKeeper 服务器上,其看到的服务端数据模型都是一致的。
- 可靠性: 一旦一次更改请求被应用,更改的结果就会被持久化,直到被下一次更改覆盖。
- 顺序一致性:所有客户端看到的数据变更顺序是一致的,按照操作被提交的全局 FIFO 顺序进行更新。但这并不保证变更会立即传播到所有节点。
- 集群部署:3~5 台(最好奇数台)机器就可以组成一个集群,每台机器都在内存保存了 ZooKeeper 的全部数据,机器之间互相通信同步数据,客户端连接任何一台机器都可以。
- **高可用:**如果某台机器宕机,会保证数据不丢失。集群中挂掉不超过一半的机器,都能保证集群可用。比如 3 台机器可以挂 1 台,5 台机器可以挂 2 台。
ZooKeeper 应用场景
ZooKeeper 概览中,我们介绍到使用其通常被用于实现诸如数据发布/订阅、负载均衡、命名服务、分布式协调/通知、集群管理、Master 选举、分布式锁和分布式队列等功能。
下面选 3 个典型的应用场景来专门说说:
- 命名服务:可以通过 ZooKeeper 的顺序节点生成全局唯一 ID。
- 数据发布/订阅:通过 Watcher 机制 可以很方便地实现数据发布/订阅。当你将数据发布到 ZooKeeper 被监听的节点上,其他机器可通过监听 ZooKeeper 上节点的变化来实现配置的动态更新。
- 分布式锁:通过创建唯一节点获得分布式锁,当获得锁的一方执行完相关代码或者是挂掉之后就释放锁。分布式锁的实现也需要用到 Watcher 机制 ,我在 分布式锁详解 这篇文章中有详细介绍到如何基于 ZooKeeper 实现分布式锁。
实际上,这些功能的实现基本都得益于 ZooKeeper 可以保存数据的功能,但是 ZooKeeper 不适合保存大量数据,这一点需要注意。
ZooKeeper 重要概念
破音:拿出小本本,下面的内容非常重要哦!
Data model(数据模型)
ZooKeeper 数据模型采用层次化的多叉树形结构,每个节点上都可以存储数据,这些数据可以是数字、字符串或者是二进制序列。并且。每个节点还可以拥有 N 个子节点,最上层是根节点以“/”来代表。每个数据节点在 ZooKeeper 中被称为 znode,它是 ZooKeeper 中数据的最小单元。并且,每个 znode 都有一个唯一的路径标识。
强调一句:ZooKeeper 主要是用来协调服务的,而不是用来存储业务数据的,所以不要放比较大的数据在 znode 上,ZooKeeper 给出的每个节点的数据大小上限是 1M 。
从下图可以更直观地看出:ZooKeeper 节点路径标识方式和 Unix 文件系统路径非常相似,都是由一系列使用斜杠"/"进行分割的路径表示,开发人员可以向这个节点中写入数据,也可以在节点下面创建子节点。这些操作我们后面都会介绍到。
znode(数据节点)
介绍了 ZooKeeper 树形数据模型之后,我们知道每个数据节点在 ZooKeeper 中被称为 znode,它是 ZooKeeper 中数据的最小单元。你要存放的数据就放在上面,是你使用 ZooKeeper 过程中经常需要接触到的一个概念。
我们通常是将 znode 分为 4 大类:
- 持久(PERSISTENT)节点:一旦创建就一直存在即使 ZooKeeper 集群宕机,直到将其删除。
- 临时(EPHEMERAL)节点:临时节点的生命周期是与 客户端会话(session) 绑定的,会话消失则节点消失。并且,临时节点只能做叶子节点 ,不能创建子节点。
- 持久顺序(PERSISTENT_SEQUENTIAL)节点:除了具有持久(PERSISTENT)节点的特性之外, 子节点的名称还具有顺序性。比如
/node1/app0000000001、/node1/app0000000002。 - 临时顺序(EPHEMERAL_SEQUENTIAL)节点:除了具备临时(EPHEMERAL)节点的特性之外,子节点的名称还具有顺序性
每个 znode 由 2 部分组成:
- stat:状态信息
- data:节点存放的数据的具体内容
如下所示,我通过 get 命令来获取 根目录下的 dubbo 节点的内容。(get 命令在下面会介绍到)。
[zk: 127.0.0.1:2181(CONNECTED) 6] get /dubbo
# 该数据节点关联的数据内容为空
null
# 下面是该数据节点的一些状态信息,其实就是 Stat 对象的格式化输出
cZxid = 0x2
ctime = Tue Nov 27 11:05:34 CST 2018
mZxid = 0x2
mtime = Tue Nov 27 11:05:34 CST 2018
pZxid = 0x3
cversion = 1
dataVersion = 0
aclVersion = 0
ephemeralOwner = 0x0
dataLength = 0
numChildren = 1Stat 类中包含了一个数据节点的所有状态信息的字段,包括事务 ID(cZxid)、节点创建时间(ctime) 和子节点个数(numChildren) 等等。
下面我们来看一下每个 znode 状态信息究竟代表的是什么吧!(下面的内容来源于《从 Paxos 到 ZooKeeper 分布式一致性原理与实践》,因为 Guide 确实也不是特别清楚,要学会参考资料的嘛! ):
| znode 状态信息 | 解释 |
|---|---|
| cZxid | create ZXID,即该数据节点被创建时的事务 id |
| ctime | create time,即该节点的创建时间 |
| mZxid | modified ZXID,即该节点最终一次更新时的事务 id |
| mtime | modified time,即该节点最后一次的更新时间 |
| pZxid | 该节点的子节点列表最后一次修改时的事务 id,只有子节点列表变更才会更新 pZxid,子节点内容变更不会更新 |
| cversion | 子节点版本号,当前节点的子节点每次变化时值增加 1 |
| dataVersion | 数据节点内容版本号,节点创建时为 0,每更新一次节点内容(不管内容有无变化)该版本号的值增加 1 |
| aclVersion | 节点的 ACL 版本号,表示该节点 ACL 信息变更次数 |
| ephemeralOwner | 创建该临时节点的会话的 sessionId;如果当前节点为持久节点,则 ephemeralOwner=0 |
| dataLength | 数据节点内容长度 |
| numChildren | 当前节点的子节点个数 |
版本(version)
在前面我们已经提到,对应于每个 znode,ZooKeeper 都会为其维护一个叫作 Stat 的数据结构,Stat 中记录了这个 znode 的三个相关的版本:
- dataVersion:当前 znode 节点的版本号
- cversion:当前 znode 子节点的版本
- aclVersion:当前 znode 的 ACL 的版本。
ACL(权限控制)
ZooKeeper 采用 ACL(AccessControlLists)策略来进行权限控制,类似于 UNIX 文件系统的权限控制。
对于 znode 操作的权限,ZooKeeper 提供了以下 5 种:
- CREATE : 能创建子节点
- READ:能获取节点数据和列出其子节点
- WRITE : 能设置/更新节点数据
- DELETE : 能删除子节点
- ADMIN : 能设置节点 ACL 的权限
其中尤其需要注意的是,CREATE 和 DELETE 这两种权限都是针对 子节点 的权限控制。
对于身份认证,提供了以下几种方式:
- world:默认方式,所有用户都可无条件访问。
- auth :不使用任何 id,代表任何已认证的用户。
- digest :用户名:密码认证方式:username:password 。
- ip : 对指定 ip 进行限制。
Watcher(事件监听器)
Watcher(事件监听器),是 ZooKeeper 中的一个很重要的特性。ZooKeeper 允许用户在指定节点上注册一些 Watcher,并且在一些特定事件触发的时候,ZooKeeper 服务端会将事件通知到感兴趣的客户端上去,该机制是 ZooKeeper 实现分布式协调服务的重要特性。
破音:非常有用的一个特性,都拿出小本本记好了,后面用到 ZooKeeper 基本离不开 Watcher(事件监听器)机制。
会话(Session)
Session 可以看作是 ZooKeeper 服务器与客户端的之间的一个 TCP 长连接,通过这个连接,客户端能够通过心跳检测与服务器保持有效的会话,也能够向 ZooKeeper 服务器发送请求并接受响应,同时还能够通过该连接接收来自服务器的 Watcher 事件通知。
Session 有一个属性叫做:sessionTimeout ,sessionTimeout 代表会话的超时时间。当由于服务器压力太大、网络故障或是客户端主动断开连接等各种原因导致客户端连接断开时,只要在sessionTimeout规定的时间内能够重新连接上集群中任意一台服务器,那么之前创建的会话仍然有效。
另外,在为客户端创建会话之前,服务端首先会为每个客户端都分配一个 sessionID。由于 sessionID是 ZooKeeper 会话的一个重要标识,许多与会话相关的运行机制都是基于这个 sessionID 的,因此,无论是哪台服务器为客户端分配的 sessionID,都务必保证全局唯一。
ZooKeeper 集群
为了保证高可用,最好是以集群形态来部署 ZooKeeper,这样只要集群中大部分机器是可用的(能够容忍一定的机器故障),那么 ZooKeeper 本身仍然是可用的。通常 3 台服务器就可以构成一个 ZooKeeper 集群了。ZooKeeper 官方提供的架构图就是一个 ZooKeeper 集群整体对外提供服务。
上图中每一个 Server 代表一个安装 ZooKeeper 服务的服务器。组成 ZooKeeper 服务的服务器都会在内存中维护当前的服务器状态,并且每台服务器之间都互相保持着通信。集群间通过 ZAB 协议(ZooKeeper Atomic Broadcast)来保持数据的一致性。
最典型集群模式:Master/Slave 模式(主备模式)。在这种模式中,通常 Master 服务器作为主服务器提供写服务,其他的 Slave 服务器从服务器通过异步复制的方式获取 Master 服务器最新的数据提供读服务。
ZooKeeper 集群角色
但是,在 ZooKeeper 中没有选择传统的 Master/Slave 概念,而是引入了 Leader、Follower 和 Observer 三种角色。如下图所示
ZooKeeper 集群中的所有机器通过一个 Leader 选举过程 来选定一台称为 “Leader” 的机器,Leader 既可以为客户端提供写服务又能提供读服务。除了 Leader 外,Follower 和 Observer 都只能提供读服务。Follower 和 Observer 唯一的区别在于 Observer 机器不参与 Leader 的选举过程,也不参与写操作的“过半写成功”策略,因此 Observer 机器可以在不影响写性能的情况下提升集群的读性能。
| 角色 | 说明 |
|---|---|
| Leader | 为客户端提供读和写的服务,负责投票的发起和决议,更新系统状态。 |
| Follower | 为客户端提供读服务,如果是写服务则转发给 Leader。参与选举过程中的投票。 |
| Observer | 为客户端提供读服务,如果是写服务则转发给 Leader。不参与选举过程中的投票,也不参与“过半写成功”策略。在不影响写性能的情况下提升集群的读性能。此角色于 ZooKeeper3.3 系列新增的角色。 |
ZooKeeper 集群 Leader 选举过程
当 Leader 服务器出现网络中断、崩溃退出与重启等异常情况时,就会进入 Leader 选举过程,这个过程会选举产生新的 Leader 服务器。
这个过程大致是这样的:
- Leader election(选举阶段):节点在一开始都处于选举阶段,只要有一个节点得到超半数节点的票数,它就可以当选准 leader。
- Discovery(发现阶段):在这个阶段,followers 跟准 leader 进行通信,同步 followers 最近接收的事务提议。
- Synchronization(同步阶段):同步阶段主要是利用 leader 前一阶段获得的最新提议历史,同步集群中所有的副本。同步完成之后准 leader 才会成为真正的 leader。
- Broadcast(广播阶段):到了这个阶段,ZooKeeper 集群才能正式对外提供事务服务,并且 leader 可以进行消息广播。同时如果有新的节点加入,还需要对新节点进行同步。
ZooKeeper 集群中的服务器状态有下面几种:
- LOOKING:寻找 Leader。
- LEADING:Leader 状态,对应的节点为 Leader。
- FOLLOWING:Follower 状态,对应的节点为 Follower。
- OBSERVING:Observer 状态,对应节点为 Observer,该节点不参与 Leader 选举。
ZooKeeper 集群为啥最好奇数台?
ZooKeeper 集群在宕掉几个 ZooKeeper 服务器之后,如果剩下的 ZooKeeper 服务器个数大于宕掉的个数的话整个 ZooKeeper 才依然可用。假如我们的集群中有 n 台 ZooKeeper 服务器,那么也就是剩下的服务数必须大于 n/2。先说一下结论,2n 和 2n-1 的容忍度是一样的,都是 n-1,大家可以先自己仔细想一想,这应该是一个很简单的数学问题了。
比如假如我们有 3 台,那么最大允许宕掉 1 台 ZooKeeper 服务器,如果我们有 4 台的时候也同样只允许宕掉 1 台。
假如我们有 5 台,那么最大允许宕掉 2 台 ZooKeeper 服务器,如果我们有 6 台的时候也同样只允许宕掉 2 台。
综上,何必增加那一个不必要的 ZooKeeper 呢?
ZooKeeper 选举的过半机制防止脑裂
何为集群脑裂?
对于一个集群,通常多台机器会部署在不同机房,来提高这个集群的可用性。保证可用性的同时,会发生一种机房间网络线路故障,导致机房间网络不通,而集群被割裂成几个小集群。这时候子集群各自选主导致“脑裂”的情况。
举例说明:比如现在有一个由 6 台服务器所组成的一个集群,部署在了 2 个机房,每个机房 3 台。正常情况下只有 1 个 leader,但是当两个机房中间网络断开的时候,每个机房的 3 台服务器都会认为另一个机房的 3 台服务器下线,而选出自己的 leader 并对外提供服务。若没有过半机制,当网络恢复的时候会发现有 2 个 leader。仿佛是 1 个大脑(leader)分散成了 2 个大脑,这就发生了脑裂现象。脑裂期间 2 个大脑都可能对外提供了服务,这将会带来数据一致性等问题。
过半机制是如何防止脑裂现象产生的?
ZooKeeper 的过半机制导致不可能产生 2 个 leader,因为少于等于一半是不可能产生 leader 的,这就使得不论机房的机器如何分配都不可能发生脑裂。
ZAB 协议和 Paxos 算法
Paxos 算法应该可以说是 ZooKeeper 的灵魂了。但是,ZooKeeper 并没有完全采用 Paxos 算法 ,而是使用 ZAB 协议作为其保证数据一致性的核心算法。另外,在 ZooKeeper 的官方文档中也指出,ZAB 协议并不像 Paxos 算法那样,是一种通用的分布式一致性算法,它是一种特别为 Zookeeper 设计的崩溃可恢复的原子消息广播算法。
ZAB 协议介绍
ZAB(ZooKeeper Atomic Broadcast,原子广播) 协议是为分布式协调服务 ZooKeeper 专门设计的一种支持崩溃恢复的原子广播协议。 在 ZooKeeper 中,主要依赖 ZAB 协议来实现分布式数据一致性,基于该协议,ZooKeeper 实现了一种主备模式的系统架构来保持集群中各个副本之间的数据一致性。
ZAB 协议两种基本的模式:崩溃恢复和消息广播
ZAB 协议包括两种基本的模式,分别是
- 崩溃恢复:当整个服务框架在启动过程中,或是当 Leader 服务器出现网络中断、崩溃退出与重启等异常情况时,ZAB 协议就会进入恢复模式并选举产生新的 Leader 服务器。当选举产生了新的 Leader 服务器,同时集群中已经有过半的机器与该 Leader 服务器完成了状态同步之后,ZAB 协议就会退出恢复模式。其中,所谓的状态同步是指数据同步,用来保证集群中存在过半的机器能够和 Leader 服务器的数据状态保持一致。
- 消息广播:当集群中已经有过半的 Follower 服务器完成了和 Leader 服务器的状态同步,那么整个服务框架就可以进入消息广播模式了。 当一台同样遵守 ZAB 协议的服务器启动后加入到集群中时,如果此时集群中已经存在一个 Leader 服务器在负责进行消息广播,那么新加入的服务器就会自觉地进入数据恢复模式:找到 Leader 所在的服务器,并与其进行数据同步,然后一起参与到消息广播流程中去。
ZAB 协议&Paxos 算法文章推荐
关于 ZAB 协议&Paxos 算法 需要讲和理解的东西太多了,具体可以看下面这几篇文章:
ZooKeeper VS ETCD
ETCD 是一种强一致性的分布式键值存储,它提供了一种可靠的方式来存储需要由分布式系统或机器集群访问的数据。ETCD 内部采用 Raft 算法作为一致性算法,基于 Go 语言实现。
与 ZooKeeper 类似,ETCD 也可用于数据发布/订阅、负载均衡、命名服务、分布式协调/通知、分布式锁等场景。那二者如何选择呢?
得物技术的浅析如何基于 ZooKeeper 实现高可用架构这篇文章给出了如下的对比表格(我进一步做了优化),可以作为参考:
| ZooKeeper | ETCD | |
|---|---|---|
| 语言 | Java | Go |
| 协议 | TCP | Grpc |
| 接口调用 | 必须要使用自己的 client 进行调用 | 可通过 HTTP 传输,即可通过 CURL 等命令实现调用 |
| 一致性算法 | Zab 协议 | Raft 算法 |
| Watcher 机制 | 较局限,一次性触发器 | 一次 Watch 可以监听所有的事件 |
| 数据模型 | 基于目录的层次模式 | 参考了 zk 的数据模型,是个扁平的 kv 模型 |
| 存储 | kv 存储,使用的是 ConcurrentHashMap,内存存储,一般不建议存储较多数据 | kv 存储,使用 bbolt 存储引擎,可以处理几个 GB 的数据。 |
| MVCC | 不支持 | 支持,通过两个 B+ Tree 进行版本控制 |
| 全局 Session | 存在缺陷 | 实现更灵活,避免了安全性问题 |
| 权限校验 | ACL | RBAC |
| 事务能力 | 提供了简易的事务能力 | 只提供了版本号的检查能力 |
| 部署维护 | 复杂 | 简单 |
ZooKeeper 在存储性能、全局 Session、Watcher 机制等方面存在一定局限性,越来越多的开源项目在替换 ZooKeeper 为 Raft 实现或其它分布式协调服务,例如:Kafka Needs No Keeper - Removing ZooKeeper Dependency (confluent.io)、Moving Toward a ZooKeeper-Less Apache Pulsar (streamnative.io)。
ETCD 相对来说更优秀一些,提供了更稳定的高负载读写能力,对 ZooKeeper 暴露的许多问题进行了改进优化。并且,ETCD 基本能够覆盖 ZooKeeper 的所有应用场景,实现对其的替代。
总结
- ZooKeeper 本身就是一个分布式程序(只要半数以上节点存活,ZooKeeper 就能正常服务)。
- 为了保证高可用,最好是以集群形态来部署 ZooKeeper,这样只要集群中大部分机器是可用的(能够容忍一定的机器故障),那么 ZooKeeper 本身仍然是可用的。
- ZooKeeper 将数据保存在内存中,这也就保证了 高吞吐量和低延迟(但是内存限制了能够存储的容量不太大,此限制也是保持 znode 中存储的数据量较小的进一步原因)。
- ZooKeeper 是高性能的。 在“读”多于“写”的应用程序中尤其地明显,因为“写”会导致所有的服务器间同步状态。(“读”多于“写”是协调服务的典型场景。)
- ZooKeeper 有临时节点的概念。 当创建临时节点的客户端会话一直保持活动,瞬时节点就一直存在。而当会话终结时,瞬时节点被删除。持久节点是指一旦这个 znode 被创建了,除非主动进行 znode 的移除操作,否则这个 znode 将一直保存在 ZooKeeper 上。
- ZooKeeper 底层其实只提供了两个功能:① 管理(存储、读取)用户程序提交的数据;② 为用户程序提供数据节点监听服务。
参考
- 《从 Paxos 到 ZooKeeper 分布式一致性原理与实践》
- 谈谈 ZooKeeper 的局限性:https://wingsxdu.com/posts/database/zookeeper-limitations/
Home (`docs/home.md`)
icon: "mdi:head-lightbulb-outline"
title: Java 面试指南(JavaGuide 后端通用面试题总结)
sitemap:
changefreq: weekly
priority: 1
head:
- meta
- name: keywords
content: Java面试,Java面试指南,Java八股文,Java面试题,Java基础面试,JVM面试,并发面试,线程池面试,Spring面试,MySQL面试,Redis面试,系统设计面试,分布式面试,后端面试
JavaGuide 是一份系统化的 Java 面试指南 和后端通用面试复习资料,内容覆盖 Java 基础、集合、并发编程、JVM、Spring/Spring Boot、MySQL、Redis、分布式、高并发、高可用和系统设计等核心知识点。
如果你正在准备校招、社招或跳槽面试,可以从 Java 后端面试通关计划 开始,再按下面的模块逐步复习高频 Java 八股文和后端面试题。
本站所有内容都已免费开源,欢迎一起维护完善,有帮助的话,欢迎 Star!
延伸资料
- Java 优质开源项目:精选 Gitee/GitHub 上适合学习、实战和写进简历的 Java 开源项目。
- 优质技术书籍推荐:覆盖计算机基础、数据库、搜索引擎、分布式系统、高可用架构等方向。
面试准备
- ⭐Java 后端面试通关计划(涵盖后端通用体系) (一定要看 :+1:)
- 如何高效准备 Java 面试?
- Java 后端面试重点总结
- Java 学习路线(最新版,4w+ 字)
- 程序员简历编写指南
- 项目经验指南
- 面试太紧张怎么办?
- 校招没有实习经历怎么办?实习经历怎么写?
Java
基础
知识点/面试题总结 : (必看:+1: ):
重要知识点详解:
- 为什么 Java 中只有值传递?
- Java 序列化详解
- 泛型&通配符详解
- Java 反射机制详解
- Java 代理模式详解
- BigDecimal 详解
- Java 魔法类 Unsafe 详解
- Java SPI 机制详解
- Java 语法糖详解
集合
知识点/面试题总结:
- Java 集合常见知识点&面试题总结(上) (必看 :+1:)
- Java 集合常见知识点&面试题总结(下) (必看 :+1:)
- Java 集合使用注意事项总结
源码分析:
- ArrayList 核心源码+扩容机制分析
- LinkedList 核心源码分析
- HashMap 核心源码+底层数据结构分析
- ConcurrentHashMap 核心源码+底层数据结构分析
- LinkedHashMap 核心源码分析
- CopyOnWriteArrayList 核心源码分析
- ArrayBlockingQueue 核心源码分析
- PriorityQueue 核心源码分析
- DelayQueue 核心源码分析
IO
并发
知识点/面试题总结 : (必看 :+1:)
重要知识点详解:
- 乐观锁和悲观锁详解
- CAS 详解
- JMM(Java 内存模型)详解
- 线程池:Java 线程池详解、Java 线程池最佳实践
- ThreadLocal 详解
- Java 并发容器总结
- Atomic 原子类总结
- AQS 详解
- CompletableFuture 详解
JVM (必看 :+1:)
JVM 这部分内容主要参考 JVM 虚拟机规范-Java8 和周志明老师的《深入理解 Java 虚拟机(第 3 版)》 (强烈建议阅读多遍!)。
- ⭐JVM 常见面试题总结
- Java 内存区域
- JVM 垃圾回收
- 类文件结构
- 类加载过程
- 类加载器
- 【待完成】最重要的 JVM 参数总结(翻译完善了一半)
- 【加餐】大白话带你认识 JVM
- JDK 监控和故障处理工具
新特性
- Java 8:Java 8 新特性总结(翻译)、Java8 常用新特性总结
- Java 9 新特性概览
- Java 10 新特性概览
- Java 11 新特性概览
- Java 12 & 13 新特性概览
- Java 14 & 15 新特性概览
- Java 16 新特性概览
- Java 17 新特性概览
- Java 18 新特性概览
- Java 19 新特性概览
- Java 20 新特性概览
- Java 21 新特性概览
- Java 22 & 23 新特性概览
- Java 24 新特性概览
- Java 25 新特性概览
计算机基础
计算机基础(计算机网络、操作系统、数据结构与算法)已独立为单独模块,详见 计算机基础知识总结。
数据库
基础
MySQL
知识点/面试题总结:
- MySQL 常见知识点&面试题总结 (必看 :+1:)
- MySQL 高性能优化规范建议总结
重要知识点:
- MySQL 索引详解
- MySQL 索引失效场景总结
- MySQL 事务隔离级别图文详解)
- MySQL 三大日志(binlog、redo log 和 undo log)详解
- InnoDB 存储引擎对 MVCC 的实现
- SQL 语句在 MySQL 中的执行过程
- MySQL 查询缓存详解
- MySQL 执行计划分析
- MySQL 自增主键一定是连续的吗
- MySQL 时间类型数据存储建议
- MySQL 隐式转换造成索引失效
Redis
知识点/面试题总结 : (必看:+1: ):
重要知识点:
- 3 种常用的缓存读写策略详解
- Redis 能做消息队列吗?怎么实现?
- Redis 5 种基本数据结构详解
- Redis 3 种特殊数据结构详解
- Redis 持久化机制详解
- Redis 内存碎片详解
- Redis 常见阻塞原因总结
- Redis 集群详解
MongoDB
搜索引擎
开发工具
Maven
Gradle
Gradle 核心概念总结(可选,目前国内还是使用 Maven 普遍一些)
Docker
Git
系统设计
基础
常用框架
Spring/SpringBoot (必看 :+1:)
知识点/面试题总结 :
重要知识点详解:
MyBatis
安全
认证授权
数据安全
定时任务
Web 实时消息推送
分布式
理论&算法&协议
RPC
ZooKeeper
这两篇文章可能有内容重合部分,推荐都看一遍。
API 网关
分布式 ID
分布式锁
分布式事务
分布式配置中心
高性能
数据库优化
负载均衡
CDN
消息队列
- 消息队列面试准备
- 消息队列基础知识总结
- Disruptor 常见知识点&面试题总结
- RabbitMQ 常见知识点&面试题总结
- RocketMQ 常见知识点&面试题总结
- Kafka 常见知识点&面试题总结
高可用
冗余设计
限流
降级&熔断
超时&重试
集群
相同的服务部署多份,避免单点故障。
灾备设计和异地多活
灾备 = 容灾 + 备份。
- 备份:将系统所产生的所有重要数据多备份几份。
- 容灾:在异地建立两个完全相同的系统。当某个地方的系统突然挂掉,整个应用系统可以切换到另一个,这样系统就可以正常提供服务了。
异地多活 描述的是将服务部署在异地并且服务同时对外提供服务。和传统的灾备设计的最主要区别在于“多活”,即所有站点都是同时在对外提供服务的。异地多活是为了应对突发状况比如火灾、地震等自然或者人为灾害。
Star 趋势
公众号
如果大家想要实时关注我更新的文章以及分享的干货的话,可以关注我的公众号“JavaGuide”。
About The Author Deprecated Java Technologies (`docs/about-the-author/deprecated-java-technologies.md`)
title: 已经淘汰的 Java 技术,不要再学了!
category: 走近作者
tag:
- 杂谈
前几天,我在知乎上随手回答了一个问题:“Java 学到 JSP 就学不下去了,怎么办?”。
出于不想让别人走弯路的心态,我回答说:已经淘汰的技术就不要学了,并顺带列举了一些在 Java 开发领域中已经被淘汰的技术。
已经淘汰的 Java 技术
我的回答原内容如下,列举了一些在 Java 开发领域中已经被淘汰的技术:
JSP
- 原因:JSP 已经过时,无法满足现代 Web 开发需求;前后端分离成为主流。
- 替代方案:模板引擎(如 Thymeleaf、Freemarker)在传统全栈开发中更流行;而在前后端分离架构中,React、Vue、Angular 等现代前端框架已取代 JSP 的角色。
- 注意:一些国企和央企的老项目可能仍然在使用 JSP,但这种情况越来越少见。
Struts(尤其是 1.x)
- 原因:配置繁琐、开发效率低,且存在严重的安全漏洞(如世界著名的 Apache Struts 2 漏洞)。此外,社区维护不足,生态逐渐萎缩。
- 替代方案:Spring MVC 和 Spring WebFlux 提供了更简洁的开发体验、更强大的功能以及完善的社区支持,完全取代了 Struts。
EJB (Enterprise JavaBeans)
- 原因:EJB 过于复杂,开发成本高,学习曲线陡峭,在实际项目中逐步被更轻量化的框架取代。
- 替代方案:Spring/Spring Boot 提供了更加简洁且功能强大的企业级开发解决方案,几乎已经成为 Java 企业开发的事实标准。此外,国产的 Solon 和云原生友好的 Quarkus 等框架也非常不错。
Java Applets
- 原因:现代浏览器(如 Chrome、Firefox、Edge)早已全面移除对 Java Applets 的支持,同时 Applets 存在严重的安全性问题。
- 替代方案:HTML5、WebAssembly 以及现代 JavaScript 框架(如 React、Vue)可以实现更加安全、高效的交互体验,无需插件支持。
SOAP / JAX-WS
- 原因:SOAP 和 JAX-WS 过于复杂,数据格式冗长(XML),对开发效率和性能不友好。
- 替代方案:RESTful API 和 RPC 更轻量、高效,是现代微服务架构的首选。
RMI(Remote Method Invocation)
- 原因:RMI 是一种早期的 Java 远程调用技术,但兼容性差、配置繁琐,且性能较差。
- 替代方案:RESTful API 和 PRC 提供了更简单、高效的远程调用解决方案,完全取代了 RMI。
Swing / JavaFX
- 原因:桌面应用在开发领域的份额大幅减少,Web 和移动端成为主流。Swing 和 JavaFX 的生态不如现代跨平台框架丰富。
- 替代方案:跨平台桌面开发框架(如 Flutter Desktop、Electron)更具现代化体验。
- 注意:一些国企和央企的老项目可能仍然在使用 Swing / JavaFX,但这种情况越来越少见。
Ant
- 原因:Ant 是一种基于 XML 配置的构建工具,缺乏易用性,配置繁琐。
- 替代方案:Maven 和 Gradle 提供了更高效的项目依赖管理和构建功能,成为现代构建工具的首选。
杠精言论
没想到,评论区果然出现了一类很常见的杠精:
“学的不是技术,是思想。那爬也是人类不需要的技术吗?为啥你一生下来得先学会爬?如果基础思想都不会就去学各种框架,到最后只能是只会 CV 的废物!”
这句话表面上看似有道理,但实际上却暴露了一个人的无知和偏执。
知识越贫乏的人,相信的东西就越绝对,因为他们从未认真了解过与自己观点相对立的角度,也缺乏对技术发展的全局认识。
举个例子,我刚开始学习 Java 后端开发的时候,完全没什么经验,就随便买了一本书开始看。当时看的是 《Java Web 整合开发王者归来》 这本书(梦开始的地方)。
在我上大学那会儿,这本书的很多内容其实已经过时了,比如它花了大量篇幅介绍 JSP、Struts、Hibernate、EJB 和 SVN 等技术。不过,直到现在,我依然非常感谢这本书,带我走进了 Java 后端开发的大门。
这本书一共 1010 页,我当时可以说是废寝忘食地学,花了很长时间才把整本书完全“啃”下来。
回头来看,我如果能有意识地避免学习这些已经淘汰的技术,真的可以节省大量时间去学习更加主流和实用的内容。
那么,这些被淘汰的技术有用吗?说句实话,屁用没有,纯粹浪费时间。
既然都要花时间学习,为什么不去学那些更主流、更有实际价值的技术呢?
现在本身就很卷,不管是 Java 方向还是其他技术方向,要学习的技术都很多。
想要理解所谓的“底层思想”,与其浪费时间在 JSP 这种已经不具备实际应用价值的技术上,不如深入学习一下 Servlet,研究 Spring 的 AOP 和 IoC 原理,从源码角度理解 Spring MVC 的工作机制。
这些内容,不仅能帮助你掌握核心的思想,还能在实际开发中真正派上用场,这难道不比花大量时间在 JSP 上更有意义吗?
还有公司在用的技术就要学吗?
我把这篇文章的相关言论发表在我的公众号之后,又收到另外一类在我看来非常傻叉的言论:
- “虽然 JSP 很老了,但还是得学学,会用就行,因为我们很多老项目还在用。”
- “很多央企和国企的老项目还在用,肯定得学学啊!”
这种观点完全是钻牛角尖!如果按这种逻辑,那你还需要去学 Struts2、SVN、JavaFX 等过时技术,因为它们也还有公司在用。我有一位大学同学毕业后去了武汉的一家国企,写了一年 JavaFX 就受不了跑了。他在之前从来没有接触过 JavaFX,招聘时也没被问过相关问题。
一定不要假设自己要面对的是过时技术栈的项目。你要找工作肯定要用主流技术栈去找,还要尽量找能让自己技术有成长,干着也舒服点。真要是找不到合适的工作,去维护老项目,那都是后话,现学现卖就行了。
对于初学者来说别人劝了还非要学习淘汰的技术,多少脑子有点不够用,基本可以告别这一行了!
About The Author Dog That Copies Other People Essay (`docs/about-the-author/dog-that-copies-other-people-essay.md`)
title: 抄袭狗,你冬天睡觉脚必冷!!!
category: 走近作者
tag:
- 杂谈
抄袭狗真的太烦了。。。
听朋友说我的文章在知乎又被盗了,原封不动地被别人用来引流。
而且!!!这还不是最气的。
这人还在文末注明的原出处还不是我的。。。
也就是说 CSDN 有另外一位抄袭狗盗了我的这篇文章并声明了原创,知乎抄袭狗又原封不动地搬运了这位 CSDN 抄袭狗的文章。
真可谓离谱他妈给离谱开门,离谱到家了。
我打开知乎抄袭狗注明的原出处链接,好家伙,一模一样的内容,还表明了原创。
看了一下 CSDN 这位抄袭狗的文章,好家伙,把我高赞回答搬运了一个遍。。。真是很勤奋了。。。
CSDN 我就不想多说了,就一大型文章垃圾场,都是各种不规范转载,各种收费下载的垃圾资源。这号称国内流量最大的技术网站贼恶心,吃香太难看,能不用就不要用吧!
像我自己平时用 Google 搜索的时候,都是直接屏蔽掉 CSDN 这个站点的。只需要下载一个叫做 Personal Blocklist 的 Chrome 插件,然后将 blog.csdn.net 添加进黑名单就可以了。
我的文章基本被盗完了,关键是我自己发没有什么流量,反而是盗我文章的那些人比我这个原作者流量还大。
这是什么世道,是人性的扭曲还是道德的沦丧?
不过,也没啥,CSDN 这垃圾网站不去发文也无妨。
看看 CSDN 热榜上的文章都是一些什么垃圾,不是各种广告就是一些毫无质量的拼凑文。
当然了,也有极少部分的高质量文章,比如涛哥、二哥、冰河、微观技术等博主的文章。
还有很多视频平台(比如抖音、哔哩哔哩)上面有很多博主直接把别人的原创拿来做个视频,用来引流或者吸粉。
今天提到的这篇被盗的文章曾经就被一个培训机构拿去做成了视频用来引流。
作为个体,咱也没啥办法,只能遇到一个举报一个。。。
About The Author Feelings After One Month Of Induction Training (`docs/about-the-author/feelings-after-one-month-of-induction-training.md`)
title: 入职培训一个月后的感受
category: 走近作者
tag:
- 个人经历
不知不觉已经入职一个多月了,在入职之前我没有在某个公司实习过或者工作过,所以很多东西刚入职工作的我来说还是比较新颖的。学校到职场的转变,带来了角色的转变,其中的差别因人而异。对我而言,在学校的时候课堂上老师课堂上教的东西,自己会根据自己的兴趣选择性接受,甚至很多课程你不想去上的话,还可以逃掉。到了公司就不一样了,公司要求你会的技能你不得不学,除非你不想干了。在学校的时候大部分人编程的目的都是为了通过考试或者找到一份好工作,真正靠自己兴趣支撑起来的很少,到了工作岗位之后我们编程更多的是因为工作的要求,相比于学校的来说会一般会更有挑战而且压力更大。在学校的时候,我们最重要的就是对自己负责,我们不断学习知识去武装自己,但是到了公司之后我们不光要对自己负责,更要对公司负责,毕竟公司出钱请你过来,不是让你一直 on beach 的。
刚来公司的时候,因为公司要求,我换上了 Mac 电脑。由于之前一直用的是 Windows 系统,所以非常不习惯。刚开始用 Mac 系统的时候笨手笨脚,自己会很明显的感觉自己的编程效率降低了至少 3 成。当时内心还是挺不爽的,心里也总是抱怨为什么不直接用 Windows 系统或者 Linux 系统。不过也挺奇怪,大概一个星期之后,自己就开始慢慢适应使用 Mac 进行编程,甚至非常喜欢。我这里不想对比 Mac 和 Windows 编程体验哪一个更好,我觉得还是因人而异,相同价位的 Mac 的配置相比于 Windows 确实要被甩几条街。不过 Mac 的编程和使用体验确实不错,当然你也可以选择使用 Linux 进行日常开发,相信一定很不错。 另外,Mac 不能玩一些主流网络游戏,对于一些克制不住自己想玩游戏的朋友是一个不错的选择。
不得不说 ThoughtWorks 的培训机制还是很不错的。应届生入职之后一般都会安排培训,与往年不同的是,今年的培训多了中国本地班(TWU-C)。作为本地班的第一期学员,说句心里话还是很不错。8 周的培训,除了工作需要用到的基本技术比如 ES6、SpringBoot 等等之外,还会增加一些新员工基本技能的培训比如如何高效开会、如何给别人正确的提 Feedback、如何对代码进行重构、如何进行 TDD 等等。培训期间不定期的有活动,比如 Weekend Trip、 City Tour、Cake time 等等。最后三周还会有一个实际的模拟项目,这个项目基本和我们正式工作的实际项目差不多,我个人感觉很不错。目前这个项目已经正式完成了一个迭代,我觉得在做项目的过程中,收获最大的不是项目中使用的技术,而是如何进行团队合作、如何正确使用 Git 团队协同开发、一个完成的迭代是什么样子的、做项目的过程中可能遇到那些问题、一个项目运作的完整流程等等。
ThoughtWorks 非常提倡分享、提倡帮助他人成长,这一点在公司的这段时间深有感触。培训期间,我们每个人会有一个 Trainer 负责,Trainer 就是日常带我们上课和做项目的同事,一个 Trainer 大概会负责 5 - 6 个人。Trainer 不定期都会给我们最近表现的 Feedback (反馈) ,我个人觉得这个并不是这是走走形式,Trainer 们都很负责,很多时候都是在下班之后找我们聊天。同事们也都很热心,如果你遇到问题,向别人询问,其他人如果知道的话一般都会毫无保留的告诉你,如果遇到大部分都不懂的问题,甚至会组织一次技术 Session 分享。上周五我在我们小组内进行了一次关于 Feign 远程调用的技术分享,因为 team 里面大家对这部分知识都不太熟悉,但是后面的项目进展大概率会用到这部分知识。我刚好研究了这部分内容,所以就分享给了组内的其他同事,以便于项目更好的进行。
另外,ThoughtWorks 也是一家非常提倡 Feedback (反馈) 文化的公司,反馈是告诉人们我们对他们的表现的看法以及他们应该如何更好地做到这一点。刚开始我并没有太在意,慢慢地自己确实感觉到正确的进行反馈对他人会有很大的帮助。因为人在做很多事情的时候,会很难发现别人很容易看到的一些小问题。就比如一个很有趣的现象一样,假如我们在做项目的时候没有测试这个角色,如果你完成了自己的模块,并且自己对这个模块测试了很多遍,你发现已经没啥问题了。但是,到了实际使用的时候会很大概率出现你之前从来没有注意的问题。解释这个问题的说法是:每个人的视野或多或少都是有盲点的,这与我们的关注点息息相关。对于自己做的东西,很多地方自己测试很多遍都不会发现,但是如果让其他人帮你进行测试的话,就很大可能会发现很多显而易见的问题。
工作之后,平时更新公众号、专栏还有维护 Github 的时间变少了。实际上,很多时候下班回来后,都有自己的时间来干自己的事情,但是自己也总是找工作太累或者时间比较零散的接口来推掉了。到了今天,翻看 Github 突然发现 14 天前别人在 Github 上给我提的 PR 我还没有处理。这一点确实是自己没有做好的地方,没有合理安排好自己的时间。实际上自己有很多想写的东西,后面会慢慢将他们提上日程。工作之后,更加发现下班后的几个小时如何度过确实很重要 ,如果你觉得自己没有完成好自己白天该做的工作的话,下班后你可以继续忙白天没有忙完的工作,如果白天的工作对于你游刃有余的话,下班回来之后,你大可去干自己感兴趣的事情,学习自己感兴趣的技术。做任何事情都要基于自身的基础,切不可好高骛远。
工作之后身边也会有很多厉害的人,多从他人身上学习我觉得是每个职场人都应该做的。这一届和我们一起培训的同事中,有一些技术很厉害的,也有一些技术虽然不是那么厉害,但是组织能力以及团队协作能力特别厉害的。有一个特别厉害的同事,在我们还在学 SpringBoot 各种语法的时候,他自己利用业余时间写了一个简化版的 SpringBoot ,涵盖了 Spring 的一些常用注解比如 @RestController、@Autowried、@Pathvairable、@RestquestParam等等(已经联系这位同事,想让他开源一下,后面会第一时间同步到公众号,期待一下吧!)。我觉得这位同事对于编程是真的有兴趣,他好像从初中就开始接触编程了,对于各种底层知识也非常感兴趣,自己写过实现过很多比较底层的东西。他的梦想是在 Github 上造一个 20k Star 以上的轮子。我相信以这位同事的能力一定会达成目标的,在这里祝福这位同事,希望他可以尽快实现这个目标。
这是我入职一个多月之后的个人感受,很多地方都是一带而过,后面我会抽时间分享自己在公司或者业余学到的比较有用的知识给各位,希望看过的人都能有所收获。
About The Author Feelings Of Half A Year From Graduation To Entry (`docs/about-the-author/feelings-of-half-a-year-from-graduation-to-entry.md`)
title: 从毕业到入职半年的感受
category: 走近作者
tag:
- 个人经历
如果大家看过我之前的介绍的话,就会知道我是 19 年毕业的几百万应届毕业生中的一员。这篇文章主要讲了一下我入职大半年的感受,文中有很多自己的主观感受,如果你们有任何不认同的地方都可以直接在评论区说出来,会很尊重其他人的想法。
简单说一下自己的情况吧!我目前是在一家外企,每天的工作和大部分人一样就是做开发。毕业到现在,差不多也算是工作半年多了,也已经过了公司 6 个月的试用期。目前在公司做过两个偏向于业务方向的项目,其中一个正在做。你很难想象我在公司做的两个业务项目的后端都没有涉及到分布式/微服务,没有接触到 Redis、Kafka 等等比较“高大上”的技术在项目中的实际运用。
第一个项目做的是公司的内部项目——员工成长系统。抛去员工成长系统这个名字,实际上这个系统做的就是绩效考核比如你在某个项目组的表现。这个项目的技术是 Spring Boot+ JPA + Spring Security + K8S + Docker + React。第二个目前正在做的是一个集成游戏 (cocos)、Web 管理端 (Spring Boot + Vue) 和小程序 (Taro) 项目。
是的,我在工作中的大部分时间都和 CRUD 有关,每天也会写前端页面。之前我认识的一个朋友 ,他听说我做的项目中大部分内容都是写业务代码之后就非常纳闷,他觉得单纯写业务代码得不到提升?what?你一个应届生,连业务代码都写不好你给我说这个!所以,我就很纳闷不知道为什么现在很多连业务代码都写不好的人为什么人听到 CRUD 就会反感?至少我觉得在我工作这段时间我的代码质量得到了提升、定位问题的能力有了很大的改进、对于业务有了更深的认识,自己也可以独立完成一些前端的开发了。
其实,我个人觉得能把业务代码写好也没那么容易,抱怨自己天天做 CRUD 工作之前,看看自己 CRUD 的代码写好没。再换句话说,单纯写 CRUD 的过程中你搞懂了哪些你常用的注解或者类吗?这就像一个只会 @Service、@Autowired、@RestController等等最简单的注解的人说我已经掌握了 Spring Boot 一样。
不知道什么时候开始大家都会觉得有实际使用 Redis、MQ 的经验就很牛逼了,这可能和当前的面试环境有关系。你需要和别人有差异,你想进大厂的话,好像就必须要这些技术比较在行,好吧,没有好像,自信点来说对于大部分求职者这些技术都是默认你必备的了。
实话实说,我在大学的时候就陷入过这个“伪命题”中。在大学的时候,我大二因为加入了一个学校的偏技术方向的校媒才接触到 Java ,当时我们学习 Java 的目的就是开发一个校园通。 大二的时候,编程相当于才入门水平的我才接触 Java,花了一段时间才掌握 Java 基础。然后,就开始学习安卓开发。
到了大三上学期,我才真正确定要走 Java 后台的方向,找 Java 后台的开发工作。学习了 3 个月左右的 WEB 开发基础之后,我就开始学习分布式方面内容比如 Redis、Dubbo 这些。我当时是通过看书 + 视频 + 博客的方式学习的,自学过程中通过看视频自己做过两个完整的项目,一个普通的业务系统,一个是分布式的系统。我当时以为自己做完之后就很牛逼了,我觉得普通的 CRUD 工作已经不符合我当前的水平了。哈哈!现在看来,当时的我过于哈皮!
这不!到了大三暑假跟着老师一起做项目的时候就出问题了。大三的时候,我们跟着老师做的是一个绩效考核系统,业务复杂程度中等。这个项目的技术用的是:SSM + Shiro + JSP。当时,做这个项目的时候我遇到各种问题,各种我以为我会写的代码都不会写了,甚至我写一个简单的 CRUD 都要花费好几天的时间。所以,那时候我都是边复习边学习边写代码。虽然很累,但是,那时候学到了很多,也让我在技术面前变得更加踏实。我觉得这“这个项目已经没有维护的可能性”这句话是我对我过的这个项目最大的否定了。
技术千变万化,掌握最核心的才是王道。我们前几年可能还在用 Spring 基于传统的 XML 开发,现在几乎大家都会用 Spring Boot 这个开发利器来提升开发速度,再比如几年前我们使用消息队列可能还在用 ActiveMQ,到今天几乎都没有人用它了,现在比较常用的就是 Rocket MQ、Kafka 。技术更新换代这么快的今天,你是无法把每一个框架/工具都学习一遍的。
很多初学者上来就想通过做项目学习,特别是在公司,我觉得这个是不太可取的。 如果的 Java 基础或者 Spring Boot 基础不好的话,建议自己先提前学习一下之后再开始看视频或者通过其他方式做项目。 还有一点就是,我不知道为什么大家都会说边跟着项目边学习做的话效果最好,我觉得这个要加一个前提是你对这门技术有基本的了解或者说你对编程有了一定的了解。
划重点!!!在自己基础没打牢的情况下,单纯跟着视频做一点用没有。你会发现你看完视频之后,让你自己写代码的时候又不会写了。
不知道其他公司的程序员是怎么样的?我感觉技术积累很大程度在乎平时,单纯依靠工作绝大部分情况只会加快自己做需求的熟练度,当然,写多了之后或多或少也会提升你对代码质量的认识(前提是你有这个意识)。
工作之余,我会利用业余时间来学习自己想学的东西。工作中的例子就是我刚进公司的第一个项目用到了 Spring Security + JWT ,因为当时自己对于这个技术不太了解,然后就在工作之外大概花了一周的时间学习写了一个 Demo 分享了出来,GitHub 地址:https://github.com/Snailclimb/spring-security-jwt-guide 。以次为契机,我还分享了
另外一个最近的例子是因为肺炎疫情在家的这段时间,自学了 Kafka,并且正在准备写一系列的入门文章,目前已经完成了:
- 大白话 Kafka 入门;
- Kafka 安装和基本功能体验;
- Spring Boot 整合 Kafka 发送和接受消息;
- Spring Boot 整合 Kafka 发送和接受消息的一些事务、错误消息处理等等。
还没完成的:
- Kafka 高级特性比如工作流程、Kafka 为什么快等等的分析;
- 源码阅读分析;
- ……
所以,我觉得技术的积累和沉淀很大程度在乎工作之外的时间(大佬和一些本身就特别厉害的除外)。
未来还有很长的路要走,即使再有精力也学不完你想学的所有技术,适当取舍、适当妥协,适当娱乐。
About The Author Internet Addiction Teenager (`docs/about-the-author/internet-addiction-teenager.md`)
title: 我曾经也是网瘾少年
category: 走近作者
tag:
- 个人经历
这篇文章写于 2021 年高考前夕。
聊到高考,无数人都似乎有很多话说。今天就假借高考的名义,简单来聊聊我的高中求学经历吧!
说实话,我自己的高中求学经历真的还不算平淡,甚至有点魔幻,所以还是有很多话想要说的。
这篇文章大概会从我的初中一直介绍到高中,每一部分我都不会花太多篇幅,就简单聊聊吧!
以下所有内容皆是事实,没有任何夸大的地方,稍微有一点点魔幻。
刚开始接触电脑
最开始接触电脑是在我刚上五年级的时候,那时候家里没电脑,刚开始上网都是在黑网吧玩的。
黑网吧大概就是下面这样式儿的,一个没有窗户的房间里放了很多台老式电脑,非常拥挤。
在黑网吧上网的经历也是一波三折,经常会遇到警察来检查或者碰到大孩子骚扰。在黑网吧上网的一年多中,我一共两次碰到警察来检查,主要是看有没有未成年人(当时黑网吧里几乎全是未成年人),实际感觉像是要问黑网吧老板要点好处。碰到大孩子骚扰的次数就比较多,大孩子经常抢我电脑,还威胁我把身上所有的钱给他们。我当时一个人也比较怂,被打了几次之后,就尽量避开大孩子来玩的时间去黑网吧,身上也只带很少的钱。小时候的性格就比较独立,在外遇到事情我一般也不会给家里人说(因为说了也没什么用,家人给我的安全感很少)。
我现在已经记不太清当时是被我哥还是我姐带进网吧的,好像是我姐。
起初的时候,自己就是玩玩流行蝴蝶剑、单机摩托之类的单机游戏。但是,也没有到沉迷的地步,只是觉得这东西确实挺好玩的,一玩就可以玩一下午,恋恋不舍。
小学毕业后开始有网瘾
开始有网瘾是在小学毕业的时候,在我玩了一款叫做 QQ 飞车 的游戏之后(好像是六年级末就开始玩了)。我艹,当时真的被这游戏吸引了。每天上课都幻想自己坐在车里面飘逸,没错,当时就觉得秋名山车神就是我啦!
我当时技术还是挺不错的,整个网吧玩这个游戏的貌似还没有可以打败我的(我们当时经常会开放切磋)。
QQ 飞车这款戏当时还挺火的,很多 90 后的小伙伴应该比较熟悉。
我记得,那时候上网还不要身份证,10 元办一张网卡就行了,网费也是一元一小时。我就经常不吃早饭,攒钱用来上网。只要口袋里有钱,我都会和我的小伙伴奔跑到网吧一起玩 QQ 飞车。青回啊!
说到这,我情不自禁地打开自己的 Windows 电脑,下载了 Wegame ,然后下载了 QQ 飞车。
到了初二的时候,就没玩 QQ 飞车了。我的等级也永久定格在了 120 级,这个等级在当时那个升级难的一匹的年代,算的上非常高的等级了。
初二网瘾爆发
网瘾爆发是在上了初中之后。初二的时候,最为猖狂,自己当时真的是太痴迷于 穿越火线 这款游戏了,比 QQ 飞车还要更痴迷一些。每天上课都在想像自己拿起枪横扫地方阵营的场景,心完全不在学习上。
我经常每天早上起早去玩别人包夜留下的机子,毕竟那时候上学也没什么钱嘛!我几乎每个周五晚上都会趁家人睡着之后,偷偷跑出去通宵。整个初二我通宵了无数次,我的眼睛就是这样近视的。
有网瘾真的很可怕,为了上网什么都敢做。当时我家住在顶楼的隔热层,我每次晚上偷偷出去上网,为了不被家里人发现,要从我的房间的窗户爬出去,穿过几栋楼,经过几间无人居住的顶楼隔热层之后再下楼。现在想想,还是比较危险的。而且,我天生比较怕黑。当时为了上网,每次穿过这么多没人居住的顶层隔热层都没怕过。你让我现在再去,我都不敢,实在是佩服当年的自己的啊!
周五晚上通宵完之后,我会睡到中午,然后下午继续去网吧玩。到了周日,基本都是直接从早上 8 点玩到晚上 9 点 10 点。那时候精力是真旺盛,真的完全不会感觉比较累,反而乐在其中。
我的最终军衔停留在了两个钻石,玩过的小伙伴应该清楚这在当时要玩多少把(现在升级比较简单)。
ps: 回坑 CF 快一年了,目前的军衔是到了两颗星中校 3 了。
那时候成绩挺差的。这样说吧!我当时在很普通的一个县级市的高中,全年级有 500 来人,我基本都是在 280 名左右。而且,整个初二我都没有学物理,上物理课就睡觉,考试就交白卷。
为什么对物理这么抵触呢?这是因为开学不久的一次物理课,物理老师误会我在上课吃东西还狡辩,扇了我一巴掌。那时候心里一直记仇到大学,想着以后自己早晚有时间把这个物理老师暴打一顿。
初三开启学习模式
初三上学期的时候突然觉悟,像是开窍了一样,当时就突然意识到自己马上就要升高中了,要开始好好搞搞学习了。
诶,其实也不算是开窍,主要还是为了让自己能在家附近上学,这样上网容易一些。因为当时我家就在我们当地的二中附近,附近有特别特别多的网吧,上网特别特别容易,加上我又能走读。
像我初中在的那个学校,年级前 80 的话基本才有可能考得上二中。经过努力,初三上学期的第一次月考,我直接从 280 多名进步到了年级 50 多名,有机会考入二中。当时还因为进步太大,被当作 进步之星 在讲台上给整个年级做演讲,分享经验。这也是我第一次在这么多人面前讲话,挺紧张的,但是挺爽的,在暗恋对象面前赚足了面子。
其实在初三的时候,我的网瘾还是很大。不过,我去玩游戏的前提都是自己把所有任务做完,并且上课听讲也相对比较认真的听。
初三那会,我通宵的次数变少了一些,但会经常晚上趁着家人睡觉了,偷偷跑出去玩到凌晨 2 点多回来。
当时,我们当地的高中有一个政策是每个学校的成绩比较优秀的学生可以参加 高中提前招生考试 ,只要考上了就不用参加中考了。我当时也有幸参加了这次考试并成功进入了我们当地的二中。
在我参加高中提前考试前的一个晚上,我半夜 12 点趁着妈妈睡着,跑去了网吧玩 CF 到凌晨 3 点多回来。就那一次我被抓了现行,到家之后发现妈妈就坐在客厅等我,训斥一顿后,我就保证以后不再晚上偷偷跑出去了。
这里要说明一点:我的智商我自己有自知之明的,属于比较普通的水平吧!前进很大的主要原因是自己基础还行,特别是英语和物理。英语是因为自己喜欢,加上小学就学了很多初中的英语课程。物理的话就很奇怪,虽然初二也不怎么听物理课,也不会物理,但是到了初三之后自己就突然开窍了。真的!我现在都感觉很奇怪。然后,到了高中之后,我的英语和物理依然是我最好的两门课。大学的兼职,我出去做家教都是教的高中物理。
高中从小班掉到平行班
由于参加了高中提前招生考试,我提前 4 个月就来到了高中,进入了小班,开始学习高中的课程。
上了高中的之后,我上课就偷偷看小说,神印王座、斗罗大陆、斗破苍穹很多小说都是当时看的。中午和晚上回家之后,就在家里玩几把 DNF。当时家里也买了电脑,姥爷给买的,是对自己顺利进入二中的奖励。到我卸载 DNF 的时候,已经练了 4 个满级的号,两个接近满级的号。
当时我的空间专门有一个相册里面放的全是 DNF 的一些照片和截图,无比痴迷于练级和刷图。
在高中待了不到一个月,我上体育课的时候不小心把腿摔断了,这也是我第一次感受到骨头断裂的头疼,实在是太难受了!
于是,我就开始休学养病。直到高中正式开学一个月之后,我才去上学,也没有参加军训。
由于我耽误了几个月的课程,因此没办法再进入小班,只能转到奥赛班。到了奥赛班之后,我继续把时间和经历都投入在游戏和小说上,于是我的成绩在奥赛班快接近倒数了。等到高二分班的时候,我成功被踢出奥赛班来到了最普通的平行班。
我成功把自己从学校最好的小班玩到奥赛班,然后再到平行班。有点魔幻吧!
高二开始奋起直追
高中觉悟是在高二下学期的时候,当时是真的觉悟了,就突然觉得游戏不香了,觉得 DNF 也不好玩了,什么杀怪打装备不过是虚无,练了再多满级的 DNF 账号也屁用没有,没钱都是浮云。
我妈妈当时还很诧异,还奇怪地问我:“怎么不玩游戏了?”(我妈属于不怎么管我玩游戏的,她觉得这东西还是要靠自觉)。
于是,我便开始牟足劲学习,每天都沉迷学习无法自拔(豪不夸张),乐在其中。虽然晚自习上完回到家已经差不多 11 点了,但也并不感觉累,反而感觉很快乐,很充实。
我的付出也很快得到了回报,我顺利返回了奥赛班。 当时,理科平行班大概有 7 个,每次考试都是平行班之间会单独排一个名次,小班和奥赛班不和我们一起排名次。后面的话,自己基本每次都能在平行班得第一,并且很多时候都是领先第二名 30 来分。由于成绩还算亮眼,高三上学期快结束的时候,我就向年级主任申请去了奥赛班。
高考前的失眠
失败之后,不要抱怨外界因素,自始至终实际都是自己的问题,自己不够强大! 然后,高考前的失眠也是我自己问题,要怪只能怪自己,别的没有任何接口。
我的高考经历其实还蛮坎坷的,毫不夸张的说,高考那今天可能是我到现在为止,经历的最难熬的时候,特别是在晚上。
我在高考那几天晚上都经历了失眠,想睡都睡不着那种痛苦想必很多人或许都体验过。
其实我在之前是从来没有过失眠的经历的。高考前夕,因为害怕自己睡不着,所以,我提前让妈妈去买了几瓶老师推荐的安神补脑液。我到现在还记得这个安神补脑液是敖东牌的。
高考那几天的失眠,我觉得可能和我喝了老师推荐的安神补脑液有关系,又或者是我自己太过于紧张了。因为那几天睡觉总会感觉有很多蚂蚁在身上爬一样,身上还起了一些小痘痘(有点像是过敏)。
这里要格外说明一点,避免引起误导:睡不着本身就是自身的问题,上述言论并没有责怪这个补脑液的意思。 另外, 这款安神补脑液我去各个平台都查了一下,发现大家对他的评价都挺好,和我们老师当时推荐的理由差不多。如果大家需要改善睡眠的话,可以咨询相关医生之后尝试一下。
高考也确实没发挥好,整个人在考场都是懵的状态。高考成绩出来之后,比我自己预估的还低了几十分,最后只上了一个双非一本。不过,好在专业选的好,吃了一些计算机专业的红利,大学期间也挺努力的。
大学生活
大学生活过的还是挺丰富的,我会偶尔通宵敲代码,也会偶尔半夜发疯跑出去和同学一起走走古城墙、去网吧锤一夜的 LOL。
大学生活专门写过一篇文章介绍:害,毕业三年了! 。
总结
整个初中我都属于有点网瘾少年的状态,不过初三的时候稍微克制一些。到了高二下学期的时候,自己才对游戏真的没有那么沉迷了。
对游戏不那么沉迷,也是因为自己意识到游戏终究只是消遣,学习才是当时最重要的事情。而且,我的游戏技术又不厉害,又不能靠游戏吃饭,什么打怪升级到最后不过是电脑中的二进制数据罢了!
这玩意必须你自己意识到,不然,单纯靠父母监督真的很难改变!如果心不在学习上面的话,那同时是不可能学好的!
我真的很反对父母过于干涉孩子的生活,强烈谴责很多父母把自己孩子的网瘾归咎于网络游戏,把自己孩子的暴力归咎于影视媒体。
时刻把自己的孩子保护起来不是一件靠谱的事情,他终究要独自面对越来越多的诱惑。到了大学,很多被父母保护太好的孩子就直接废了。他们没有独立意识,没有抗拒诱惑的定力!
About The Author Javaguide 100k Star (`docs/about-the-author/javaguide-100k-star.md`)
title: JavaGuide 开源项目 100K Star 了!
category: 走近作者
tag:
- 个人经历
2021-03-21,晚上 12 点,肝完了我正在做的一个项目的前端的某块功能,我随手打开了我的 GitHub 主页。
好家伙!几天没注意,JavaGuide 这个项目直接上了 100K star。
其实,这个真没啥好嘚瑟的。因为,教程类的含金量其实是比较低的,Star 数量比较多主要也是因为受众面比较广,大家觉得不错,点个 star 就相当于收藏了。很多特别优秀的框架,star 数量可能只有几 K。所以,单纯看 star 数量没啥意思,就当看个笑话吧!
维护这个项目的过程中,也被某些人 diss 过:“md 项目,没啥含金量,给国人丢脸!”。
对于说这类话的人,我觉得对我没啥影响,就持续完善,把 JavaGuide 做的更好吧!其实,国外的很多项目也是纯 MD 啊!就比如外国的朋友发起的 awesome 系列、求职面试系列。无需多说,行动自证!凎!
开源非常重要的一点就是协作。如果你开源了一个项目之后,就不再维护,别人给你提交 issue/pr,你都不处理,那开源也没啥意义了!
我的公号的小伙伴都是通过这个项目关注我的,趁着午休,简单复盘一下,也算是对关注这个项目的小伙伴负责。
我在大三开始准备秋招面试的时候,创建了 JavaGuide 这个项目,2018-05-07 这一天我提交了第 1 个 commit。
到今天(2021-03-23)为止,这个仓库已经累计有 2933 次 commit,累计有 207 位朋友参与到了项目中来。
累计有 511 个 issue 和 575 个 PR。所有的 PR 都已经被处理,仅有 15 个左右的 issue 我还未抽出时间处理。
其实,相比于 star 数量,你看看仓库的 issue 和 PR 更能说明你的项目是否有价值。
那些到处骗 star 甚至是 刷 star 的行为,我就不多说了,有点丢人。人家觉得你的项目还不错,能提供价值,自然就给你点 star 了。
未来几年,我还是会持续完善 JavaGuide。
希望自己以后能开源一些有价值的轮子吧!继续加油!
About The Author My Article Was Stolen And Made Into Video And It Became Popular (`docs/about-the-author/my-article-was-stolen-and-made-into-video-and-it-became-popular.md`)
title: 某培训机构盗我文章做成视频还上了B站热门
category: 走近作者
tag:
- 杂谈
时间回到 2021-02-25,我在刷哔哩哔哩的时候发现,哔哩哔哩某 UP 主(某培训机构),擅自将我在知乎的一个回答做成了视频。
原滋原味啊!我艹。甚至,连我开头的自我调侃还加上了!真的牛皮!
你盗我原创,视频你用心做好点也行啊!至少也可以让这么优质的内容得到传播嘛!
结果,好家伙,视频做的像坨屎一样,配音也贼违和!
麻烦这个培训机构看到这篇文章之后可以考虑换一个人做类似恶心的事情哈!这人完全没脑子啊!
我随便找了一个视频看,发现也还是盗用别人的原创。
其他的视频就不用多看了,是否还是剽窃别人的原创,原封不动地做成视频,大家心里应该有数。
他们这样做的目的就是一个:引流到自己的 QQ 群,然后忽悠你买课程。
我并不认为是这完全都是培训机构的问题。培训机构的员工为了流量而做这种恶心的事情,也导致了现在这种事情被越来越频繁地发生。
所以,你会发现,哔哩哔哩和知乎上有越来越多培训机构的小号,到处剽窃原创,盗发。
我身边很多原创号主的文章都经常被某些培训机构盗发。
有时候真的会比较生气,毕竟你自己辛辛苦苦的原创,别人复制粘贴一下就白嫖了!
但是,我相信,这种靠剽窃别人原创来吸引流量的行为,终究只是跳梁小丑的行为罢了!
只有那些用心输出内容的创作者,才能走的更远,更安稳!
后来,我在我的公众号上发了一篇名为《好家伙!某培训机构盗我文章做成视频还上了热门》 的文章,吐槽自己的原创被某机构白嫖。
谁能想到,培训机构的人竟然找人来让我删文章了!讲真,这俩人是真的奇葩啊!
还让我格局大点?我去你丫的!明明就是我的原创,你自己不删,反而找人联系我删除!有脑子不?
其实,我这人是比较好说话的,现实生活中脾气也是出了名的好(前提是没有触犯到我的原则的情况)。
搞笑的是!他们在让我删文的同时,他们 B 站盗发的视频还都在,还在继续为他们引流。
如果他们把账号注销了,我或许还能考虑放一手。但是,文章是肯定不会删的。
现在,看后续情况吧!我随时可以动用法律来维护自己的权益,只是看我想不想,毕竟也挺麻烦对吧!
大家不用担心,这都是小事,我女朋友就是学法律的,国内的某法学双一流学校。
咱不怕事!凎!!!
About The Author My College Life (`docs/about-the-author/my-college-life.md`)
title: 害,毕业三年了!
category: 走近作者
star: 1
tag:
- 个人经历
关于初高中的生活,可以看 2020 年我写的 我曾经也是网瘾少年 这篇文章。
2019 年 6 月份毕业,距今已经过去了 3 年。趁着高考以及应届生毕业之际,简单聊聊自己的大学生活。
下面是正文。
我本科毕业于荆州校区的长江大学,一所不起眼的双非一本。
在这里度过的四年大学生活还是过的挺开心的,直到现在,我依然非常怀念!
在学校的这几年的生活,总体来说,还算是比较丰富多彩的。我会偶尔通宵敲代码,也会偶尔半夜发疯跑出去和同学一起走走古城墙、去网吧锤一夜的 LOL。
写下这篇杂文,记录自己逝去的大学生活!希望未来继续砥砺前行,不忘初心!
大一
大一那会,我没有把精力放在学习编程上,大部分时间都在参加课外活动。
或许是因为来到了一座新鲜的城市,对周围的一切都充满了兴趣。又或许是因为当时的我还比较懵懂,也没有任何学习方向。
这一年,我和班里的一群新同学去逛了荆州的很多地方比如荆州博物馆、长江大桥、张居正故居、关帝庙。
即使如此,我当时还是对未来充满了希望,憧憬着工作之后的生活。
我还记得当时我们 6 个室友那会一起聊天的时候,其他 5 个室友都觉得说未来找工作能找一个 6k 的就很不错了。我当时就说:“怎么得至少也要 8k 吧!”。他们无言,觉得我的想法太天真。
其实,我当时内心想的是至少是月薪 1w 起步,只是不太好意思直接说出来。
我不爱出风头,性格有点内向。刚上大学那会,内心还是有一点不自信,干什么事情都畏畏缩缩,还是迫切希望改变自己的!
于是,凭借着一腔热血,我尝试了很多我之前从未尝试过的事情:露营、户外烧烤、公交车演讲、环跑古城墙、徒步旅行、异地求生、圣诞节卖苹果、元旦晚会演出...。
下面这些都是我和社团的小伙伴利用课外时间自己做的,在圣诞节那周基本都卖完了。我记得,为了能够多卖一些,我们还挨个去每一个寝室推销了一遍。
我还参加了大一元旦晚会,不过,那次演出我还是没放开,说实话,感觉没有表现出应该有的那味。
经过这次演出之后,我发现我是真的没有表演的天赋,很僵硬。并且,这种僵硬呆板是自己付出努力之后也没办法改变的。
下图是某一次社团聚餐,我喝的有点小醉之后,被朋友拍下的。
那时候,还经常和和社团的几位小伙伴一起去夜走荆州古城墙。
不知道社团的大家现在过得怎么样呢?
虽然这些经历对于我未来的工作和发展其实没有任何帮助,但却让我的大学生活更加完整,经历了更多有趣的事情,有了更多可以回忆的经历。
我的室友们都窝在寝室玩游戏、玩手机的时候,我很庆幸自己做了这些事情。
个人感觉,大一的时候参加一些不错的社团活动,认识一些志同道合的朋友还是很不错的!
参加课外活动之余,CS 专业的小伙伴,尽量早一点养成一个好的编程习惯,学好一门编程语言,然后平时没事就刷刷算法题。
办补习班
大一暑假的时候,我作为负责人,在孝感的小乡镇上办过 5 个补习班(本来是 7 个,后来砍掉了 2 个) 。
从租房子、租借桌椅再到招生基本都是从零开始做的。
每个周末我都会从荆州坐车跑到孝感,在各个县城之间来回跑。绝大部分时候,只有我一个人,偶尔也会有几个社团的小伙伴陪我一起。
记忆犹新,那一年孝感也是闹洪水,还挺严重的。
有一次我差点回不去学校参加期末考试。虽然没有备考,但是也没有挂过任何一门课,甚至很多科目考的还不错。不过,这还是对我绩点产生了比较大的影响,导致我后面没有机会拿到奖学金。
这次比较赶时间,所以就坐的是火车回学校。在火车上竟然还和别人撞箱子了!
当时去小乡镇上的时候,自己最差的时候住过 15 元的旅馆。真的是 15 元,你没看错。就那种老旧民房的小破屋,没有独卫,床上用品也很不卫生,还不能洗澡。
下面这个还是我住过最豪华的一个,因为当时坐客车去了孝感之后,突然下大雨,我就在车站附近找了一个相对便宜点的。
为了以更低的价钱租到房子,我经常和房东砍价砍的面红耳赤。
说句心里话,这些都是我不太愿意去做的事情,我本身属于比较爱面子而且不那么自信的人。
当时,我需要在各个乡镇来回跑,每天就直接顶着太阳晒 。每次吃饭都特别香,随便炒个蔬菜都能吃几碗米饭。
我本身是比较挑食的,这次经历让我真正体会到人饿了之后吃嘛嘛香!
我一个人给 6 个老师加上 10 来个学生和房东们一家做了一个多月的饭,我的厨艺也因此得到了很大的锻炼。
这些学生有小学的,也有初中的,都比较听话。有很多还是留守儿童,爸爸妈妈在外打工,跟着爷爷奶奶一起生活。
加上我的话,我们一共有 4 位老师,我主要讲的是初中和高中的物理课。
学生们都挺听话,没有出现和我们几个老师闹过矛盾。只有两个调皮的小学生被我训斥之后,怀恨在心,写下了一些让我忍俊不禁的话!哈哈哈哈!太可爱了!
离开之前的前一天的晚上,我和老师们商量请一些近点的同学们来吃饭。我们一大早就出去买菜了,下图是做成后的成品。虽然是比较简单的一顿饭,但我们吃的特别香。
那天晚上还有几个家长专门跑过来看我做饭,家长们说他们的孩子非常喜欢我做的饭,哈哈哈!我表面淡然说自己做的不好,实则内心暗暗自喜,就很“闷骚”的一个人,哈哈哈!
不知道这些学生们,现在怎么样呢?怀念啊!
培训班结束,我回家之后,我爸妈都以为我是逃荒回来的。
自己赚钱去孤儿院
大一尾声的时候,还做了一件非常有意义的事情。我和我的朋友们去了一次孤儿院(荆州私立孤儿教养院)。这个孤儿院曾经还被多家电视台报道过,目前也被百度百科收录。
孤儿院的孩子们,大多是一些无父无母或者本身有一些疾病被父母遗弃的孩子。
去之前,我们买了很多小孩子的玩具、文具、零食这些东西。这些钱的来源也比较有意义,都是我和社团的一些小伙伴自己去外面兼职赚的一些钱。
勿以善小而不为!引用《爱的风险》这首歌的一句歌词:“只要人人都献出一点爱,世界将变成美好的人间” 。
我想看看这个孤儿院的现状,于是在网上有搜了一下,看到了去年 1 月份荆州新闻网的一份报道。
孤儿教养院创办 33 年来,累计收养孤儿 85 人,其中有 5 人参军入伍报效祖国,20 人上大学,有的早已参加工作并成家立业。
叔叔也慢慢老了,白发越来越多。有点心酸,想哭,希望有机会再回去看看您!一定会的!
徒步旅行
大一那会还有一件让我印象非常深刻的事情——徒步旅行。
我和一群社团的小伙伴,徒步走了接近 45 公里。我们从学校的西校区,徒步走到了枝江那边的一个沙滩。
是真的全程步行,这还是我第一次走这么远。
走到目的地的时候,我的双腿已经不听使唤,脚底被磨了很多水泡。
我们在沙滩上露营,烧烤,唱歌跳舞,一直到第二天早上才踏上回学校的路程。
大二
到了大二,我开始把自己的重点转移到编程知识的学习上。
不过,我遇到一个让我比较纠结的问题:社团里玩的最好的几个朋友为了能让社团能继续延续下去,希望我和他们一起来继续带这个团队。
但是,我当时已经规划好了自己大二要做的事情,真的想把精力都放在编程学习上,想要好好沉淀一下自己的技术。
迫于无奈,我最终还是妥协,选择了和朋友一起带社团。毕竟,遇到几个真心的朋友属实不易!
带社团
带社团确实需要花费很多业余时间,除了每周要从东校区打车到西校区带着他们跑步之外,我们还需要经常带着他们组织一些活动。
比如我们一起去了长江边上烧烤露营。
再比如我们一起去环跑了古城墙。
大学那会,我还是非常热爱运动的!
大二那会,我就已经环跑了 3 次古城墙。
加入长大在线
在大二的时候,我还加入了学校党委宣传部下的组织——长大在线。这是一个比较偏技术性质的组织,主要负责帮学校做做网站、APP 啥的。
在百度上,还能搜索到长大在线的词条。
莫名其妙还被发了一个记者证,哈哈哈!
我选的是安卓组,然后我就开始了学习安卓开发的旅程。
刚加入这个组织的时候,我连 HTML、CSS、JS、Java、Linux 这些名词都不知道啥意思。
再到后面,我留下来当了副站长,继续为组织服务了大半年多。
第一次参加比赛
那会也比较喜欢去参加一些学校的比赛,也获得过一些不错的名次,让我印象最深的是一次 PPT 大赛,这也是我第一次参加学校的比赛。
参加比赛之前,自己也是一个 PPT 小白,苦心学了一周多之后,我的一个作品竟然顺利获得了第一名。
也正是因为这次比赛,我免费拥有了自己的第一个机械键盘,这个键盘陪我度过了后面的大学生活。
确定技术方向
在大二上学期末,我最终确定了自己以后要走的技术方向是走 Java 后端。于是,我就开始制定学习计划,开始了自己的 Java 后端领域的打怪升级之路。
每次忙到很晚,一个人走在校园的时候还是很爽的!非常喜欢这种安静的感觉。
当时身体素质真好,熬夜之后第二天照常起来上课学习。现在熬个夜,后面两天直接就废了!
到了大三,我基本把 Java 后端领域一些必备的技术都给过了一遍,还用自己学的东西做了两个实战项目。
由于缺少正确的人指导,我当时学的时候也走了很多弯路,浪费了不少时间(我很羡慕大家能有我,就很厚脸皮!)。
那个时候还贼自恋,没事就喜欢自拍一张。
国庆节的时候也不回家,继续在学校刷 Java 视频和书籍。
我记得那次国庆节的时候效率还是非常高的,学习起来也特别有动力。
大三
整个大三,我依然没有周末,基本没有什么娱乐时间。绝大部分时间都是一个人在寝室默默学习,平时偶尔也会去图书馆和办公室。
虽然室友经常会玩游戏和看剧什么的,但是我对我并没有什么影响。一个人戴上耳机之后,世界仿佛都是自己的。
和很多大佬可能不太一样,比起图书馆和办公室,我在寝室的学习效率更高一些。
JavaGuide 诞生
我的开源项目 JavaGuide 和公众号都是这一年启动的。
目前的话,JavaGuide 也已经 100k star ,我的公众号也已经有 15w+ 的关注。
接私活赚钱
一些机遇也让我这一年也接了一些私活赚钱。为了能够顺利交付,偶尔也会熬夜。当时的心态是即使熬夜也还是很开心、充实。每次想到自己通过技术赚到了钱,就会非常有动力。
我也曾写过文章分享过接私活的经历:唠唠嗑!大学那会接私活赚了 3w+ 。
不过,我接的几个私活也是比较杂的,并不太适合作为简历上的项目经历。
于是,为了能让简历上的项目经历看着更好看一些,我自己也找了两个项目做。一个是我跟着视频一起做的,是一个商城类型的项目。另外一个是自己根据自己的想法做的,是一个视频网站类型的项目。
商城类型的项目大概的架构图如下(没有找到当时自己画的原图):
那会商城项目貌似也已经烂大街了,用的人比较多。为了让自己的商城项目更有竞争力,对照着视频教程做完之后,我加入了很多自己的元素比如更换消息队列 ActiveMQ 为 Kafka、增加二级缓存。
在暑假的时候,还和同学老师一起做了一个员工绩效管理的企业真实项目。这个项目和我刚进公司做的项目,非常非常相似,不过公司做得可能更高级点 ,代码质量也要更高一些。实在是太巧了!
我记得当时自己独立做项目的时候,遇到了很多问题。就很多时候,你看书很容易就明白的东西,等到你实践的时候,总是会遇到一些小问题。我一般都是通过 Google 搜索解决的,用好搜索引擎真的能解决自己 99% 的问题。
参加软件设计大赛
大三这一年也有遗憾吧!我和几位志同道合的朋友一起参加过一个软件设计大赛,我们花了接近两个月做的系统顺利进入了复赛。
不过,我后面因为自己个人觉得再花时间做这个系统学不到什么东西还浪费时间就直接退出了。然后,整个团队就散了。
其实,先来回头看也是可以学到东西的,自己当时的心态有点飘了吧,心态有一些好高骛远。
现在想来,还是挺对不起那些一起奋斗到深夜的小伙伴。
人生就是这样,一生很长,任何时候你回头看过去的自己,肯定都会有让自己后悔的事情。
放弃读研
当时,我也有纠结过是否读研,毕竟学校确实一般,读个研确实能够镀点金,提升一下学历。
不过,我最终还是放弃了读研。当时比较自信,心里就觉得自己不需要读研也能够找到好工作。
实习
大三还找了一家离学校不远的公司实习,一位老学长创办的。不过,说实话哈,总体实习体验很差,没有学到什么东西不说,还耽误了自己很多已经计划好的事情。
我记得当时这个公司很多项目还是在用 JSP,用的技术很老。如果是老项目还好,我看几个月前启动的项目也还是用的 JSP,就很离谱。。。
当时真的很难受,而且一来就想着让你上手干活,活还贼多,干不完还想让你免费加班。。。
当时也没办法,因为荆州实在是找不到其他公司可以让你实习,你又没办法跑到其他城市去实习。这也是放弃选择一二线城市的学校带来的问题吧!
大四
开始找工作
找实习找工作时候,才知道大学所在的城市的重要性。
由于,我的学校在荆州,而且本身学校就很一般,因此,基本没有什么比较好的企业来招人。
当时,唯一一个还算可以的就是苏宁,不过,我遇到的那个苏宁的 HR 还挺恶心的,第一轮面试的时候就开始压薪资了,问我能不能加班。然后,我也就对苏宁没有了想法。
秋招我犯了一个比较严重的问题,那就是投递简历开始的太晚。我是把学校的项目差不多做完之后,才开始在网上投递简历。这个时候,暑假差不多已经结束了,秋招基本已经尾声了。
可能也和学校环境有一些关系,当时,身边的同学没有参加秋招的。大三暑假的时候,都跑去搞学院组织的实习。我是留在学校做项目,没有去参加那次实习。
我觉得学校还是非常有必要提醒学生们把握住秋招这次不错的机会的!
在网上投递了一些简历之后,很多笔试我觉得做的还可以的都没有回应。
我有点慌了!于是,我就从荆州来到武汉,想在武大华科这些不错的学校参加一些宣讲会。
到了武汉之后,我花了一天时间找了一个蛋壳公寓住下。第二天,我就跑去武汉理工大学参加宣讲会。
当天,我就面试了自己求职过程中的第一家公司—玄武科技。
就是这样一家中小型的公司,当时来求职面试的很多都是武大华科的学生。不过,他们之中一定有很多人和我一样,就是单纯来刷一波经验,找找信心。
整个过程也就持续了 3 天左右,我就顺利的拿下了玄武科技的 offer。不过,最终没有签约。
拿到 Offer
来武汉之前,我实际上已经在网上投递了 ThoughtWorks,并且,作业也已经通过了。
当时,我对 ThoughtWorks 是最有好感的,内心的想法就是:“拿下了 ThoughtWorks,就不再面试其他公司了”。
奈何 ThoughtWorks 的进度太慢,担心之余,才来武汉面试其他公司留个保底。
不过,我最终如愿以偿获得了 ThoughtWorks 的 offer。
面试 ThoughtWorks 的过程就不多说了,我在《结束了我短暂的秋招,说点自己的感受》这篇文章中有提到。
几点建议
说几点自己的建议,虽然我不优秀,但毕竟你可以更优秀:
- 确定好自己的方向,搞清你是要考研还是要找工作。如果你要考研的话,好好上每一门可能是考研的科目,平时有时间也要敲代码,最好也能做一个项目,对你复试还有能力提升都有帮助。找工作的话,尽早确定好自己的方向,心里有一个规划,搞清自己的优势和劣势。
- 尽可能早一点以求职为导向来学习,这样更有针对性,并且可以大概率减己处在迷茫的时间,很大程度上还可以让自己少走很多弯路。
- 自学很重要,养成自学的习惯,学会学习。
- 不要觉得逃课就是坏学生。我大学逃了很多课,逃课的大部分时间都是在学自己觉得更重要的东西,逃的大部分也是不那么重要并且不会影响我毕业的课。
- 大学恋爱还是相对来说很纯粹的,遇到合适的可以尝试去了解一下, 别人不喜欢你的话不要死缠烂打,这种东西强求不来。你不得不承认,你了解一个人欲望还是始于他的长相而并不是有趣的灵魂。
- 管理自己的身材,没事去跑跑步,别当油腻男。
- 别太看重绩点。我觉得绩点对于找工作还有考研实际的作用都可以忽略不计,不过不挂科还是比较重要的。但是,绩点确实在奖学金评选和保研名额选取上占有最大的分量。
- 别太功利性。做事情以及学习知识都不要奢求它能立马带给你什么,坚持和功利往往是成反比的。
- ……
后记
我们在找工作的过程中难免会遇到卡学历的情况,特别是我们这种学校本身就比较一般的。我觉得这真的不可厚非,没有什么不公平,要怪就只能怪自己没有考上好的学校。
考虑到招聘成本和时间,公司一定更愿意在学校本身比较好的人中选拔人才。
我也曾抱怨过自己为什么不在 211 或者 985 的学校。但,其实静下心来想一想,本来考不上 211 或者 985 就是自己的问题,而且在我们计算机这个领域,学历本身就相对于其他专业稍微要更加公平一点。
我身边专科、三本毕业就进大厂的人也比比皆是。我这句话真不是鸡汤,为了鼓励一些学校出身不太好的朋友。
多行动,少抱怨。
About The Author Writing Technology Blog Six Years (`docs/about-the-author/writing-technology-blog-six-years.md`)
title: 坚持写技术博客六年了!
category: 走近作者
tag:
- 杂谈
坚持写技术博客已经有六年了,也算是一个小小的里程碑了。
一开始,我写技术博客就是简单地总结自己课堂上学习的课程比如网络、操作系统。渐渐地,我开始撰写一些更为系统化的知识点详解和面试常见问题总结。
许多人都想写技术博客,但却不清楚这对他们有何好处。有些人开始写技术博客,却不知道如何坚持下去,也不知道该写些什么。这篇文章我会认真聊聊我对记录技术博客的一些看法和心得,或许可以帮助你解决这些问题。
写技术博客有哪些好处?
学习效果更好,加深知识点的认识
费曼学习法 大家应该已经比较清楚了,这是一个经过实践证明非常有效的学习方式。费曼学习法的命名源自 Richard Feynman,这位物理学家曾获得过诺贝尔物理学奖,也曾参与过曼哈顿计划。
所谓费曼学习法,就是当你学习了一个新知识之后,想象自己是一个老师:用最简单、最浅显直白的话复述、表达复杂深奥的知识,最好不要使用行业术语,让非行业内的人也能听懂。为了达到这种效果,最好想象你是在给一个 80 多岁或 8 岁的小孩子上课,甚至他们都能听懂。
看书、看视频这类都属于是被动学习,学习效果比较差。费曼学习方法属于主动学习,学习效果非常好。
写技术博客实际就是教别人的一种方式。 不过,记录技术博客的时候是可以有专业术语(除非你的文章群体是非技术人员),只是你需要用自己的话表述出来,尽量让别人一看就懂。切忌照搬书籍或者直接复制粘贴其他人的总结!
如果我们被动的学习某个知识点,可能大部分时候都是仅仅满足自己能够会用的层面,你并不会深究其原理,甚至很多关键概念都没搞懂。
如果你是要将你所学到的知识总结成一篇博客的话,一定会加深你对这个知识点的思考。很多时候,你为了将一个知识点讲清楚,你回去查阅很多资料,甚至需要查看很多源码,这些细小的积累在潜移默化中加深了你对这个知识点的认识。
甚至,我还经常会遇到这种情况:写博客的过程中,自己突然意识到自己对于某个知识点的理解存在错误。
写博客本身就是一个对自己学习到的知识进行总结、回顾、思考的过程。记录博客也是对于自己学习历程的一种记录。随着时间的流逝、年龄的增长,这又何尝不是一笔宝贵的精神财富呢?
知识星球的一位球友还提到写技术博客有助于完善自己的知识体系:
帮助别人的同时获得成就感
就像我们程序员希望自己的产品能够得到大家的认可和喜欢一样。我们写技术博客在某一方面当然也是为了能够得到别人的认可。
当你写的东西对别人产生帮助的时候,你会产生成就感和幸福感。
这种成就感和幸福感会作为 正向反馈 ,继续激励你写博客。
但是,即使受到很多读者的赞赏,也要保持谦虚学习的太多。人外有人,比你技术更厉害的读者多了去,一定要虚心学习!
当然,你可以可能会受到很多非议。可能会有很多人说你写的文章没有深度,还可能会有很多人说你闲的蛋疼,你写的东西网上/书上都有。
坦然对待这些非议,做好自己,走好自己的路就好!用行动自证!
可能会有额外的收入
写博客可能还会为你带来经济收入。输出价值的同时,还能够有合理的经济收入,这是最好的状态!
为什么说是可能呢? 因为就目前来看,大部分人还是很难短期通过写博客有收入。我也不建议大家一开始写博客就奔着赚钱的目的,这样功利性太强了,效果可能反而不好。就比如说你坚持了写了半年发现赚不到钱,那你可能就会坚持不下去了。
我自己从大二开始写博客,大三下学期开始将自己的文章发布到公众号上,一直到大四下学期,才通过写博客赚到属于自己的第一笔钱。
第一笔钱是通过微信公众号接某培训机构的推广获得的。没记错的话,当时通过这个推广为自己带来了大约 500 元的收入。虽然这不是很多,但对于还在上大学的我来说,这笔钱非常宝贵。那时我才知道,原来写作真的可以赚钱,这也让我更有动力去分享自己的写作。可惜的是,在接了两次这家培训机构的广告之后,它就倒闭了。
之后,很长一段时间我都没有接到过广告。直到网易的课程合作找上门,一篇文章 1000 元,每个月接近一篇,发了接近两年,这也算是我在大学期间比较稳定的一份收入来源了。
老粉应该大部分都是通过 JavaGuide 这个项目认识我的,这是我在大三开始准备秋招面试时创建的一个项目。没想到这个项目竟然火了一把,一度霸占了 GitHub 榜单。可能当时国内这类开源文档教程类项目太少了,所以这个项目受欢迎程度非常高。
项目火了之后,有一个国内比较大的云服务公司找到我,说是要赞助 JavaGuide 这个项目。我既惊又喜,担心别人是骗子,反复确认合同之后,最终确定以每月 1000 元的费用在我的项目首页加上对方公司的 banner。
随着时间的推移,以及自己后来写了一些比较受欢迎、比较受众的文章,我的博客知名度也有所提升,通过写博客的收入也增加了不少。
增加个人影响力
写技术博客是一种展示自己技术水平和经验的方式,能够让更多的人了解你的专业领域知识和技能。持续分享优质的技术文章,一定能够在技术领域增加个人影响力,这一点是毋庸置疑的。
有了个人影响力之后,不论是对你后面找工作,还是搞付费知识分享或者出书,都非常有帮助。
拿我自己来说,已经很多知名出版社的编辑找过我,协商出一本的书的事情。这种机会应该也是很多人梦寐以求的。不过,我都一一拒绝了,因为觉得自己远远没有达到能够写书的水平。
其实不出书最主要的原因还是自己嫌麻烦,整个流程的事情太多了。我自己又是比较佛系随性的人,平时也不想把时间都留给工作。
怎样才能坚持写技术博客?
不可否认,人都是有懒性的,这是人的本性。我们需要一个目标/动力来 Push 一下自己。
就技术写作而言,你的目标可以以技术文章的数量为标准,比如:
- 一年写多少篇技术文章。我个人觉得一年的范围还是太长了,不太容易定一个比较合适的目标。
- 每月输出一篇高质量的技术文章。这个相对容易实现一些,每月一篇,一年也有十二篇了,也很不错了。
不过,以技术文章的数量为目标有点功利化,文章的质量同样很重要。一篇高质量的技术文可能需要花费一周甚至半个月的业余时间才能写完。一定要避免自己刻意追求数量,而忽略质量,迷失技术写作的本心。
我个人给自己定的目标是:每个月至少写一篇原创技术文章或者认真修改完善过去写的三篇技术文章 (像开源项目推荐、开源项目学习、个人经验分享、面经分享等等类型的文章不会被记入)。
我的目标对我来说比较容易完成,因此不会出现为了完成目标而应付任务的情况。在我状态比较好,工作也不是很忙的时候,还会经常超额完成任务。下图是我今年 3 月份完成的任务(任务管理工具:Microsoft To-Do)。除了 gossip 协议是去年写的之外,其他都是 3 月份完成的。
如果觉得以文章数量为标准过于功利的话,也可以比较随性地按照自己的节奏来写作。不过,一般这种情况下,你很可能过段时间就忘了还有这件事,开始慢慢抵触写博客。
写完一篇技术文章之后,我们不光要同步到自己的博客,还要分发到国内一些常见的技术社区比如博客园、掘金。分发到其他平台的原因是获得关注进而收获正向反馈(动力来源之一)与建议,这是技术写作能坚持下去的非常重要的一步,一定要重视!!!
说实话,当你写完一篇自认为还不错的文章的幸福感和成就感还是有的。但是,让自己去做这件事情还是比较痛苦的。 就好比你让自己出去玩很简单,为了达到这个目的,你可以有各种借口。但是,想要自己老老实实学习,还是需要某个外力来督促自己的。
写哪些方向的博客比较好?
通常来说,写下面这些方向的博客会比较好:
- 详细讲解某个知识点:一定要有自己的思考而不是东拼西凑。不仅要介绍知识点的基本概念和原理,还需要适当结合实际案例和应用场景进行举例说明。
- 问题排查/性能优化经历:需要详细描述清楚具体的场景以及解决办法。一定要有足够的细节描述,包括出现问题的具体场景、问题的根本原因、解决问题的思路和具体步骤等等。同时,要注重实践性和可操作性,帮助读者更好地学习理解。
- 源码阅读记录:从一个功能点出发描述其底层源码实现,谈谈你从源码中学到了什么。
最重要的是一定要重视 Markdown 规范,不然内容再好也会显得不专业。
详见 Markdown 规范 (很重要,尽量按照规范来,对你工作中写文档会非常有帮助)
有没有什么写作技巧分享?
句子不要过长
句子不要过长,尽量使用短句(但也不要太短),这样读者更容易阅读和理解。
尽量让文章更加生动有趣
尽量让文章更加生动有趣,比如你可以适当举一些形象的例子、用一些有趣的段子、歇后语或者网络热词。
不过,这个也主要看你的文章风格。
使用简单明了的语言
避免使用阅读者可能无法理解的行话或复杂语言。
注重清晰度和说服力,保持简单。简单的写作是有说服力的,一个五句话的好论点会比一百句话的精彩论点更能打动人。为什么格言、箴言这类文字容易让人接受,与简洁、直白也有些关系。
使用视觉效果
图表、图像等视觉效果可以让朴素的文本内容更容易理解。记得在适当的地方使用视觉效果来增强你的文章的表现力。
技术文章配图色彩要鲜明
下面是同样内容的两张图,都是通过 drawio 画的,小伙伴们更喜欢哪一张呢?
我相信大部分小伙伴都会选择后面一个色彩更鲜明的!
色彩的调整不过花费了我不到 30s 的时间,带来的阅读体验的上升却是非常之大!
确定你的读者
写作之前,思考一下你的文章的主要受众全体是谁。受众群体确定之后,你可以根据受众的需求和理解水平调整你的写作风格和内容难易程度。
审查和修改
在发表之前一定要审查和修改你的文章。这将帮助你发现错误、澄清任何令人困惑的信息并提高文档的整体质量。
好文是改出来的,切记!!!
总结
总的来说,写技术博客是一件利己利彼的事情。你可能会从中收获到很多东西,你写的东西也可能对别人也有很大的帮助。但是,写技术博客还是比较耗费自己时间的,你需要和工作以及生活做好权衡。
About The Author Zhishixingqiu Two Years (`docs/about-the-author/zhishixingqiu-two-years.md`)
title: JavaGuide 知识星球介绍:后端 + AI 应用开发、简历修改与实战项目
category: 知识星球
star: 2
head:
- meta
- name: keywords
content: JavaGuide知识星球,Java知识星球,后端学习,Java面试资料,Java面试指北,Java后端面试,AI应用开发,大模型应用开发,Spring AI,RAG,Agent,MCP,简历修改,简历优化,一对一答疑,Java实战项目,后端实战项目,大模型实战项目,AI面试项目,JavaGuide星球
JavaGuide 知识星球是我长期维护的 后端 + AI 应用开发与求职成长社群,主要面向正在准备校招、社招、转行和技术进阶的同学,也适合想从传统后端延伸到 AI 应用开发的开发者。
后端仍然是星球的基本盘。Java 基础、数据库、Spring、Redis、消息队列、分布式、高可用、系统设计和面试准备,这些内容会继续更新。AI 应用开发是目前重点投入的方向,内容会覆盖大模型 API、Prompt 与上下文工程、RAG、Agent、MCP、评测和 AI 系统设计,并通过实战项目讲清楚如何把大模型能力接入后端业务。
星球里的 AI 内容侧重应用开发和工程落地,不以模型训练、算法研究和论文复现为主。对于 Java 后端开发者来说,原来积累的数据库、缓存、队列、鉴权、异步任务和稳定性治理经验,在 AI 应用里依然用得上。
如果你正在找系统的后端面试资料、想优化简历、需要一个能写进简历的后端或 AI 实战项目,或者希望有人结合你的情况给出具体建议,这篇文章会完整介绍 JavaGuide 知识星球能提供什么、适合哪些人、为什么值得加入。
在 2019 年 12 月 29 号,经过了大概一年左右的犹豫期,我正式确定要开始做一个自己的星球,帮助学习 Java 和准备 Java 面试的同学。一转眼,已经六年了。感谢大家一路陪伴,我会信守承诺,继续认真维护这个以后端为基本盘、持续补充 AI 应用开发内容的知识星球,不让信任我的读者失望。
我是比较早一批做星球的技术号主,也是坚持做下来的那一少部人(大部分博主割一波韭菜就不维护星球了)。最开始的一两年,纯粹靠爱发电。当初定价非常低(一顿饭钱),加上刚工作的时候比较忙,提供的服务也没有现在这么多。
慢慢的价格提上来,星球的收入确实慢慢也上来了。不过,考虑到我的受众主要是学生,定价依然比同类星球低很多。另外,我也没有弄训练营的打算,虽然训练营对于我这个流量来说可以赚到更多钱。
我有自己的原则,不割韭菜,用心做内容,真心希望帮助到他人!
我的知识星球评价如何?
知识星球是一个私密、长期的知识社群,用来连接创作者和铁杆读者。相比微信群,它更适合沉淀内容、做系统化的学习和信息管理。
下面是今年收到了部分好评,每一条都是真实存在的。我看到很多培训班或者机构通过虚构一些不存在的好评来欺骗他人购买高价服务(行业内非常常见),真的很难理解。
在这里,不只有理论,更有具体、可落地的求职/转行指导:
- 有球友入球后,在多次一对一建议下,很快就收到了美国大模型应用开发的面试并通过;
- 有球友在指导下顺利转行,拿到满意的中厂 Offer。
不少球友评价我是“良心博主”:深夜 11 点多还在帮忙改简历、给建议;对非科班、大龄转行等焦虑问题,也会耐心一一解答,做到有问必回。
口碑是最好的证明!这里有连续续费三年的老球友,也有因为信任而把星球推荐给弟弟妹妹的朋友。
下面是部分球友今年的求职战绩分享(只是一小部分,有校招,也有社招),同样完全真实。每年面试季之后,星球就有大量的球友询问 offer 如何选择。
我的知识星球能为你提供什么?
致力于打造最优质的 Java 面试交流星球(后端面试通用)!加入我们,你将获得远超票价的一站式成长服务:
💎 核心面试求职服务
- 简历深度精修:提供免费的一对一简历修改服务(已累计帮助 9000+ 位球友,好评如潮)。
- 6 大精品专栏:永久阅读权限,内容涵盖高频面试题、源码解析、实战项目,构建完整知识体系。
- 独家面试手册:多本原创 PDF 后端面试手册免费领取,全网独家。
- 有问必答:一对一免费提问,提供专属求职指南,拒绝焦虑。
🚀 实战项目
星球已经推出的实战项目如下:
- ⭐AI 智能面试辅助平台 + RAG 知识库:基于 Spring Boot 4.0 + Java 21 + Spring AI 2.0 开发。非常适合作为学习和简历项目,学习门槛低,帮助提升求职竞争力,是主打就业的实战项目。
- 手写 RPC 框架:从零开始基于 Netty+Kyro+Zookeeper 实现一个简易的 RPC 框架。麻雀虽小五脏俱全,项目代码注释详细,结构清晰。
今年陆续还会推出更多企业级实战案例(预告一下,下一个是大家期待的:企业智能客服)!
🔥 氛围与福利
- 海量资源:Java 优质面试资源持续更新分享。
- 抱团成长:打卡活动、读书交流、线下聚会,让学习之路不再孤单。
- 惊喜福利:不定期节日抽奖、送书送课,福利拿到手软。
💡 总结:这里的任何一项服务(尤其是简历修改和面试资料),单独拎出来的价值都已远超星球门票。
目前星球正在做活动,两本书的价格,就能让你拥有上万培训班的服务!
这里再提供一张 30元的优惠卷(价格马上上调,老用户扫码续费半价 ):
专属专栏
星球更新了 《Java 面试指北》、《Java 必读源码系列》(目前已经整理了 Dubbo 2.6.x、Netty 4.x、SpringBoot2.1 的源码)、 《从零开始写一个 RPC 框架》(已更新完)、《Kafka 常见面试题/知识点总结》 等多个优质专栏。
《Java 面试指北》内容概览:
进入星球之后,这些专栏即可免费永久阅读,永久同步更新!
实战项目
星球已经推出的实战项目如下:
- ⭐AI 智能面试辅助平台 + RAG 知识库:基于 Spring Boot 4.0 + Java 21 + Spring AI 2.0 开发。非常适合作为学习和简历项目,学习门槛低,帮助提升求职竞争力,是主打就业的实战项目。
- 手写 RPC 框架:从零开始基于 Netty+Kyro+Zookeeper 实现一个简易的 RPC 框架。麻雀虽小五脏俱全,项目代码注释详细,结构清晰。
今年陆续还会推出更多企业级实战案例!并且,星球还分享了很多高频项目经历的优化版介绍和面试准备(持续更新中)。
PDF 面试手册
进入星球就免费赠送多本优质 PDF 面试手册。
优质精华主题沉淀
星球沉淀了几年的优质精华主题,内容涵盖面经、面试题、工具网站、技术资源、程序员进阶攻略等内容,干货非常多。
并且,每个月都会整理出当月优质的主题,方便大家阅读学习,避免错过优质的内容。毫不夸张,单纯这些优质主题就足够门票价值了。
加入星球之后,一定要记得抽时间把星球精华主题看看,相信你一定会有所收货!
JavaGuide 知识星球优质主题汇总传送门:https://www.yuque.com/snailclimb/rpkqw1/ncxpnfmlng08wlf1(为了避免这里成为知识杂货铺,我会对严格筛选入选的优质主题)。
简历修改
一到面试季,我平均一天晚上至少要看 15 ~30 份简历。过了面试季的话,找我看简历的话会稍微少一些。要不然的话,是真心顶不住!
简单统计了一下,到目前为止,我至少帮助 9000+ 位球友提供了免费的简历修改服务。
我会针对每一份简历给出详细的修改完善建议,用心修改,深受好评!
一对一提问
加入即可解锁 1 V 1 免费提问权益,拒绝敷衍和套话,我会结合你的实际情况,给出最真诚、最落地的建议。
数据是最好的证明:截至目前,我已在星球内累计深度答疑 10000+ 次,微信私聊帮助球友 5000+ 人。无论是技术瓶颈还是职场迷茫,我都愿做你破局路上的引路人。
下面是今年做的一小部分答疑,感受一下:
我没法保证每个问题都能像上面这样写一长段,这也会取决于你的提问本身。但我可以承诺的是:我会认真看完每一个问题,尽我所能帮你少走弯路、少花冤枉钱。
光是这项一对一答疑服务,其实就已经远远值回星球的门票价了。
学习打卡
星球的学习打卡活动可以督促自己和其他球友们一起学习交流。
看球友们的打卡也能有收货,最重要的是这个学习氛围对于自己自律非常有帮助!
!
不定时福利
不定时地在星球送书、送专栏、发红包,福利多多,
是否收费?
星球是需要付费才能进入的。 为什么要收费呢?
- 维护好星球是一件费时费力的事情,每到面试季,我经常凌晨还在看简历和回答球友问题。市面上单单一次简历修改服务也至少需要 200+,而简历修改也只是我的星球提供的服务的冰山一角。除此之外,我还要抽时间写星球专属的面试专栏和实战项目教程,单单是这些专栏和项目的价值就远超星球门票了。
- 星球提供的服务比较多,如果我是免费提供这些服务的话,是肯定忙不过来的。付费这个门槛可以帮我筛选出真正需要帮助的那批人。
- 免费的东西才是最贵的,加入星球之后无任何其他需要付费的项目,统统免费!
- 合理的收费是对我付出劳动的一种正向激励,促进我继续输出!同时,这份收入还可以让我们家人过上更好的生活。虽然累点,但也是值得的!
如何加入?
目前星球正在做活动,两本书的价格,就能让你拥有上万培训班的服务!
这里再提供一张 30元的优惠卷(价格马上上调,老用户扫码续费半价 ):
🚀 入圈必做:星球大部分资料都已经整理分类好放在了星球使用指南中,干货满满,一定要看!
无任何套路,无任何潜在收费项。用心做内容,不割韭菜!
不过, 一定要确定需要再进 。并且, 三天之内觉得内容不满意可以全额退款 。
Ai TODO (`docs/ai/TODO.md`)
sitemap: false
head:
- meta
- name: robots
content: noindex, nofollow
AI 内容规划 TODO
最近整理:2026-08-15
配套素材索引:AI 写作素材索引。写新文章前先查素材索引和现有正文,避免重复检索、重复造概念框架。
已完成或已补齐
| 内容 | 状态 |
|---|---|
llm-basis/llm-evaluation.md |
已完成,已进入大模型基础 README 和顶层 README |
system-design/llm-gateway.md |
已完成,已进入系统设计 README 和顶层 README |
agent/workflow-graph-loop.md |
已进入 Agent README、顶层 README 和面试题 |
agent/multi-agent.md |
已完成,已进入 Agent README、顶层 README 和面试题 |
interview-questions/agent-project-interview-guide.md |
已完成,已进入面试题 README、顶层 README 和项目经历指南 |
system-design/ai-application-architecture.md |
已进入系统设计 README、顶层 README 和面试题 |
system-design/ai-voice.md |
已进入系统设计 README、顶层 README 和面试题 |
MATERIALS.md |
已新增为内部写作素材索引,不进站点索引 |
P0 · 系统设计和安全补全
| 文件名 | 标题 | 核心切入 |
|---|---|---|
system-design/llm-security.md |
LLM 应用安全实战:Prompt 注入、工具越权与数据泄露防护 | 从传统“输入不可信”切入 AI 新攻击面,覆盖 Prompt Injection、Indirect Injection、工具权限边界、MCP Server 风险、最小权限、审计和 OWASP LLM Top 10 |
system-design/ai-observability.md |
AI 可观测性与 Trace:为什么 Agent 失败不能只看最终答案 | 一次请求里的模型调用、检索、工具调用、上下文拼装、重试、fallback 全链路 span,覆盖 Langfuse、OpenTelemetry、自建审计表和 Java 后端落地结构 |
agent/tool-calling.md(不新建) |
补链接到已有工具调用文章(已补) | 已链接 结构化输出与 Function Calling;只有 Agent Loop 特有、现文未覆盖的缺口才另写 |
P1 · Agent 工程短板补全
| 文件名 | 标题 | 核心切入 |
|---|---|---|
agent/agent-evaluation.md(不新建) |
补链接到已有评测文章(已补) | 已链接 Agent 应用怎么评测;只有 Agent Loop 特有、现文未覆盖的缺口才另写 |
llm-basis/llm-model-selection.md |
大模型选型指南:通用、推理、代码、多模态模型怎么选 | 不同能力维度对比、Router/fallback/多模型编排、客服/RAG/代码/语音 Agent 的选型表 |
P1 · RAG 深水区扩展
| 文件名 | 标题 | 核心切入 |
|---|---|---|
embedding-reranker.md |
Embedding 与 Reranker 模型选型:RAG 效果差未必是向量库的问题 | 不同 Embedding 模型能力对比、Reranker 原理、选型场景 |
rag-multimodal.md |
多模态 RAG:PDF 表格、图片、截图与视频的知识库处理 | 企业知识库最难处理的是 PDF 表格和截图、OCR、图表理解、多模态检索 |
finetune-vs-rag.md |
微调、蒸馏与 RAG 怎么选:什么时候该做数据训练? | SFT / LoRA / DPO / RFT 原理对比,什么时候调 Prompt 已经不够了 |
P2 · Java AI 框架专题
| 文件名 | 标题 | 写作顺序 |
|---|---|---|
framework/README.md |
AI 框架专题:Spring AI、LangChain4j 与 AI Workflow 工程落地 | 先补目录入口,避免 framework/ 长期空置 |
spring-ai.md |
Spring AI 入门与实战:Java 后端如何接入大模型 | 先写,贴合 JavaGuide 读者群体 |
langchain4j.md |
LangChain4j 实战:Java 应用如何构建 RAG 和 Agent | 第二篇 |
ai-workflow-framework.md |
LangGraph / Spring AI Alibaba Graph:AI Workflow、Graph、Loop 如何落地 | 第三篇,与 workflow-graph-loop.md 互相引用 |
P2 · MCP 进阶与合规
| 文件名 | 标题 | 核心切入 |
|---|---|---|
mcp-advanced.md |
MCP 生产安全与高级能力:Roots、Sampling、Elicitation 与权限边界 | MCP Server 不是工具集合而是新攻击面 |
ai-compliance.md |
AI 合规与隐私治理:AI 应用上线前安全、审计、隐私要查什么 | 企业落地越来越常见,面试频率会上升 |
建议下一步实际动手顺序
system-design/llm-security.md:JavaGuide 读者对安全话题接受度高,可以从传统 Web 安全自然过渡到 AI 新攻击面。system-design/ai-observability.md:能和harness-engineering.md、rag-optimization.md、llm-evaluation.md接上,形成“调试 -> 评测 -> 观测”闭环。- 工具调用与 Agent 评测入口:已在 Agent README、入门篇和面试题补链接到已有文章;不新建
agent/tool-calling.md或agent/agent-evaluation.md,只有 Agent Loop 特有、现文未覆盖的缺口才另写。 framework/README.md+framework/spring-ai.md:framework/目前为空,先补 Java 读者最容易用上的 Spring AI。
维护规则
- 新增文章后,同步检查顶层 README、子专题 README、面试题入口、
MATERIALS.md和本文。 - 写面向读者的文章时不要链接内部维护文档,内部维护文档保持
sitemap: false和noindex, nofollow。 - 具体模型、平台能力、价格、上下文窗口、API 参数这类容易变化的信息,写入正文前要重新核对官方文档。
- 完成一项后及时从待办移动到“已完成或已补齐”,避免下次维护时重复判断。
Ai Ai Core Concepts (`docs/ai/ai-core-concepts.md`)
title: AI 核心概念总览:LLM、Agent、RAG、MCP 与系统设计
category: AI
tag:
- AI
- 大模型
- AI Agent
- RAG
- MCP
- AI 系统设计
head: - meta
- name: keywords
content: AI核心概念,大模型核心概念,LLM,Token,Agent,Agent Loop,ReAct,Plan-and-Execute,RAG,Embedding,MCP,Skills,多Agent,Prompt Engineering,Context Engineering,Function Calling,Tool Calling,GraphRAG,LLM Gateway,AI评测,AI可观测性,Prompt Injection,语音Agent
模型为什么每次回答不完全一样?Agent 为什么需要循环?RAG 已经检索到文档,为什么仍然可能答错?这些问题分别落在模型生成、任务执行、知识检索和应用治理几条链路上。
本文把 JavaGuide AI 专题中的相关概念放到一起,具体实现和案例仍由各专题文章展开。
大模型基础
相关原文:
- LLM 运行机制:Token、上下文窗口与采样参数怎么影响输出
- 大模型 API 调用工程实践:流式输出、重试、限流与结构化返回
- 大模型提示词工程(Prompt Engineering)是什么?提示词技巧有哪些?
- 大模型结构化输出:从 JSON 契约到 Function Calling 落地
- AI 应用评测体系:从 Golden Set 构建到线上灰度闭环
LLM
输入法看到“今天天气真”时,可能把“好”放在候选词里。自回归大模型的生成过程与此类似:它根据当前上下文预测一个 Token,把结果追加到上下文,再预测下一个,直到遇到结束标记或达到输出限制。这个过程称为自回归生成(Autoregressive Generation)。
一次生成里,Token 是模型逐步处理和输出的文本单元;上下文窗口限制单次调用能够容纳的 Token 总量;Temperature、Top-p 等参数影响候选 Token 的采样;Max Tokens 控制本次输出最多占用多少 Token。
Token
模型先用 Tokenizer 把文本切成大小不等的片段。这里没有固定的“一字一 Token”或“一词一 Token”:英文单词可能被拆开,中文词也可能拆成多个 Token,某些高频字词又可能合在一起。
逐字切分会拉长序列,整词切分则需要很大的词表。BPE、Unigram 等子词切分算法在两者之间取舍:高频片段尽量保留,低频词继续拆分。具体切分结果由模型供应商使用的 Tokenizer 决定。
容量规划可以先按经验估算,计费和监控则应读取 API 返回的实际 usage。
Token 化过程示例:
- 原文:
你好,我是小 G。 - 切分:
[你好][,][我是][小 G][。] - 统计:原文 9 字符 → Token 数 5 个 → 压缩比约 1.8 倍
上面的切分只用于说明过程,不代表某个具体模型的真实结果。不同供应商或模型使用的 Tokenizer 不同,同一段文本得到的 Token 序列也可能不同。
上下文窗口
上下文窗口是 LLM 的“工作记忆”(Working Memory)。它决定了模型在任何时刻可以处理或“记住”的文本量(以 Token 为单位)。
- 对话连续性:决定模型能进行多长的多轮对话而不遗忘早期细节。
- 单次处理能力:决定模型一次性能够处理的最大文档、代码库或数据样本。
“模型支持 128K/200K/1M”指的是一次调用里能放进模型的总 Token 上限。大多数模型的上下文窗口包含输入与输出的总和,但部分供应商(如 Google Gemini)对输入和输出分别设限,使用前请查阅具体 API 文档。
上下文窗口往往被隐形成本占用:
- System Prompt:调节模型行为的系统指令(对用户隐藏,但占用窗口)。
- User Prompt:业务数据与指令。
- 多轮对话历史:过往的消息记录。
- RAG 检索片段:从外部知识库检索到的补充信息。
- 工具调用 Schema:函数定义与参数结构。
- 格式开销:特殊字符、换行符、Markdown 标记等。
- 模型生成的输出 Token:输出也占用上下文窗口。
这些内容会共同占用窗口,因此能留给业务正文的空间往往远小于标称上限。
采样参数
模型每一步会给词表中每个候选 Token 打一个分数(内部叫 logits),分数越高说明模型越觉得这个词应该出现在这里。
举个例子,假设模型正在补全“今天天气真__”,它可能给出这样的分数:
| 候选 Token | 原始分数(logit) |
|---|---|
| 好 | 5.0 |
| 不错 | 3.2 |
| 棒 | 2.1 |
| 糟糕 | 0.5 |
| 紫色 | -8.0 |
原始分数不是概率,需要经过 softmax 才能得到概率分布。假设候选集合只有表中的五项,计算结果约为:
| 候选 Token | 概率 |
|---|---|
| 好 | 81.21% |
| 不错 | 13.42% |
| 棒 | 4.47% |
| 糟糕 | 0.90% |
| 紫色 | < 0.001% |
模型随后按这个概率分布采样,决定输出哪个 Token。
解码参数(Temperature、Top-p、Top-k 等)就是在这个“打分 → 概率 → 抽签”的过程中施加控制:
- Temperature:调整概率分布的“形状”,让高分选项更突出,或者让各选项更均匀。
- Top-p / Top-k:直接砍掉不靠谱的候选项,缩小“抽签池”。
- Penalty 系列:对已经出现过的词降分,防止“复读机”。
Prompt
Prompt 是输入给大语言模型(LLM)的任务指令。LLM 根据当前上下文生成后续 Token,也能表现出一定的语义理解和指令跟随能力;但任务边界不清时,它仍可能偏题或补写不存在的信息。
Prompt 的作用是缩小模型的生成范围,质量与长度没有直接关系。比起堆叠修饰词,更重要的是交代清楚 Role、Task、Context 和 Format 四项信息。
| 要素 | 作用 | 常见表述 |
|---|---|---|
| Role(角色) | 告诉模型该用哪个领域的知识和语气 | “你是一位 10 年经验的 Java 架构师” |
| Task(任务) | 说明要完成什么动作 | “请评审以下代码的性能问题” |
| Context(上下文) | 补充和任务相关的背景 | “当前线上 QPS 2000,响应时间超 500ms” |
| Format(格式) | 规定输出长什么样 | “输出 JSON,包含 bottleneck、solution 两个字段” |
结构化输出
先看一个非常常见的 Prompt:
请判断下面用户反馈属于哪类工单,返回 JSON。
用户反馈:我付款成功了,但是订单一直显示待支付。模型可能返回:
{
"category": "payment",
"priority": "high",
"reason": "用户付款成功但订单状态未更新"
}这段结果可以阅读,却还不是后端能够稳定消费的契约。业务侧还要明确这些边界:
category只能是PAYMENT、LOGISTICS、AFTER_SALE、ACCOUNT。priority只能是LOW、MEDIUM、HIGH。confidence必须是0到1之间的小数。reason可以为空吗?最大长度是多少?- 如果用户输入缺少信息,应该返回
NEED_MORE_INFO,还是继续猜?
自然语言 Prompt 很难长期守住这些边界,还需要分清 JSON Mode、JSON Schema 和 Structured Outputs 各自负责什么:
- JSON Mode 是一种输出模式,约束模型返回合法 JSON。
- JSON Schema 是一种结构描述规范,用来定义 JSON 应该包含哪些字段、字段类型是什么、哪些必填、枚举值有哪些、是否允许额外字段。
- Structured Outputs 是模型供应商提供的结构化生成能力,它接收 JSON Schema 或类似 Schema,让模型在生成阶段尽量或严格贴合这份结构。
JSON Schema 负责描述契约,Structured Outputs、Function Calling / Tool Calling 等模型 API 能力负责在生成阶段应用这份契约。只提供 Schema 文本而没有对应的生成约束,模型仍可能返回不符合字段要求的内容。
Function Calling / Tool Calling
模型返回 query_order 和参数 {"orderId": "1029384756"} 时,Java 方法还没有执行。Function Calling / Tool Calling 只让模型生成结构化的调用意图,后端服务、Agent Runtime、MCP Host 或供应商托管环境才负责执行工具。
一次完整调用包含模型生成意图、业务校验、工具执行和结果回填:
- 服务端注册工具名、用途和参数 Schema。
- 用户提出“帮我查一下订单 1029384756 到哪了”。
- 模型选择
query_order,并生成参数{"orderId": "1029384756"}。 - 业务侧校验字段类型、必填项、用户权限、订单归属和幂等键。
- 校验通过后,业务侧再调用订单系统、数据库或 HTTP API。
- 工具结果连同
tool_use_id回填给模型。Anthropic 要求tool_use_id严格匹配,Gemini 3 也会为每个functionCall生成唯一id;并行调用时缺少对应 ID,返回结果可能配错工具请求。 - 模型根据工具结果组织最终回复。
生产级 LLM 调用
用户看到的一次模型回复,背后会经过业务校验、上下文组装、模型路由、供应商调用、响应解析和结果落库。某一段缺少状态记录或错误处理,页面上往往只剩一句笼统的“模型调用失败”。
生产系统至少要记录八个阶段:
- 校验用户身份、租户、套餐、功能权限和请求大小。
- 组装 System Prompt、用户输入、历史消息、RAG 证据、工具 Schema 和输出格式约束。
- 预估输入 Token 并预留输出预算,必要时裁剪历史、压缩上下文或切换模型。
- 通过统一客户端或模型网关选择模型、供应商、超时、重试和限流策略。
- 调用供应商 API,接收同步响应或 SSE、WebSocket 等流式响应。
- 解析增量文本、结束原因、工具调用、Token 用量、拒答和结构化结果。
- 保存回答、调用尝试、成本、错误原因和业务状态。
- 记录 Trace、TTFT、总耗时、重试次数、限流比例和解析失败率。
重试时还要区分错误类型。网络瞬断、部分供应商 5xx 和过载错误可以在总体截止时间内有限重试;参数错误、鉴权错误和安全拒答继续提交同一请求通常没有意义。只要存在重试,就要设计幂等键,并把业务消息、模型调用尝试和供应商请求分别记录,避免重复回答或重复执行带副作用的工具。
AI 应用评测
公开 Benchmark 可以用来筛掉明显不合适的模型,不能代替业务验证。真实请求会带有错别字、口语缩写、截图、多语言和前后矛盾,少数高风险路径也可能被平均分掩盖。
业务评测通常从 Golden Set 开始。每条用例都要包含输入、预期结果或成功标准、场景标签和评分方式。RAG 需要分别评估检索与生成;Agent 除了最终回答,还要检查工具选择、参数、执行轨迹和外部系统的真实状态。
常用评分方式各有分工:
| 方法 | 适合检查的内容 | 主要限制 |
|---|---|---|
| 规则评测 | JSON 格式、字段完整性、枚举值、数值边界、测试结果 | 只能覆盖可明确编码的条件 |
| LLM-as-Judge | 相关性、忠实度、完整性、连贯性和语气 | 存在位置、长度等评分偏差 |
| 人工评测 | 高风险样本、复杂语义和争议结果 | 速度慢、成本高、一致性难控 |
一套可持续的评测流程会把离线评测、生产 Trace 回放和线上灰度串起来:开发阶段用固定数据集比较版本,发布前回放真实轨迹,上线后从 Badcase 和用户反馈中补充样本。Prompt、模型、检索策略、工具版本和评测结果必须关联,否则分数变化后仍然找不到原因。
Agent
相关原文:
- AI Agent 核心概念:Agent Loop、Plan-and-Execute、A2A、Agentic Workflows、Tools 注册
- AI 工作流中的 Workflow、Graph 与 Loop:从概念到实现
- 上下文工程(Context Engineering) 是什么?和 Prompt Engineering 有什么区别?
- AI Agent 记忆系统:短期记忆、长期记忆与记忆演化机制
- 多 Agent 协作系统设计:任务拆分、状态共享、冲突处理与失败恢复
- 什么是 Model Context Protocol (MCP)?和 Function Calling、Agent 什么关系?
- Agent Skills 是什么?和 Prompt、MCP 到底差在哪?
- Harness Engineering:六层检查框架、上下文管理与工程实践
- Loop Engineering 是什么?为什么说它是新瓶装旧酒?
什么是 Agent?
普通模型调用生成一段文本后就结束了。Agent Runtime 会继续检查模型是否提出了工具调用,执行工具,把结果写回上下文,再让模型决定下一步。LangChain 等框架的 Agent 实现因此经常围绕一个循环展开。
这套系统通常写成:Agent = LLM + Planning + Memory + Tools。LLM 处理任务理解和决策,Planning 管理步骤与依赖,Memory 保存当前状态或历史信息,Tools 连接数据库、API、文件系统和代码执行环境。
短期记忆通常保留本次会话和任务状态,长期记忆负责跨会话复用用户偏好、历史决策或任务经验。工具执行产生的返回值属于 Observation(观察),运行时把它追加到上下文,模型才能根据真实结果继续判断。
Agent Loop
Agent Loop 把模型决策和工具执行连成反馈循环。每轮读取当前上下文,由模型选择直接回复还是调用工具;运行时执行动作并保存结果,然后进入下一轮。
- 初始化 System Prompt、工具列表和用户请求。
- 模型读取上下文,返回文本或工具调用意图。
- 运行时执行工具,把结果和状态写回上下文。
- 模型不再调用工具时结束;达到轮次、Token、时间或费用上限时,由运行时强制停止。
循环本身并不复杂。任务持续时间变长后,历史消息、工具结果和中间状态会不断占用上下文,早期约束也可能被后续内容稀释。Context Engineering 负责决定哪些信息继续保留、何时压缩,以及下一轮真正需要看到什么。
ReAct
ReAct 是 Reasoning + Acting,由 Shunyu Yao 等人在 2022 年提出,论文是《ReAct: Synergizing Reasoning and Acting in Language Models》。它把推理、行动和观察交替放进同一条轨迹:模型先决定一个动作,环境返回结果,下一轮再依据这份结果继续。
比如模型不知道订单当前状态时,可以先调用查询工具。订单系统返回“已支付、待发货”后,模型再决定直接回答,还是继续查询物流。外部结果给后续判断提供了依据,但不会自动保证答案正确。
落地 ReAct 时,运行时要保存执行历史和当前状态,向模型提供可用工具或 Skills,并把工具响应作为 Observation 关联到对应调用。系统告警、用户补充信息等环境变化,也要在后续轮次中更新。
多轮工具调用会增加响应延迟和 Token 消耗,结果质量也受工具描述、参数校验、返回数据和停止条件影响。Trace 可以记录它执行过哪些动作,但模型输出的推理文本不能直接等同于内部决策机制。
Plan-and-Execute
Plan-and-Execute 是 LangChain 团队在 2023 年提出的模式。规划阶段先产出步骤和依赖关系,执行器再逐项处理,因此更适合能够提前识别主要阶段的长任务。
全局计划能让依赖和完成条件更早暴露出来。环境或任务目标在执行中发生变化时,旧计划也可能失效,这时需要重新规划,而不是继续照着原步骤运行。
项目里可以用计划控制外层阶段,在某个步骤内部运行 ReAct 子循环。确定性依赖由计划约束,局部信息不足时再通过工具调用补齐。
Workflow、Graph 与 Loop
Agent 把下一步选择交给模型,Workflow 则由代码或图结构预先规定节点顺序、条件跳转和失败处理。两者都能调用模型和工具,差别主要在控制权放在哪里。
步骤稳定、权限要求明确的流程通常交给 Workflow。遇到无法提前穷举的检索、分析或工具选择时,可以在某个节点中嵌入 Agent 子循环。这类 Agentic Workflow 由外层流程限制执行范围,局部决策再交给模型。
AI 工作流的数据结构是有向图(Graph),三个元素:Node(节点)负责执行,Edge(边)负责控制流,State(状态)在节点之间共享上下文。
多 Agent
多 Agent 管理的是能够独立完成子目标的执行者,多阶段 Prompt Chain 管理的是一条 Workflow 中的处理步骤。把一个角色拆成独立 Agent 的前提,是它拥有自己的 Prompt、工具、上下文、权限、运行状态和交付结果。
不同环节需要明显不同的工具、权限和上下文,或者任务能够拆成几个相对独立的方向时,多 Agent 才可能带来收益。若几个角色共用相同上下文和工具,只是分别生成相似文本,增加的主要是模型调用次数、延迟和归并成本。
常见编排方式包括:
| 模式 | 控制方式 | 适用场景 |
|---|---|---|
| Sequential | 代码按固定顺序调用多个 Agent | 步骤确定,前后依赖清楚 |
| Parallel | 独立任务并行执行,完成后统一归并 | 多路检索、多维评审、投票 |
| Router | 规则或模型把请求路由给一个或多个专家 | 业务域清楚、分类相对稳定 |
| Supervisor/Manager | 主 Agent 动态拆任务并调用子 Agent | 子任务无法提前完全确定,需要统一答案 |
| Handoff | 当前 Agent 把控制权和必要上下文交给其他角色 | 客服分流、分阶段对话 |
角色拆分后还要定义任务契约,包括子目标、成功标准、依赖、允许使用的工具、输出 Schema、产物位置、超时、Token 预算和失败策略。多个 Agent 同时写共享状态或调用有副作用的工具时,仍然要靠幂等、版本控制、锁、补偿和检查点保证一致性,不能让模型自行协商写入顺序。
Context Engineering
上下文混乱时,即使模型能力足够,Agent 也可能漏掉约束、重复调用工具或偏离当前任务。
Context Engineering 负责在有限的 Token 窗口中选择、组织和更新模型当前需要的信息,包括规则、记忆、工具描述、会话状态、外部观察结果和 Token 预算。Prompt Engineering 更关注指令本身如何表达,是其中一个更局部的问题。
进一步的设计方法见《提示词工程(Prompt Engineering)》和《上下文工程(Context Engineering)》。
Memory
记忆系统通常分两层:短期记忆和长期记忆。短期记忆是 Session 级的,服务当前任务;长期记忆跨越 Session,保存需要复用的用户偏好、历史决策和任务经验。两类数据的生命周期、权限和更新方式不同,存储时应在逻辑上分开。
按功能目的看,Agent 记忆可以分成三类。
| 功能类型 | 核心问题 | 存储内容 | 典型场景 |
|---|---|---|---|
| 事实记忆 | 智能体知道什么 | 用户偏好、环境状态、显式事实 | 记住用户的技术栈偏好 |
| 经验记忆 | 智能体如何改进 | 过往轨迹、成败教训、策略知识 | 从失败的代码审查中学习 |
| 工作记忆 | 智能体当前思考什么 | 当前推理上下文、任务进展 | 多步推理中的中间状态 |
长期记忆和 RAG 技术上很像,都会用向量库和语义检索。但它们服务的对象不一样。
RAG 挂载的是可检索知识源,比如公司规章、产品文档、实时数据库查询结果。它既可以服务共享知识库,也可以根据用户、租户、角色和会话执行权限过滤或个性化检索。RAG 与长期记忆的主要区别在数据来源和生命周期:前者检索外部知识,后者保存交互中形成、需要跨会话复用的信息。
长期记忆管理的是 Agent 与特定用户交互中动态沉淀的个性化经验,比如用户偏好、习惯、历史决策、专属背景。它高度个性化,因人而异。
MCP
MCP 全称是 Model Context Protocol,中文一般叫“模型上下文协议”。名称中的三个词对应了协议的适用范围:
- Model:面向大模型应用;
- Context:把外部上下文、工具和数据源带给模型;
- Protocol:用一套标准协议把交互方式定下来。
MCP 定义了 MCP Client 和 MCP Server 之间的通信方式。Host 承载用户交互和模型调用,Client 与 Server 通信,Server 对外提供具体能力;模型本身不会因为接入 MCP 而获得内置插件。
Function Calling、MCP、Agent 和 Skills 经常出现在同一套系统里,但各自处理不同问题:Function Calling 让模型表达调用工具的意图;MCP 规定宿主如何发现外部能力并连接后端服务;Agent 负责在多轮执行中决定下一步;Skills 则保存完成某类任务所需的流程和经验。
Skills
用户提出“帮我分析这份报表”时,Prompt 只表达了当前任务。模型若要读取文件,会通过 Function Calling 生成 read_file 的结构化参数;这个工具可以由 MCP Server 提供。至于分析顺序,例如先核对字段含义,再查异常值,最后结合业务解释结果,更适合写进 Skill。
Skill 是一份可被 Agent 发现、按需加载的任务说明。接口格式、日志字段、慢 SQL 排查顺序、代码 Review 的检查重点等团队经验,都可以放进 SKILL.md,避免每次把整套规则写进 Prompt。它与 Prompt、Function Calling 和 MCP 分别处理任务输入、调用意图、工具连接和执行方法,职责可以同时出现在一次任务里。
Skill 的加载和执行包含五步:
- 用户提出任务(Prompt)
- 宿主把可用 Skills 的简短描述放进上下文(Skill 元数据)
- 模型判断当前任务命中了某个 Skill(Skill 路由)
- 宿主再把完整
SKILL.md加载进来(延迟加载) - 模型按照 Skill 里的流程去调工具、读资料、写结果(执行)
Harness Engineering
模型负责推理和生成,却不会自行保存任务状态、限制文件权限、执行测试或处理工具超时。系统提示词、工具调用、文件系统、沙箱、编排、Hooks、反馈和约束机制共同组成 Harness。工程上常用 Agent = Model + Harness 提醒开发者:模型之外的运行环境同样决定任务能否持续执行。
Vivek Trivedi 在《The Anatomy of an Agent Harness》中也采用了先区分模型职责、再检查外围系统的思路。排查 Agent 时,除了模型输出,还要看上下文是否完整、工具是否可用、执行环境是否受控,以及失败后能否恢复。
Prompt Engineering、Context Engineering 和 Harness Engineering 的作用范围不同。Prompt 关注指令表达,Context 决定当前调用要提供哪些信息,Harness 管理模型之外的执行、验证、观测和恢复机制。
| 层级 | 解决的问题 | 关注点 | 典型工作 |
|---|---|---|---|
| Prompt Engineering | 怎么把指令说清楚 | 让模型理解意图,减少局部歧义 | 系统提示词设计、Few-shot 示例、思维链引导 |
| Context Engineering | 该给 Agent 看什么 | 在合适时机给模型提供正确且必要的信息 | 上下文管理、RAG、记忆注入、Token 优化 |
| Harness Engineering | 系统怎么持续执行、纠偏、观测和恢复 | 长链路任务中的持续正确、偏差修正、故障恢复 | 文件系统、沙箱、约束执行、反馈回路、观测 |
Loop Engineering
Loop Engineering 为 Agent 的重复执行补齐触发、目标、上下文、验证、状态和停止条件。每一轮都要留下可检查的结果,任务完成、失败、预算耗尽或需要审批时退出循环。
落到工程上,主要看这些点:
- 触发:谁来启动这轮任务?手动命令、定时任务、CI 失败、PR 创建、Issue 更新,还是某个消息事件。
- 目标:什么状态算完成?全部测试通过、CI green、覆盖率达到某个数值、页面截图对齐设计稿,还是只生成待人工确认的草稿。
- 上下文:Agent 每轮要看哪些文件、规则、历史状态、工具结果和项目约定。
- 行动:Agent 能改代码、跑测试、查 GitHub、读日志、发 PR,还是只能输出建议。
- 观察:它怎么知道刚才那一步做对了?测试输出、lint、类型检查、截图、审查评论、日志摘要都可以是观察结果。
- 状态:这轮试过什么、失败在哪里、下一步做什么,要写到外部文件、Issue、Linear 卡片或数据库里,不能只靠当前对话记住。
- 停止:什么时候退出,什么时候转人工,什么时候因为预算或轮次耗尽直接停。
最小循环可以写成一条执行链:读取上下文 → 判断下一步 → 调用工具或输出答案 → 回写结果 → 检查停止条件。ReAct 把 Reasoning 和 Acting 交替放进这条循环;Loop Engineering 还要处理循环外部的触发、验证、持久化、预算和人工接管。
RAG
相关原文:
- RAG 基础概念:检索、生成与工程取舍
- RAG 向量索引算法和向量数据库
- RAG 文档处理与切分策略:从解析、清洗、Chunking 到多模态内容处理
- RAG 优化:从召回、重排到上下文工程
- GraphRAG:用图结构补充向量检索
- RAG 知识库文档如何更新:增量更新、版本控制、去重与全量重建
什么是 RAG?
RAG(Retrieval-Augmented Generation,检索增强生成) 把信息检索接入大语言模型的生成过程。系统先从数据库、文档集合或企业内部系统中查找与问题相关的内容,再把检索结果和原始问题一起交给 LLM。模型由此获得参数知识之外的证据。
RAG 主要补充三类信息缺口:
- 知识时效性:预训练模型的参数知识停在训练数据截止时间点。RAG 可以在请求发生时检索新事件、政策和产品文档。
- 私有数据访问:企业产品文档、知识库和客户数据不在公开训练语料中。应用可以先按用户权限检索,再只提供回答所需的片段。
- 答案依据:检索证据能约束模型的回答范围,但不能彻底消除幻觉。检索错误、上下文噪声、引用错配和模型不遵循指令仍会产生错误答案,生产系统还要配合引用校验、答案评估、拒答和人工反馈。
RAG 工作原理
RAG 的工程链路通常分两个阶段:离线索引和在线检索生成。索引阶段把原始文档处理成可检索的数据结构;在线阶段在用户提问时完成查询理解、检索召回、上下文构建和答案生成。
索引和检索阶段的简化流程图如下:
索引阶段主要做这些事:
- 输入文档:文本文件、PDF、网页、数据库记录都可以,只要有内容。
- 清理文档:去掉 HTML 标签、特殊字符等噪声。
- 增强文档:补充元数据,比如时间戳、分类标签,为后续检索提供过滤维度。
- 文档拆分(Chunking):用文本分割器把文档切成较小片段。这一步要兼顾语义完整性、Embedding 模型输入长度、生成模型上下文窗口和召回粒度。Chunk 太大容易引入噪声,太小又可能丢上下文。拆分策略会直接影响召回质量,详细可以看 RAG 文档处理篇。
- 向量化表示(Embedding Generation):通过嵌入模型将文本片段映射为语义向量,也就是高维稠密向量。常见嵌入模型包括 OpenAI 的
text-embedding-3-small/text-embedding-3-large,以及 Hugging Face 上的开源模型。 - 存储到向量存储或索引系统:把嵌入向量、原始内容和对应元数据存入向量存储或向量索引系统,比如 Milvus、pgvector、Elasticsearch / OpenSearch 向量检索,或基于 Faiss 构建本地向量索引。向量数据库选型、索引算法和 pgvector 实践可以看 RAG 向量库篇。
Embedding
Embedding 就是把文本变成一串数字。更准确地说,它会把文本映射到一个高维稠密向量空间里,让语义接近的文本在向量空间中距离更近。
比如这三句话:
- “如何申请退款?”
- “退款流程是什么?”
- “订单怎么取消并退钱?”
它们字面不一样,但语义接近。好的 Embedding 模型会把它们映射到相近位置,向量检索才能把相关 Chunk 找出来。
Embedding 维度常见的有 768、1024、1536、3072 等。维度是模型设计和训练方式的一部分,不能脱离模型直接得出“维度越高,语义效果越好”的结论;较高维度通常会增加存储、索引和相似度计算成本。以 OpenAI Embedding 为例,text-embedding-3-small 默认输出 1536 维,text-embedding-3-large 默认输出 3072 维,并支持通过 dimensions 参数降低输出维度。
向量检索与向量数据库
RAG 的检索流程里,最基础的一步是:把用户问题和文档都变成向量,再用相似度搜索找到最相关的文档片段。
可以把它理解成这样:
- 文档进入知识库后,先被切成 Chunk。
- 每个 Chunk 通过 Embedding 模型转成一个向量。
- 向量和原文、元数据一起写入向量数据库。
- 用户提问时,问题也会被转成查询向量。
- 向量数据库检索出最相似的 Top-K 文档向量。
- 系统把这些文档片段放进 Prompt,交给 LLM 生成答案。
Embedding 负责把文本变成可比较的向量,向量检索据此查找语义接近的内容。向量检索只是 RAG 的一种实现;RAG 还可以使用 BM25、SQL、知识图谱、搜索 API 或其他业务查询来取得外部证据。
在小规模 Demo 里,几千条文档向量可以直接放在内存里暴力搜索。但真实 RAG 系统里,文档量很快会到百万级、千万级,甚至更大。
向量数据库除了保存向量,还要处理相似度索引、元数据过滤、更新删除、并发查询和持久化等工程问题:
文档处理
文档从上传到进入向量库,中间要经过至少六个环节:
质量校验不应只发生在入库之后。Chunking 阶段完成采样校验,可以提前发现问题,避免把低质量数据大批量写入向量库。
每个环节的核心风险:
| 环节 | 典型问题 | 最终影响 |
|---|---|---|
| 文件上传 | 格式伪造、大小超限、编码混乱 | 解析器崩溃或静默失败 |
| 格式校验 | 扩展名和实际 MIME 类型不符 | 选错解析器 |
| Layout 解析 | PDF 多栏、表格合并单元格、页眉页脚 | 结构丢失、上下文错位 |
| 清洗去噪 | 乱码、特殊字符、重复空行、目录残留 | 噪声入索引、Embedding 失真 |
| Chunking | 语义截断、上下文断裂、块太大或太小 | 召回不准、答案残缺 |
| Metadata | 没保存来源、页码、版本、权限 | 无法过滤、无法引用 |
| 入库 | 向量维度不一致、Token 超限 | 检索失败、索引损坏 |
很多团队把精力放在换哪个 embedding 模型上面,但实际上如果数据在这一步就已经坏掉了,换模型只会让损坏更稳定。
Chunking
如果文档本身有清晰结构,按结构切通常更合适。NVIDIA 的一组测试中,Page-Level Chunking(按页面切分)在金融报告和法律文档上表现最好,平均准确率为 0.648,方差也最低。页面边界在这类材料中经常承载章节或版式语义,切分时应尽量保留。
不过别盲目迷信页面级切分。这个优势相对于 Token 切分其实只有 0.3-4.5 个百分点,而且在 FinanceBench 数据集上,1024-token 切分反而比页面级更优(0.579 vs 0.566)。NVIDIA 测试的文档类型(金融报告、法律文档)是分页本身就携带语义的场景——如果你的 PDF 是 Word 随便导出的那种,页面级切分不会带来额外收益。另外,查询类型也影响最优策略:事实型查询适合 256-512 Token 的小块,分析型查询适合 1024+ Token 或页面级切分。
不同文档类型可以先选一组符合其结构的切分方式,再用业务查询评测:
| 文档类型 | 推荐切分方式 | 实现工具 |
|---|---|---|
| Markdown | 按标题层级(H1/H2/H3)切 | MarkdownHeaderTextSplitter |
| HTML | 按标签层级切(h1~h6、p、div) | HTMLHeaderTextSplitter |
| 按页或章节切 | chunk_by_title、chunk_by_page |
|
| 代码 | 按函数、类、包切 | PythonCodeTextSplitter |
| 论文 | 按章节、段落、表格切 | Layout-aware Parser |
Chunk 较小时,向量检索更容易命中具体句子,但模型拿到的上下文可能不完整;Chunk 较大时,段落信息保留得更多,无关内容也会一起进入候选集。
Parent-Child Chunk 将检索粒度和生成上下文分开。比如先用约 300 Token 的子块建立索引,每个子块再关联一个约 1200 Token 的父段落。查询命中子块后,系统把对应父段落交给模型。300/1200 只是起始参数,仍要根据文档结构、查询类型和评测结果调整。
Hybrid Search
“如何取消订阅”和“关闭自动续费”用词不同,向量检索更容易建立语义关联;E1027、ABX-4421 这类错误码和型号则更适合 BM25 的精确词匹配。Hybrid Search 同时保留两路候选。
| 查询类型 | 向量检索表现 | BM25 表现 | 建议 |
|---|---|---|---|
| “如何取消订阅” | 能匹配“关闭自动续费” | 可能匹配不到 | 保留向量召回 |
| “错误码 E1027” | 可能召回泛化故障 | 精确命中错误码 | 必须保留关键词召回 |
| “ABX-4421 型号参数” | 容易找相似型号 | 精确命中 SKU | 必须保留关键词召回 |
| “Java 线程池拒绝策略区别” | 语义理解较好 | 能匹配关键词 | 混合更稳 |
| “最新 v3.2 价格政策” | 需要语义和时间条件 | 可匹配版本号 | Metadata + Hybrid |
两路结果需要经过融合,才能进入后续重排:
- 向量检索返回语义相似候选。
- BM25 或稀疏向量返回关键词候选。
- 用 RRF 或归一化加权分数合并。
- 对合并后的候选去重,再进入 Rerank。
Microsoft Azure AI Search、Google Vertex AI Vector Search、Weaviate 等产品的官方文档都介绍了 Hybrid Search 和 RRF。RRF 按候选的排名位置融合结果,不必直接比较 BM25 分数和向量余弦分数。
混合检索是否值得引入要看查询分布。错误码、产品型号、配置项和专有名词较多时,关键词召回通常不能省;文档高度结构化且查询很少包含精确词时,增加一路 BM25 未必能带来足够收益。
Query Rewrite
用户提交给检索系统的往往是这些短句:
- “这个报错咋整?”
- “钱能退吗?”
- “线上那个限流问题是不是又来了?”
“这个”“钱”“线上那个问题”依赖会话上下文,直接检索时缺少对象和约束。Query Rewrite 会补充必要实体、同义表达或过滤条件,让查询更接近知识库中的写法,同时保留用户原始意图。
常见策略如下:
| 策略 | 适用场景 | 例子 |
|---|---|---|
| 规范化改写 | 口语化、缩写、上下文缺失 | “钱能退吗”改成“退款政策、退款条件、退款流程” |
| Multi-Query | 表达可能有多种说法 | 同时检索“取消订阅”“关闭自动续费”“停止会员计划” |
| Query Decomposition | 问题包含多个子问题 | 把“对比 Stripe 和 Square 的手续费和争议处理”拆成 4 个子问题 |
| Step-back Query | 问题太细,缺背景 | 先检索“订阅计费规则”,再回答具体取消问题 |
| HyDE | 查询太短,和文档形态差异大 | 先生成假设答案,再用假设答案向量检索真实文档 |
| Self-Query | 问题里包含过滤条件 | 从“查 2025 年 Java 相关政策”提取年份和类别过滤 |
LangChain 的 MultiQueryRetriever、SelfQueryRetriever 等组件提供了对应实现。原始 query 应保留为一路召回输入,再与改写 query 的结果融合;只保留改写结果时,模型一旦理解错意图,后续候选会整体偏离。
Rerank
用户问:“线程池为什么会触发拒绝策略?”
向量召回可能找出这些片段:
- 线程池核心参数说明。
- 拒绝策略枚举列表。
- 队列满、线程数达到 maximumPoolSize 后触发拒绝策略的条件。
- 线程池使用示例代码。
前两条与“线程池”“拒绝策略”在语义上接近,第 3 条才直接说明触发条件。双塔检索分别编码 query 和 document,再按向量距离快速召回;Rerank 通常使用 Cross-Encoder 或专用重排模型,把 query 与每条候选一起评分,将能够回答问题的内容排到前面。精度更细,计算开销也更高,因此一般只处理粗召回后的少量候选。
一条常见链路如下,候选数量要结合延迟预算和评测结果调整:
- Metadata 预过滤。
- Hybrid Search 粗召回 30 到 100 条。
- 去重和相邻片段合并。
- Rerank 选出 5 到 10 条。
- 上下文压缩后放入 Prompt。
GraphRAG
GraphRAG(Graph-based Retrieval-Augmented Generation)是一类把图结构用于检索增强的方案。系统可以把文档中的实体、关系和结构化上下文显式建模,查询时沿图关系收集证据,再交给大模型生成答案。
GraphRAG 改变了检索对象。图数据库可以承载这些数据,但是否使用某个图数据库产品并不是判断 GraphRAG 的唯一条件。
传统向量 RAG 主要检索文本 Chunk。GraphRAG 可以检索节点、边、图路径和原始文本证据;社区摘要是 Microsoft GraphRAG 等实现采用的一种索引形式,并非所有 GraphRAG 系统都要求使用。
打个比方:
- 向量 RAG 像在图书馆里按语义找几页相似内容。
- GraphRAG 像先整理出人物关系图、事件时间线和主题目录,再沿着关系线索找证据。
向量 RAG 擅长判断“这段话和我的问题像不像”,GraphRAG 更擅长理解“这些对象之间到底怎么连起来”。
| 维度 | 传统向量 RAG | GraphRAG |
|---|---|---|
| 检索对象 | 文本 Chunk | 实体、关系、路径、社区摘要、原文片段 |
| 核心能力 | 语义相似度召回 | 关系推理、图遍历、全局主题聚合 |
| 数据结构 | 向量索引为主 | 知识图谱 + 向量索引 + 全文索引 |
| 适合问题 | 局部事实问答、文档片段解释 | 多跳关系问答、跨文档归纳、复杂业务分析 |
知识库更新
RAG 知识库需要持续处理新增、修改和删除。更新完成后,检索结果要与当前文档一致,权限元数据也要同步;某个写入端失败时,系统还要能定位具体文档,并恢复到上一版健康索引。
索引和查询必须使用同一套 Embedding 模型及版本。不同模型生成的向量空间不通用,即使维度相同,相似度分数也没有可比性。升级 Embedding 模型或 Chunk 策略时,通常需要建立新索引,用同一批评测数据对比后再切换别名,旧索引保留一段时间用于回滚。
日常更新和结构性迁移适合用不同策略:
| 维度 | 增量更新 | 全量重建 |
|---|---|---|
| 处理范围 | 只处理新增、修改和删除的文档 | 重新处理整个知识库 |
| 适用场景 | 高频日常变更、分钟级同步 | 模型升级、Chunk 策略调整、严重故障恢复 |
| 主要依赖 | Webhook、CDC、轮询和消息队列 | 源系统快照、新旧索引并行和原子别名切换 |
| 主要风险 | 事件漏检、乱序、重复和部分成功 | 重建成本高,切换或回滚设计不完整影响服务 |
生产环境常用“事件驱动 + 轮询兜底 + 对账修复”的组合。元数据库记录文档版本、内容 Hash、Embedding 版本、Chunk 版本和各索引端状态;向量库、全文索引与元数据库发生部分成功时,后台补偿任务根据状态重试和对账。
AI 系统设计
相关原文:
- AI 应用系统设计:从 Prompt Demo 到生产级架构
- 大模型网关详解:多模型路由、Fallback、限流与成本控制
- AI 可观测性与 Trace:如何还原 Agent 的完整执行过程
- LLM/Agent 安全实战:从 Prompt Injection、工具越权到沙箱隔离
- AI 语音技术详解:从 ASR、TTS 到实时语音 Agent 的工程化落地
生产级 AI 应用架构
最简单的 AI Demo 只有一条链路:前端提交问题,后端拼接 Prompt,调用模型 API,再返回答案。它能验证产品想法,却没有处理生产系统里的稳定性、权限、成本、可观测、评测和数据治理。
生产级 AI 应用通常按职责拆成几层:
| 层级 | 主要职责 |
|---|---|
| 入口层 | 认证鉴权、请求标准化、限流、幂等和敏感内容预处理 |
| 业务编排层 | 选择普通问答、RAG、Agent 或异步任务,控制确认、重试和降级 |
| Prompt 与 Context 管理 | 管理模板版本、变量注入、Token 预算、灰度发布和回滚 |
| 模型网关 | 统一模型接入、路由、限流、Fallback、成本归因和调用观测 |
| 知识、记忆与工具层 | 分别管理外部知识、个性化记忆和真实业务操作 |
| 评测与观测层 | 记录调用链、版本、质量、延迟、成本和 Badcase,支持回放与发布门禁 |
这些模块不一定都要独立部署。早期项目可以在单体应用中划分清楚职责,调用量、团队数量或治理需求增长后再拆服务。关键是让模型生成和确定性业务规则分开:模型负责理解意图、生成内容和提出工具调用,身份、权限、金额、审批与幂等由代码和业务系统决定。
LLM Gateway
LLM Gateway 是应用层和模型供应商之间的治理入口。它除了处理鉴权、限流、转发等普通 API 问题,还要管理模型选择、Token 预算、上下文长度、供应商差异、流式输出、工具调用、结构化响应、成本统计和 Prompt 版本。
LLM Router 只负责为当前请求选择模型,是 Gateway 中的一个环节。Gateway 管理的是从请求进入到结果返回的完整生命周期,包括统一接入、路由、Fallback、错误处理、观测、审计和成本归因。
模型调用量很小、只接一家供应商的内部工具,可以先用共享 LLMClient 统一处理密钥、超时、重试和日志。多个服务、团队或租户开始复用模型能力后,再逐步补充模型注册表、配额、路由、缓存、成本和审计;LLM Gateway 是一组需要集中治理的职责,不一定从第一天起就是独立服务。
AI 可观测性与 Trace
AI 接口返回 HTTP 200,不代表回答正确或任务完成。RAG 可能在问题改写、召回、重排或上下文截断阶段出错;Agent 还可能选错工具、重复执行有副作用的操作,或者在多 Agent 归并时漏掉结果。
常见观测信号各自回答不同问题:
| 信号 | 主要回答的问题 | 典型内容 |
|---|---|---|
| Metrics | 系统整体是否异常 | 请求量、错误率、P95 延迟、Token 用量、工具失败率 |
| Logs | 某个时间点发生了什么事件 | 异常堆栈、状态变更、重试原因、业务告警 |
| Trace | 一次请求经历了哪些步骤 | 模型、检索、工具、Agent 和上下游关系 |
| Evaluation | 输出质量和任务结果是否合格 | 正确性、忠实度、工具选择、任务完成度、安全性 |
| Audit | 谁在什么权限下执行了什么操作 | 用户身份、审批记录、权限决策、外部写操作 |
长任务还要区分 Session、Run、Trace、Span 和 Attempt。Session 串起多轮会话,Run 表示一次任务,Trace 对应一次实际调用链,Span 表示其中的模型、检索或工具操作,Attempt 记录同一步骤的第几次尝试。人工审批或异步恢复后可以创建新 Trace,但要沿用同一个 runId。
确认过的失败 Trace 应脱敏后回流到 Badcase 集合。修复 Prompt、RAG、工具或编排逻辑后,用这些轨迹执行离线回放和回归评测,再通过灰度发布验证线上效果。
LLM 与 Agent 安全
Agent 接入 RAG、Memory、工具、MCP、Skills、浏览器和代码执行后,外部内容既能影响模型判断,也可能触发真实业务操作。用户输入、网页、邮件、RAG 片段、Memory、工具结果、其他 Agent 消息和模型生成的参数都要按不可信数据处理。
Prompt Injection 描述的是攻击指令进入应用并改变既定任务;间接注入的指令藏在网页、邮件、文档、图片、RAG 或工具结果中;Jailbreak 主要试图绕过模型本身的安全策略。三者可以重叠,靠 System Prompt 中的一句“忽略外部指令”无法覆盖完整调用链。
安全控制要落在模型之外:
- 检索前根据用户、租户和数据范围做权限过滤。
- 根据场景和身份裁剪可见工具,执行前重新鉴权。
- 将通用读写接口拆成业务动作明确、参数受限的工具。
- 高风险操作绑定具体工具、参数、资源版本和有效期进行审批。
- Prompt、日志和 Trace 默认只保存必要元数据,原文按用途、权限和期限受控留存。
- 代码执行和网络访问放进受限环境,并限制文件、进程、资源和网络出口。
- 把模型、Prompt、MCP Server、Skill、数据集、容器镜像和评测器纳入供应链治理。
模型可以提出操作建议,最终权限、业务规则、审批和执行结果必须由服务端校验。
实时语音 Agent
语音 Agent 在普通 Agent 链路之外增加了音频采集、前处理、VAD、ASR、TTS、流式播放和打断处理。一轮对话会依次经过这些环节:
音频采集 -> 降噪/回声消除 -> VAD -> 流式 ASR -> 上下文组装
-> LLM/工具调用 -> 流式 TTS -> 音频播放 -> 状态回写文本聊天慢 1 秒通常还能接受,语音对话慢 1 秒就会出现明显停顿。优化时要看端到端 P95/P99 延迟,把能并行的环节提前:ASR 产生稳定前缀后预判意图,LLM 输出第一个短句后立即启动 TTS,客户端收到音频块后边收边播。
打断也不只是暂停播放器。系统要停止当前 TTS、清空待播放队列、取消仍在生成的模型请求,并判断已经触发的工具能否撤销。工具存在外部副作用时,打断策略需要和幂等、补偿及任务状态一起设计。
Books Cs Basics (`docs/books/cs-basics.md`)
title: 计算机基础必读经典书籍
category: 计算机书籍
icon: "mdi:desktop-classic"
head:
- meta
- name: keywords
content: 计算机基础书籍精选
考虑到很多同学比较喜欢看视频,因此,这部分内容我不光会推荐书籍,还会顺便推荐一些我觉得不错的视频教程和各大高校的 Project。
操作系统
为什么要学习操作系统?
从对个人能力方面提升来说,操作系统中的很多思想、很多经典的算法,你都可以在我们日常开发使用的各种工具或者框架中找到它们的影子。比如说我们开发的系统使用的缓存(比如 Redis)和操作系统的高速缓存就很像。CPU 中的高速缓存有很多种,不过大部分都是为了解决 CPU 处理速度和内存处理速度不对等的问题。我们还可以把内存可以看作外存的高速缓存,程序运行的时候我们把外存的数据复制到内存,由于内存的处理速度远远高于外存,这样提高了处理速度。同样地,我们使用的 Redis 缓存就是为了解决程序处理速度和访问常规关系型数据库速度不对等的问题。高速缓存一般会按照局部性原理(2-8 原则)根据相应的淘汰算法保证缓存中的数据是经常会被访问的。我们平常使用的 Redis 缓存很多时候也会按照 2-8 原则去做,很多淘汰算法都和操作系统中的类似。既说了 2-8 原则,那就不得不提命中率了,这是所有缓存概念都通用的。简单来说也就是你要访问的数据有多少能直接在缓存中直接找到。命中率高的话,一般表明你的缓存设计比较合理,系统处理速度也相对较快。
从面试角度来说,尤其是校招,对于操作系统方面知识的考察是非常非常多的。
简单来说,学习操作系统能够提高自己思考的深度以及对技术的理解力,并且,操作系统方面的知识也是面试必备。
如果你要系统地学习操作系统的话,最硬核最权威的书籍是 《操作系统导论》 。你可以再配套一个 《深入理解计算机系统》 加深你对计算机系统本质的认识,美滋滋!
另外,去年新出的一本国产的操作系统书籍也很不错:《现代操作系统:原理与实现》 (夏老师和陈老师团队的力作,值得推荐)。
如果你比较喜欢动手,对于理论知识比较抵触的话,我推荐你看看 《30 天自制操作系统》 ,这本书会手把手教你编写一个操作系统。
纸上学来终觉浅 绝知此事要躬行!强烈推荐 CS 专业的小伙伴一定要多多实践!!!
其他相关书籍推荐:
- 《自己动手写操作系统》:不光会带着你详细分析操作系统原理的基础,还会用丰富的实例代码,一步一步地指导你用 C 语言和汇编语言编写出一个具备操作系统基本功能的操作系统框架。
- 《现代操作系统》:内容很不错,不过,翻译的一般。如果你是精读本书的话,建议把课后习题都做了。
- 《操作系统真象还原》:这本书的作者毕业于北京大学,前百度运维高级工程师。因为在大学期间曾重修操作系统这一科,后对操作系统进行深入研究,著下此书。
- 《深度探索 Linux 操作系统》:跟着这本书的内容走,可以让你对如何制作一套完善的 GNU/Linux 系统有了清晰的认识。
- 《操作系统设计与实现》:操作系统的权威教学教材。
- 《Orange'S:一个操作系统的实现》:从只有二十行的引导扇区代码出发,一步一步地向读者呈现一个操作系统框架的完成过程。配合《操作系统设计与实现》一起食用更佳!
如果你比较喜欢看视频的话,推荐哈工大李治军老师主讲的慕课 《操作系统》,内容质量吊打一众国家精品课程。
课程的大纲如下:
主要讲了一个基本操作系统中的六个基本模块:CPU 管理、内存管理、外设管理、磁盘管理与文件系统、用户接口和启动模块 。
课程难度还是比较大的,尤其是课后的 lab。如果大家想要真正搞懂操作系统底层原理的话,对应的 lab 能做尽量做一下。正如李治军老师说的那样:“纸上得来终觉浅,绝知此事要躬行”。
如果你能独立完成几个 lab 的话,我相信你对操作系统的理解绝对要上升几个台阶。当然了,如果你仅仅是为了突击面试的话,那就不需要做 lab 了。
说点心里话,我本人非常喜欢李治军老师讲的课,我觉得他是国内不可多得的好老师。他知道我们国内的教程和国外的差距在哪里,也知道国内的学生和国外学生的差距在哪里,他自己在努力着通过自己的方式来缩小这个差距。真心感谢,期待李治军老师的下一个课程。
还有下面这个国外的课程 《深入理解计算机系统 》 也很不错。
计算机网络
计算机网络是一门系统性比较强的计算机专业课,各大名校的计算机网络课程打磨的应该都比较成熟。
要想学好计算机网络,首先要了解的就是 OSI 七层模型或 TCP/IP 五层模型,即应用层(应用层、表示层、会话层)、传输层、网络层、数据链路层、物理层。
关于这门课,首先强烈推荐参考书是机械工业出版社的《计算机网络——自顶向下方法》。该书目录清晰,按照 TCP/IP 五层模型逐层讲解,对每层涉及的技术都展开了详细讨论,基本上高校里开设的课程的教学大纲就是这本书的目录了。
如果你觉得上面这本书看着比较枯燥的话,我强烈推荐+安利你看看下面这两本非常有趣的网络相关的书籍:
- 《图解 HTTP》:讲漫画一样的讲 HTTP,很有意思,不会觉得枯燥,大概也涵盖也 HTTP 常见的知识点。因为篇幅问题,内容可能不太全面。不过,如果不是专门做网络方向研究的小伙伴想研究 HTTP 相关知识的话,读这本书的话应该来说就差不多了。
- 《网络是怎样连接的》:从在浏览器中输入网址开始,一路追踪了到显示出网页内容为止的整个过程,以图配文,讲解了网络的全貌,并重点介绍了实际的网络设备和软件是如何工作的。
除了理论知识之外,学习计算机网络非常重要的一点就是:“动手实践”。这点和我们编程差不多。
GitHub 上就有一些名校的计算机网络试验/Project:
- 哈工大计算机网络实验
- 《计算机网络-自顶向下方法(原书第 6 版)》编程作业,Wireshark 实验文档的翻译和解答。
- 计算机网络的期末 Project,用 Python 编写的聊天室
- CMU 的计算机网络课程
我知道,还有很多小伙伴可能比较喜欢边看视频边学习。所以,我这里再推荐几个顶好的计算机网络视频讲解。
1、哈工大的计算机网络课程:国家精品课程,截止目前已经开了 10 次课了。大家对这门课的评价都非常高!所以,非常推荐大家看一下!
2、王道考研的计算机网络:非常适合 CS 专业考研的小朋友!这个视频目前在哔哩哔哩上已经有 1.6w+ 的点赞。
算法
先来看三本入门书籍。 这三本入门书籍中的任何一本拿来作为入门学习都非常好。
我个人比较倾向于 《我的第一本算法书》 这本书籍,虽然它相比于其他两本书集它的豆瓣评分略低一点。我觉得它的配图以及讲解是这三本书中最优秀,唯一比较明显的问题就是没有代码示例。但是,我觉得这不影响它是一本好的算法书籍。因为本身下面这三本入门书籍的目的就不是通过代码来让你的算法有多厉害,只是作为一本很好的入门书籍让你进入算法学习的大门。
再推荐几本比较经典的算法书籍。
这本书内容非常清晰易懂,适合数据结构和算法小白阅读。书中把一些常用的数据结构和算法都介绍到了!
我在大二的时候被我们的一个老师强烈安利过!自己也在当时购买了一本放在宿舍,到离开大学的时候自己大概看了一半多一点。因为内容实在太多了!另外,这本书还提供了详细的 Java 代码,非常适合学习 Java 的朋友来看,可以说是 Java 程序员的必备书籍之一了。
下面这些书籍都是经典中的经典,但是阅读起来难度也比较大,不做太多阐述,神书就完事了!
如果你仅仅是准备算法面试的话,不建议你阅读下面这些书籍。
经典名著,ACM 冠军、亚军这种算法巨佬都强烈推荐的一本书籍。这本书的作者也非常厉害,Java 之父 James Gosling 就是他的学生。
很多人都说这本书不是教你具体的算法,而是教你一种编程的思考方式。这种思考方式不仅仅在编程领域适用,在其他同样适用。
这是一本被 GitHub 上的爆火的计算机自学项目 Teach Yourself Computer Science 强烈推荐的一本算法书籍。
类似的神书还有 《算法导论》、《计算机程序设计艺术(第 1 卷)》 。
如果说你要准备面试的话,下面这几本书籍或许对你有帮助!
这本面试宝典上面涵盖了很多经典的算法面试题,如果你要准备大厂面试的话一定不要错过这本书。
《剑指 Offer》 对应的算法编程题部分的开源项目解析:CodingInterviews 。
《程序员代码面试指南(第 2 版)》里的大部分题目相比于《剑指 offer》 来说要难很多,题目涵盖面相比于《剑指 offer》也更加全面。全书一共有将近 300 道真实出现过的经典代码面试题。
视频的话,推荐北京大学的国家精品课程—程序设计与算法(二)算法基础,讲的非常好!
这个课程把七种基本的通用算法(枚举、二分、递归、分治、动态规划、搜索、贪心)都介绍到了。各种复杂算法问题的解决,都可能用到这些基本的思想。并且,这个课程的一部分的例题和 ACM 国际大学生程序设计竞赛中的中等题相当,如果你能够解决这些问题,那你的算法能力将超过绝大部分的高校计算机专业本科毕业生。
数据结构
其实,上面提到的很多算法类书籍(比如 《算法》 和 《算法导论》)都详细地介绍了常用的数据结构。
我这里再另外补充基本和数据结构相关的书籍。
入门类型的书籍,读起来比较浅显易懂,适合没有数据结构基础或者说数据结构没学好的小伙伴用来入门数据结构。
质量很高,介绍了常用的数据结构和算法。
类似的还有 《数据结构与算法分析:C 语言描述》、《数据结构与算法分析:C++ 描述》
视频的话推荐你看浙江大学的国家精品课程—《数据结构》 。
姥姥的数据结构讲的非常棒!不过,还是有一些难度的,尤其是课后练习题。
计算机专业基础课
数学和英语属于通用课,一般在大一和大二两学年就可以全部修完,大二大三逐渐接触专业课。通用课作为许多高中生升入大学的第一门课,算是高中阶段到本科阶段的一个过渡,从职业生涯重要性上来说,远不及专业课重要,但是在本科阶段的学习生活规划中,有着非常重要的地位。由于通用课的课程多,学分重,占据了本科阶段绩点的主要部分,影响到学生在前两年的专业排名,也影响到大三结束时的推免资格分配,也就是保研。而从升学角度来看,对于攻读研究生和博士生的小伙伴来说,数学和英语这两大基础课,还是十分有用的。
数学
微积分(高等数学)
微积分,即传说中的高数,成为了无数新大一心中的痛。但好在,大学的课程考核没那么严格,期末想要拿高分,也不至于像高中那样刷题刷的那么狠。微积分对于计算机专业学生的重要性,主要体现在计算机图形学中的函数变换,机器学习中的梯度算法,信号处理等领域。
微积分的知识体系包括微分和积分两部分,一般会先学微分,再学积分,也有的学校把高数分为两个学期。微分就是高中的导数的升级版,对于大一萌新来说还算比较友好。积分恰好是微分的逆运算,思想上对大一萌新来说比较新,一时半会可能接受不了。不过这门课所有的高校都有开设,而且大部分的名校都有配套的网课,教材也都打磨的非常出色,结合网课和教材的“啃书”学习模式,这门课一定不会落下。
书籍的话,推荐《普林斯顿微积分读本》。这本书详细讲解了微积分基础、极限、连续、微分、导数的应用、积分、无穷级数、泰勒级数与幂级数等内容。
线性代数(高等代数)
线性代数的思维模式就更加复杂了一些,它定义了一个全新的数学世界,所有的符号、定理都是全新的,唯一能尝试的去理解的方式,大概就是用几何的方式去理解线性代数了。由于线性代数和几何学有着密不可分的关系,比如空间变换的理论支撑就是线性代数,因此,网上有着各种“可视化学习线性代数”的学习资源,帮助理解线性代数的意义,有助于公式的记忆。
书籍的话,推荐中科大李尚志老师的 《线性代数学习指导》 。
概率论与数理统计
对于计算机专业的小伙伴来说,这门课可能是概率论更有用一点,而非数理统计。可能某些学校只开设概率论课程,也可能数理统计也教,但仅仅是皮毛。概率论的学习路线和微积分相似,就是一个个公式辅以实例,不像线性代数那么抽象,比较贴近生活。在现在的就业形势下,概率论与数理统计专业的学生,应该是数学专业最好就业的了,他们通常到岗位上会做一些数据分析的工作,因此,这门课程确实是数据分析的重要前置课程,概率论在机器学习中的重要性也就不言而喻了。
书籍的话,推荐 《概率论与数理统计教程》 。这本书共八章,前四章为概率论部分,主要叙述各种概率分布及其性质,后四章为数理统计部分,主要叙述各种参数估计与假设检验。
离散数学(集合论、图论、近世代数等)
离散数学是计算机专业的专属数学,但实际上对于本科毕业找工作的小伙伴来说,离散数学还并没有发挥它的巨大作用。离散数学的作用主要在在图研究等领域,理论性极强,需要读研深造的小伙伴尽可能地扎实掌握。
英语
英语算是大学里面比较灵活的一项技能了,有的人会说,“英语学的越好,对个人发展越有利”,此话说的没错,但是对于一些有着明确发展目标的小伙伴,可能英语技能并不在他们的技能清单内。接下来的这些话只针对计算机专业的小伙伴们哦。
英语课在大学本科一般只有前两年开设,小伙伴们可以记住,想用英语课来提升自己的英语水平的,可以打消这个念头了。 英语水平的提高全靠自己平时的积累和练习,以及有针对性的刷题。
英语的大学四六级一定要过。 这是必备技能,绝大部分就业岗位都要看四六级水平的,最起码要通过的。四级比高中英语稍微难一些,一般的小伙伴可能会卡在六级上,六级需要针对性的训练一下,因为大学期间能接触英语的实在太少了,每学期一门英语课是不足以保持自己的英语水平的。对于一些来自于偏远地区,高中英语基础薄弱的,考四六级会更加吃力。建议考前集中训练一下历年真题,辅以背一下高频词汇,四六级通过只需要 425 分,这个分数线还是比较容易达到的。稍微好一点的小伙伴可能冲一下 500 分,要是能考到 600 分的话,那是非常不错的水平了,算是简历上比较有亮点的一项。
英语的雅思托福考试只限于想要出国的小伙伴,以及应聘岗位对英语能力有特殊要求的。雅思托福考试裸考不容易通过,花钱去比较靠谱的校外补课班应该是一个比较好的选择。
对于计算机专业的小伙伴来说,英语能力还是比较重要的,虽然应聘的时候不会因为没有雅思托福成绩卡人,但是你起码要能够:
- 熟练使用英文界面的软件、系统等
- 对于外网的一些博客、bug 解决方案等,阅读无压力
- 熟练阅读英文文献
- 具备一定的英文论文的撰写能力
毕竟计算机语言就是字符语言,听说读写中最起码要满足读写这两项不过分吧。
编译原理
编译原理相比于前面介绍的专业课,地位显得不那么重要了。编译原理的重要性主要体现在:
- 底层语言、引擎或高级语言的开发,如 MySQL,Java 等
- 操作系统或嵌入式系统的开发
- 词法、语法、语义的思想,以及自动机思想
编译原理的重要前置课程就是形式语言与自动机,自动机的思想在词法分析当中有着重要应用,学习了这门课后,应该就会发现许多场景下,自动机算法的妙用了。
总的来说,这门课对于各位程序员的职业发展来说,相对不那么重要,但是从难度上来说,学习这门课可以对编程思想有一个较好的巩固。学习资源的话,除了课堂上的幻灯片课件以外,还可以把 《编译原理》 这本书作为参考书,用以辅助自己学不懂的地方(大家口中的龙书,想要啃下来还是有一定难度的)。
其他书籍推荐:
我上面推荐的书籍的难度还是比较高的,真心很难坚持看完。这里强烈推荐哈工大的编译原理视频课程,真心不错,还是国家精品课程,关键还是又漂亮有温柔的美女老师讲的!
Books Database (`docs/books/database.md`)
title: 数据库必读经典书籍
category: 计算机书籍
icon: "mdi:database-outline"
head:
- meta
- name: keywords
content: 数据库书籍精选
数据库基础
数据库基础这块,如果你觉得书籍比较枯燥,自己坚持不下来的话,我推荐你可以先看看一些不错的视频,北京师范大学的《数据库系统原理》、哈尔滨工业大学的《数据库系统(下):管理与技术》就很不错。
《数据库系统原理》这个课程的老师讲的非常详细,而且每一小节的作业设计的也与所讲知识很贴合,后面还有很多配套实验。
如果你比较喜欢动手,对于理论知识比较抵触的话,推荐你看看《如何开发一个简单的数据库》 ,这个 project 会手把手教你编写一个简单的数据库。
GitHub 上也已经有大佬用 Java 实现过一个简易的数据库,介绍的挺详细的,感兴趣的朋友可以去看看。地址:https://github.com/alchemystar/Freedom 。
除了这个用 Java 写的之外,db_tutorial 这个项目是国外的一个大佬用 C 语言写的,朋友们也可以去瞅瞅。
只要利用好搜索引擎,你可以找到各种语言实现的数据库玩具。
纸上学来终觉浅 绝知此事要躬行!强烈推荐 CS 专业的小伙伴一定要多多实践!!!
《数据库系统概念》
《数据库系统概念》这本书涵盖了数据库系统的全套概念,知识体系清晰,是学习数据库系统非常经典的教材!不是参考书!
《数据库系统实现》
如果你也想要研究 MySQL 底层原理的话,我推荐你可以先阅读一下《数据库系统实现》。
不管是 MySQL 还是 Oracle ,它们总体的架子是差不多的,不同的是其内部的实现比如数据库索引的数据结构、存储引擎的实现方式等等。
这本书有些地方还是翻译的比较蹩脚,有能力看英文版的还是建议上手英文版。
《数据库系统实现》 这本书是斯坦福的教材,另外还有一本《数据库系统基础教程》是前置课程,可以带你入门数据库。
MySQL
我们网站或者 APP 的数据都是需要使用数据库来存储数据的。
一般企业项目开发中,使用 MySQL 比较多。如果你要学习 MySQL 的话,可以看下面这 3 本书籍:
- 《MySQL 必知必会》:非常薄!非常适合 MySQL 新手阅读,很棒的入门教材。
- 《高性能 MySQL》:MySQL 领域的经典之作!学习 MySQL 必看!属于进阶内容,主要教你如何更好地使用 MySQL 。既有有理论,又有实践!如果你没时间都看一遍的话,我建议第 5 章(创建高性能的索引)、第 6 章(查询性能优化) 你一定要认真看一下。
- 《MySQL 技术内幕》:你想深入了解 MySQL 存储引擎的话,看这本书准没错!
视频的话,你可以看看动力节点的 《MySQL 数据库教程视频》。这个视频基本上把 MySQL 的相关一些入门知识给介绍完了。
另外,强推一波 《MySQL 是怎样运行的》 这本书,内容很适合拿来准备面试。讲的很细节,但又不枯燥,内容非常良心!
PostgreSQL
和 MySQL 一样,PostgreSQL 也是开源免费且功能强大的关系型数据库。PostgreSQL 的 Slogan 是“世界上最先进的开源关系型数据库” 。
最近几年,由于 PostgreSQL 的各种新特性过于优秀,使用 PostgreSQL 代替 MySQL 的项目越来越多了。
如果你还在纠结是否尝试一下 PostgreSQL 的话,建议你看看这个知乎话题:PostgreSQL 与 MySQL 相比,优势何在? - 知乎 。
《PostgreSQL 指南:内幕探索》
《PostgreSQL 指南:内幕探索》这本书主要介绍了 PostgreSQL 内部的工作原理,包括数据库对象的逻辑组织与物理实现,进程与内存的架构。
刚工作那会需要用到 PostgreSQL ,看了大概 1/3 的内容,感觉还不错。
《PostgreSQL 技术内幕:查询优化深度探索》
《PostgreSQL 技术内幕:查询优化深度探索》这本书主要讲了 PostgreSQL 在查询优化上的一些技术实现细节,可以让你对 PostgreSQL 的查询优化器有深层次的了解。
Redis
Redis 就是一个使用 C 语言开发的数据库,不过与传统数据库不同的是 Redis 的数据是存在内存中的 ,也就是它是内存数据库,所以读写速度非常快,因此 Redis 被广泛应用于缓存方向。
如果你要学习 Redis 的话,强烈推荐下面这两本书:
- 《Redis 设计与实现》 :主要是 Redis 理论知识相关的内容,比较全面。我之前写过一篇文章 《7 年前,24 岁,出版了一本 Redis 神书》 来介绍这本书。
- 《Redis 核心原理与实践》:主要是结合源码来分析 Redis 的重要知识点比如各种数据结构和高级特性。
另外,《Redis 开发与运维》 这本书也非常不错,既有基础介绍,又有一线开发运维经验分享。
Books Distributed System (`docs/books/distributed-system.md`)
title: 分布式必读经典书籍 category: 计算机书籍 icon: "mdi:transit-connection-variant"
《深入理解分布式系统》
《深入理解分布式系统》 是 2022 年出版的一本分布式中文原创书籍,主要讲的是分布式领域的基本概念、常见挑战以及共识算法。
作者用了大量篇幅来介绍分布式领域中非常重要的共识算法,并且还会基于 Go 语言带着你从零实现了一个共识算法的鼻祖 Paxos 算法。
实话说,我还没有开始看这本书。但是!这本书的作者的博客上的分布式相关的文章我几乎每一篇都认真看过。作者从 2019 年开始构思《深入理解分布式系统》,2020 年开始动笔,花了接近两年的时间才最终交稿。
作者专门写了一篇文章来介绍这本书的背后的故事,感兴趣的小伙伴可以自行查阅:https://zhuanlan.zhihu.com/p/487534882 。
最后,放上这本书的代码仓库和勘误地址:https://github.com/tangwz/DistSysDeepDive 。
《数据密集型应用系统设计》
强推一波 《Designing Data-Intensive Application》 (DDIA,数据密集型应用系统设计),值得读很多遍!豆瓣有接近 90% 的人看了这本书之后给了五星好评。
这本书主要讲了分布式数据库、数据分区、事务、分布式系统等内容。
书中介绍的大部分概念你可能之前都听过,但是在看了书中的内容之后,你可能会豁然开朗:“哇塞!原来是这样的啊!这不是某技术的原理么?”。
这本书我之前专门写过知乎回答介绍和推荐,没看过的朋友可以看看:有哪些你看了以后大呼过瘾的编程书? 。另外,如果你在阅读这本书的时候感觉难度比较大,很多地方读不懂的话,我这里推荐一下《深入理解分布式系统》作者写的《DDIA 逐章精读》小册。
《深入理解分布式事务》
《深入理解分布式事务》 这本书的其中一位作者是 Apache ShenYu(incubating)网关创始人、Hmily、RainCat、Myth 等分布式事务框架的创始人。
学习分布式事务的时候,可以参考一下这本书。虽有一些小错误以及逻辑不通顺的地方,但对于各种分布式事务解决方案的介绍,总体来说还是不错的。
《从 Paxos 到 Zookeeper》
《从 Paxos 到 Zookeeper》 是一本带你入门分布式理论的好书。这本书主要介绍几种典型的分布式一致性协议,以及解决分布式一致性问题的思路,其中重点讲解了 Paxos 和 ZAB 协议。
PS:Zookeeper 现在用的不多,可以不用重点学习,但 Paxos 和 ZAB 协议还是非常值得深入研究的。
《深入理解分布式共识算法》
《深入理解分布式共识算法》 详细剖析了 Paxos、Raft、Zab 等主流分布式共识算法的核心原理和实现细节。如果你想要了解分布式共识算法的话,不妨参考一下这本书的总结。
《微服务架构设计模式》
《微服务架构设计模式》 的作者 Chris Richardson 被评为世界十大软件架构师之一、微服务架构先驱。这本书汇集了 44 个经过实践验证的架构设计模式,这些模式用来解决诸如服务拆分、事务管理、查询和跨服务通信等难题。书中的内容不仅理论扎实,还通过丰富的 Java 代码示例,引导读者一步步掌握开发和部署生产级别的微服务架构应用。
《凤凰架构》
《凤凰架构》 这本书是周志明老师多年架构和研发经验的总结,内容非常干货,深度与广度并存,理论结合实践!
正如书名的副标题“构建可靠的大型分布式系统”所说的那样,这本书的主要内容就是讲:“如何构建一套可靠的分布式大型软件系统” ,涵盖了下面这些方面的内容:
- 软件架构从单体到微服务再到无服务的演进之路。
- 架构师应该在架构设计时应该注意哪些问题,有哪些比较好的实践。
- 分布式的基石比如常见的分布式共识算法 Paxos、Multi Paxos。
- 不可变基础设施比如虚拟化容器、服务网格。
- 向微服务迈进的避坑指南。
这本书我推荐过很多次了。详见历史文章:
其他
- 《分布式系统 : 概念与设计》:偏教材类型,内容全而无趣,可作为参考书籍;
- 《分布式架构原理与实践》:2021 年出版的,没什么热度,我也还没看过。
Books Java (`docs/books/java.md`)
title: Java 必读经典书籍 category: 计算机书籍 icon: "mdi:language-java"
Java 基础
《Head First Java》这本书的内容很轻松有趣,可以说是我学习编程初期最喜欢的几本书之一了。同时,这本书也是我的 Java 启蒙书籍。我在学习 Java 的初期多亏了这本书的帮助,自己才算是跨进 Java 语言的大门。
我觉得我在 Java 这块能够坚持下来,这本书有很大的功劳。我身边的很多朋友学习 Java 初期都是看的这本书。
有很多小伙伴就会问了:这本书适不适合编程新手阅读呢?
我个人觉得这本书还是挺适合编程新手阅读的,毕竟是 “Head First” 系列。
这两本书也非常不错。不过,这两本书的内容很多,全看的话比较费时间。我现在是把这两本书当做工具书来用,就比如我平时写文章的时候,碰到一些 Java 基础方面的问题,经常就翻看这两本来当做参考!
我当时在大学的时候就买了两本放在寝室,没事的时候就翻翻。建议有点 Java 基础之后再读,介绍的还是比较深入和全面的,非常推荐。
另外,这本书的作者去年新出版了《On Java》,我更推荐这本,内容更新,介绍了 Java 的 3 个长期支持版(Java 8、11、17)。
毕竟,这是市面上目前唯一一本介绍了 Java 的 3 个长期支持版(Java 8、11、17)的技术书籍。
Java 8 算是一个里程碑式的版本,现在一般企业还是用 Java 8 比较多。掌握 Java 8 的一些新特性比如 Lambda、Stream API 还是挺有必要的。这块的话,我推荐 《Java 8 实战》 这本书。
一本非常低调的好书,相比于入门书来说,内容更有深度。适合初学者,同时也适合大家拿来复习 Java 基础知识。
Java 并发
这本书还是非常适合我们用来学习 Java 多线程的,讲解非常通俗易懂,作者从并发编程基础到实战都是信手拈来。
另外,这本书的作者加多自身也会经常在网上发布各种技术文章。这本书也是加多大佬这么多年在多线程领域的沉淀所得的结果吧!他书中的内容基本都是结合代码讲解,非常有说服力!
这个是我第二本要推荐的书籍,比较适合作为多线程入门/进阶书籍来看。这本书内容同样是理论结合实战,对于每个知识点的讲解也比较通俗易懂,整体结构也比较清。
这本开源书籍是几位大厂的大佬开源的。这几位作者为了写好《深入浅出 Java 多线程》这本书阅读了大量的 Java 多线程方面的书籍和博客,然后再加上他们的经验总结、Demo 实例、源码解析,最终才形成了这本书。
这本书的质量也是非常过硬!给作者们点个赞!这本书有统一的排版规则和语言风格、清晰的表达方式和逻辑。并且每篇文章初稿写完后,作者们就会互相审校,合并到主分支时所有成员会再次审校,最后再通篇修订了三遍。
在线阅读:https://redspider.gitbook.io/concurrent/。
这本书主要是对 Java Concurrent 包中一些比较重要的源码进行了讲解,另外,像 JMM、happen-before、CAS 等等比较重要的并发知识这本书也都会一并介绍到。
不论是你想要深入研究 Java 并发,还是说要准备面试,你都可以看看这本书。
JVM
这本书就一句话形容:国产书籍中的战斗机,实实在在的优秀! (真心希望国内能有更多这样的优质书籍出现!加油!💪)
这本书的第 3 版 2019 年底已经出来了,新增了很多实在的内容比如 ZGC 等新一代 GC 的原理剖析。目前豆瓣上是 9.5 的高分,🐂 不 🐂 我就不多说了!
不论是你面试还是你想要在 Java 领域学习的更深,你都离不开这本书籍。这本书不光要看,你还要多看几遍,里面都是干货。这本书里面还有一些需要自己实践的东西,我建议你也跟着实践一下。
类似的书籍还有 《实战 Java 虚拟机》、《虚拟机设计与实现:以 JVM 为例》 ,这两本都是非常不错的!
如果你对实战比较感兴趣,想要自己动手写一个简易的 JVM 的话,可以看看 《自己动手写 Java 虚拟机》 这本书。
书中的代码是基于 Go 语言实现的,搞懂了原理之后,你可以使用 Java 语言模仿着写一个,也算是练练手! 如果你当前没有能力独立使用 Java 语言模仿着写一个的话,你也可以在网上找到很多基于 Java 语言版本的实现,比如《zachaxy 的手写 JVM 系列》 。
这本书目前在豆瓣有 8.2 的评分,我个人觉得张秀宏老师写的挺好的,这本书值得更高的评分。
另外,R 大在豆瓣发的《从表到里学习 JVM 实现》这篇文章中也推荐了很多不错的 JVM 相关的书籍,推荐小伙伴们去看看。
常用工具
非常重要!非常重要!特别是 Git 和 Docker。
- IDEA:熟悉基本操作以及常用快捷。相关资料: 《IntelliJ IDEA 简体中文专题教程》 。
- Maven:强烈建议学习常用框架之前可以提前花几天时间学习一下Maven的使用。(到处找 Jar 包,下载 Jar 包是真的麻烦费事,使用 Maven 可以为你省很多事情)。相关阅读:Maven 核心概念总结。
- Git:基本的 Git 技能也是必备的,试着在学习的过程中将自己的代码托管在 Github 上。相关阅读:Git 核心概念总结。
- Docker:学着用 Docker 安装学习中需要用到的软件比如 MySQL ,这样方便很多,可以为你节省不少时间。相关资料:《Docker - 从入门到实践》 。
除了这些工具之外,我强烈建议你一定要搞懂 GitHub 的使用。一些使用 GitHub 的小技巧,你可以看Github 实用小技巧总结这篇文章。
常用框架
框架部分建议找官方文档或者博客来看。
Spring/SpringBoot
Spring 和 SpringBoot 真的很重要!
一定要搞懂 AOP 和 IOC 这两个概念。Spring 中 bean 的作用域与生命周期、SpringMVC 工作原理详解等等知识点都是非常重要的,一定要搞懂。
企业中做 Java 后端,你一定离不开 SpringBoot ,这个是必备的技能了!一定一定一定要学好!
像 SpringBoot 和一些常见技术的整合你也要知识怎么做,比如 SpringBoot 整合 MyBatis、 ElasticSearch、SpringSecurity、Redis 等等。
下面是一些比较推荐的书籍/专栏。
不建议当做入门书籍读,入门的话可以找点国人的书或者视频看。这本定位就相当于是关于 Spring 的一个概览,只有一些基本概念的介绍和示例,涵盖了 Spring 的各个方面,但都不够深入。就像作者在最后一页写的那样:“学习 Spring,这才刚刚开始”。
对于 Spring5 的新特性介绍的比较详细,也说不上好。另外,感觉全书翻译的有一点蹩脚的味道,还有一点枯燥。全书的内容比较多,我一般拿来当做工具书参考。
稍微有点啰嗦,但是原理介绍的比较清楚。
SpringBoot 解析,不适合初学者。我是去年入手的,现在就看了几章,后面没看下去。书很厚,感觉很多很多知识点的讲解过于啰嗦和拖沓,不过,这本书对于 SpringBoot 内部原理讲解的还是很清楚。
比较一般的一本书,可以简单拿来看一下。
MyBatis
MyBatis 国内用的挺多的,我的建议是不需要花太多时间在上面。当然了,MyBatis 的源码还是非常值得学习的,里面有很多不错的编码实践。这里推荐两本讲解 MyBatis 源码的书籍。
我的好朋友小傅哥出版的一本书。这本书以实践为核心,摒弃 MyBatis 源码中繁杂的内容,聚焦于 MyBaits 中的核心逻辑,简化代码实现过程,以渐进式的开发方式,逐步实现 MyBaits 中的核心功能。
这本书的配套项目的仓库地址:https://github.com/fuzhengwei/small-mybatis 。
这本书通过 MyBatis 开源代码讲解源码阅读的流程和方法!一共对 MyBatis 源码中的 300 多个类进行了详细解析,包括其背景知识、组织方式、逻辑结构、实现细节。
这本书的配套示例仓库地址:https://github.com/yeecode/MyBatisDemo 。
Netty
这本书可以用来入门 Netty ,内容从 BIO 聊到了 NIO、之后才详细介绍为什么有 Netty、Netty 为什么好用以及 Netty 重要的知识点讲解。
这本书基本把 Netty 一些重要的知识点都介绍到了,而且基本都是通过实战的形式讲解。
内容都是关于使用 Netty 的实践案例比如内存泄露这些东西。如果你觉得你的 Netty 已经完全入门了,并且你想要对 Netty 掌握的更深的话,推荐你看一下这本书。
《跟闪电侠学 Netty:Netty 即时聊天实战与底层原理》
2022 年 3 月出版的一本书。这本书分为上下两篇,上篇通过一个即时聊天系统的实战案例带你入门 Netty,下篇通过 Netty 源码分析带你搞清 Netty 比较重要的底层原理。
性能调优
希望能有更多这 Java 性能优化方面的好书!
O'Reilly 家族书,性能调优的入门书,我个人觉得性能调优是每个 Java 从业者必备知识。
这本书介绍的实战内容很不错,尤其是 JVM 调优,缺点也比较明显,就是内容稍微有点老。市面上这种书很少。这本书不适合初学者,建议对 Java 语言已经比价掌握了再看。另外,阅读之前,最好先看看周志明大佬的《深入理解 Java 虚拟机》。
网站架构
看过很多网站架构方面的书籍,比如《大型网站技术架构:核心原理与案例分析》、《亿级流量网站架构核心技术》、《架构修炼之道——亿级网关、平台开放、分布式、微服务、容错等核心技术修炼实践》等等。
目前我觉得能推荐的只有李运华老师的 《从零开始学架构》 和 余春龙老师的 《软件架构设计:大型网站技术架构与业务架构融合之道》 。
《从零开始学架构》这本书对应的有一个极客时间的专栏—《从零开始学架构》,里面的很多内容都是这个专栏里面的,两者买其一就可以了。我看了很小一部分,内容挺全面的,是一本真正在讲如何做架构的书籍。
事务与锁、分布式(CAP、分布式事务……)、高并发、高可用 《软件架构设计:大型网站技术架构与业务架构融合之道》 这本书都有介绍到。
面试
《JavaGuide 面试突击版》
JavaGuide 的面试版本,涵盖了 Java 后端方面的大部分知识点比如 集合、JVM、多线程还有数据库 MySQL 等内容。
公众号后台回复:“面试突击” 即可免费获取,无任何套路。
Books Search Engine (`docs/books/search-engine.md`)
title: 搜索引擎必读经典书籍 category: 计算机书籍 icon: "mdi:magnify"
Lucene
Elasticsearch 在 Apache Lucene 的基础上开发而成,学习 ES 之前,建议简单了解一下 Lucene 的相关概念。
《Lucene 实战》 是国内为数不多的中文版本讲 Lucene 的书籍,适合用来学习和了解 Lucene 相关的概念和常见操作。
Elasticsearch
《一本书讲透 Elasticsearch:原理、进阶与工程实践》
基于 8.x 版本编写,目前全网最新的 Elasticsearch 讲解书籍。内容覆盖 Elastic 官方认证的核心知识点,源自真实项目案例和企业级问题解答。
极客时间的这门课程基于 Elasticsearch 7.1 版本讲解,还算比较新。并且,作者是 eBay 资深技术专家,有 20 年的行业经验,课程质量有保障!
如果你想进一步深入研究 Elasticsearch 原理的话,可以看看张超老师的这本书。这是市面上唯一一本写 Elasticsearch 源码的书。
Books Software Quality (`docs/books/software-quality.md`)
title: 软件质量必读经典书籍
category: 计算机书籍
icon: "mdi:check-network-outline"
head:
- meta
- name: keywords
content: 软件质量书籍精选
下面推荐都是我看过并且我觉得值得推荐的书籍。
不过,这些书籍都比较偏理论,只能帮助你建立一个写优秀代码的意识标准。 如果你想要编写更高质量的代码、更高质量的软件,还是应该多去看优秀的源码,多去学习优秀的代码实践。
代码整洁之道
必看书籍!无需多言。编程书籍领域的瑰宝。
世界顶级、国宝级别的 Martin Fowler 的书籍,可以说是软件开发领域最经典的几本书之一。目前已经出了第二版。
这是一本值得你看很多遍的书籍。
《Clean Code》是 Bob 大叔的一本经典著作,强烈建议小伙伴们一定要看看。
Bob 大叔将自己对整洁代码的理解浓缩在了这本书中,真可谓是对后生的一大馈赠。
《Effective Java 》这本书是 Java 领域国宝级别的书,非常经典。Java 程序员必看!
这本书主要介绍了在 Java 编程中很多极具实用价值的经验规则,这些经验规则涵盖了大多数开发人员每天所面临的问题的解决方案。这篇文章能够非常实际地帮助你写出更加清晰、健壮和高效的代码。本书中的每条规则都以简短、独立的小文章形式出现,并通过例子代码加以进一步说明。
其实,《代码大全(第 2 版)》这本书我本身是不太想推荐给大家了。但是,看在它的豆瓣评分这么高的份上,还是拿出来说说吧!
这也是一本非常经典的书籍,第二版对第一版进行了重写。
我简单地浏览过全书的内容,感觉内容总体比较虚,对于大部分程序员的作用其实不大。如果你想要切实地提高自己的代码质量,《Clean Code》和 《编写可读代码的艺术》我觉得都要比《代码大全》这本书更好。
不过,最重要的还是要多看优秀的源码,多学习优秀的代码实践。
《编写可读代码的艺术》这本书要表达的意思和《Clean Code》很像,你看它俩的目录就可以看出来了。
在我看来,如果你看过 《Clean Code》 的话,就不需要再看这本书了。当然,如果你有时间和精力,也可以快速过一遍。
另外,我这里还要推荐一个叫做 write-readable-code 的仓库。这个仓库的作者免费分享了一系列基于《编写可读代码的艺术》这本书的视频。这一系列视频会基于 Java 语言来教你如何优化咱们的代码。
在实践中学习的效果肯定会更好!推荐小伙伴们都抓紧学起来啊!
程序员职业素养
《 The Clean Coder》是 Bob 大叔的又一经典著作。
《Clean Code》和《 The Clean Coder》这两本书在国内都翻译为 《代码整洁之道》,我觉得这个翻译还是不够优雅的。
另外,两者的内容差异也很大。《Clean Code》这本书从代码层面来讲解如何提高自己的代码质量。而《The Clean Coder》这本书则是从如何成为一名更优秀的开发者的角度来写的,比如这书会教你如何在自己的领域更专业、如何说不、如何做时间管理、如何处理压力等等。
架构整洁之道
你没看错,《架构整洁之道》这本书又是 Bob 大叔的经典之作。
这本书我强烈安利!认真读完之后,我保证你对编程本质、编程语言的本质、软件设计、架构设计可以有进一步的认识。
国内的很多书籍和专栏都借鉴了《架构整洁之道》 这本书。毫不夸张地说,《架构整洁之道》就是架构领域最经典的书籍之一。
正如作者说的那样:
如果深入研究计算机编程的本质,我们就会发现这 50 年来,计算机编程基本没有什么大的变化。编程语言稍微进步了一点,工具的质量大大提升了,但是计算机程序的基本构造没有什么变化。
虽然我们有了新的编程语言、新的编程框架、新的编程范式,但是软件架构的规则仍然和 1946 年阿兰·图灵写下第一行机器代码的时候一样。
这本书就是为了把这些永恒不变的软件架构规则展现出来。
项目管理
这本书主要描述了软件开发的基本定律:一个需要 10 天才能干完的活,不可能让 10 个人在 1 天干完!
看书名的第一眼,感觉不像是技术类的书籍。但是,就是这样一个看似和编程不沾边的书名,却成了编程领域长久相传的经典。
这本书对于现代软件尤其是复杂软件的开发的规范化有深刻的意义。
这本领域驱动设计方面的经典之作一直被各种推荐,但是我还来及读。
其他
- 《代码的未来》:这本书的作者是 Ruby 之父松本行弘,算是一本年代比较久远的书籍(13 年出版),不过,还是非常值得一读。这本书的内容主要介绍是编程/编程语言的本质。我个人还是比较喜欢松本行弘的文字风格,并且,你看他的文章也确实能够有所收获。
- 《深入浅出设计模式》:比较有趣的风格,适合设计模式入门。
- 《软件架构设计:大型网站技术架构与业务架构融合之道》:内容非常全面。适合面试前突击一些比较重要的理论知识,也适合拿来扩充/完善自己的技术广度。
- 《微服务架构设计模式》:这本书是世界十大软件架构师之一、微服务架构先驱 Chris Richardson 亲笔撰写,豆瓣评分 9.6。示例代码使用 Java 语言和 Spring 框架。帮助你设计、实现、测试和部署基于微服务的应用程序。
最后再推荐两个相关的文档:
- 阿里巴巴 Java 开发手册:https://github.com/alibaba/p3c
- Google Java 编程风格指南:http://www.hawstein.com/posts/google-java-style.html
Database Basis (`docs/database/basis.md`)
title: 数据库基础常见面试题总结
category: 数据库
tag:
- 数据库基础
head: - meta
- name: keywords
content: 数据库,数据库管理系统,DBMS,数据库系统,DBA,SQL,DDL,DML,数据模型,关系型数据库,主键,外键,ER图
数据库知识基础,这部分内容一定要理解记忆。虽然这部分内容只是理论知识,但是非常重要,这是后面学习 MySQL 数据库的基础。PS: 这部分内容由于涉及太多概念性内容,所以参考了维基百科和百度百科相应的介绍。
什么是数据库, 数据库管理系统, 数据库系统, 数据库管理员?
这四个概念描述了从数据本身到管理整个体系的不同层次,我们常用一个图书馆的例子来把它们串联起来理解。
- 数据库 (Database - DB): 它就像是图书馆里,书架上存放的所有书籍和资料。从技术上讲,数据库就是按照一定数据模型组织、描述和储存起来的、可以被各种用户共享的结构化数据的集合。它就是我们最终要存取的核心——信息本身。
- 数据库管理系统 (Database Management System - DBMS): 它就像是整个图书馆的管理系统,包括图书的分类编目规则、借阅归还流程、安全检查系统等等。从技术上讲,DBMS 是一种大型软件,比如我们常用的 MySQL、Oracle、PostgreSQL 软件。它的核心职责是科学地组织和存储数据、高效地获取和维护数据;为我们屏蔽了底层文件操作的复杂性,提供了一套标准接口(如 SQL)来操纵数据,并负责并发控制、事务管理、权限控制等复杂问题。
- 数据库系统 (Database System - DBS): 它就是整个正常运转的图书馆。这是一个更大的概念,不仅包括书(DB)和管理系统(DBMS),还包括了硬件、应用和使用的人。
- 数据库管理员 (Database Administrator - DBA ): 他就是图书馆的馆长,负责整个数据库系统正常运行。他的职责非常广泛,包括数据库的设计、安装、监控、性能调优、备份与恢复、安全管理等等,确保整个系统的稳定、高效和安全。
DB 和 DBMS 我们通常会搞混,这里再简单提一下:通常我们说“用 MySQL 数据库”,其实是用 MySQL(DBMS)来管理一个或多个数据库(DB)。
DBMS 有哪些主要的功能
/* Detailed source-code truncated for AI context efficiency. */DBMS 通常提供四大核心功能:
- 数据定义: 这是 DBMS 的基础。它提供了一套数据定义语言(Data Definition Language - DDL),让我们能够创建、修改和删除数据库中的各种对象。这不仅仅是定义表的结构(比如字段名、数据类型),还包括定义视图、索引、触发器、存储过程等。
- 数据操作: 这是我们作为开发者日常使用最多的功能。它提供了一套数据操作语言(Data Manipulation Language - DML),核心就是我们熟悉的增、删、改、查(CRUD)操作。它让我们能够方便地对数据库中的数据进行操作和检索。
- 数据控制: 这是保证数据正确、安全、可靠的关键。通常包含并发控制、事务管理、完整性约束、权限控制、安全性限制等功能。
- 数据库维护: 这部分功能是为了保障数据库系统的长期稳定运行。它包括了数据的导入导出、数据库的备份与恢复、性能监控与分析、以及系统日志管理等。
你知道哪些类型的 DBMS?
关系型数据库
除了我们最常用的关系型数据库(RDBMS),比如 MySQL(开源首选)、PostgreSQL(功能最全)、Oracle(企业级),它们基于严格的表结构和 SQL,非常适合结构化数据和需要事务保证的场景,例如银行交易、订单系统。
近年来,为了应对互联网应用带来的海量数据、高并发和多样化数据结构的需求,涌现出了一大批 NoSQL 和 NewSQL 数据库。
NoSQL 数据库
它们的共同特点是为了极致的性能和水平扩展能力,在某些方面(通常是事务)做了妥协。
1. 键值数据库,代表是 Redis。
- 特点: 数据模型极其简单,就是一个巨大的 Map,通过 Key 来存取 Value。内存操作,性能极高。
- 适用场景: 非常适合做缓存、会话存储、计数器等对读写性能要求极高的场景。
2. 文档数据库,代表是 MongoDB。
- 特点: 它存储的是半结构化的文档(比如 JSON/BSON),结构灵活,不需要预先定义表结构。
- 适用场景: 特别适合那些数据结构多变、快速迭代的业务,比如用户画像、内容管理系统、日志存储等。
3. 列式数据库,代表是 HBase, Cassandra。
- 特点: 数据是按列族而不是按行来存储的。这使得它在对大量行进行少量列的读取时,性能极高。
- 适用场景: 专为海量数据存储和分析设计,非常适合做大数据分析、监控数据存储、推荐系统等需要高吞吐量写入和范围扫描的场景。
4. 图形数据库,代表是 Neo4j。
- 特点: 数据模型是节点(Nodes)和边(Edges),专门用来存储和查询实体之间的复杂关系。
- 适用场景: 在社交网络(好友关系)、推荐引擎(用户-商品关系)、知识图谱、欺诈检测(资金流动关系)等场景下,表现远超关系型数据库。
NewSQL 数据库
由于 NoSQL 不支持事务,很多对于数据安全要求非常高的系统(比如财务系统、订单系统、交易系统)就不太适合使用了。不过,这类系统往往有存储大量数据的需求。
这些系统往往只能通过购买性能更强大的计算机,或者通过数据库中间件来提高存储能力。不过,前者的金钱成本太高,后者的开发成本太高。
于是,NewSQL 就来了!
简单来说,NewSQL 就是:分布式存储+SQL+事务 。NewSQL 不仅具有 NoSQL 对海量数据的存储管理能力,还保持了传统数据库支持 ACID 和 SQL 等特性。因此,NewSQL 也可以称为 分布式关系型数据库。
NewSQL 数据库设计的一些目标:
- 横向扩展(Scale Out) : 通过增加机器的方式来提高系统的负载能力。与之类似的是 Scale Up(纵向扩展),升级硬件设备的方式来提高系统的负载能力。
- 强一致性(Strict Consistency):在任意时刻,所有节点中的数据是一样的。
- 高可用(High Availability):系统几乎可以一直提供服务。
- 支持标准 SQL(Structured Query Language) :PostgreSQL、MySQL、Oracle 等关系型数据库都支持 SQL 。
- 事务(ACID) : 原子性(Atomicity)、一致性(Consistency)、 隔离性(Isolation); 持久性(Durability)。
- 兼容主流关系型数据库 : 兼容 MySQL、Oracle、PostgreSQL 等常用关系型数据库。
- 云原生 (Cloud Native):可在公有云、私有云、混合云中实现部署工具化、自动化。
- HTAP(Hybrid Transactional/Analytical Processing) :支持 OLTP 和 OLAP 混合处理。
NewSQL 数据库代表:Google 的 F1/Spanner、阿里的 OceanBase、PingCAP 的 TiDB 。
什么是元组, 码, 候选码, 主码, 外码, 主属性, 非主属性?
在关系型数据库理论中,理解元组、码、候选码、主码、外码、主属性和非主属性这些核心概念,对于数据库设计和规范化至关重要。这些概念构成了关系数据库的理论基础。
graph TD
A[关系数据库概念] --> B[数据组织]
A --> C[码的类型]
A --> D[属性分类]
B --> B1[元组<br/>表中的行记录]
B --> B2[属性<br/>表中的列]
C --> C1[码<br/>唯一标识]
C1 --> C2[候选码<br/>最小唯一标识集]
C2 --> C3[主码<br/>选定的候选码]
C1 --> C4[外码<br/>引用其他表主码]
D --> D1[主属性<br/>候选码中的属性]
D --> D2[非主属性<br/>不在候选码中的属性]
C3 -.关联.-> C4
C2 -.构成.-> D1
style A fill:#4CA497,stroke:#00838F,stroke-width:3px,color:#fff
style B fill:#00838F,stroke:#005D7B,stroke-width:2px,color:#fff
style C fill:#E99151,stroke:#005D7B,stroke-width:2px,color:#fff
style D fill:#005D7B,stroke:#00838F,stroke-width:2px,color:#fff
style B1 fill:#E4C189,stroke:#00838F,stroke-width:1px
style B2 fill:#E4C189,stroke:#00838F,stroke-width:1px
style C1 fill:#E4C189,stroke:#E99151,stroke-width:1px
style C2 fill:#E4C189,stroke:#E99151,stroke-width:1px
style C3 fill:#C44545,stroke:#005D7B,stroke-width:2px,color:#fff
style C4 fill:#E4C189,stroke:#E99151,stroke-width:1px
style D1 fill:#E4C189,stroke:#005D7B,stroke-width:1px
style D2 fill:#E4C189,stroke:#005D7B,stroke-width:1px基础概念
- 元组(Tuple): 元组是关系数据库中的基本单位,在二维表中对应一行记录。每个元组包含了一个实体的完整信息。例如,在学生表中,每个学生的完整信息(学号、姓名、年龄等)构成一个元组。
- 码(Key): 码是能够唯一标识关系中元组的一个或多个属性的集合。码的主要作用是保证数据的唯一性和完整性。
码的分类
- 候选码(Candidate Key): 候选码是能够唯一标识元组的最小属性集合,其任何真子集都不能唯一标识元组。一个关系可能有多个候选码。例如,在学生表中,如果"学号"能唯一标识学生,同时"身份证号"也能唯一标识学生,那么{学号}和{身份证号}都是候选码。
- 主码/主键(Primary Key): 主码是从候选码中选择的一个,用于唯一标识关系中的元组。每个关系只能有一个主码,但可以有多个候选码。选择主码时通常考虑:简单性、稳定性、无业务含义等因素。
- 外码/外键(Foreign Key): 外码是一个关系中的属性或属性组,它对应另一个关系的主码。外码用于建立和维护两个关系之间的联系,是实现参照完整性的重要机制。例如,在选课表中的"学号"如果引用学生表的主码"学号",则选课表中的"学号"就是外码。
属性分类
- 主属性(Prime Attribute): 主属性是包含在任何一个候选码中的属性。如果一个关系有多个候选码,那么这些候选码中出现的所有属性都是主属性。例如,工人关系(工号,身份证号,姓名,性别,部门)中,如果{工号}和{身份证号}都是候选码,那么"工号"和"身份证号"都是主属性。
- 非主属性(Non-prime Attribute): 非主属性是不包含在任何候选码中的属性。这些属性完全依赖于候选码来确定其值。在上述工人关系中,"姓名"、"性别"、"部门"都是非主属性。
什么是 ER 图?
我们做一个项目的时候一定要试着画 ER 图来捋清数据库设计,这个也是面试官问你项目的时候经常会被问到的。
ER 图 全称是 Entity Relationship Diagram(实体联系图),提供了表示实体类型、属性和联系的方法。
ER 图由下面 3 个要素组成:
- 实体:通常是现实世界的业务对象,当然使用一些逻辑对象也可以。比如对于一个校园管理系统,会涉及学生、教师、课程、班级等等实体。在 ER 图中,实体使用矩形框表示。
- 属性:即某个实体拥有的属性,属性用来描述组成实体的要素,对于产品设计来说可以理解为字段。在 ER 图中,属性使用椭圆形表示。
- 联系:即实体与实体之间的关系,在 ER 图中用菱形表示,这个关系不仅有业务关联关系,还能通过数字表示实体之间的数量对照关系。例如,一个班级会有多个学生就是一种实体间的联系。
下图是一个学生选课的 ER 图,每个学生可以选若干门课程,同一门课程也可以被若干人选择,所以它们之间的关系是多对多(M: N)。另外,还有其他两种实体之间的关系是:1 对 1(1:1)、1 对多(1: N)。
erDiagram
STUDENT {
string student_id PK "学号"
string name "姓名"
string gender "性别"
date birth_date "出生日期"
string department "学院名称"
}
COURSE {
string course_id PK "课程编号"
string course_name "课程名称"
string location "课程地点"
string instructor "开课教师"
float credits "成绩"
}
ENROLLMENT {
string student_id FK "学号"
string course_id FK "课程编号"
float grade "成绩"
}
STUDENT ||--o{ ENROLLMENT : "选课"
COURSE ||--o{ ENROLLMENT : "被选"
style STUDENT fill:#4CA497,stroke:#00838F,stroke-width:2px
style COURSE fill:#005D7B,stroke:#00838F,stroke-width:2px
style ENROLLMENT fill:#E99151,stroke:#C44545,stroke-width:2px数据库范式了解吗?
数据库范式有 3 种:
- 1NF(第一范式):属性不可再分。
- 2NF(第二范式):1NF 的基础之上,消除了非主属性对于码的部分函数依赖。
- 3NF(第三范式):3NF 在 2NF 的基础之上,消除了非主属性对于码的传递函数依赖 。
1NF(第一范式)
属性(对应于表中的字段)不能再被分割,也就是这个字段只能是一个值,不能再分为多个其他的字段了。1NF 是所有关系型数据库的最基本要求 ,也就是说关系型数据库中创建的表一定满足第一范式。
2NF(第二范式)
2NF 在 1NF 的基础之上,消除了非主属性对于码的部分函数依赖。如下图所示,展示了第一范式到第二范式的过渡。第二范式在第一范式的基础上增加了一个列,这个列称为主键,非主属性都依赖于主键。
一些重要的概念:
- 函数依赖(functional dependency):若在一张表中,在属性(或属性组)X 的值确定的情况下,必定能确定属性 Y 的值,那么就可以说 Y 函数依赖于 X,写作 X → Y。
- 部分函数依赖(partial functional dependency):如果 X→Y,并且存在 X 的一个真子集 X0,使得 X0→Y,则称 Y 对 X 部分函数依赖。比如学生基本信息表 R 中(学号,身份证号,姓名)当然学号属性取值是唯一的,在 R 关系中,(学号,身份证号)->(姓名),(学号)->(姓名),(身份证号)->(姓名);所以姓名部分函数依赖于(学号,身份证号);
- 完全函数依赖(Full functional dependency):在一个关系中,若某个非主属性数据项依赖于全部关键字称之为完全函数依赖。比如学生基本信息表 R(学号,班级,姓名)假设不同的班级学号有相同的,班级内学号不能相同,在 R 关系中,(学号,班级)->(姓名),但是(学号)->(姓名)不成立,(班级)->(姓名)不成立,所以姓名完全函数依赖与(学号,班级);
- 传递函数依赖:在关系模式 R(U)中,设 X,Y,Z 是 U 的不同的属性子集,如果 X 确定 Y、Y 确定 Z,且有 X 不包含 Y,Y 不确定 X,(X∪Y)∩Z=空集合,则称 Z 传递函数依赖(transitive functional dependency) 于 X。传递函数依赖会导致数据冗余和异常。传递函数依赖的 Y 和 Z 子集往往同属于某一个事物,因此可将其合并放到一个表中。比如在关系 R(学号 , 姓名, 系名,系主任)中,学号 → 系名,系名 → 系主任,所以存在非主属性系主任对于学号的传递函数依赖。
3NF(第三范式)
3NF 在 2NF 的基础之上,消除了非主属性对于码的传递函数依赖 。符合 3NF 要求的数据库设计,基本上解决了数据冗余过大,插入异常,修改异常,删除异常的问题。比如在关系 R(学号 , 姓名, 系名,系主任)中,学号 → 系名,系名 → 系主任,所以存在非主属性系主任对于学号的传递函数依赖,所以该表的设计,不符合 3NF 的要求。
主键和外键有什么区别?
从定义和属性上看,它们的区别是:
- 主键 (Primary Key): 它的核心作用是唯一标识表中的每一行数据。因此,主键列的值必须是唯一的 (Unique) 且不能为空 (Not Null)。一张表只能有一个主键。主键保证了实体完整性。
- 外键 (Foreign Key): 它的核心作用是建立并强制两张表之间的关联关系。一张表中的外键列,其值必须对应另一张表中某行的候选键值(通常是主键,也可以是唯一键),或者是一个 NULL 值。因此,外键的值可以重复,也可以为空。一张表可以有多个外键,分别关联到不同的表。外键保证了引用完整性。
用一个简单的电商例子来说明:假设我们有两张表:users (用户表) 和 orders (订单表)。
- 在
users表中,user_id列是主键。每个用户的user_id都是独一无二的,我们用它来区分张三和李四。 - 在
orders表中,order_id是它自己的主键。同时,它会有一个user_id列,这个列就是一个外键,它引用了users表的user_id主键。
这个外键约束就保证了:
- 你不能创建一个不属于任何已知用户的订单(
user_id在users表中不存在)。 - 你不能删除一个已经下了订单的用户(除非设置了级联删除等特殊规则)。
为什么不推荐使用外键与级联?
对于外键和级联,阿里巴巴开发手册这样说到:
【强制】不得使用外键与级联,一切外键概念必须在应用层解决。
说明: 以学生和成绩的关系为例,学生表中的 student_id 是主键,那么成绩表中的 student_id 则为外键。如果更新学生表中的 student_id,同时触发成绩表中的 student_id 更新,即为级联更新。外键与级联更新适用于单机低并发,不适合分布式、高并发集群;级联更新是强阻塞,存在数据库更新风暴的风险;外键影响数据库的插入速度
为什么不要用外键呢?大部分人可能会这样回答:
- 增加了复杂性: a. 每次做 DELETE 或者 UPDATE 都必须考虑外键约束,会导致开发的时候很痛苦, 测试数据极为不方便; b. 外键的主从关系是定的,假如哪天需求有变化,数据库中的这个字段根本不需要和其他表有关联的话就会增加很多麻烦。
- 增加了额外工作:数据库需要增加维护外键的工作,比如当我们做一些涉及外键字段的增,删,更新操作之后,需要触发相关操作去检查,保证数据的一致性和正确性,这样会不得不消耗数据库资源。如果在应用层面去维护的话,可以减小数据库压力;
- 对分库分表不友好:因为分库分表下外键是无法生效的。
- ……
我个人觉得上面这种回答不是特别的全面,只是说了外键存在的一个常见的问题。实际上,我们知道外键也是有很多好处的,比如:
- 保证了数据库数据的一致性和完整性;
- 级联操作方便,减轻了程序代码量;
- ……
所以说,不要一股脑的就抛弃了外键这个概念,既然它存在就有它存在的道理,如果系统不涉及分库分表,并发量不是很高的情况还是可以考虑使用外键的。
什么是存储过程?
graph LR
A[存储过程] --> B[定义特征]
A --> C[优势]
A --> D[劣势]
A --> E[应用现状]
B --> B1[SQL语句集合]
B --> B2[包含逻辑控制]
B --> B3[预编译机制]
C --> C1[执行速度快]
C --> C2[运行稳定]
C --> C3[简化复杂操作]
D --> D1[调试困难]
D --> D2[扩展性差]
D --> D3[无移植性]
D --> D4[占用数据库资源]
E --> E1[传统企业<br/>使用较多]
E --> E2[互联网公司<br/>很少使用]
E --> E3[阿里规范<br/>明确禁用]
style A fill:#4CA497,stroke:#00838F,stroke-width:3px,color:#fff
style B fill:#00838F,stroke:#005D7B,stroke-width:2px,color:#fff
style C fill:#E99151,stroke:#C44545,stroke-width:2px,color:#fff
style D fill:#C44545,stroke:#005D7B,stroke-width:2px,color:#fff
style E fill:#005D7B,stroke:#00838F,stroke-width:2px,color:#fff
style B1 fill:#E4C189,stroke:#00838F,stroke-width:1px
style B2 fill:#E4C189,stroke:#00838F,stroke-width:1px
style B3 fill:#E4C189,stroke:#00838F,stroke-width:1px
style C1 fill:#E4C189,stroke:#E99151,stroke-width:1px
style C2 fill:#E4C189,stroke:#E99151,stroke-width:1px
style C3 fill:#E4C189,stroke:#E99151,stroke-width:1px
style D1 fill:#E4C189,stroke:#C44545,stroke-width:1px
style D2 fill:#E4C189,stroke:#C44545,stroke-width:1px
style D3 fill:#E4C189,stroke:#C44545,stroke-width:1px
style D4 fill:#E4C189,stroke:#C44545,stroke-width:1px
style E1 fill:#E4C189,stroke:#005D7B,stroke-width:1px
style E2 fill:#E4C189,stroke:#005D7B,stroke-width:1px
style E3 fill:#E4C189,stroke:#005D7B,stroke-width:1px存储过程是数据库中预编译的SQL语句集合,它将多条SQL语句和程序逻辑控制语句(如IF-ELSE、WHILE循环等)封装在一起,形成一个可重复调用的数据库对象。
存储过程的优势:
在传统企业级应用中,存储过程具有一定的实用价值。当业务逻辑复杂时,需要执行大量SQL语句才能完成一个业务操作,此时可以将这些语句封装成存储过程,简化调用过程。由于存储过程在创建时就已经编译并存储在数据库中,执行时无需重新编译,因此相比动态SQL语句具有更好的执行性能。同时,一旦存储过程调试完成,其运行相对稳定可靠。
存储过程的局限性:
然而,在现代互联网架构中,存储过程的使用越来越少。主要原因包括:调试困难,缺乏成熟的调试工具;扩展性差,修改业务逻辑需要直接修改数据库对象;移植性差,不同数据库系统的存储过程语法差异较大;占用数据库资源,增加数据库服务器负担;版本管理困难,不便于进行代码版本控制。
行业规范:
基于以上原因,许多互联网公司的开发规范中明确限制或禁止使用存储过程。例如,《阿里巴巴Java开发手册》中明确规定禁止使用存储过程,推荐将业务逻辑放在应用层实现,保持数据库的简单和高效。
DROP、DELETE、TRUNCATE 有什么区别?
在数据库操作中,DROP、DELETE 和 TRUNCATE 是三个常用的数据删除命令,它们在功能、性能和使用场景上存在显著差异。
DROP命令:
- 语法:
DROP TABLE 表名 - 作用:完全删除整个表,包括表结构、数据、索引、触发器、约束等所有相关对象
- 使用场景:当表不再需要时使用
TRUNCATE命令:
- 语法:
TRUNCATE TABLE 表名 - 作用:清空表中所有数据,但保留表结构
- 特点:自增长字段(AUTO_INCREMENT)会重置为初始值(通常为1)
- 使用场景:需要快速清空表数据但保留表结构时使用
DELETE命令:
- 语法:
DELETE FROM 表名 WHERE 条件 - 作用:删除满足条件的数据行,不带WHERE子句时删除所有数据
- 特点:自增长字段不会重置,继续从之前的值递增
- 使用场景:需要有选择地删除部分数据时使用
TRUNCATE 和不带 WHERE子句的 DELETE、以及 DROP 都会删除表内的数据,但是 TRUNCATE 和 DELETE 只删除数据不删除表的结构(定义),执行 DROP 语句,此表的结构也会删除,也就是执行DROP 之后对应的表不复存在。
对表结构的影响
DROP:删除表结构和所有数据,表将不复存在TRUNCATE:仅删除数据,保留表结构和定义DELETE:仅删除数据,保留表结构和定义
触发器
DELETE操作会触发相关的DELETE触发器TRUNCATE和DROP不会触发DELETE触发器
事务和回滚
DROP和TRUNCATE属于DDL操作,执行后立即生效,不能回滚DELETE属于DML操作,可以回滚(在事务中)
执行速度
一般来说:DROP > TRUNCATE > DELETE(这个我没有实际测试过)。
DELETE命令执行的时候会产生数据库的binlog日志,而日志记录是需要消耗时间的,但是也有个好处方便数据回滚恢复。TRUNCATE命令执行的时候不会产生数据库日志,因此比DELETE要快。除此之外,还会把表的自增值重置和索引恢复到初始大小等。DROP命令会把表占用的空间全部释放掉。
Tips:你应该更多地关注在使用场景上,而不是执行效率。
DML 语句和 DDL 语句区别是?
- DML 是数据库操作语言(Data Manipulation Language)的缩写,是指对数据库中表记录的操作,主要包括表记录的插入、更新、删除和查询,是开发人员日常使用最频繁的操作。
- DDL (Data Definition Language)是数据定义语言的缩写,简单来说,就是对数据库内部的对象进行创建、删除、修改的操作语言。它和 DML 语言的最大区别是 DML 只是对表内部数据的操作,而不涉及到表的定义、结构的修改,更不会涉及到其他对象。DDL 语句更多的被数据库管理员(DBA)所使用,一般的开发人员很少使用。
另外,由于SELECT不会对表进行破坏,所以有的地方也会把SELECT单独区分开叫做数据库查询语言 DQL(Data Query Language)。
数据库设计通常分为哪几步?
graph TD
A[数据库设计流程] --> B[1.需求分析]
B --> C[2.概念结构设计]
C --> D[3.逻辑结构设计]
D --> E[4.物理结构设计]
E --> F[5.数据库实施]
F --> G[6.运行和维护]
B --> B1[数据需求<br/>功能需求<br/>性能需求]
C --> C1[E-R建模<br/>实体关系图]
D --> D1[关系模型<br/>表结构设计<br/>规范化]
E --> E1[存储结构<br/>索引设计<br/>分区策略]
F --> F1[编程开发<br/>测试部署<br/>数据迁移]
G --> G1[性能监控<br/>备份恢复<br/>优化调整]
G -.反馈.-> B
style A fill:#4CA497,stroke:#00838F,stroke-width:3px,color:#fff
style B fill:#00838F,stroke:#005D7B,stroke-width:2px,color:#fff
style C fill:#E99151,stroke:#005D7B,stroke-width:2px,color:#fff
style D fill:#005D7B,stroke:#00838F,stroke-width:2px,color:#fff
style E fill:#C44545,stroke:#005D7B,stroke-width:2px,color:#fff
style F fill:#E99151,stroke:#005D7B,stroke-width:2px,color:#fff
style G fill:#00838F,stroke:#005D7B,stroke-width:2px,color:#fff
style B1 fill:#E4C189,stroke:#00838F,stroke-width:1px
style C1 fill:#E4C189,stroke:#E99151,stroke-width:1px
style D1 fill:#E4C189,stroke:#005D7B,stroke-width:1px
style E1 fill:#E4C189,stroke:#C44545,stroke-width:1px
style F1 fill:#E4C189,stroke:#E99151,stroke-width:1px
style G1 fill:#E4C189,stroke:#00838F,stroke-width:1px1. 需求分析阶段
目标: 深入了解和分析用户需求,明确系统边界
主要工作:
- 收集和分析数据需求:确定需要存储哪些数据,数据量大小,数据更新频率
- 明确功能需求:系统需要支持哪些业务操作,各操作的优先级
- 定义性能需求:响应时间要求,并发用户数,数据吞吐量
- 确定安全需求:数据访问权限,加密要求,审计要求
产出物: 需求规格说明书、数据字典初稿
2. 概念结构设计阶段
目标: 将需求转化为信息世界的概念模型
主要工作:
- 识别实体:确定系统中的主要对象
- 定义属性:明确每个实体的特征
- 建立联系:确定实体之间的关系(一对一、一对多、多对多)
- 绘制E-R图(实体-关系图)
产出物: E-R图、概念数据模型文档
3. 逻辑结构设计阶段
目标: 将概念模型转换为特定DBMS支持的逻辑模型
主要工作:
- E-R图向关系模型转换:将实体转换为表,属性转换为字段
- 规范化处理:通过范式化消除数据冗余和更新异常(通常达到3NF)
- 定义完整性约束:主键、外键、唯一性约束、检查约束
- 优化模型:根据性能需求进行适当的反规范化
产出物: 逻辑数据模型、表结构设计文档
4. 物理结构设计阶段
目标: 确定数据的物理存储方案和访问方法
主要工作:
- 选择存储引擎:如MySQL的InnoDB、MyISAM等
- 设计索引策略:确定需要建立的索引类型和字段
- 分区设计:对大表进行分区以提高性能
- 确定存储参数:表空间大小、数据文件位置、缓冲区配置
- 制定备份策略:全量备份、增量备份的频率和方式
产出物: 物理设计文档、索引设计方案
5. 数据库实施阶段
目标: 将设计转化为实际运行的数据库系统
主要工作:
- 创建数据库和表结构:编写和执行DDL语句
- 开发存储过程和触发器(如需要)
- 编写应用程序接口
- 导入初始数据
- 系统集成测试:功能测试、性能测试、压力测试
- 用户培训和文档编写
产出物: 数据库脚本、测试报告、用户手册
6. 运行和维护阶段
目标: 确保数据库系统稳定高效运行
主要工作:
- 日常监控:性能监控、空间监控、错误日志分析
- 性能优化:查询优化、索引调整、参数调优
- 数据备份和恢复:定期备份、恢复演练
- 安全管理:权限管理、安全补丁更新、审计
- 容量规划:预测数据增长,提前扩容
- 变更管理:需求变更的评估和实施
产出物: 运维报告、优化方案、变更记录
设计原则
在整个设计过程中应遵循:数据独立性原则、完整性原则、安全性原则、可扩展性原则和标准化原则。
参考
- https://blog.csdn.net/rl529014/article/details/48391465
- https://www.zhihu.com/question/24696366/answer/29189700
- https://blog.csdn.net/bieleyang/article/details/77149954
Database Character Set (`docs/database/character-set.md`)
title: 字符集详解:字符集是什么?怎么用?
category: 数据库
tag:
- 数据库基础
head: - meta
- name: keywords
content: 字符集,字符编码,UTF-8,UTF-16,GBK,GB2312,utf8mb4,ASCII,Unicode,MySQL字符集,emoji存储
MySQL 字符编码集中有两套 UTF-8 编码实现:utf8 和 utf8mb4。
如果使用 utf8 的话,存储 emoji 符号和一些比较复杂的汉字、繁体字就会出错。
为什么会这样呢?这篇文章可以从源头给你解答。
字符集是什么?
字符是各种文字和符号的统称,包括各个国家文字、标点符号、表情、数字等等。 字符集 就是一系列字符的集合。字符集的种类较多,每个字符集可以表示的字符范围通常不同,就比如说有些字符集是无法表示汉字的。
计算机只能存储二进制的数据,那英文、汉字、表情等字符应该如何存储呢?
我们要将这些字符和二进制的数据一一对应起来,比如说字符“a”对应“01100001”,反之,“01100001”对应 “a”。我们将字符对应二进制数据的过程称为"字符编码",反之,二进制数据解析成字符的过程称为“字符解码”。
字符编码是什么?
字符编码是一种将字符集中的字符与计算机中的二进制数据相互转换的方法,可以看作是一种映射规则。也就是说,字符编码的目的是为了让计算机能够存储和传输各种文字信息。
每种字符集都有自己的字符编码规则,常用的字符集编码规则有 ASCII 编码、 GB2312 编码、GBK 编码、GB18030 编码、Big5 编码、UTF-8 编码、UTF-16 编码等。
有哪些常见的字符集?
常见的字符集有:ASCII、GB2312、GB18030、GBK、Unicode……。
不同的字符集的主要区别在于:
- 可以表示的字符范围
- 编码方式
ASCII
ASCII (American Standard Code for Information Interchange,美国信息交换标准代码) 是一套主要用于现代美国英语的字符集(这也是 ASCII 字符集的局限性所在)。
为什么 ASCII 字符集没有考虑到中文等其他字符呢? 因为计算机是美国人发明的,当时,计算机的发展还处于比较雏形的时代,还未在其他国家大规模使用。因此,美国发布 ASCII 字符集的时候没有考虑兼容其他国家的语言。
ASCII 字符集至今为止共定义了 128 个字符,其中有 33 个控制字符(比如回车、删除)无法显示。
一个 ASCII 码长度是一个字节也就是 8 个 bit,比如“a”对应的 ASCII 码是“01100001”。不过,最高位是 0 仅仅作为校验位,其余 7 位使用 0 和 1 进行组合,所以,ASCII 字符集可以定义 128(2^7)个字符。
由于,ASCII 码可以表示的字符实在是太少了。后来,人们对其进行了扩展得到了 ASCII 扩展字符集 。ASCII 扩展字符集使用 8 位(bits)表示一个字符,所以,ASCII 扩展字符集可以定义 256(2^8)个字符。
GB2312
我们上面说了,ASCII 字符集是一种现代美国英语适用的字符集。因此,很多国家都捣鼓了一个适合自己国家语言的字符集。
GB2312 字符集是一种对汉字比较友好的字符集,共收录 6700 多个汉字,基本涵盖了绝大部分常用汉字。不过,GB2312 字符集不支持绝大部分的生僻字和繁体字。
对于英语字符,GB2312 编码和 ASCII 码是相同的,1 字节编码即可。对于非英字符,需要 2 字节编码。
GBK
GBK 字符集可以看作是 GB2312 字符集的扩展,兼容 GB2312 字符集,共收录了 20000 多个汉字。
GBK 中 K 是汉语拼音 Kuo Zhan(扩展)中的“Kuo”的首字母。
GB18030
GB18030 完全兼容 GB2312 和 GBK 字符集,纳入中国国内少数民族的文字,且收录了日韩汉字,是目前为止最全面的汉字字符集,共收录汉字 70000 多个。
BIG5
BIG5 主要针对的是繁体中文,收录了 13000 多个汉字。
Unicode & UTF-8
为了更加适合本国语言,诞生了很多种字符集。
我们上面也说了不同的字符集可以表示的字符范围以及编码规则存在差异。这就导致了一个非常严重的问题:使用错误的编码方式查看一个包含字符的文件就会产生乱码现象。
就比如说你使用 UTF-8 编码方式打开 GB2312 编码格式的文件就会出现乱码。示例:“牛”这个汉字 GB2312 编码后的十六进制数值为 “C5A3”,而 “C5A3” 用 UTF-8 解码之后得到的却是 “ţ”。
你可以通过这个网站在线进行编码和解码:https://www.haomeili.net/HanZi/ZiFuBianMaZhuanHuan
这样我们就搞懂了乱码的本质:编码和解码时用了不同或者不兼容的字符集 。
为了解决这个问题,人们就想:“如果我们能够有一种字符集将世界上所有的字符都纳入其中就好了!”。
然后,Unicode 带着这个使命诞生了。
Unicode 字符集中包含了世界上几乎所有已知的字符。不过,Unicode 字符集并没有规定如何存储这些字符(也就是如何使用二进制数据表示这些字符)。
然后,就有了 UTF-8(8-bit Unicode Transformation Format)。类似的还有 UTF-16、 UTF-32。
UTF-8 使用 1 到 4 个字节为每个字符编码, UTF-16 使用 2 或 4 个字节为每个字符编码,UTF-32 固定位 4 个字节为每个字符编码。
UTF-8 可以根据不同的符号自动选择编码的长短,像英文字符只需要 1 个字节就够了,这一点 ASCII 字符集一样 。因此,对于英语字符,UTF-8 编码和 ASCII 码是相同的。
UTF-32 的规则最简单,不过缺陷也比较明显,对于英文字母这类字符消耗的空间是 UTF-8 的 4 倍之多。
UTF-8 是目前使用最广的一种字符编码。
MySQL 字符集
MySQL 支持很多种字符集的方式,比如 GB2312、GBK、BIG5、多种 Unicode 字符集(UTF-8 编码、UTF-16 编码、UCS-2 编码、UTF-32 编码等等)。
查看支持的字符集
你可以通过 SHOW CHARSET 命令来查看,支持 like 和 where 子句。
默认字符集
在 MySQL5.7 中,默认字符集是 latin1 ;在 MySQL8.0 中,默认字符集是 utf8mb4
字符集的层次级别
MySQL 中的字符集有以下的层次级别:
server(MySQL 实例级别)database(库级别)table(表级别)column(字段级别)
它们的优先级可以简单的认为是从上往下依次增大,也即 column 的优先级会大于 table 等其余层次的。如指定 MySQL 实例级别字符集是utf8mb4,指定某个表字符集是latin1,那么这个表的所有字段如果不指定的话,编码就是latin1。
server
不同版本的 MySQL 其 server 级别的字符集默认值不同,在 MySQL5.7 中,其默认值是 latin1 ;在 MySQL8.0 中,其默认值是 utf8mb4 。
当然也可以通过在启动 mysqld 时指定 --character-set-server 来设置 server 级别的字符集。
mysqld
mysqld --character-set-server=utf8mb4
mysqld --character-set-server=utf8mb4 \
--collation-server=utf8mb4_0900_ai_ci或者如果你是通过源码构建的方式启动的 MySQL,你可以在 cmake 命令中指定选项:
cmake . -DDEFAULT_CHARSET=latin1
或者
cmake . -DDEFAULT_CHARSET=latin1 \
-DDEFAULT_COLLATION=latin1_german1_ci此外,你也可以在运行时改变 character_set_server 的值,从而达到修改 server 级别的字符集的目的。
server 级别的字符集是 MySQL 服务器的全局设置,它不仅会作为创建或修改数据库时的默认字符集(如果没有指定其他字符集),还会影响到客户端和服务器之间的连接字符集,具体可以查看 MySQL Connector/J 8.0 - 6.7 Using Character Sets and Unicode。
database
database 级别的字符集是我们在创建数据库和修改数据库时指定的:
CREATE DATABASE db_name
[[DEFAULT] CHARACTER SET charset_name]
[[DEFAULT] COLLATE collation_name]
ALTER DATABASE db_name
[[DEFAULT] CHARACTER SET charset_name]
[[DEFAULT] COLLATE collation_name]如前面所说,如果在执行上述语句时未指定字符集,那么 MySQL 将会使用 server 级别的字符集。
可以通过下面的方式查看某个数据库的字符集:
USE db_name;
SELECT @@character_set_database, @@collation_database;SELECT DEFAULT_CHARACTER_SET_NAME, DEFAULT_COLLATION_NAME
FROM INFORMATION_SCHEMA.SCHEMATA WHERE SCHEMA_NAME = 'db_name';table
table 级别的字符集是在创建表和修改表时指定的:
CREATE TABLE tbl_name (column_list)
[[DEFAULT] CHARACTER SET charset_name]
[COLLATE collation_name]]
ALTER TABLE tbl_name
[[DEFAULT] CHARACTER SET charset_name]
[COLLATE collation_name]如果在创建表和修改表时未指定字符集,那么将会使用 database 级别的字符集。
column
column 级别的字符集同样是在创建表和修改表时指定的,只不过它是定义在列中。下面是个例子:
CREATE TABLE t1
(
col1 VARCHAR(5)
CHARACTER SET latin1
COLLATE latin1_german1_ci
);如果未指定列级别的字符集,那么将会使用表级别的字符集。
连接字符集
前面说到了字符集的层次级别,它们是和存储相关的。而连接字符集涉及的是和 MySQL 服务器的通信。
连接字符集与下面这几个变量息息相关:
character_set_client:描述了客户端发送给服务器的 SQL 语句使用的是什么字符集。character_set_connection:描述了服务器接收到 SQL 语句时使用什么字符集进行翻译。character_set_results:描述了服务器返回给客户端的结果使用的是什么字符集。
它们的值可以通过下面的 SQL 语句查询:
SELECT * FROM performance_schema.session_variables
WHERE VARIABLE_NAME IN (
'character_set_client', 'character_set_connection',
'character_set_results', 'collation_connection'
) ORDER BY VARIABLE_NAME;SHOW SESSION VARIABLES LIKE 'character\_set\_%';如果要想修改前面提到的几个变量的值,有以下方式:
1、修改配置文件
[mysql]
# 只针对MySQL客户端程序
default-character-set=utf8mb42、使用 SQL 语句
set names utf8mb4
# 或者一个个进行修改
# SET character_set_client = utf8mb4;
# SET character_set_results = utf8mb4;
# SET collation_connection = utf8mb4;JDBC 对连接字符集的影响
不知道你们有没有碰到过存储 emoji 表情正常,但是使用类似 Navicat 之类的软件的进行查询的时候,发现 emoji 表情变成了问号的情况。这个问题很有可能就是 JDBC 驱动引起的。
根据前面的内容,我们知道连接字符集也是会影响我们存储的数据的,而 JDBC 驱动会影响连接字符集。
mysql-connector-java (JDBC 驱动)主要通过这几个属性影响连接字符集:
characterEncodingcharacterSetResults
以 DataGrip 2023.1.2 来说,在它配置数据源的高级对话框中,可以看到 characterSetResults 的默认值是 utf8 ,在使用 mysql-connector-java 8.0.25 时,连接字符集最后会被设置成 utf8mb3 。那么这种情况下 emoji 表情就会被显示为问号,并且当前版本驱动还不支持把 characterSetResults 设置为 utf8mb4 ,不过换成 mysql-connector-java driver 8.0.29 却是允许的。
具体可以看一下 StackOverflow 的 DataGrip MySQL stores emojis correctly but displays them as?这个回答。
UTF-8 使用
通常情况下,我们建议使用 UTF-8 作为默认的字符编码方式。
不过,这里有一个小坑。
MySQL 字符编码集中有两套 UTF-8 编码实现:
utf8:utf8编码只支持1-3个字节 。 在utf8编码中,中文是占 3 个字节,其他数字、英文、符号占一个字节。但 emoji 符号占 4 个字节,一些较复杂的文字、繁体字也是 4 个字节。utf8mb4:UTF-8 的完整实现,正版!最多支持使用 4 个字节表示字符,因此,可以用来存储 emoji 符号。
为什么有两套 UTF-8 编码实现呢? 原因如下:
因此,如果你需要存储emoji类型的数据或者一些比较复杂的文字、繁体字到 MySQL 数据库的话,数据库的编码一定要指定为utf8mb4 而不是utf8 ,要不然存储的时候就会报错了。
演示一下吧!(环境:MySQL 5.7+)
建表语句如下,我们指定数据库 CHARSET 为 utf8 。
CREATE TABLE `user` (
`id` varchar(66) CHARACTER SET utf8mb3 NOT NULL,
`name` varchar(33) CHARACTER SET utf8mb3 NOT NULL,
`phone` varchar(33) CHARACTER SET utf8mb3 DEFAULT NULL,
`password` varchar(100) CHARACTER SET utf8mb3 DEFAULT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8;当我们执行下面的 insert 语句插入数据到数据库时,果然报错!
INSERT INTO `user` (`id`, `name`, `phone`, `password`)
VALUES
('A00003', 'guide哥😘😘😘', '181631312312', '123456');报错信息如下:
Incorrect string value: '\xF0\x9F\x98\x98\xF0\x9F...' for column 'name' at row 1参考
- 字符集和字符编码(Charset & Encoding):https://www.cnblogs.com/skynet/archive/2011/05/03/2035105.html
- 十分钟搞清字符集和字符编码:http://cenalulu.github.io/linux/character-encoding/
- Unicode-维基百科:https://zh.wikipedia.org/wiki/Unicode
- GB2312-维基百科:https://zh.wikipedia.org/wiki/GB_2312
- UTF-8-维基百科:https://zh.wikipedia.org/wiki/UTF-8
- GB18030-维基百科: https://zh.wikipedia.org/wiki/GB_18030
- MySQL8 文档:https://dev.mysql.com/doc/refman/8.0/en/charset.html
- MySQL5.7 文档:https://dev.mysql.com/doc/refman/5.7/en/charset.html
- MySQL Connector/J 文档:https://dev.mysql.com/doc/connector-j/8.0/en/connector-j-reference-charsets.html
Database Nosql (`docs/database/nosql.md`)
title: NoSQL基础常见面试题总结
category: 数据库
tag:
- NoSQL
- MongoDB
- Redis
head: - meta
- name: keywords
content: NoSQL,Redis,MongoDB,HBase,Cassandra,键值数据库,文档数据库,图数据库,宽列存储,SQL与NoSQL区别
NoSQL 是什么?
NoSQL(Not Only SQL 的缩写)泛指非关系型的数据库,主要针对的是键值、文档以及图形类型数据存储。并且,NoSQL 数据库天生支持分布式,数据冗余和数据分片等特性,旨在提供可扩展的高可用高性能数据存储解决方案。
一个常见的误解是 NoSQL 数据库或非关系型数据库不能很好地存储关系型数据。NoSQL 数据库可以存储关系型数据—它们与关系型数据库的存储方式不同。
NoSQL 数据库代表:HBase、Cassandra、MongoDB、Redis。
SQL 和 NoSQL 有什么区别?
| SQL 数据库 | NoSQL 数据库 | |
|---|---|---|
| 数据存储模型 | 结构化存储,具有固定行和列的表格 | 非结构化存储。文档:JSON 文档,键值:键值对,宽列:包含行和动态列的表,图:节点和边 |
| 发展历程 | 开发于 1970 年代,重点是减少数据重复 | 开发于 2000 年代后期,重点是提升可扩展性,减少大规模数据的存储成本 |
| 例子 | Oracle、MySQL、Microsoft SQL Server、PostgreSQL | 文档:MongoDB、CouchDB,键值:Redis、DynamoDB,宽列:Cassandra、 HBase,图表:Neo4j、 Amazon Neptune、Giraph |
| ACID 属性 | 提供原子性、一致性、隔离性和持久性 (ACID) 属性 | 通常不支持 ACID 事务,为了可扩展、高性能进行了权衡,少部分支持比如 MongoDB 。不过,MongoDB 对 ACID 事务 的支持和 MySQL 还是有所区别的。 |
| 性能 | 性能通常取决于磁盘子系统。要获得最佳性能,通常需要优化查询、索引和表结构。 | 性能通常由底层硬件集群大小、网络延迟以及调用应用程序来决定。 |
| 扩展 | 垂直(使用性能更强大的服务器进行扩展)、读写分离、分库分表 | 横向(增加服务器的方式横向扩展,通常是基于分片机制) |
| 用途 | 普通企业级的项目的数据存储 | 用途广泛比如图数据库支持分析和遍历连接数据之间的关系、键值数据库可以处理大量数据扩展和极高的状态变化 |
| 查询语法 | 结构化查询语言 (SQL) | 数据访问语法可能因数据库而异 |
NoSQL 数据库有什么优势?
NoSQL 数据库非常适合许多现代应用程序,例如移动、Web 和游戏等应用程序,它们需要灵活、可扩展、高性能和功能强大的数据库以提供卓越的用户体验。
- 灵活性: NoSQL 数据库通常提供灵活的架构,以实现更快速、更多的迭代开发。灵活的数据模型使 NoSQL 数据库成为半结构化和非结构化数据的理想之选。
- 可扩展性: NoSQL 数据库通常被设计为通过使用分布式硬件集群来横向扩展,而不是通过添加昂贵和强大的服务器来纵向扩展。
- 高性能: NoSQL 数据库针对特定的数据模型和访问模式进行了优化,这与尝试使用关系数据库完成类似功能相比可实现更高的性能。
- 强大的功能: NoSQL 数据库提供功能强大的 API 和数据类型,专门针对其各自的数据模型而构建。
NoSQL 数据库有哪些类型?
NoSQL 数据库主要可以分为下面四种类型:
- 键值:键值数据库是一种较简单的数据库,其中每个项目都包含键和值。这是极为灵活的 NoSQL 数据库类型,因为应用可以完全控制 value 字段中存储的内容,没有任何限制。Redis 和 DynanoDB 是两款非常流行的键值数据库。
- 文档:文档数据库中的数据被存储在类似于 JSON(JavaScript 对象表示法)对象的文档中,非常清晰直观。每个文档包含成对的字段和值。这些值通常可以是各种类型,包括字符串、数字、布尔值、数组或对象等,并且它们的结构通常与开发者在代码中使用的对象保持一致。MongoDB 就是一款非常流行的文档数据库。
- 图形:图形数据库旨在轻松构建和运行与高度连接的数据集一起使用的应用程序。图形数据库的典型使用案例包括社交网络、推荐引擎、欺诈检测和知识图形。Neo4j 和 Giraph 是两款非常流行的图形数据库。
- 宽列:宽列存储数据库非常适合需要存储大量的数据。Cassandra 和 HBase 是两款非常流行的宽列存储数据库。
下面这张图片来源于 微软的官方文档 | 关系数据与 NoSQL 数据。
参考
- NoSQL 是什么?- MongoDB 官方文档:https://www.mongodb.com/zh-cn/nosql-explained
- 什么是 NoSQL? - AWS:https://aws.amazon.com/cn/nosql/
- NoSQL vs. SQL Databases - MongoDB 官方文档:https://www.mongodb.com/zh-cn/nosql-explained/nosql-vs-sql
Distributed System Api Gateway (`docs/distributed-system/api-gateway.md`)
title: API 网关详解:核心功能、工作原理与 Spring Cloud Gateway / Kong / APISIX 选型
category: 分布式
tag:
- API 网关
head: - meta
- name: keywords
content: API 网关,微服务网关,Spring Cloud Gateway,Kong,APISIX,ShenYu,Zuul,限流熔断,负载均衡,网关选型,网关面试题
什么是网关?
API 网关(API Gateway)是位于客户端与后端服务之间的统一入口,所有客户端请求先经过网关,再由网关路由到具体的目标服务。
在这组分布式文章里,网关属于“流量入口”这一层。RPC 主要讲服务之间怎么互相调用,网关讲外部请求进入系统后怎么做路由、鉴权、限流、灰度和协议适配。如果你只想看 Spring Cloud Gateway 的路由、Predicate、Filter 和限流细节,可以继续读 Spring Cloud Gateway 面试题总结。
核心价值
在微服务架构下,一个系统被拆分为多个服务。像安全认证、流量控制、日志、监控等功能是每个服务都需要的。如果没有网关,我们需要在每个服务中单独实现这些功能,导致:
- 代码重复:相同逻辑在多个服务中冗余实现
- 管理分散:缺乏统一的配置和监控视图
- 维护成本高:功能变更需要修改所有服务
核心职责
网关的功能虽然繁多,但核心可以概括为两件事:
| 职责 | 说明 | 典型功能 |
|---|---|---|
| 请求转发 | 将客户端请求路由到正确的目标服务 | 动态路由、负载均衡、协议转换 |
| 请求过滤 | 在请求到达后端服务前/后进行拦截处理 | 身份认证、权限校验、限流熔断、日志记录 |
下表是抽象层面的两类核心职责,落到具体能力上,会衍生出下一节列举的十余项网关功能。
网关可以提供请求转发、安全认证(身份/权限认证)、流量控制、负载均衡、降级熔断、日志、监控、参数校验、协议转换等功能。
网关在微服务架构中的位置:所有客户端请求先到达网关,网关负责统一的认证鉴权、流量控制、路由分发,后端服务专注于业务逻辑处理。
高可用部署
引入网关后会增加一次网络转发(性能损耗在内网环境下通常可忽略),但同时也引入了新的单点风险。因此,网关服务本身必须保障高可用:
如下图所示,网关服务外层通过 Nginx(或其他负载均衡设备/软件)进行负载转发以达到高可用。Nginx 在部署时也应考虑高可用,避免单点风险。
网关能提供哪些功能?
绝大部分网关可以提供下面这些功能(有一些功能需要借助其他框架或者中间件):
- 请求转发:将请求转发到目标微服务。
- 负载均衡:根据各个微服务实例的负载情况或者具体的负载均衡策略配置对请求实现动态的负载均衡。
- 安全认证:对用户请求进行身份验证并仅允许可信客户端访问 API,并且还能够使用类似 RBAC 等方式来授权。
- 参数校验:支持参数映射与校验逻辑。
- 日志记录:记录所有请求的行为日志供后续使用。
- 监控告警:从业务指标、机器指标、JVM 指标等方面进行监控并提供配套的告警机制。
- 流量控制:对请求的流量进行控制,也就是限制某一时刻内的请求数。
- 熔断降级:实时监控请求的统计信息,达到配置的失败阈值后,自动熔断,返回默认值。
- 响应缓存:当用户请求获取的是一些静态的或更新不频繁的数据时,一段时间内多次请求获取到的数据很可能是一样的。对于这种情况可以将响应缓存起来。这样用户请求可以直接在网关层得到响应数据,无需再去访问业务服务,减轻业务服务的负担。
- 响应聚合:某些情况下用户请求要获取的响应内容可能会来自于多个业务服务。通用网关可以做简单聚合,但复杂聚合更推荐放在 BFF(Backend For Frontend)或 GraphQL 层,避免把业务编排逻辑沉淀到基础设施层。
- 灰度发布:将请求动态分流到不同的服务版本(最基本的一种灰度发布)。
- 异常处理:对于业务服务返回的异常响应,可以在网关层在返回给用户之前做转换处理。这样可以把一些业务侧返回的异常细节隐藏,转换成用户友好的错误提示返回。
- API 文档:如果计划将 API 暴露给组织以外的开发人员,那么必须考虑使用 API 文档,例如 Swagger 或 OpenAPI。
- 协议转换:通过协议转换整合后台基于 REST、AMQP、Dubbo 等不同风格和实现技术的微服务,面向 Web/Mobile、开放平台等特定客户端提供统一服务。
- 证书管理:将 SSL 证书部署到 API 网关,由一个统一的入口管理接口,降低了证书更换时的复杂度。
需要注意的是,网关并不适合承载所有逻辑。强业务规则校验、复杂字段映射、长事务、长连接业务逻辑、细粒度业务授权等,通常应该回到业务服务、BFF 或 GraphQL 层处理。网关更适合做协议级、通用型、跨服务的能力,避免演化成难以维护的“巨石网关”。
下图来源于百亿规模 API 网关服务 Shepherd 的设计与实现 - 美团技术团队 - 2021这篇文章。
有哪些常见的网关系统?
Netflix Zuul
Zuul 是 Netflix 开发的一款提供动态路由、监控、弹性、安全的网关服务,基于 Java 技术栈开发,可以和 Eureka、Ribbon、Hystrix 等组件配合使用。
Zuul 核心架构如下:
Zuul 主要通过过滤器(类似于 AOP)来过滤请求,从而实现网关必备的各种功能。
我们可以自定义过滤器来处理请求,并且,Zuul 生态本身就有很多现成的过滤器供我们使用。就比如限流可以使用社区扩展 spring-cloud-zuul-ratelimit(这里只是举例说明)。需要区分的是:Hystrix 主要负责熔断、超时、降级和线程池/信号量隔离,并不是严格意义上的 QPS 限流组件。
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-zuul</artifactId>
</dependency>
<dependency>
<groupId>com.marcosbarbero.cloud</groupId>
<artifactId>spring-cloud-zuul-ratelimit</artifactId>
<version>2.2.0.RELEASE</version>
</dependency>Zuul 1.x 基于同步 IO,性能较差。Zuul 2.x 基于 Netty 实现了异步 IO,性能得到了大幅改进。
重要提示:Spring Cloud Netflix 中 Zuul 1.x、Ribbon、Hystrix 等模块已进入维护模式,Spring Cloud 主流发行版也早已转向 Spring Cloud Gateway。尽管 Netflix 开源了 Zuul 2.x,但 Zuul 2.x 并未被集成到 Spring Cloud 主流版本中。对于 Spring Cloud 技术栈的新项目,不建议再选用 Zuul 1.x;存量项目应结合重构窗口逐步迁移到 Spring Cloud Gateway 或其他现代网关。
- GitHub 地址: https://github.com/Netflix/zuul
- 官方 Wiki: https://github.com/Netflix/zuul/wiki
Spring Cloud Gateway
Spring Cloud Gateway 属于 Spring Cloud 生态系统中的网关,其诞生的目标是为了替代老牌网关 Zuul(准确说是 Zuul 1.x)。值得注意的是,Spring Cloud Gateway 的起步时间早于 Zuul 2.x,两者属于不同的技术演进路线。
为什么 Spring Cloud Gateway 性能更好?
| 版本 | IO 模型 | 线程模型 | 吞吐量 | 延迟 |
|---|---|---|---|---|
| Zuul 1.x | 同步阻塞(Servlet) | 每请求一线程 | 低 | 高 |
| Zuul 2.x | 异步非阻塞(Netty) | 事件循环 | 高 | 低 |
| Spring Cloud Gateway | 异步非阻塞(Netty) | 事件循环 | 高 | 低 |
Spring Cloud Gateway 基于 Spring WebFlux 实现,而不是传统的 Spring Web MVC。Spring WebFlux 使用 Reactor 库来实现响应式编程模型,底层基于 Netty 实现异步非阻塞的 I/O。
响应式编程的优势:
- 非阻塞 I/O:无需为每个请求分配独立线程,少量线程即可处理大量并发连接
- 背压机制:Reactor 的背压主要作用于网关进程内部的响应式链路,避免 in-flight 请求把自身压垮。端到端的过载保护仍需依赖显式的限流、舱壁、超时和熔断,例如
RequestRateLimiter、CircuitBreaker等过滤器 - 资源利用率高:线程上下文切换开销大幅降低
核心概念
Spring Cloud Gateway 的核心组件包括三个部分:
- Route(路由):网关的基本构建块,由 ID、目标 URI、断言集合和过滤器集合组成
- Predicate(断言):基于 Java 8
Predicate函数式接口实现,用于匹配 HTTP 请求(如路径、方法、请求头等)。例如Path=/api/users/**、Method=GET、Header=X-Request-Id, \d+,多个 Predicate 通过逻辑与组合 - Filter(过滤器):
GatewayFilter的实例,用于在请求被发送到下游服务之前或之后修改请求和响应
Spring Cloud Gateway 和 Zuul 2.x 都是通过过滤器来处理请求,但 Spring Cloud Gateway 与 Spring 生态系统(如 Eureka、Consul、Config)集成更加紧密。目前,对于 Java 技术栈的新项目,Spring Cloud Gateway 通常是更主流的选择。需要注意的是,Spring Cloud Gateway 4.x/5.x 文档中已经同时提供 Server 与 Proxy Exchange 两类形态,并分别提供 WebFlux 和 Web MVC 兼容路径。熟悉 Servlet 技术栈、暂时不想引入响应式编程复杂度的团队,也可以评估 Spring Cloud Gateway Server MVC。
在能力边界上也要说清楚:Spring Cloud Gateway 内置了 RequestRateLimiter 过滤器,常见 Redis 实现基于令牌桶算法,但 Redis 限流还需要引入对应的 reactive Redis 依赖;熔断能力则是通过 Spring Cloud CircuitBreaker 适配 Resilience4j,需要额外引入 spring-cloud-starter-circuitbreaker-reactor-resilience4j 等依赖。路由配置也不只是“内存配置”,默认可以写在 YAML 中,也可以通过 RouteDefinitionRepository、Redis 路由仓库或自定义实现接入外部配置,并结合刷新机制动态生效。
- GitHub 地址: https://github.com/spring-cloud/spring-cloud-gateway
- 官网: https://spring.io/projects/spring-cloud-gateway
OpenResty
根据官方介绍:
OpenResty 是一个基于 Nginx 与 Lua 的高性能 Web 平台,其内部集成了大量精良的 Lua 库、第三方模块以及大多数的依赖项。用于方便地搭建能够处理超高并发、扩展性极高的动态 Web 应用、Web 服务和动态网关。
OpenResty 基于 Nginx,主要还是看中了其优秀的高并发能力。不过,由于 Nginx 采用 C 语言开发,二次开发门槛较高。如果想在 Nginx 上实现一些自定义的逻辑或功能,就需要编写 C 语言的模块,并重新编译 Nginx。
为了解决这个问题,OpenResty 集成并维护了 ngx_http_lua_module、ngx_stream_lua_module 等模块,把 Lua/LuaJIT 整合进了 Nginx,从而让我们能够在 Nginx 内部嵌入 Lua 脚本,使得可以通过简单的 Lua 语言来扩展网关的功能,比如实现自定义的路由规则、过滤器、缓存策略等。
Lua 是一种非常快速的动态脚本语言,它的运行速度接近于 C 语言。LuaJIT 是 Lua 的一个即时编译器,它可以显著提高 Lua 代码的执行效率。LuaJIT 将一些常用的 Lua 函数和工具库预编译并缓存,这样在下次调用时就可以直接使用缓存的字节码,从而大大加快了执行速度。
关于 OpenResty 的入门以及网关安全实战推荐阅读这篇文章:每个后端都应该了解的 OpenResty 入门以及网关安全实战。
- GitHub 地址: https://github.com/openresty/openresty
- 官网地址: https://openresty.org/
Kong
Kong 是一款基于 OpenResty (Nginx + Lua)的高性能、云原生、可扩展、生态丰富的网关系统,主要由 3 个组件组成:
- Kong Server:基于 Nginx 的服务器,用来接收 API 请求。
- PostgreSQL:用来存储操作数据(传统数据库模式)。Kong 早期也支持 Cassandra,但 Kong Gateway 3.4 起已经移除 Cassandra DB 支持,新部署不建议再选 Cassandra。
- Kong Manager:官方 UI 管理工具,提供可视化的 API 管理、监控和配置功能(有 OSS 开源版和 Enterprise 企业版)。也可使用 RESTful Admin API 进行管理。
Kong 传统模式依赖外部数据库存储配置,架构相对复杂,需要额外保障数据库层的高可用。但自 Kong 1.1 版本起,已支持 DB-less 模式(无库模式):
- 传统模式:使用 PostgreSQL 存储配置,适合需要通过 Admin API 持久化管理 API 数据的场景
- DB-less 模式:通过声明式配置文件管理,无需部署数据库,架构更加轻量
- Hybrid 模式:控制平面使用数据库管理配置,数据平面不直接连接数据库,适合多集群、跨区域分发配置
- Kubernetes Ingress 模式:通过 ConfigMap 或 CRD(Kubernetes Custom Resource Definitions)管理配置,无需数据库,是 K8s 环境下的主流用法
注意:本文后续讨论的 Kong 高可用问题,主要针对传统数据库模式。在 K8s 环境使用 Ingress Controller 模式,或使用 DB-less/Hybrid 模式时,架构和运维关注点会有明显差异。
Kong 提供了插件机制来扩展其功能,插件在 API 请求响应循环的生命周期中被执行。比如在服务上启用 Zipkin 插件:
$ curl -X POST http://kong:8001/services/{service}/plugins \
--data "name=zipkin" \
--data "config.http_endpoint=http://your.zipkin.collector:9411/api/v2/spans" \
--data "config.sample_ratio=0.001"Kong 本身就是一个 Lua 应用程序,并且是在 OpenResty 的基础之上做了一层封装的应用。归根结底就是利用 Lua 嵌入 Nginx 的方式,赋予了 Nginx 可编程的能力,这样以插件的形式在 Nginx 这一层能够做到无限想象的事情。例如限流、安全访问策略、路由、负载均衡等等。编写一个 Kong 插件,就是按照 Kong 插件编写规范,写一个自己自定义的 Lua 脚本,然后加载到 Kong 中,最后引用即可。
除了 Lua,Kong 还可以基于 Go、JavaScript、Python 等语言开发插件,得益于对应的 PDK(插件开发工具包)。
关于 Kong 插件的详细介绍,推荐阅读官方文档:https://docs.konghq.com/gateway/latest/kong-plugins/,写的比较详细。
- GitHub 地址: https://github.com/Kong/kong
- 官网地址: https://konghq.com/kong
APISIX
APISIX 是一款基于 OpenResty 和 etcd 的高性能、云原生、可扩展的网关系统。
etcd 是使用 Go 语言开发的一个开源的、高可用的分布式 Key-Value 存储系统,使用 Raft 协议做分布式共识。
与传统 API 网关相比,APISIX 具有动态路由和插件热加载,特别适合微服务系统下的 API 管理。并且,APISIX 与 SkyWalking(分布式链路追踪系统)、Zipkin(分布式链路追踪系统)、Prometheus(监控系统)等 DevOps 生态工具对接都十分方便。
作为 Nginx 和 Kong 的替代项目,APISIX 已于 2020-07-15 成为 Apache 顶级项目。国内目前已经有很多知名企业(比如金山、有赞、爱奇艺、腾讯、贝壳)使用 APISIX 处理核心的业务流量。
APISIX 在路由动态性、基于 etcd 的配置热更新、插件生态、控制台等方面对 Kong 形成了有力竞争。不过,API 网关的性能对比高度依赖版本、部署拓扑、路由数量、插件链长度、请求体大小、TLS 开关和连接复用方式,建议根据自身场景做基准测试,不要直接照搬任一厂商的营销口径。
APISIX 同样支持定制化的插件开发。开发者除了能够使用 Lua 语言开发插件,还能通过下面两种方式开发来避开 Lua 语言的学习成本:
- 通过 Plugin Runner 来支持更多的主流编程语言(比如 Java、Python、Go 等等)。通过这样的方式,可以让后端工程师通过本地 RPC 通信,使用熟悉的编程语言开发 APISIX 的插件。这样做的好处是减少了开发成本,提高了开发效率,但是在性能上会有一些损失。
- 使用 Wasm(WebAssembly) 开发插件。Wasm 被嵌入到了 APISIX 中,用户可以使用 Wasm 去编译成 Wasm 的字节码在 APISIX 中运行。
Wasm 是基于堆栈的虚拟机的二进制指令格式,一种低级汇编语言,旨在非常接近已编译的机器代码,并且非常接近本机性能。Wasm 最初是为浏览器构建的,但是随着技术的成熟,在服务器端看到了越来越多的用例。
- GitHub 地址:https://github.com/apache/apisix
- 官网地址: https://apisix.apache.org/zh/
ShenYu
ShenYu 是一款基于 WebFlux 的可扩展、高性能、响应式网关,Apache 顶级开源项目。
ShenYu 通过插件扩展功能,插件是 ShenYu 的灵魂,并且插件也是可扩展和热插拔的。不同的插件实现不同的功能。ShenYu 自带了诸如限流、熔断、转发、重写、重定向和路由监控等插件。
- GitHub 地址: https://github.com/apache/shenyu
- 官网地址: https://shenyu.apache.org/
网关对比一览
| 特性 | Zuul 1.x | Zuul 2.x | Spring Cloud Gateway | Kong | APISIX | ShenYu |
|---|---|---|---|---|---|---|
| IO 模型 | 同步阻塞 | 异步非阻塞 | 异步非阻塞 | 异步非阻塞 | 异步非阻塞 | 异步非阻塞 |
| 底层技术 | Servlet | Netty | WebFlux/Server MVC + Netty/Servlet 容器 | OpenResty(Nginx + Lua) | OpenResty + etcd | WebFlux + Netty |
| 性能 | 低 | 高 | 高 | 很高 | 很高 | 高 |
| 动态配置 | 默认需重启,可扩展 | 支持,需自建体系 | 支持 | 支持 | 支持(热更新) | 支持 |
| 配置存储 | 本地配置/内存 | 自定义 | 默认 YAML/内存,可接 Redis 或自定义路由仓库 | PostgreSQL / YAML / K8s CRD | etcd(分布式) | 内存/数据库/注册中心 |
| 限流熔断 | 限流靠扩展,熔断靠 Hystrix | 需集成 | 限流内置 RequestRateLimiter;熔断需接 CircuitBreaker |
插件 | 插件 | 插件 |
| 生态系统 | Netflix(维护模式) | Netflix | Spring Cloud | Kong / 插件生态 | Apache | Apache |
| 运维复杂度 | 低 | 中 | 低到中 | 中(DB-less) / 高(DB Mode) | 中 | 中 |
| 学习曲线 | 平缓 | 平缓 | 平缓到中 | 陡峭(Lua) | 陡峭(Lua) | 平缓(Java) |
| 适用场景 | 遗留系统维护 | Netflix 存量体系 | Spring Cloud 生态 | 云原生、多语言 | 云原生、高性能 | Java 生态 |
上表中的“性能”只是经验分级,不能替代基准测试。实际性能会受路由数量、插件链长度、请求体大小、TLS 开关、连接复用率、日志采样率、下游延迟等因素影响。选型前建议用 wrk2、vegeta、k6 等工具在自身流量画像下复测,并重点观察 P99/P999 延迟、错误率、CPU/内存占用,而不是只看单纯 QPS。
如何选择?
选择 API 网关需要综合考虑技术栈、性能要求、团队能力和运维成本。
双层网关架构
在中大型微服务体系中,常见做法是把网关拆成两层:
- 流量网关:通常部署在边缘层,可以选择 Kong、APISIX、Nginx/OpenResty、Envoy Gateway 等,负责 SSL 终止、WAF、全局限流、IP 黑白名单、DDoS 防护、协议适配等偏基础设施的能力。
- 业务网关:通常部署在内网,可以选择 Spring Cloud Gateway、ShenYu 或自研网关,负责微服务路由、细粒度鉴权、参数校验、灰度路由、业务侧观测等更贴近业务系统的能力。
如果系统规模较小、业务域单一、团队人力有限,单层网关通常更简单;当外部流量治理和内部业务路由的职责开始互相干扰时,再拆成双层会更稳妥。如果团队已经全面使用 Service Mesh,也可以评估直接用 Envoy/Istio Ingress 承担南北向流量入口,避免代理层级过多。
| 场景 | 推荐方案 | 理由 |
|---|---|---|
| Spring Cloud 生态 | Spring Cloud Gateway | 与 Spring Boot/Spring Cloud 无缝集成,配置简单 |
| 高性能 / 云原生 | APISIX | 基于 etcd 的热更新、性能优异、云原生架构 |
| 多语言生态 | Kong | 插件丰富、支持多语言开发、社区成熟 |
| Netflix 存量体系 | 维持现状并规划迁移 | Zuul 1.x、Ribbon、Hystrix 等已进入维护模式,新项目不建议继续扩展 |
| 双层架构 | Kong/APISIX/Envoy(流量网关) + Spring Cloud Gateway(业务网关) | 流量网关处理 SSL、WAF、全局限流;业务网关处理微服务鉴权、参数校验、灰度路由 |
参考
- Spring Cloud Gateway 官方文档:https://docs.spring.io/spring-cloud-gateway/reference/
- Spring Cloud Netflix 维护模式说明:https://cloud.spring.io/spring-cloud-netflix/multi/multi__modules_in_maintenance_mode.html
- Kong Gateway 3.4 Breaking Changes:https://docs.konghq.com/gateway/latest/breaking-changes/34x/
- ASF 宣布 Apache APISIX 成为顶级项目:https://news.apache.org/foundation/entry/the-apache-software-foundation-announces66
- RFC 9113 HTTP/2:https://www.ietf.org/rfc/rfc9113.html
- Kong 插件开发教程[通俗易懂]:https://cloud.tencent.com/developer/article/2104299
- API 网关 Kong 实战:https://xie.infoq.cn/article/10e4dab2de0bdb6f2c3c93da6
- Spring Cloud Gateway 原理介绍和应用:https://blog.fintopia.tech/60e27b0e2078082a378ec5ed/
- 微服务为什么要用到 API 网关?:https://apisix.apache.org/zh/blog/2023/03/08/why-do-microservices-need-an-api-gateway/
Distributed System Distributed Configuration Center (`docs/distributed-system/distributed-configuration-center.md`)
title: 分布式配置中心详解:Apollo、Nacos、Spring Cloud Config 与 K8s ConfigMap 对比
category: 分布式
keywords:
- 配置中心
head: - meta
- name: keywords
content: 配置中心,分布式配置中心,Apollo,Nacos,Spring Cloud Config,Kubernetes ConfigMap,配置推送,长轮询,灰度发布,配置中心面试题
为什么要用配置中心?
微服务架构下,应用被拆分为大量独立部署的服务,每个服务都有自己的配置(服务地址、数据库参数、功能开关等)。配置项数量会随着服务数量、环境数量和集群数量一起增长。传统配置文件方式存在以下问题:
- 修改需重启:无论配置在代码库还是外部文件中,很多应用都需要重启进程才能让新配置生效。
- 与发版耦合:如果配置放在代码库中,配置变更往往要跟代码发版绑定,难以独立灰度和回滚。
- 安全性不足:敏感配置(数据库密码、API Key)直接写在代码库中容易泄露。
- 缺乏权限控制:无法对配置的查看、修改、发布等操作进行细粒度权限管控。
- 配置分散难管理:多环境(开发/测试/生产)、多集群的配置分散在各处,难以统一维护。
此外,配置中心通常提供以下增强能力:
- 版本管理:记录每次配置变更的修改人、修改时间、修改内容,支持一键回滚。
- 灰度发布:先将配置推送给部分实例验证,降低变更风险(Apollo、Nacos 1.1.0+ 支持)。
当然,不是所有系统都需要上配置中心。单体应用、单环境、配置项很少且变更频率低的场景,application-{profile}.yml、环境变量或 Kubernetes ConfigMap + 滚动重启通常就够了。配置中心会带来额外的运维成本、故障域和排查链路,小团队或低频配置场景不必过度工程化。
从分布式系统视角看,配置中心属于典型控制面:它负责决定“当前应该使用哪一份配置”,客户端负责拉取、缓存、监听和刷新。配置发布、灰度、回滚、客户端本地快照,处理的都是集中决策和客户端容灾之间的取舍。这个取舍可以和 分布式协调详解 放在一起理解。
配置中心还经常和 RPC 以及 API 网关 一起出现:RPC 框架需要拿到服务地址、超时、重试、降级等治理配置,网关也可能依赖配置中心做动态路由和灰度规则。它们解决的问题不同,但在真实微服务体系里通常会连在同一条链路上。
常见的配置中心有哪些?如何选择?
| 方案 | 状态 | 特点 |
|---|---|---|
| Spring Cloud Config | 活跃 | Spring 生态原生支持,基于 Git 存储 |
| Nacos | 活跃 | 阿里开源,配置中心 + 服务发现二合一 |
| Apollo | 活跃 | 携程开源,配置管理、权限和审计能力较强 |
| K8s ConfigMap | 活跃 | Kubernetes 原生方案 |
| Disconf / Qconf | 长期不活跃 | 不建议新项目使用 |
选型建议:
- 只需配置中心 → Apollo(管理能力更细)或 Nacos(单机启动更轻)
- 需要配置中心 + 服务发现 → Nacos
- Spring Cloud 体系且追求简单 → Spring Cloud Config
- Kubernetes 环境 → K8s ConfigMap 挂载 + 应用层文件监听。ConfigMap 以 Volume 挂载时会被 kubelet 周期同步,最终可见时间取决于 kubelet 同步周期和本地缓存传播方式;环境变量方式和
subPath挂载不会自动更新。热重载可以用 inotify 监听挂载文件,也可以用 Spring Cloud Kubernetes 通过 K8s Watch API 监听 ConfigMap 变更并触发刷新。
Apollo vs Nacos vs Spring Cloud Config
版本说明:以下对比基于 Apollo 2.x、Nacos 2.x、Spring Cloud Config 4.x/5.x。Spring Boot 3 体系通常对应 Spring Cloud Config 4.x,Spring Boot 4 体系对应更新的 Spring Cloud 2025.x 发行列车;如果仍在 Spring Boot 2 体系,对应的是 Spring Cloud Config 3.x。
| 功能点 | Apollo | Nacos | Spring Cloud Config |
|---|---|---|---|
| 配置界面 | 支持(权限、审计、发布流程较完整) | 支持 | 无(通常通过 Git 平台操作) |
| 配置实时生效 | 支持(HTTP 长轮询,通常秒级感知) | 支持(gRPC 变更通知 + 客户端拉取) | 半实时(需触发 refresh 或 Bus 广播) |
| 版本管理 | 原生支持 | 原生支持 | 依赖 Git |
| 权限管理 | 支持(应用/命名空间/环境等多层粒度) | 支持 | 依赖 Git 平台 |
| 灰度发布 | 支持(规则更细) | 支持(1.1.0+,能力相对基础) | 不支持 |
| 配置回滚 | 支持 | 支持 | 依赖 Git |
| 告警通知 | 支持 | 支持 | 不支持 |
| 多语言 | 支持(Open API / 多语言客户端) | 支持(Open API / 多语言客户端) | 更偏 Spring 应用 |
| 多环境 | 支持(通常物理隔离) | 支持(多用 Namespace 逻辑隔离) | 需配合多 Git 仓库 |
| 依赖组件 | MySQL(注册中心默认内嵌在 Config Service) | 外部 MySQL(生产推荐)/ 嵌入式 Derby + JRaft | Git + 可选消息队列 |
深度对比:
- Apollo:在权限模型、发布审计、发布前 diff、灰度规则等管理特性上更细,适合对配置治理要求较高的团队。多环境(FAT/UAT/PROD)物理隔离场景下,需为每个环境部署 Config Service、Admin Service 和独立数据库,运维门槛中等偏高
- Nacos:配置 + 注册中心二合一,部署简单(单机模式仅一个 Jar 包)。生产集群推荐使用外部 MySQL;嵌入式 Derby + JRaft 更适合测试或小规模场景。Nacos 的 Namespace/Group/DataId 模型上手快,但环境隔离通常偏逻辑隔离
- Spring Cloud Config:架构最简单(基于 Git),但实时性差,需要额外组件实现自动刷新
配置中心、注册中心与 K8s ConfigMap 的边界
- 应用配置中心(Apollo/Nacos/Spring Cloud Config):主要解决业务参数、开关、阈值、连接信息等应用配置的集中管理、审计、灰度和动态刷新。
- 服务注册中心(Eureka/Nacos/Consul):主要解决服务实例注册、发现和健康状态同步。Nacos 同时提供配置中心和注册中心能力,但两类职责仍然不同。
- Kubernetes ConfigMap:主要解决 Pod 启动参数、环境变量、挂载文件等容器运行时配置管理,不天然提供发布审批、灰度规则和应用内对象刷新。
- Service Mesh / Ingress 配置:主要解决流量路由、熔断、重试、超时、灰度流量等治理策略,配置对象通常是 CRD 或控制平面资源。
配置中心核心设计要点
设计或选型配置中心时,需关注以下能力:
1. 配置推送机制
| 模式 | 实时性 | 服务端压力 | 实现复杂度 | 适用场景 |
|---|---|---|---|---|
| 推模式 | 高(毫秒级) | 高(需维护连接) | 高 | 强实时性要求 |
| 拉模式 | 低(秒~分钟级) | 高(无效轮询) | 低 | 配置变更极少 |
| 长轮询 | 中高(秒级) | 中等(海量连接时内存压力大) | 中 | 主流方案 |
推送机制说明:
- Apollo:采用 HTTP 长轮询。客户端发起请求,服务端若有变更立即返回;无变更则挂起请求(服务端默认约 60s,客户端 read timeout 通常更长),期间一旦有变更立即响应。
- Nacos 2.x:服务发现链路升级为 gRPC 双向流,实时性更好;配置中心链路更准确地说是“变更通知 + 客户端拉取”的两阶段模型,服务端通知配置发生变化,客户端再按需拉取最新配置内容。
注意:严格说,长轮询仍是客户端发起的拉取请求,只是服务端通过挂起请求实现近实时;本表按行业惯例将其单列以突出运行特征。长轮询虽然比短轮询节省 CPU 和网络开销,但当客户端规模达到十万级时,服务端仍需维持海量挂起请求。以 Apollo 为例,服务端基于 Spring MVC
DeferredResult挂起请求,底层依托 Servlet 3.0 异步特性和 Tomcat NIO Connector 承载,对内存和连接数上限仍有要求。
2. 必备功能清单
- 权限控制:配置的查看、修改、发布需分级授权
- 审计日志:完整记录配置变更的操作人、时间、内容
- 版本管理:每次发布生成版本号,支持回滚到任意历史版本
- 灰度发布:配置先推送到部分实例,验证通过后全量发布
- 多环境隔离:开发、测试、生产环境配置独立管理
- 高可用部署:配置中心自身需要集群化部署,避免单点故障
3. 客户端容灾与启动顺序
配置中心是基础设施,一旦不可用会影响大量业务应用。因此客户端必须具备容灾能力:
- 多级缓存:优先读内存配置;配置中心不可用时读取本地快照;本地快照也不存在时使用代码里的兜底默认值或拒绝启动。
- 降级启动:对于非关键配置,可以先用本地快照启动,再异步连接配置中心;对于数据库地址、加密密钥这类关键配置,可以选择“无配置不启动”。
- 断线重连:长轮询