全栈开发宝典

从需求到上线的一套完整方法论:选型、编码、数据、工程、安全、架构、性能。可离线单文件查阅,按图索骥即可。

15核心篇章(含 Python/Java)
63可复用代码片段
24决策对照表
0外部依赖(离线可用)

怎么用这份宝典

  • 当字典查:左上角搜索框输入关键词(JWT、索引、Docker、N+1…),无关章节自动淡出。
  • 当规范用:带 清单 标记的段落可直接作为 Code Review 检查项。
  • 当路线图:第 11 章给出从初级到架构师的能力阶梯与 6 个练手项目。

贯穿全篇的六条铁律

1. 先跑通再优化

任何优化都建立在「有一条能验证的基准」之上。没有测量的优化是玄学。

2. 简单优先

能用一个进程解决,就不要上微服务;能用 SQL 解决,就不要引中间件。复杂度是最大的技术债。

3. 边界防御

所有跨越信任边界的输入都是敌人:用户输入、第三方响应、数据库返回、配置文件。

4. 失败是常态

网络会超时、磁盘会满、依赖会挂。写代码时先问:它挂了会怎样?重试、超时、熔断、降级四件套。

5. 可观测优先

没有日志和指标的系统等于盲盒。上线前先想清楚:出问题时我靠什么定位。

6. 自动化一切重复

格式化、检查、测试、构建、部署——任何重复三次以上的动作都该被脚本化。

01全栈能力地图

全栈不是「什么都会一点」,而是能以端到端视角交付并负责一个完整系统。下面这张图是能力的分层结构。

系统分层与能力项

用户 / 客户端CDN / 边缘网关 / LB应用服务缓存数据库对象存储异步队列
L0 · 基础内功

数据结构与算法、计算机网络(TCP/HTTP/TLS)、操作系统(进程/线程/IO)、SQL、Git、Linux 命令行。决定上限

L1 · 前端

HTML/CSS 布局、TypeScript、框架(React/Vue)、状态管理、构建工具、性能(Core Web Vitals)、可访问性。

L2 · 后端

HTTP 语义、REST/RPC、分层架构、认证授权、校验、错误处理、任务队列、文件上传、第三方集成。

L3 · 数据

关系建模、索引与执行计划、事务与隔离级别、缓存策略、搜索引擎、迁移与扩容。

L4 · 工程

代码规范、测试金字塔、CI/CD、容器化、环境管理、可观测性(日志/指标/链路)、发布策略。

L5 · 架构

服务拆分边界、一致性与分布式事务、事件驱动、容量规划、高可用与容灾、成本治理。

一次请求的全生命周期(排查问题时的心智模型)

flow
DNS 解析 → TCP 握手 → TLS 握手 → 发送 HTTP 请求
  → CDN/网关(限流、鉴权、路由)
  → 应用进程(反序列化 → 参数校验 → 鉴权 → 业务编排)
  → 缓存查询(命中则返回)
  → 数据库查询(事务 / 锁 / 索引)
  → 渲染响应 → 网络回传 → 浏览器解析 → 首屏渲染 → 交互就绪

★ 每一步都可能成为瓶颈,定位时按序逐层排除,而不是猜测。
排查口诀:先看现象(慢?错?无响应?)→ 定范围(单用户/全量/特定接口)→ 看指标(CPU/内存/IO/连接数/慢查询)→ 看日志(traceId 串联)→ 复现 → 修复 → 加监控与告警。

02技术选型决策

选型的本质不是选「最好的」,而是选团队能驾驭、生态够成熟、出问题查得到的。下面是主流场景的推荐矩阵。

按场景选型

场景首选备选选型要点
内容/营销站、SEO 优先Next.js / Nuxt(SSR+SSG)Astro静态化程度越高,成本与延迟越低
后台管理 / 中台系统React + Ant Design / Vue + Element PlusSvelte组件库成熟度 > 框架新鲜度
中大型业务后端NestJS(TS,强约定)Fastify / Express团队规模大时,约定优于灵活
轻量 BFF / 边缘函数Hono / ElysiaExpress冷启动与包体积敏感场景
高并发实时Go / RustNode + ClusterCPU 密集别用 Node 单线程
数据密集 / 报表Python(FastAPI + Polars)Node生态决定效率
企业级后端 / 金融电信Java Spring Boot 3Go强类型、生态成熟、事务与中间件支持最全、人才供给多
AI / LLM 应用Python(FastAPI + vLLM/LangChain)Node模型与数据生态几乎全在 Python 侧
主数据库PostgreSQLMySQL 8Pg 的 JSONB/CTE/扩展基本无短板
缓存 / 会话 / 队列RedisValkey注意持久化与内存淘汰策略
搜索Elasticsearch / OpenSearchPg 全文检索(小规模)数据量 < 百万且需求简单,别上 ES
文件存储S3 协议对象存储MinIO(自建)永远不要存本地磁盘
部署Docker + Compose(小)→ K8s(大)ServerlessK8s 的运维成本常被低估

决策树:我该不该引入 XX?

decision
1. 现有方案是否已经无法解决?  (是 → 继续,否 → 停)
2. 问题能否用 20 行代码解决?  (能 → 自己写,不引依赖)
3. 引入的维护成本谁承担?      (无人承担 → 停)
4. 是否有降级/回滚方案?       (无 → 先设计方案)
5. 团队是否有 1 人以上真正熟悉?(无 → 先做技术验证 Spike)

★ 依赖的隐性成本 = 学习成本 + 升级成本 + 故障排查成本 + 安全补丁成本

版本与环境基线建议

建议说明
Node.js使用 LTS(偶数版本)生产环境永不追 newest;用 .nvmrc 锁定
包管理器pnpm(省磁盘、快、严格依赖)必须提交 lockfile,CI 用 frozen-lockfile
TypeScriptstrict: true新项目不开 strict 等于白用 TS
数据库与云厂商 LTS 保持一致大版本升级单独排期
反模式警示:为「可能的未来需求」提前引入 Kafka、K8s、微服务、多租户——90% 的情况下系统活不到那一天。遵循 YAGNI,但保留清晰的模块边界,让未来的拆分变得容易。

03前端工程

前端的第一性原理:把数据准确、快速、无障碍地呈现给用户,并可靠地收集用户意图。框架会变,这三点不会。

3.1 现代 CSS 布局速查

90% 的布局需求用 Flex + Grid 就能解决,不需要引 UI 框架的栅格系统。

Flex:一维排列

沿主轴排布,适合导航栏、按钮组、垂直居中。gap 替代 margin 拼接。

Grid:二维网格

行列同时控制,适合卡片墙、后台框架、不规则布局。

css
/* —— 万能居中 —— */
.center { display:grid; place-items:center; }
.center-x { display:flex; justify-content:center; }
.holy { display:flex; align-items:center; justify-content:space-between; }

/* —— 经典后台骨架:头部 + 侧栏 + 内容 —— */
.layout{
  display:grid; height:100dvh;
  grid-template-columns:240px 1fr;
  grid-template-rows:56px 1fr;
  grid-template-areas:"header header" "side main";
}
.layout>header{grid-area:header} .layout>aside{grid-area:side;overflow:auto}
.layout>main{grid-area:main;overflow:auto}

/* —— 自适应卡片墙(不用媒体查询) —— */
.cards{display:grid;gap:16px;grid-template-columns:repeat(auto-fill,minmax(260px,1fr))}

/* —— 底部吸附:内容不足时 footer 仍在底部 —— */
.page{display:flex;flex-direction:column;min-height:100dvh}
.page>main{flex:1}

/* —— 单行省略 / 多行省略 —— */
.ell{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.ell-2{display:-webkit-box;-webkit-line-clamp:2;-webkit-box-orient:vertical;overflow:hidden}

/* —— 现代响应式:容器查询(按父容器而非视口) —— */
.wrap{container-type:inline-size}
@container (max-width:480px){ .card{grid-template-columns:1fr} }

/* —— 深浅色 —— */
:root{--bg:#fff;--fg:#111}
@media(prefers-color-scheme:dark){:root{--bg:#0b0e14;--fg:#dbe2ee}}
易错点:min-width:0 — Flex/Grid 子项默认 min-width:auto,长文本会撑破容器,需显式设 0 才能正常省略号截断;② 100vh 在移动端含地址栏高度,改用 100dvh;③ 滚动容器要同时给父级设 min-height:0

3.2 TypeScript 核心

TS 的价值不在「加类型注解」,而在用类型系统表达业务约束,让非法状态不可表示。

typescript
// 1) 判别联合:让非法状态无法构造
type Result<T> =
  | { ok:true; data:T }
  | { ok:false; code:'NOT_FOUND'|'FORBIDDEN'; msg:string };

function handle(r:Result<User>){
  if(r.ok){ console.log(r.data.name); }   // 此处 data 已收窄
  else   { console.log(r.code, r.msg); }
}

// 2) 泛型约束 + keyof:类型安全的取值
function pluck<T,K extends keyof T>(obj:T, keys:K[]):Pick<T,K>{
  return keys.reduce((a,k)=>(a[k]=obj[k],a), {} as Pick<T,K>);
}

// 3) 常用工具类型
// Partial 全部可选 | Required 全部必填 | Pick 挑字段 | Omit 排除字段
// Record<K,V> 映射 | ReturnType 取函数返回 | Awaited 解 Promise
// NonNullable 去空 | Exclude/Extract 联合运算
type UserDTO = Omit<User,'passwordHash'|'salt'> & { roleName:string };

// 4) satisfies:校验字面量结构但保留精确类型(比 as 安全)
const routes = {
  home:'/', login:'/login', user:'/u/:id'
} satisfies Record<string,string>;

// 5) 类型守卫:把 unknown 收窄成具体类型
function isUser(x:unknown):x is User{
  return typeof x==='object' && x!==null && 'id' in x && 'email' in x;
}

// 6) 模板字面量类型:约束字符串格式
type EventName = `on${Capitalize<'click'|'change'>}`;  // 'onClick'|'onChange'

// 7) 运行时校验:类型在编译后被擦除,边界处必须 zod 校验
import { z } from 'zod';
const CreateUser = z.object({
  email: z.string().email(),
  age:   z.number().int().min(0).max(150),
  role:  z.enum(['admin','user']).default('user'),
});
type CreateUserInput = z.infer<typeof CreateUser>;   // 类型由 schema 推导
避坑:① 不要用 as 掩盖错误——那是把编译期问题推迟到运行时;② 不要写 any,至少用 unknown 强制检查;③ 枚举优先用字符串联合字面量而非 enum(enum 会生成运行时代码且类型不兼容)。

3.3 React 核心

心智模型

UI = f(state)。你描述的是「在某个状态下 UI 应该长什么样」,而不是「如何一步步把 DOM 改成目标样子」。

tsx
// ✅ 声明式:状态变,UI 自动变
const [list,setList]=useState<User[]>([]);
const [q,setQ]=useState('');
// 派生数据直接算,不要额外存 state(避免状态不同步)
const filtered = useMemo(()=>list.filter(u=>u.name.includes(q)),[list,q]);

// ❌ 反模式:把派生数据放进 state + useEffect 同步
// const [filtered,setFiltered]=useState([]);
// useEffect(()=>setFiltered(list.filter(...)),[list,q]);  // 多一次渲染且易出 bug

// ✅ 请求数据的标准写法(含取消 + 竞态处理)
useEffect(()=>{
  const ac = new AbortController();
  setLoading(true);
  fetch(`/api/users?q=${q}`,{signal:ac.signal})
    .then(r=>r.json())
    .then(d=>setList(d.items))
    .catch(e=>{ if(e.name!=='AbortError') setError(e); })
    .finally(()=>setLoading(false));
  return ()=>ac.abort();     // 关键:清理,避免后发先至的响应覆盖
},[q]);

Hooks 要点

Hook用途典型坑
useState组件内状态异步更新批处理:用 setX(v=>v+1) 而非 setX(x+1)
useEffect与外部系统同步依赖数组写不全 → 闭包过期;务必返回清理函数
useMemo缓存昂贵计算计算成本 < 比较成本时是负优化
useCallback稳定函数引用仅当传给 memo 子组件或作为 effect 依赖时才需要
useRef保存不触发渲染的值 / DOM 引用不要在渲染期间读写 ref.current
useReducer复杂状态机状态逻辑多且相互关联时优于多个 useState

状态管理选型

方案适用说明
useState / useReducer组件内、局部默认选择,能不提升就不提升
Context低频全局(主题、语言、当前用户)值变化会让所有消费者重渲染,别放高频数据
URL(searchParams)筛选条件、分页、弹窗可分享可回退,被严重低估的方案
服务端状态库(TanStack Query / SWR)所有「来自服务端的数据」强烈推荐缓存、重试、失效、乐观更新全包
Zustand / Jotai少量客户端全局状态轻量,替代 Redux 的现代选择
Redux Toolkit大型团队、需时间旅行调试样板代码仍多,非必要不引入
经验法则:服务端数据 → TanStack Query;客户端 UI 状态 → Zustand;其余 → 组件本地 state。三类分清后,90% 的状态管理难题消失。

错误边界

tsx
class ErrorBoundary extends React.Component<
  {fallback:React.ReactNode; children:React.ReactNode},
  {hasError:boolean}>
{
  state = { hasError:false };
  static getDerivedStateFromError(){ return { hasError:true }; }
  componentDidCatch(err:Error, info:React.ErrorInfo){
    reportError(err, info.componentStack);   // 上报 Sentry 等
  }
  render(){ return this.state.hasError ? this.props.fallback : this.props.children; }
}
// 用法:包在路由级或模块级,避免一个组件崩溃白屏整页

3.4 Next.js 实战(App Router)

bash
npx create-next-app@latest app --ts --app --tailwind --eslint --src-dir
# 目录约定
app/
  layout.tsx        # 根布局(必须含 html/body)
  page.tsx          # 路由页面
  loading.tsx       # 该段的 Suspense 兜底
  error.tsx         # 该段的错误边界('use client')
  not-found.tsx     # 404
  route.ts          # Route Handler(API)
  [id]/page.tsx     # 动态段
  (group)/          # 路由分组,不影响 URL
  @modal/           # 平行路由
middleware.ts       # 请求前拦截(鉴权/重写/国际化)
tsx
// Server Component(默认):在服务端执行,可直接查库
export default async function Page({ searchParams }:{searchParams:{page?:string}}){
  const page = Number(searchParams.page ?? 1);
  const items = await db.post.findMany({ take:20, skip:(page-1)*20 });
  return <PostList items={items} />;
}

// 缓存策略(Next 14+)
export const revalidate = 60;                       // ISR:60 秒重新生成
export const dynamic = 'force-dynamic';             // 每次请求都渲染
const r = await fetch(url,{ next:{ revalidate:300, tags:['posts'] } });
revalidateTag('posts');                             // 写操作后主动失效

// Client Component:需要交互/浏览器 API 时加 'use client'
'use client';
export function Like({ id }:{id:string}){
  const [n,setN]=useState(0);
  return <button onClick={()=>setN(n+1)}>{n}</button>;
}

// Route Handler
export async function GET(req:Request){
  const { searchParams } = new URL(req.url);
  return Response.json({ ok:true, q:searchParams.get('q') });
}
Server vs Client 边界:默认全用 Server Component(更小的 JS 包、更快的首屏),只在需要 useState/useEffect、事件、浏览器 API 时才下沉为 Client Component,且把 'use client' 尽量推到组件树的叶子节点。传给 Client 组件的 props 必须可序列化(不能传函数、Date 需注意)。

3.5 前端性能优化

Core Web Vitals 目标值

指标含义良好常用手段
LCP最大内容绘制(首屏速度)< 2.5sSSR/SSG、图片预加载、CDN、压缩、减少阻塞资源
INP交互到下一帧的延迟< 200ms拆分长任务、防抖、Web Worker、减少重渲染
CLS累积布局偏移< 0.1给图片/广告位设宽高、字体 font-display:swap + 尺寸预留
TTFB首字节时间< 800ms边缘缓存、数据库优化、连接复用

优化清单

  • 路由级代码分割(React.lazy / Next 自动分割),首屏 JS ≤ 200KB gzip
  • 图片:用 WebP/AVIF、next/image 自动尺寸、loading="lazy"、首屏图 priority
  • 字体:子集化、自托管、preload 关键字体、font-display:swap
  • 长列表虚拟化(> 1000 行用 react-window / TanStack Virtual)
  • 防抖节流:resizescroll、搜索输入(250ms 防抖)
  • 缓存头:静态资源 Cache-Control: public, max-age=31536000, immutable + 内容哈希文件名
  • 开启 Brotli/gzip、HTTP/2、移除未使用依赖(npx depcheck
  • view-transition / CSS transform 动画,避免触发 layout 的属性(width/top/left)
ts
// 防抖 + 节流的正确实现
export function debounce<T extends (...a:any[])=>void>(fn:T, wait=250){
  let t:ReturnType<typeof setTimeout>;
  return (...args:Parameters<T>)=>{ clearTimeout(t); t=setTimeout(()=>fn(...args),wait); };
}
export function throttle<T extends (...a:any[])=>void>(fn:T, wait=100){
  let last=0;
  return (...args:Parameters<T>)=>{
    const now=Date.now();
    if(now-last>=wait){ last=now; fn(...args); }
  };
}
// 长任务让出主线程
const yieldToMain = ()=>new Promise(r=>setTimeout(r,0));

04后端服务

后端的核心职责:在不可信的输入和不可靠的依赖之上,正确地持久化业务状态

4.1 框架与分层

推荐分层(依赖只能向下)

text
Controller / Router   → 解析协议、参数校验、组装响应(不含业务规则)
    ↓
Service / UseCase     → 业务编排、事务边界、权限判定(不含 HTTP 概念)
    ↓
Repository / DAO      → 数据访问、SQL、缓存读写(不含业务规则)
    ↓
Infrastructure        → DB / Redis / MQ / 第三方 SDK

★ 铁律:上层可依赖下层,下层绝不反向依赖上层。
★ Service 层不出现 req/res 对象,否则无法被 CLI/队列/测试复用。
★ 跨层异常统一由最外层(全局异常过滤器)转换为 HTTP 响应。
typescript · express 分层示例
// —— 统一成功/失败响应 ——
type Ok<T> = { code:0; data:T; traceId:string };
type Err    = { code:number; message:string; traceId:string };

// —— 业务异常(带 HTTP 语义但不依赖框架)——
export class BizError extends Error {
  constructor(public status:number, message:string, public code=status){
    super(message);
  }
}

// service/user.service.ts —— 纯业务,可单测
export class UserService {
  constructor(private repo:UserRepo, private mailer:Mailer){}
  async register(input:CreateUserInput){
    if(await this.repo.existsByEmail(input.email))
      throw new BizError(409,'邮箱已注册');
    const hash = await argon2.hash(input.password);   // 绝不明文存密码
    const user = await this.repo.create({...input, passwordHash:hash});
    await this.mailer.sendWelcome(user.email);        // 副作用放最后,失败不阻断主流程
    return sanitize(user);                            // 出参脱敏
  }
}

// —— 全局错误处理(Express 中间件)——
app.use((err:Error,req:Request,res:Response,next:NextFunction)=>{
  const traceId = req.traceId;
  if(err instanceof BizError){
    return res.status(err.status).json({code:err.code,message:err.message,traceId});
  }
  logger.error({err,traceId,url:req.url},'unhandled');
  res.status(500).json({code:500,message:'服务器内部错误',traceId});  // 不泄漏堆栈
});

参数校验(边界防御第一道关)

typescript
const PageQuery = z.object({
  page:  z.coerce.number().int().min(1).max(10000).default(1),
  size:  z.coerce.number().int().min(1).max(100).default(20),   // 必须限制上限!
  sort:  z.enum(['createdAt','name']).default('createdAt'),
  order: z.enum(['asc','desc']).default('desc'),
});
app.get('/users', async (req,res)=>{
  const q = PageQuery.parse(req.query);          // 校验失败自动抛 400
  res.json(await userService.list(q));
});

4.2 REST API 设计规范

要点正确做法错误做法
资源命名复数名词 /users/users/1/orders动词 /getUser/createOrder
动作表达用 HTTP 方法:GET/POST/PUT/PATCH/DELETE全部 POST + 路径里写动作
非 CRUD 动作子资源动词化 POST /orders/1/cancelPOST /cancelOrder
分页?page=1&size=20 或游标 ?cursor=xxx&size=20一次性返回全部
状态码200/201/204/400/401/403/404/409/422/429/500一律 200 + body 里放 code
版本URL 前缀 /api/v1 或 Header(二选一,全局统一)混用
错误体{code,message,details?,traceId}返回 HTML 堆栈

状态码速查

语义典型场景
200成功,返回体有内容查询、更新成功
201已创建POST 创建成功,响应带 Location
204成功但无返回体DELETE
400请求本身有问题(参数/格式)校验失败
401未认证(不知道你是谁)token 缺失/过期
403已认证但无权限普通用户访问管理接口
404资源不存在ID 不存在(也可对无权限资源返回 404 防探测)
409状态冲突唯一键冲突、重复提交
422语义正确但业务校验不通过余额不足
429限流需带 Retry-After
500服务端未知错误必须打日志 + traceId,不暴露细节
幂等性:GET/PUT/DELETE 天然幂等;POST 必须靠幂等键保障(客户端生成 Idempotency-Key,服务端唯一索引去重或缓存首次结果)。支付、下单、发券接口不做幂等 = 定时炸弹。
typescript · 幂等中间件
app.post('/orders', async (req,res,next)=>{
  const key = req.header('Idempotency-Key');
  if(!key) return res.status(400).json({message:'缺少 Idempotency-Key'});
  const cacheKey = `idem:${req.userId}:${key}`;
  const cached = await redis.get(cacheKey);
  if(cached) return res.json(JSON.parse(cached));       // 重复请求:直接回放
  const result = await orderService.create(req.userId, req.body);
  await redis.set(cacheKey, JSON.stringify(result), 'EX', 86400);
  res.status(201).json(result);
});

4.3 认证与授权

维度Session + CookieJWT
状态服务端有状态(存 Redis)无状态(自包含)
撤销即时(删 Redis 即可)困难(需黑名单/短 TTL + 刷新令牌)
跨域/移动端需处理 SameSite/CORS友好(放 Authorization 头)
XSS 风险低(HttpOnly Cookie 不可被 JS 读)高(存 localStorage 会被窃取)
CSRF 风险需防护(SameSite=Lax + CSRF Token)低(不自动携带)
建议Web 后台首选第三方/网关/短时效场景
推荐组合:Access Token(短,15min,内存/Authorization)+ Refresh Token(长,7d,HttpOnly + Secure + SameSite=Strict Cookie,绑定 UA/IP,一次性轮换)。刷新时检测重放(旧 token 已被用则整条令牌链失效)。
typescript
// 密码存储:argon2id > bcrypt(cost 12) > scrypt。禁止 MD5/SHA1/裸 SHA256
import argon2 from 'argon2';
const hash = await argon2.hash(pwd, { type:argon2.argon2id });
const ok   = await argon2.verify(hash, pwd);

// JWT 签发:只放必要声明,别塞敏感信息(payload 仅 base64,可解码)
const access = jwt.sign({ sub:user.id, role:user.role, tv:user.tokenVersion },
                        process.env.JWT_SECRET!, { expiresIn:'15m' });

// 鉴权中间件(RBAC)
function requireRole(...roles:Role[]){
  return (req,res,next)=>{
    if(!req.user) return res.status(401).end();
    if(!roles.includes(req.user.role)) return res.status(403).end();
    next();
  };
}
app.delete('/admin/users/:id', requireRole('admin'), handler);

// ★ 越权防护:查询必须带上归属条件,不能只查 id
await db.post.deleteMany({ where:{ id, authorId:req.user.id } });  // ✅ 而非 delete({where:{id}})
高危清单:① 令牌放 URL query(会被日志/Referer 泄漏);② JWT 永不过期;③ 用 alg:none 或不校验算法(必须显式指定 algorithms:['HS256']);④ 密码重置令牌可重复使用、不过期;⑤ 登录接口无限速(必须加失败次数限制 + 验证码)。

4.4 异步任务与消息

凡是不需要用户在本次请求中等结果的事,都应该异步化:发邮件、生成报表、图片处理、对账、推送。

typescript · BullMQ
const emailQ = new Queue('email',{ connection:redis });

// 生产者
await emailQ.add('welcome', { userId }, {
  attempts:3,                                    // 重试次数
  backoff:{ type:'exponential', delay:2000 },    // 指数退避
  removeOnComplete:{ count:1000 },
  removeOnFail:{ age:7*86400 },
});

// 消费者(独立进程)
new Worker('email', async job=>{
  if(job.name==='welcome') await mailer.sendWelcome(job.data.userId);
}, { connection:redis, concurrency:10,
     limiter:{ max:100, duration:60_000 } });    // 限流保护下游

// 定时任务(也可用 cron / 数据库锁保证单实例)
await emailQ.upsertJobScheduler('digest', { pattern:'0 9 * * *' }, { name:'digest' });
异步三原则:幂等——同一条消息重复消费结果一致(用业务唯一键去重);② 可观测——记录任务状态、耗时、失败原因、重试次数;③ 兜底——死信队列 + 告警 + 人工补偿入口。定时任务要防并发执行(分布式锁)与补跑(记录执行水位)。

4.5 实时通信与外部集成

方案方向适用
轮询C→S简单状态刷新,间隔 > 10s,能不用就不用
SSE(EventSource)S→C 单向推荐AI 流式输出、进度推送、通知。原生支持断线重连
WebSocket双向协同编辑、IM、实时游戏。需自己处理心跳/重连/鉴权
typescript · SSE
res.writeHead(200,{
  'Content-Type':'text/event-stream',
  'Cache-Control':'no-cache, no-transform',
  'Connection':'keep-alive',
  'X-Accel-Buffering':'no',        // 关键:禁止 Nginx 缓冲
});
for await (const chunk of stream){
  res.write(`data: ${JSON.stringify(chunk)}\n\n`);
}
res.write('event: done\ndata: {}\n\n'); res.end();

调用第三方 API 的四件套

typescript
// ① 超时(默认无超时 = 连接泄漏)② 重试(仅幂等 + 仅网络/5xx)
// ③ 熔断(连续失败快速失败)④ 降级(返回兜底数据)
const api = axios.create({ baseURL, timeout:3000 });        // 连接+读取超时

async function callWithRetry<T>(fn:()=>Promise<T>, n=3):Promise<T>{
  let lastErr;
  for(let i=0;i<n;i++){
    try{ return await fn(); }
    catch(e){
      lastErr = e;
      const retryable = !e.response || e.response.status >= 500;  // 4xx 不重试
      if(!retryable || i===n-1) break;
      await sleep(2**i * 300 + Math.random()*200);   // 指数退避 + 抖动防惊群
    }
  }
  throw lastErr;
}

05数据库与存储

数据库是系统里最难替换、最容易成为瓶颈的部分。建模阶段偷的懒,会用十倍的运维代价偿还。

5.1 建模原则

决策建议理由
主键BIGSERIAL 或 UUIDv7(时间有序)UUIDv4 随机写入导致 B+ 树页分裂,性能差
时间timestamptz(Pg)/ datetime(3)(MySQL)永远用 UTC 存储,展示层转时区
金额DECIMAL(18,2) 或「分」为单位的 BIGINT禁用 float浮点有精度误差
字符串有明确上限用 VARCHAR(n),长文本 TEXTPg 中两者性能一致
布尔/状态状态多时用 SMALLINT/枚举表而非多个 bool避免「已支付且已取消」的非法组合
JSONPg JSONB(可建 GIN 索引)仅用于「结构不定的扩展属性」,核心字段必须列式
软删除deleted_at + 部分唯一索引见下方注意
审计字段created_at / updated_at / created_by / updated_by排查问题时救命
sql
CREATE TABLE users (
  id          BIGSERIAL PRIMARY KEY,
  email       VARCHAR(255) NOT NULL,
  password_hash TEXT       NOT NULL,
  role        SMALLINT     NOT NULL DEFAULT 0,
  created_at  TIMESTAMPTZ  NOT NULL DEFAULT now(),
  updated_at  TIMESTAMPTZ  NOT NULL DEFAULT now(),
  deleted_at  TIMESTAMPTZ
);

-- 软删除下保证未删除记录唯一(Pg 部分索引)
CREATE UNIQUE INDEX uk_users_email ON users(email) WHERE deleted_at IS NULL;

-- 常用组合查询索引:注意「区分度高的列放前面」
CREATE INDEX idx_users_role_created ON users(role, created_at DESC);

COMMENT ON COLUMN users.role IS '0=member,1=admin,2=superadmin';
软删除代价:所有查询都要带 WHERE deleted_at IS NULL,忘一次就是数据泄漏;唯一约束要改写成部分索引;关联查询性能下降。只在「业务确实需要恢复/审计」时使用,并考虑历史表归档替代。

5.2 索引与执行计划

最左前缀原则

sql
-- 索引 (a, b, c) 能加速:
WHERE a=1                         ✅
WHERE a=1 AND b=2                 ✅
WHERE a=1 AND b=2 AND c=3         ✅
WHERE a=1 ORDER BY b              ✅
WHERE b=2                         ❌ 未用最左列
WHERE a=1 AND c=3                 ⚠️ 只用到 a,c 部分无法定位
WHERE a>1 AND b=2                 ⚠️ 范围列之后的列无法再用索引定位

-- 覆盖索引:查询列全在索引里,无需回表 → Using index
CREATE INDEX idx_cover ON orders(user_id, status) INCLUDE (amount);

-- 函数/表达式索引:别在列上包函数(会导致索引失效)
WHERE lower(email)='a@b.com'      ❌
CREATE INDEX idx_lower_email ON users(lower(email));   ✅ 建表达式索引

-- 隐式转换陷阱:字符串列传数字、不同字符集/排序规则 JOIN → 索引失效
-- LIKE 前缀匹配可用索引,'%xxx' 不可用(改用全文检索/倒排)

EXPLAIN 怎么看

关键字含义优化方向
Seq Scan全表扫描大表上出现需警惕 → 加索引 / 限制返回范围
Index Scan走索引 + 回表正常
Index Only Scan覆盖索引,不回表最优
Bitmap Heap Scan多索引合并后回表考虑建组合索引
rows 估算 ≫ 实际统计信息过期执行 ANALYZE 或调大统计采样
Nested Loop + 大表驱动表行数大时灾难改用 Hash Join / 加索引
Using filesort / temporary排序/分组未用索引建符合排序顺序的索引
sql
EXPLAIN (ANALYZE, BUFFERS) SELECT * FROM orders WHERE user_id=42 ORDER BY id DESC LIMIT 20;

-- MySQL 慢查询定位
SET GLOBAL slow_query_log='ON'; SET GLOBAL long_query_time=1;
SELECT * FROM sys.statement_analysis ORDER BY avg_latency DESC LIMIT 10;

-- Pg 慢查询:pg_stat_statements(必装扩展)
SELECT calls, round(mean_exec_time::numeric,2) ms, query
FROM pg_stat_statements ORDER BY mean_exec_time DESC LIMIT 10;

分页优化

sql
-- ❌ 深分页:OFFSET 100000 要扫描并丢弃 10 万行
SELECT * FROM orders ORDER BY id LIMIT 20 OFFSET 100000;

-- ✅ 游标分页(keyset):利用索引直接定位
SELECT * FROM orders WHERE id < :last_id ORDER BY id DESC LIMIT 20;

-- ✅ 延迟关联:先查 ID 再回表,减少回表量
SELECT * FROM orders o JOIN
  (SELECT id FROM orders ORDER BY id LIMIT 20 OFFSET 100000) t USING(id);

5.3 事务与并发

隔离级别脏读不可重复读幻读说明
Read Uncommitted✗可能几乎不用
Read Committed多数数据库默认,够用
Repeatable Read✗(Pg 实际可避免)MySQL 默认
Serializable性能最低,用于强一致场景
sql · 并发控制
-- 悲观锁:先锁后改,适合高冲突(库存、余额)
BEGIN;
SELECT * FROM sku WHERE id=1 FOR UPDATE;      -- 行锁(必须走索引,否则锁表!)
UPDATE sku SET stock=stock-1 WHERE id=1 AND stock>=1;
COMMIT;

-- 乐观锁:版本号,适合低冲突(大多数业务)
UPDATE sku SET stock=stock-1, version=version+1
WHERE id=1 AND version=:expected_version;
-- 影响行数 0 → 说明被并发修改,重试或报错

-- 原子 upsert(避免「先查再插」的竞态)
INSERT INTO user_login(user_id, date, times) VALUES (1, CURRENT_DATE, 1)
ON CONFLICT (user_id, date) DO UPDATE SET times = user_login.times + 1;

-- 防死锁:固定加锁顺序(如都按 id 升序)、缩短事务、避免事务内做远程调用
事务铁律:① 事务内禁止远程调用(HTTP/RPC/发短信)——网络延迟会拖长锁持有时间,极易雪崩;② 事务要短,只包住必要的写操作;③ 明确设置锁等待超时(lock_timeoutstatement_timeout);④ 出现死锁先看日志里的两条 SQL 加锁顺序。

5.4 ORM 实战

  • 必须开启 SQL 日志(开发环境),否则你永远不知道 ORM 生成了什么
  • 警惕 N+1 查询:列表页 + 循环里查关联数据,10 行变 11 条 SQL
  • select 明确字段,别默认 SELECT *
  • 批量操作用 createMany / bulkInsert,别在循环里单条插入
  • 迁移必须可回滚、向前兼容(先加字段 → 双写 → 切读 → 删旧字段)
  • 连接池大小 ≈ CPU核数 * 2 + 磁盘数,不是越大越好(Pg 建议 10~30)
typescript · Prisma
// ❌ N+1:每篇都查一次作者
const posts = await db.post.findMany();
for(const p of posts) p.author = await db.user.findUnique({where:{id:p.authorId}});

// ✅ include 一次性 join / 或 dataloader 批处理
const posts = await db.post.findMany({
  select:{ id:true, title:true, author:{ select:{ id:true, name:true } } },
  take:20, skip:(page-1)*20,
  orderBy:{ createdAt:'desc' },
});
const [items, total] = await db.$transaction([
  db.post.findMany({...}), db.post.count({where}),
]);

// 事务(隔离级别 + 超时)
await db.$transaction(async (tx)=>{
  await tx.order.create({data});
  await tx.sku.update({where:{id}, data:{stock:{decrement:1}}});
}, { isolationLevel:'ReadCommitted', timeout:5000 });

5.5 Redis 缓存

数据结构选型

结构典型场景
String缓存对象(序列化 JSON)、计数器(INCR)、分布式锁
Hash对象部分字段读写(购物车、用户配置)
List队列/栈、最新动态(LPUSH + LTRIM
Set去重、标签、共同好友(SINTER)、抽奖
ZSet排行榜、延迟队列(score=执行时间)、优先级队列
Bitmap签到、DAU 统计(1 亿用户日活仅需 12MB)
HyperLogLogUV 去重计数(0.81% 误差,12KB 固定内存)
Stream可靠消息队列(支持消费组、ACK、重试)

缓存三大问题

穿透

查一个根本不存在的 key,每次都打到 DB。
解法:缓存空值(短 TTL)+ 布隆过滤器 + 参数校验拦截非法 ID。

击穿

热点 key 过期瞬间,海量请求直击 DB。
解法:互斥锁重建(singleflight)+ 逻辑过期(不设 TTL,后台异步刷新)+ 热点永不过期。

雪崩

大批 key 同时过期。
解法:TTL 加随机抖动 + 多级缓存 + 限流降级 + 提前预热。

typescript
// Cache-Aside 标准读路径(最常用)
async function getOrLoad<T>(key:string, ttl:number, load:()=>Promise<T>):Promise<T>{
  const hit = await redis.get(key);
  if(hit) return JSON.parse(hit);

  // 单飞锁:同一 key 只有一个请求去回源,其余短暂等待
  const lockKey = `lock:${key}`;
  const got = await redis.set(lockKey,'1','EX',10,'NX');
  if(!got){ await sleep(50); return getOrLoad(key,ttl,load); }   // 递归等一等

  try{
    const data = await load();
    const val = data === null ? '__NULL__' : JSON.stringify(data);
    await redis.set(key, val, 'EX', ttl + Math.floor(Math.random()*ttl*0.2)); // 抖动防雪崩
    return data;
  } finally { await redis.del(lockKey); }
}

// 写路径:先更新 DB,再删除缓存(不是更新缓存!)
await db.user.update(...);  await redis.del(`user:${id}`);
// 高一致要求:延迟双删(删 → 等 500ms → 再删)或订阅 binlog(Canal/Debezium)异步失效

// 分布式锁(Redlock 简化版,务必设置自动过期 + 校验随机值再删)
const val = crypto.randomUUID();
const ok = await redis.set(lockKey, val, 'EX', 30, 'NX');
// 释放时用 Lua 保证「判断 + 删除」原子性:
// if redis.call('get',KEYS[1])==ARGV[1] then return redis.call('del',KEYS[1]) else return 0 end
缓存不是银弹:引入缓存会带来一致性成本。先问:这个查询真的慢吗?能否用索引/SQL 优化解决?缓存只加在读多写少、能容忍短暂不一致的数据上。同时必须设置 maxmemory-policy(通常 allkeys-lru),否则内存打满会写失败。

数据库扩展路径(按顺序,不要跳步)

SQL/索引优化加缓存读写分离垂直拆分(按业务分库)水平分片(分库分表)换专用存储(ES/ClickHouse)

06工程化与 DevOps

工程化的目标:让「正确的做法」成为「最省力的做法」。靠人自觉遵守的规范一定会失效,靠工具强制的才会。

6.1 Git 协作规范

分支模型(中小团队推荐 GitHub Flow)

text
main          ← 永远可部署,保护分支,禁止直推
 └ feature/xxx  ← 从 main 切出,PR 合并后删除
 └ fix/xxx
 └ release/v1.2 ← 需要并行维护多版本时才引入(Git Flow)

流程:切分支 → 小步提交 → 提 PR(需 1 人 Review + CI 全绿)→ Squash Merge → 删分支

Conventional Commits

bash
<type>(<scope>): <subject>
feat(user): 新增手机号登录
fix(order): 修复优惠券重复核销
perf(db): 订单列表深分页改游标分页
refactor(auth): 抽离权限校验中间件
docs(api): 补充分页参数说明
test(pay): 增加退款幂等用例
chore(deps): 升级 vite 到 5.4
revert: 回滚 feat(user): xxx

★ type: feat|fix|perf|refactor|docs|test|build|ci|chore|revert
★ 好处:自动生成 CHANGELOG、语义化版本号、一眼看懂改动意图

常用操作

bash
# 把当前分支的提交整理干净后再合入(个人分支用,公共分支禁用)
git rebase -i HEAD~3                 # squash / reword / drop
git rebase main                      # 同步主干,避免「合并提交」污染历史

# 只把某次提交挪过来(线上热修常用)
git cherry-pick <commit>

# 撤销已 push 的错误提交(保留历史,安全)
git revert <commit>

# 手滑切走了分支,找回丢失的提交
git reflog  →  git checkout <hash>

# 临时保存工作区
git stash push -m "wip"  /  git stash pop

# 提交前自检:只看本次改动的 diff
git diff --staged
规则:① 一次提交只做一件事(便于 revert 和定位);② 提交信息写「为什么」而不是「改了什么」(代码本身就是改了什么);③ 绝不提交密钥、.envnode_modules、构建产物;误提交后用 git filter-repo 清理并立即轮换密钥

6.2 测试金字塔

text
      /E2E\        10%  关键业务链路(登录→下单→支付),慢但覆盖真实链路
     /------\
    / 集成测试 \    30%  API + DB + 缓存,最有性价比
   /------------\
  /   单元测试    \  60%  纯函数、业务规则,快(毫秒级)、无外部依赖
 /----------------\

★ 经验:不要追求覆盖率数字,追求「改动后敢不敢发版」。
★ 有价值的测试:覆盖 bug 复现路径、边界条件、金额/权限/幂等等易错逻辑。
typescript · Vitest
describe('UserService.register', ()=>{
  it('邮箱重复时抛 409', async ()=>{
    const repo = { existsByEmail: vi.fn().mockResolvedValue(true),
                   create: vi.fn() } as unknown as UserRepo;
    await expect(new UserService(repo, mailer).register(input))
      .rejects.toMatchObject({ status:409 });
    expect(repo.create).not.toHaveBeenCalled();     // 断言副作用未发生
  });
});

// 集成测试:起真实 DB(Testcontainers)或独立 test schema,跑事务回滚
beforeEach(async ()=>{ await db.$executeRaw`BEGIN`; });
afterEach (async ()=>{ await db.$executeRaw`ROLLBACK`; });

// E2E(Playwright):只测关键路径
test('下单流程', async ({page}) =>{
  await page.goto('/login');
  await page.fill('[name=email]', 'a@b.com');
  await page.click('button[type=submit]');
  await expect(page).toHaveURL('/');
});

6.3 CI/CD

yaml · GitHub Actions
name: CI
on: [push, pull_request]
jobs:
  build:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v4
        with: { node-version: 22, cache: pnpm }
      - run: corepack enable && pnpm install --frozen-lockfile
      - run: pnpm lint && pnpm typecheck
      - run: pnpm test --coverage
      - run: pnpm build
      - uses: docker/build-push-action@v6       # 仅 main 分支推送镜像
        if: github.ref == 'refs/heads/main'
        with: { push: true, tags: ghcr.io/org/app:${{ github.sha }} }

发布策略

策略做法适用
滚动发布逐台替换实例常规,需保证接口前后兼容
蓝绿部署两套环境切换流量回滚最快(切回即可),成本高
金丝雀/灰度先放 5% 流量,观察指标后放量推荐核心业务
特性开关代码上线但功能关闭,随时开启解耦「部署」与「发布」
数据库变更与代码发布分离:采用向后兼容的迁移——① 先加可空字段/新表;② 上线双写代码;③ 跑数据回填;④ 切读到新字段;⑤ 观察无问题后再删旧字段。每一步都可独立回滚。

6.4 Docker

dockerfile
# —— 多阶段构建:产物镜像不含 devDependencies 与源码工具 ——
FROM node:22-alpine AS deps
WORKDIR /app
COPY package.json pnpm-lock.yaml ./
RUN corepack enable && pnpm install --frozen-lockfile

FROM node:22-alpine AS build
WORKDIR /app
COPY --from=deps /app/node_modules ./node_modules
COPY . .
RUN pnpm build && pnpm prune --prod      # 构建完再裁掉开发依赖

FROM node:22-alpine AS run
WORKDIR /app
ENV NODE_ENV=production
# ★ 用非 root 用户运行
RUN addgroup -S app && adduser -S app -G app
COPY --from=build --chown=app:app /app/node_modules ./node_modules
COPY --from=build --chown=app:app /app/dist ./dist
USER app
EXPOSE 3000
# ★ 用 exec 形式 + init 处理信号,保证优雅退出
CMD ["node","dist/main.js"]
yaml · compose 本地环境
services:
  app:
    build: .
    ports: ["3000:3000"]
    env_file: .env
    depends_on:
      db:  { condition: service_healthy }
      rds: { condition: service_started }
    restart: unless-stopped
  db:
    image: postgres:16-alpine
    environment: { POSTGRES_PASSWORD: dev, POSTGRES_DB: app }
    volumes: ["pgdata:/var/lib/postgresql/data"]
    healthcheck:
      test: ["CMD-SHELL","pg_isready -U postgres"]
      interval: 5s  retries: 10
  rds:
    image: redis:7-alpine
    command: ["redis-server","--maxmemory","256mb","--maxmemory-policy","allkeys-lru"]
volumes: { pgdata: {} }
  • .dockerignore 必须排除 node_modules.gitdist(否则构建上下文巨大且缓存失效)
  • Dockerfile 中变化少的层放前面(先 COPY lockfile 装依赖,再 COPY 源码),最大化命中缓存
  • 容器必须优雅退出:监听 SIGTERM,关闭连接后再退出,否则滚动发布丢请求
  • 健康检查(/healthz)+ 就绪检查(/readyz)分开,前者看进程,后者看依赖
  • 日志输出 stdout(不要写文件),由容器运行时收集

6.5 配置与可观测性

12-Factor 配置

bash
# .env.example 提交到仓库(只有键,无值);.env 加入 .gitignore
DATABASE_URL=postgres://user:pass@localhost:5432/app
REDIS_URL=redis://localhost:6379
JWT_SECRET=                 # 必填,启动时校验,缺失直接 fail-fast
LOG_LEVEL=info

# 启动时校验配置,避免运行到一半才发现缺配置
const env = z.object({
  DATABASE_URL: z.string().url(),
  JWT_SECRET:   z.string().min(32),
  PORT:         z.coerce.number().default(3000),
}).parse(process.env);        // 校验失败进程立即退出并打印缺哪个变量

可观测性三支柱

日志 Log

结构化 JSON,带 traceId/userId。禁止在日志输出密码、token、身份证、手机号(脱敏)。用 level 分级,生产只用 info 及以上。

指标 Metric

RED 黄金三指标:Rate(QPS)、Errors(错误率)、Duration(耗时 P50/P95/P99)。配告警阈值与 SLO。

链路 Trace

OpenTelemetry 自动埋点跨服务传播 traceparent,一个 traceId 串起全链路,定位慢在哪一跳。

typescript · pino 结构化日志
import pino from 'pino';
export const logger = pino({
  level: process.env.LOG_LEVEL ?? 'info',
  redact: ['req.headers.authorization','password','idCard'],   // 自动脱敏
  base: { service:'api', version:APP_VERSION },
});

// 每个请求注入 traceId(中间件)
const traceId = req.header('x-trace-id') ?? crypto.randomUUID();
const log = logger.child({ traceId, userId:req.user?.id });
req.log = log;   // 后续所有日志自动带上上下文

log.info({ rows:120, ms:35 }, 'query users');
告警原则:告警必须可行动——收到后要么需要人处理,要么就该删掉。「CPU 80%」不是好告警,「支付成功率 5 分钟跌破 95%」才是。每条告警配一个 Runbook(处理步骤)。

07安全防御

安全的第一原则:不信任任何来自边界外的数据。第二原则:默认拒绝,显式允许

OWASP Top 10 防御对照

风险防御手段
A01 失效的访问控制(越权)每个查询带归属条件;服务端每次请求都校验权限(不依赖前端隐藏按钮);资源 ID 用 UUID 或校验归属;禁止遍历 ID
A02 加密失败全站 HTTPS(HSTS);密码用 argon2id/bcrypt;敏感字段加密存储;禁止自研加密算法
A03 注入参数化查询/预编译(ORM 默认安全,但 $queryRaw 拼字符串就危险);禁止拼接 SQL/命令/LDAP
A04 不安全设计威胁建模:画数据流图,标出信任边界;业务层面防刷(限购、风控、验证码)
A05 安全配置错误关掉调试模式与目录列表;删除示例账号;最小化开放端口;安全响应头(CSP/X-Frame-Options/X-Content-Type-Options)
A06 易受攻击组件pnpm audit / Dependabot / SCA 扫描纳入 CI;及时升级
A07 认证失败登录限速 + 失败锁定;MFA;会话超时;密码强度策略;防凭据填充
A08 数据完整性校验第三方回调签名;锁定依赖版本(lockfile);CI 校验产物哈希
A09 日志与监控不足记录登录/权限变更/敏感操作审计日志;日志集中化且防篡改;告警
A10 SSRF禁止用用户提供的 URL 直接请求;白名单域名 + 禁止内网 IP(含 169.254.169.254 元数据服务);禁止跟随重定向到内网

代码级防御速查

typescript / http
// 1) SQL 注入 —— 绝不用模板字符串拼 SQL
await db.$queryRaw`SELECT * FROM users WHERE email = ${email}`;   // ✅ 参数化
// await db.$queryRawUnsafe(`... '${email}'`);                    // ❌ 注入之门

// 2) XSS —— 框架默认转义;危险场景用 DOMPurify
// ❌ dangerouslySetInnerHTML={{__html: userInput}}
// ✅ 富文本:服务端清洗 + 前端 DOMPurify.sanitize() + CSP

// 3) CSRF —— SameSite=Strict Cookie + 双重提交 Token(同源校验 Origin)
res.cookie('sid', id, { httpOnly:true, secure:true, sameSite:'strict', maxAge:7*86400000 });

// 4) 文件上传 —— 校验类型用「魔数」而非扩展名;重命名;存对象存储;禁止执行权限
if(!['image/jpeg','image/png'].includes(mimeFromBuffer))
  throw new BizError(400,'不支持的文件类型');

// 5) 敏感信息泄漏 —— 出参白名单,禁止直接吐 ORM 实体
const safe = (({id,name,email})=>({id,name,email}))(user);   // ✅ 显式挑字段

// 6) 安全响应头(Helmet 一句话搞定)
Strict-Transport-Security: max-age=31536000; includeSubDomains
Content-Security-Policy: default-src 'self'; frame-ancestors 'none'
X-Content-Type-Options: nosniff
X-Frame-Options: DENY
Referrer-Policy: strict-origin-when-cross-origin

限流(必做)

typescript · 令牌桶
// Redis 滑动窗口限流(Lua 保证原子性)
-- KEYS[1]=key  ARGV[1]=window(ms)  ARGV[2]=limit
redis.call('ZREMRANGEBYSCORE', KEYS[1], 0, now - ARGV[1])
local cnt = redis.call('ZCARD', KEYS[1])
if cnt < tonumber(ARGV[2]) then
  redis.call('ZADD', KEYS[1], now, now .. '-' .. math.random())
  redis.call('PEXPIRE', KEYS[1], ARGV[1])
  return 1
end
return 0

// 分层限流:网关(全局 IP 级)→ 应用(用户/接口级)→ 下游(防打爆依赖)
// 登录/短信/支付接口单独配置更严格阈值(如 5 次/分钟 + 图形验证码)
上线前安全自查(5 分钟):① 所有密钥在环境变量/密钥管理中,代码中无硬编码;② HTTPS 全站 + HSTS;③ 所有写/敏感接口有鉴权且有归属校验;④ 无 SQL 字符串拼接;⑤ 上传有类型/大小限制;⑥ 错误信息不泄漏堆栈;⑦ 依赖 audit 无高危;⑧ 默认账号已删除;⑨ 管理后台不暴露公网或加 IP 白名单 + MFA。

08架构演进

架构不是设计出来的,是在约束下演进而来的。所有架构决策都是在回答:这个复杂度,值得吗?

8.1 从单体到微服务

单体模块化单体按域拆分的服务微服务
形态适用优点代价
单体团队 < 5 人,业务早期部署简单、事务简单、调试方便代码耦合、发布互相阻塞
模块化单体(推荐)大多数公司的终点保留单体优势,边界清晰,随时可拆需要纪律维持模块边界
微服务多团队并行、独立扩缩容需求独立部署、技术异构、故障隔离分布式事务、链路追踪、运维成本翻倍
什么时候才该拆微服务?同时满足以下 3 条中的 2 条:① 团队规模 > 8~10 人且按业务线分组(康威定律);② 不同模块的资源需求差异巨大(如 AI 推理需 GPU);③ 不同模块发布频率/稳定性要求差异极大。
只因为「代码乱」而拆,只会得到一堆分布式单体——更乱,且更难调试。

8.2 DDD 战术速览

概念含义落地
实体 Entity有唯一标识、有生命周期Order(靠 id 区分)
值对象 Value Object无 id,靠属性值相等Money(100,'CNY')Address
聚合根 Aggregate Root一致性边界,外部只能通过它修改内部Order 聚合内含 OrderItem
仓储 Repository聚合的持久化抽象OrderRepo.save(order)
领域服务跨聚合的业务逻辑TransferService
领域事件已发生的事实,触发后续动作OrderPaid
限界上下文术语与模型的边界(拆分依据)订单上下文 vs 物流上下文
实践建议:不必全套照搬 DDD。中小项目只要做到「目录按业务域划分而非按技术层划分」(modules/order/ 内自带 controller/service/repo,而不是全局 controllers/ + services/),就已经获得 80% 的收益。

8.3 事件驱动与消息

typescript
// 领域事件:发布(在事务内写 outbox 表,保证不丢)
await db.$transaction(async tx=>{
  const order = await tx.order.create({data});
  await tx.outbox.create({ data:{
    topic:'order.paid',
    payload:{ orderId:order.id, amount:order.amount },
  }});
});
// 独立 Relay 进程轮询 outbox → 投递 MQ → 标记已发送(至少一次)

// 消费端必须幂等(消息会重复投递)
async function onOrderPaid(evt){
  const exists = await db.processedEvent.findUnique({where:{eventId:evt.id}});
  if(exists) return;                                  // 已处理过,直接返回
  await db.$transaction(async tx=>{
    await tx.processedEvent.create({data:{eventId:evt.id}});  // 去重表唯一索引
    await tx.inventory.deduct(evt.payload);
  });
}

8.4 一致性方案对比

方案一致性复杂度适用
本地事务强一致单库,首选
Outbox + MQ最终一致跨服务首选本地事务保证事件不丢
Saga(编排/协同)最终一致长事务,需补偿(下单→扣库存→支付→发货)
TCC(Try/Confirm/Cancel)准强一致很高金融级,需预留资源
2PC/XA强一致高且性能差基本不用(锁资源时间长)

8.5 高并发三板斧 + 容量估算

缓存

减少计算与 IO:本地缓存 → 分布式缓存 → CDN → 浏览器缓存。命中率与一致性权衡。

异步

削峰填谷:请求进队列,消费者按能力处理。把「同步等待」变成「稍后通知」。

分流

水平扩展无状态服务;读写分离;分库分表;按用户/地域分片;限流降级保核心。

量级参考数值含义
单机 Nginx~5 万并发连接静态转发几乎无瓶颈
Node 服务(纯 IO)1~3k QPS/核CPU 密集任务会骤降
PostgreSQL 单实例简单查询 ~1万 QPS写受 WAL 刷盘限制,约几千 TPS
Redis 单实例~10 万 QPS受网络与命令复杂度影响
估算公式QPS = 日活 × 人均请求数 / 86400 × 峰值系数(3~5)日活 10 万 × 20 次 ≈ 23 QPS 均值,峰值 ~100 QPS

09性能清单

优化顺序永远:测量 → 定位瓶颈 → 改 → 复测。凭感觉优化是最大的时间浪费。

前端
  • 首屏 JS ≤ 200KB gzip
  • 图片 WebP/AVIF + 响应式尺寸
  • 路由级代码分割
  • 长列表虚拟化
  • 静态资源长缓存 + 内容哈希
  • 避免布局抖动(预留尺寸)
后端
  • 消除 N+1 查询
  • 批量接口代替循环单查
  • 连接池合理配置
  • 耗时操作异步化
  • 响应开启压缩
  • 加超时与熔断
数据
  • 慢查询全量治理
  • 索引覆盖高频查询
  • 深分页改游标
  • 大字段拆表
  • 冷热数据分离归档
  • 避免 SELECT *

压测方法

javascript · k6
import http from 'k6/http';
import { check, sleep } from 'k6';

export const options = {
  stages: [ {duration:'1m',target:50}, {duration:'3m',target:50}, {duration:'1m',target:0} ],
  thresholds: { http_req_duration:['p95<500'], http_req_failed:['rate<0.01'] },
};
export default function(){
  const r = http.get('https://api.example.com/users',{headers:{Authorization:`Bearer ${TOKEN}`}});
  check(r, { 'status 200': (x)=>x.status===200 });
  sleep(1);
}
// k6 run --vus 50 --duration 5m script.js
// ★ 压测要渐增,观察拐点(QPS 不再涨而延迟飙升处即容量上限)
定位口诀:延迟高 → 看是 CPU 密集、IO 等待还是锁等待;QPS 上不去 → 看连接池、线程池、带宽、数据库连接数;偶发超时 → 看 GC 停顿、慢查询、网络抖动、下游限流。

10Python 全栈

Python 的定位:胶水层与数据/AI 侧的首选。用它的强项(生态、表达力、数据处理),避开它的弱项(CPU 密集、全局解释器锁)。

10.1 选型与工程结构

框架适用要点
FastAPI(推荐)API 服务、微服务、AI 服务原生 async、Pydantic 校验、自动生成 OpenAPI,性能接近 Node
Django内容/后台/中台系统自带 ORM、Admin、权限、迁移,"开箱即用全家桶",适合快速交付
Flask小工具、内部服务极简,但大型项目需自己组装,新项目一般直接上 FastAPI
数据/AIPolars / Pandas / NumPy报表、ETL、模型服务
bash · uv(当前最快的 Python 包管理)
# 安装 uv(替代 pip/venv/pipx/poetry 的一体化工具,快 10~100 倍)
curl -LsSf https://astral.sh/uv/install.sh | sh

uv python install 3.12          # 管理解释器版本
uv venv && source .venv/bin/activate
uv add fastapi uvicorn[standard] sqlalchemy asyncpg pydantic-settings
uv add --dev pytest ruff mypy httpx
uv lock && uv sync --frozen      # 锁文件 + CI 复现安装
uv run pytest                    # 自动在虚拟环境中执行

# 传统方式(pyproject.toml 同样适用)
python -m venv .venv && pip install -r requirements.txt
pip freeze > requirements.txt    # 建议改用 pip-compile / uv lock
text · 推荐项目结构(FastAPI)
app/
  main.py              # 应用入口、装配路由与中间件
  core/
    config.py          # pydantic-settings 读取环境变量(带校验)
    security.py        # 密码哈希、JWT
    deps.py            # 依赖注入:get_db / get_current_user
  api/v1/
    routers/user.py    # 路由(薄,只做参数与响应)
  domain/              # 业务规则(不依赖框架)
  models/              # SQLAlchemy ORM 模型
  schemas/             # Pydantic DTO(入参/出参)
  services/            # 业务编排
  repositories/        # 数据访问
tests/
alembic/               # 数据库迁移
pyproject.toml

10.2 类型与代码风格

python
from dataclasses import dataclass
from typing import Annotated, Literal, TypeAlias, Self
from pydantic import BaseModel, Field, EmailStr, field_validator

# 1) 现代类型写法(3.10+):X | None 取代 Optional[X]
def find(uid: int) -> User | None: ...

# 2) 类型别名与字面量约束
Role: TypeAlias = Literal["admin", "user", "guest"]

# 3) Pydantic v2:运行时校验 + 自动文档(唯一可信的边界校验)
class CreateUserIn(BaseModel):
    model_config = {"extra": "forbid"}            # 拒绝未知字段,防字段误传
    email: EmailStr
    password: str = Field(min_length=8, max_length=72)   # bcrypt 上限 72 字节
    role: Role = "user"

    @field_validator("password")
    @classmethod
    def strong(cls, v: str) -> str:
        if v.isalpha(): raise ValueError("密码需包含数字")
        return v

class UserOut(BaseModel):
    model_config = {"from_attributes": True}      # 允许从 ORM 对象构造
    id: int
    email: EmailStr

# ★ 出参必须单独定义 schema,绝不直接返回 ORM 对象(会泄漏 password_hash)

# 4) 工具链配置(pyproject.toml 片段)
# [tool.ruff]  line-length = 100 ; select = ["E","F","I","UP","B","SIM"]
# [tool.mypy]  strict = true
# ruff check . && ruff format .   (替代 flake8+black+isort,快 100 倍)

10.3 FastAPI 实战

python
from contextlib import asynccontextmanager
from fastapi import FastAPI, Depends, HTTPException, status, Query, Request
from sqlalchemy.ext.asyncio import AsyncSession

@asynccontextmanager
async def lifespan(app: FastAPI):
    await init_db()          # 启动时初始化连接池
    yield
    await engine.dispose()   # 关闭时优雅释放

app = FastAPI(lifespan=lifespan, title="API", version="1.0.0")

# —— 依赖注入:数据库连接按请求作用域自动开关 ——
async def get_db() -> AsyncIterator[AsyncSession]:
    async with SessionLocal() as s:
        try:
            yield s
            await s.commit()
        except Exception:
            await s.rollback(); raise

DbDep = Annotated[AsyncSession, Depends(get_db)]
CurrentUser = Annotated[User, Depends(get_current_user)]   # 鉴权依赖可复用

@app.post("/users", response_model=UserOut, status_code=status.HTTP_201_CREATED)
async def create_user(payload: CreateUserIn, db: DbDep):
    if await user_repo.exists_by_email(db, payload.email):
        raise HTTPException(409, "邮箱已注册")
    return await user_service.register(db, payload)

@app.get("/users", response_model=list[UserOut])
async def list_users(
    page: int = Query(1, ge=1), size: int = Query(20, ge=1, le=100), db: DbDep = None
): ...

# —— 全局异常处理:统一错误体 + traceId ——
@app.exception_handler(Exception)
async def unhandled(request: Request, exc: Exception):
    trace_id = request.headers.get("x-trace-id", uuid4().hex)
    logger.exception("unhandled", extra={"trace_id": trace_id})
    return JSONResponse(status_code=500,
                        content={"code": 500, "message": "服务器内部错误", "traceId": trace_id})

# ★ 关键性能规则:
# - def(同步)路由会自动跑线程池,async def 里绝不能调用阻塞 IO
# - async def 中调用 requests/time.sleep/同步 ORM = 阻塞整个事件循环,全站卡顿
# - 阻塞库要么改成 def 路由,要么用 run_in_executor / 换 httpx、asyncpg

10.4 SQLAlchemy 2.0 与迁移

python
from sqlalchemy.orm import DeclarativeBase, Mapped, mapped_column, selectinload
from sqlalchemy import select, func

class Base(DeclarativeBase): pass

class User(Base):
    __tablename__ = "users"
    id: Mapped[int] = mapped_column(primary_key=True)
    email: Mapped[str] = mapped_column(unique=True, index=True)
    created_at: Mapped[datetime] = mapped_column(server_default=func.now())

# 2.0 风格查询(不再是 Query API)
stmt = select(User).where(User.email == email).limit(1)
user = (await db.execute(stmt)).scalar_one_or_none()

# ★ N+1 治理:用 selectinload / joinedload 预加载
stmt = select(Post).options(selectinload(Post.author)).limit(20)   # ✅ 2 条 SQL
# for p in posts: p.author   ❌ 循环里触发 20 条查询

# 分页(游标优于 offset)
stmt = select(Post).where(Post.id < cursor).order_by(Post.id.desc()).limit(20)

# 事务:session 默认以 commit 结束;嵌套用 savepoint
async with db.begin():
    ...

# Alembic 迁移
# uv add alembic && alembic init alembic
# alembic revision --autogenerate -m "add users"   (必须人工审阅生成的脚本!)
# alembic upgrade head / downgrade -1
连接池:异步驱动(asyncpg)必须用 NullPool 之外要配 pool_size(默认 5)与 max_overflowpool_pre_ping=True 防止数据库重启后的失效连接。连接数 ≈ workers × pool_size,别超过数据库 max_connections。

10.5 异步与并发

场景方案
IO 密集(HTTP、DB、文件)asyncio + async 驱动(httpx / asyncpg / aiofiles),单进程可扛数千并发
CPU 密集(图像处理、加密、模型推理)multiprocessing / ProcessPoolExecutor,或把任务丢给 Celery worker 独立进程
混合run_in_executor 把阻塞调用丢线程池,避免卡死事件循环
后台任务Celery(重) / RQ、Arq(轻) / FastAPI BackgroundTasks(仅轻量、进程内、不保证不丢)
python
# 并发抓取:Semaphore 限制并发,防止打爆下游
async def fetch_all(urls: list[str]):
    sem = asyncio.Semaphore(10)
    async with httpx.AsyncClient(timeout=3.0) as client:
        async def one(u: str):
            async with sem:
                r = await client.get(u)
                r.raise_for_status()
                return r.json()
        return await asyncio.gather(*map(one, urls), return_exceptions=True)

# CPU 密集:交给进程池
loop = asyncio.get_running_loop()
result = await loop.run_in_executor(process_pool, heavy_compute, data)

# ★ GIL 真相:3.13 起可选 free-threading,但生态未就绪。
#   生产高并发仍以「多进程 + 异步 IO」为主:uvicorn --workers N

10.6 测试

python · pytest
import pytest
from httpx import AsyncClient, ASGITransport

@pytest.fixture
async def client(db_session) -> AsyncClient:
    app.dependency_overrides[get_db] = lambda: db_session    # 替换依赖
    async with AsyncClient(transport=ASGITransport(app), base_url="http://t") as c:
        yield c
    app.dependency_overrides.clear()

@pytest.mark.parametrize("email,expect", [("a@b.com",201), ("bad",422), (None,422)])
async def test_create_user(client, email, expect):
    r = await client.post("/users", json={"email": email, "password": "Ab123456"})
    assert r.status_code == expect

# 每个用例独立事务并回滚,保证互相隔离
@pytest.fixture
async def db_session():
    async with engine.connect() as conn:
        ts = await conn.begin()
        yield AsyncSession(bind=conn)
        await ts.rollback()

# uv run pytest -q --cov=app --cov-report=term-missing

10.7 部署与典型坑

bash / dockerfile
# 生产运行:gunicorn 管进程 + uvicorn worker 管协程
gunicorn app.main:app -k uvicorn.workers.UvicornWorker \
  --workers 4 --worker-connections 1000 --timeout 60 --graceful-timeout 30 --bind 0.0.0.0:8000
# workers 建议 = CPU 核数(IO 密集可 2*核+1),不要盲目加大

# Dockerfile(uv 多阶段)
FROM python:3.12-slim AS build
COPY --from=ghcr.io/astral-sh/uv:latest /uv /usr/local/bin/uv
WORKDIR /app
COPY pyproject.toml uv.lock ./
RUN uv sync --frozen --no-dev --no-editable      # 依赖与源码分离,缓存友好
FROM python:3.12-slim
COPY --from=build --chown=app:app /app/.venv /app/.venv
COPY --chown=app:app ./app /app/app
USER app
ENV PATH="/app/.venv/bin:$PATH"
CMD ["gunicorn","app.main:app","-k","uvicorn.workers.UvicornWorker","--workers","4"]
Python 十大坑:① 可变默认参数 def f(x=[])(共享同一列表,用 None 哨兵);② 循环内定义 lambda/闭包捕获同一变量;③ except: 裸捕获吞掉 KeyboardInterrupt,务必指定异常类型;④ 循环 import(把公共依赖下沉到独立模块);⑤ 用 == 比较浮点数;⑥ datetime.now() 无时区(统一用 datetime.now(timezone.utc));⑦ 在 async 函数中调用阻塞 IO;⑧ 全局可变状态(多 worker 下不共享);⑨ 忘记 if __name__ == "__main__" 导致多进程启动异常;⑩ 依赖未锁版本,某天上游 breaking change 直接线上爆炸。

11Java 全栈

Java 的主场:大型企业级系统、复杂业务、强事务与高并发后端。它的价值在于成熟的生态、强类型与可预测的性能表现。

11.1 技术栈与工程结构

推荐说明
JDKJDK 17(LTS)或 21(LTS,含虚拟线程)新项目直接上 21;老系统 8/11 逐步迁移
框架Spring Boot 3.xJakarta EE 命名空间(jakarta.* 而非 javax.*
构建Maven(稳) / Gradle(快、灵活)单体多模块用 Maven,构建耗时敏感用 Gradle
数据MyBatis-Plus(SQL 可控)或 JPA(快速 CRUD)见 11.4 对比
工具Lombok(谨慎)、MapStruct(DTO 映射)、Flyway(迁移)MapStruct 编译期生成,性能远优于反射 BeanUtils
text · 推荐分包(按业务域优先)
com.example.app
 ├ common/          # 统一响应 R<T>、异常、工具、常量
 │   ├ Result.java  GlobalExceptionHandler.java
 ├ config/          # 配置类、拦截器、WebMvcConfigurer
 ├ security/        # 认证、鉴权、JWT 过滤器
 └ modules/
    └ order/        # ★ 按域聚合,而不是全局 controller/service/mapper 平铺
       ├ OrderController.java
       ├ OrderService.java / OrderServiceImpl.java
       ├ OrderMapper.java
       ├ entity/Order.java          # 与表对应(PO)
       ├ dto/OrderCreateReq.java OrderVO.java
       └ convert/OrderConvert.java  # MapStruct 接口

11.2 语言要点(JDK 17+)

java
// 1) record:不可变 DTO,一行替代 getter/equals/hashCode/toString
public record UserVO(Long id, String email, String roleName) {}

// 2) 密封接口:限定实现范围,配合 switch 模式匹配可穷尽分支
public sealed interface PayResult permits Success, Failed, Pending {}

// 3) 模式匹配 + switch 表达式
String desc = switch (result) {
    case Success s  -> "成功 " + s.txnId();
    case Failed f   -> "失败 " + f.reason();
    case Pending p  -> "处理中";
};   // 密封接口无需 default,编译期保证穷尽

// 4) Optional 的正确用法:只用于「返回值」,不要做参数/字段
public Optional<User> findByEmail(String email) { ... }
findByEmail(e).map(User::getName).orElse("匿名");        // ✅
// Optional.ofNullable(x).get()  ❌ 不如直接判空

// 5) 文本块(SQL/JSON 可读性救星)
String sql = """
    SELECT id, email FROM users
    WHERE created_at >= ? AND status = ?
    """;

// 6) 异常:受检异常用于「调用方必须处理」,业务异常用自定义 RuntimeException
public class BizException extends RuntimeException {
    private final int code;
    public BizException(int code, String msg){ super(msg); this.code = code; }
}

11.3 Spring Boot 实战

java
@RestController
@RequestMapping("/api/v1/orders")
@RequiredArgsConstructor          // 构造器注入(优于 @Autowired 字段注入,便于测试)
public class OrderController {
    private final OrderService orderService;

    @PostMapping
    public Result<OrderVO> create(@Valid @RequestBody OrderCreateReq req) {
        return Result.ok(orderService.create(req));
    }

    @GetMapping
    public Result<PageResult<OrderVO>> page(
            @RequestParam(defaultValue = "1") @Min(1) int page,
            @RequestParam(defaultValue = "20") @Max(100) int size) {
        return Result.ok(orderService.page(page, size));
    }
}

// 入参校验
public record OrderCreateReq(
    @NotNull Long skuId,
    @NotNull @Min(1) @Max(9999) Integer quantity,
    @NotBlank @Size(max = 200) String remark) {}

// 统一响应 + 全局异常处理
@RestControllerAdvice
@Slf4j
public class GlobalExceptionHandler {
    @ExceptionHandler(MethodArgumentNotValidException.class)
    public Result<Void> onValid(MethodArgumentNotValidException e) {
        String msg = e.getBindingResult().getFieldErrors().stream()
            .map(f -> f.getField() + ":" + f.getDefaultMessage())
            .collect(Collectors.joining(", "));
        return Result.fail(400, msg);
    }
    @ExceptionHandler(BizException.class)
    public Result<Void> onBiz(BizException e) { return Result.fail(e.getCode(), e.getMessage()); }
    @ExceptionHandler(Exception.class)
    public Result<Void> onErr(Exception e, HttpServletRequest req) {
        String traceId = MDC.get("traceId");
        log.error("unhandled uri={} traceId={}", req.getRequestURI(), traceId, e);
        return Result.fail(500, "服务器内部错误");     // 不暴露堆栈
    }
}
@Transactional 六大失效场景(面试与实战高频):① 方法是 private/final(无法被代理);② 同类内部方法自调用(未经过代理,最隐蔽——注入自己或用 AopContext);③ 异常被 try-catch 吞掉未抛出;④ 抛出的异常类型默认不回滚(只有 RuntimeException/Error 回滚,需 rollbackFor = Exception.class);⑤ 数据库引擎非 InnoDB;⑥ 事务方法内有耗时远程调用(设计问题锁持有时间过长,务必移出事务)。
java
@Service
@RequiredArgsConstructor
public class OrderServiceImpl implements OrderService {
    private final OrderMapper mapper;
    private final InventoryClient inventory;

    @Transactional(rollbackFor = Exception.class, timeout = 5)   // 明确回滚范围 + 超时
    public OrderVO create(OrderCreateReq req) {
        if (mapper.existsByBizNo(req.bizNo())) return mapper.getByBizNo(req.bizNo()); // 幂等
        mapper.insert(Order.from(req));
        // ❌ 禁止:远程调用放事务内(网络慢 → 行锁长期不释放 → 雪崩)
        // inventory.deduct(req.skuId(), req.quantity());
        return OrderConvert.INSTANCE.toVO(po);
    }

    @Transactional(propagation = Propagation.REQUIRES_NEW)   // 独立事务:日志必须落库
    public void audit(Long orderId, String action) { ... }
}

11.4 数据层选型

方案优点缺点适用
MyBatis / MyBatis-PlusSQL 完全可控、易优化、DBA 友好需写 XML、改字段要同步复杂查询、报表、国内主流
JPA / Hibernate开发快、领域模型友好、自动 DDL复杂 SQL 难优化、易踩懒加载坑CRUD 为主的管理系统
jOOQ类型安全的 SQL、功能最强商业许可(部分 DB)、学习成本SQL 密集型系统
JdbcTemplate极简、透明样板代码多轻量服务
java / sql
// JPA 的 N+1:默认懒加载,循环访问关联对象触发 N 条 SQL
// ✅ 方案1:@EntityGraph 指定抓取
@EntityGraph(attributePaths = {"items"})
List<Order> findByUserId(Long userId);

// ✅ 方案2:JPQL fetch join
@Query("select distinct o from Order o left join fetch o.items where o.userId = :uid")
List<Order> findWithItems(@Param("uid") Long uid);

// ✅ 方案3:批量查询后内存组装(大数据量最稳)
// 先查 orders,再用 where order_id in (...) 一次性查 items,Map 分组回填

// MyBatis 防注入:#{} 预编译,${} 是字符串拼接(仅限动态表名/排序且需白名单)
// ✅ WHERE email = #{email}     ❌ WHERE email = '${email}'

// 连接池 HikariCP(Spring Boot 默认)
spring.datasource.hikari.maximum-pool-size=20        # ≈ CPU*2+磁盘,非越大越好
spring.datasource.hikari.connection-timeout=3000     # 拿不到连接快速失败,别让线程堆积
spring.datasource.hikari.leak-detection-threshold=60000   # 检测连接泄漏

// 迁移 Flyway:V1__init.sql / V2__add_index.sql,只在 CI 中顺向执行

11.5 并发与 JVM

java
// ❌ 禁止 Executors.newFixedThreadPool:队列无界 → OOM
// ✅ 手动创建 ThreadPoolExecutor,明确队列与拒绝策略
@Bean
public ThreadPoolExecutor bizExecutor() {
    return new ThreadPoolExecutor(
        8, 16, 60, TimeUnit.SECONDS,
        new ArrayBlockingQueue<>(500),                 // 有界队列
        new ThreadFactoryBuilder().setNameFormat("biz-%d").build(),  // 命名便于排查
        new ThreadPoolExecutor.CallerRunsPolicy());    // 反压:让调用方自己执行
}

// 并行编排
CompletableFuture<User> u  = supplyAsync(() -> userSvc.get(id), pool);
CompletableFuture<List<Order>> o = supplyAsync(() -> orderSvc.list(id), pool);
return u.thenCombine(o, UserDetail::of).orTimeout(2, TimeUnit.SECONDS).join();

// JDK 21 虚拟线程:IO 密集场景可显著提升吞吐(无需池化,一个请求一个线程)
@Bean TomcatProtocolHandlerCustomizer<?> vt() {
    return h -> h.setExecutor(Executors.newVirtualThreadPerTaskExecutor());
}
// ★ 虚拟线程不解决 CPU 密集问题;synchronized 会 pin 住载体线程(改用 ReentrantLock)
bash · JVM 常用参数
-Xms2g -Xmx2g                    # 堆大小固定,避免动态扩缩抖动(必须等值)
-XX:+UseG1GC -XX:MaxGCPauseMillis=200     # 大堆低延迟首选 G1;JDK17+ 可试 ZGC
-XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=/tmp   # OOM 自动留现场
-Xlog:gc*:file=/logs/gc.log:time,uptime:filecount=5,filesize=50m
-Duser.timezone=Asia/Shanghai -Dfile.encoding=UTF-8     # 时区与编码,必配
-XX:NativeMemoryTracking=summary   # 排查堆外内存泄漏

# 线上排查三件套
jps -l                    # 找 pid
jstack <pid> > stack.txt  # 线程栈(查死锁、线程池打满、CPU 飙高)
jmap -histo <pid> | head -30     # 对象直方图(查内存泄漏)
jstat -gcutil <pid> 1000         # 每秒 GC 情况
arthas                    # 阿里开源,在线诊断神器(watch/trace/dashboard)

11.6 测试与可观测

java
@SpringBootTest
@Testcontainers          // 起真实数据库/Redis,比 H2 更贴近生产
class OrderServiceTest {
    @Container static PostgreSQLContainer<?> pg =
        new PostgreSQLContainer<>("postgres:16-alpine");

    @MockBean InventoryClient inventory;         // 隔离外部依赖
    @Autowired OrderService orderService;

    @Test void 缺货时下单失败() {
        when(inventory.deduct(anyLong(), anyInt())).thenReturn(false);
        assertThrows(BizException.class, () -> orderService.create(req));
        verify(orderMapper, never()).insert(any());     // 断言副作用未发生
    }
}

// 可观测:Actuator + Micrometer + OpenTelemetry
management.endpoints.web.exposure.include=health,info,metrics,prometheus
management.endpoint.health.probes.enabled=true     # K8s 就绪/存活探针
// 自定义业务指标(下单成功率、支付耗时)比 JVM 指标更有价值
dockerfile · Java 分层构建
FROM maven:3.9-eclipse-temurin-21 AS build
WORKDIR /app
COPY pom.xml .
RUN mvn -B dependency:go-offline          # 依赖层单独缓存
COPY src ./src
RUN mvn -B -DskipTests package

FROM eclipse-temurin:21-jre-alpine
WORKDIR /app
# ★ 用 jar 分层(spring-boot 已支持 layertools),改代码只传应用层
COPY --from=build /app/target/app.jar app.jar
RUN adduser -S app && chown app app.jar
USER app
ENTRYPOINT ["java","-XX:+UseContainerSupport","-XX:MaxRAMPercentage=75",\
            "-XX:+ExitOnOutOfMemoryError","-jar","/app.jar"]
# ★ 必加 UseContainerSupport:让 JVM 识别容器内存限制,否则按宿主机内存分配堆
Java 常见坑:equals/hashCode 未同时重写 → HashMap/Set 行为异常;② BigDecimal 必须用字符串构造(new BigDecimal("0.1"),用 double 构造会有精度问题);③ SimpleDateFormat 非线程安全(用 DateTimeFormatter);④ ArrayList 在 foreach 中删除元素抛并发修改异常;⑤ 实体类直接作为出参 → 序列化循环引用 + 敏感字段泄漏(必须转 VO);⑥ 日志用 log.error("x"+e) 而非 log.error("x", e),丢失堆栈;⑦ Stream 中做副作用修改外部状态;⑧ @Async@Transactional 自调用失效同源(代理问题)。

12速查表 Cheat Sheet

Git

bash
git switch -c feat/x          # 新建并切换分支
git restore --staged file     # 取消暂存
git restore file              # 丢弃工作区修改
git commit --amend --no-edit  # 补进上一次提交
git rebase -i HEAD~3          # 交互式整理提交
git log --oneline --graph -20 # 图形化看历史
git diff main...HEAD          # 看分支相对 main 的全部改动
git bisect start/bad/good     # 二分定位引入 bug 的提交
git worktree add ../hotfix main   # 多工作区并行开发

Docker

bash
docker compose up -d --build          # 构建并后台启动
docker compose logs -f app --tail 100 # 跟踪日志
docker compose exec db psql -U postgres
docker system df / docker system prune -a   # 查看/清理磁盘占用
docker stats                          # 实时资源占用
docker build --build-arg BUILDKIT_INLINE_CACHE=1 -t app .
docker image history app:latest       # 分析镜像层体积
COPY --chown=app:app ...              # 避免 root 权限问题

Linux / 排查

bash
# 端口与连接
lsof -i :3000          # 谁占用了端口
ss -lntp               # 监听端口
ss -s                  # 连接总数统计(查 TIME_WAIT 堆积)

# 资源
top / htop             # CPU 内存
iostat -x 1            # 磁盘 IO(%util 接近 100 说明磁盘瓶颈)
vmstat 1               # 上下文切换、swap
df -h / du -sh *|sort -h   # 磁盘空间

# 日志与进程
journalctl -u app -f --since "10 min ago"
tail -f app.log | grep --color ERROR
strace -p <pid>        # 跟踪系统调用(慎用,影响性能)
kill -TERM <pid>       # 优雅退出;-9 是最后手段

SQL

sql
-- 窗口函数:分组内排名 / 去重取最新一条
SELECT * FROM (
  SELECT *, ROW_NUMBER() OVER (PARTITION BY user_id ORDER BY created_at DESC) rn
  FROM orders
) t WHERE rn = 1;

-- CTE:可读性 + 递归查询(组织树、菜单树)
WITH RECURSIVE tree AS (
  SELECT id,name,parent_id FROM dept WHERE parent_id IS NULL
  UNION ALL
  SELECT d.id,d.name,d.parent_id FROM dept d JOIN tree t ON d.parent_id=t.id
) SELECT * FROM tree;

-- 分组统计 + 条件聚合
SELECT date_trunc('day',created_at) d,
       count(*) total,
       count(*) FILTER (WHERE status=1) paid    -- Pg 的条件聚合
FROM orders GROUP BY 1 ORDER BY 1 DESC;

-- 批量 upsert / 去重插入
INSERT INTO tag(name) VALUES ('a'),('b') ON CONFLICT (name) DO NOTHING;

Python

bash
uv venv && source .venv/bin/activate      # 虚拟环境(uv 比 pip 快 10~100 倍)
uv add fastapi / uv add --dev pytest ruff mypy
uv run python -m app.main                  # 免激活执行
pip install -e . / pip-compile pyproject.toml

uv run pytest -q --cov=app --cov-report=term-missing    # 测试 + 覆盖率
uv run pytest -k "user and not slow" -x -vv             # 过滤 + 首次失败即停
ruff check . --fix && ruff format .                    # lint + 格式化
mypy app                                                # 静态类型检查

python -m cProfile -s cumulate app.py      # 性能剖析
python -m py_compile / python -X importtime -c "import app"   # 查慢导入
pipdeptree / uv tree                       # 依赖树,查冲突来源

Java / JVM

bash
# Maven
mvn -B clean package -DskipTests           # 打包(-B 非交互,CI 必加)
mvn -B dependency:tree                     # 依赖树,查冲突
mvn -B dependency:go-offline               # 预下载依赖(Docker 分层缓存用)
mvn -B versions:display-dependency-updates # 检查可升级依赖
mvn -T 1C clean install                    # 并行构建多模块

# Gradle
./gradlew bootJar          ./gradlew test --tests '*OrderServiceTest'
./gradlew dependencies --configuration runtimeClasspath

# 运行时排查
jps -l                                     # 列出 Java 进程
jstack <pid> | grep -A20 "BLOCKED"         # 查死锁 / 线程阻塞
jmap -histo <pid> | head -30               # 对象直方图,定位内存泄漏
jstat -gcutil <pid> 1000                   # 每秒 GC 概况
jcmd <pid> VM.native_memory summary        # 堆外内存
java -XX:+PrintFlagsFinal -version | grep HeapSize   # 查看默认堆大小

Nginx 关键配置

nginx
# 反向代理(必须带这些头,否则后端拿不到真实信息)
location /api/ {
  proxy_pass http://app:3000/;
  proxy_set_header Host $host;
  proxy_set_header X-Real-IP $remote_addr;
  proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
  proxy_set_header X-Forwarded-Proto $scheme;
  proxy_connect_timeout 5s;  proxy_read_timeout 60s;
}

# SPA history 路由
location / { try_files $uri $uri/ /index.html; }

# 静态资源长缓存
location /assets/ { expires 1y; add_header Cache-Control "public, immutable"; }

# 限流(需先在 http 块定义 limit_req_zone)
limit_req_zone $binary_remote_addr zone=api:10m rate=10r/s;
location /api/ { limit_req zone=api burst=20 nodelay; }

# SSE 必须关缓冲
proxy_buffering off;  proxy_cache off;  chunked_transfer_encoding on;

TypeScript / Node 一行流

bash
# 项目体检
npx depcheck                       # 找未使用依赖
npx madge --circular src           # 找循环依赖
npx knip                           # 找未使用文件/导出/类型
pnpm audit --audit-level=high      # 安全漏洞
pnpm why <pkg>                     # 谁依赖了它
pnpm dlx npm-check-updates -i      # 交互式升级依赖

# 排查
node --inspect-brk dist/main.js    # 断点调试
node --cpu-prof --heap-prof app.js # 生成性能剖析文件
npx clinic doctor -- node app.js   # 自动诊断性能问题

13成长路线与练手项目

能力阶梯

阶段标志核心任务
初级(0~1 年)能在指导下完成明确需求写出可读代码、熟练 Git/Debug、理解所用框架的约定
中级(1~3 年)独立负责一个模块端到端交付数据库建模、API 设计、写测试、定位线上问题
高级(3~5 年)负责一个子系统,主导技术方案性能调优、架构权衡、带人、把不确定性变成计划
架构师(5 年+)为整个系统的演进负责业务抽象、技术选型、治理复杂度、控制成本与风险

六个练手项目(按难度递增,每个都能覆盖全栈链路)

① 个人博客

SSR 渲染 + Markdown + 标签/搜索 + 评论。练:路由、SEO、CRUD、部署。

② 待办/看板协作

多用户 + 拖拽排序 + 实时同步(SSE/WebSocket)。练:鉴权、权限、实时、乐观更新。

③ 电商下单

商品/购物车/库存/订单/支付回调。练:事务、锁、幂等、状态机、超时关单。

④ 短链 + 统计

短码生成 + 高并发跳转 + UV/PV 统计。练:缓存、布隆过滤器、Bitmap、限流。

⑤ 多租户 SaaS

租户隔离 + 订阅计费 + RBAC + 审计日志。练:数据隔离方案、权限模型、计费状态机。

⑥ IM / 协同编辑

消息可靠投递 + 已读回执 + CRDT 协同。练:长连接、消息序号、冲突解决。

高效学习习惯

  • 带着问题学:先写代码遇到瓶颈,再去看原理,记忆最牢
  • 读源码 > 读博客:从 500 行的小库开始(如 clsx、mitt、p-limit)
  • 输出倒逼输入:写技术方案、做内部分享、写复盘文档
  • 建立自己的代码片段库:把重复三次的代码沉淀为模板/脚手架
  • 复盘线上事故:每一个 bug 都问「为什么没在更早的阶段被发现」

14面试高频考点

浏览器 / 网络

输入 URL 到页面展示发生了什么?· 三次握手与四次挥手 · HTTPS/TLS 握手过程 · 跨域与 CORS 预检 · 强缓存与协商缓存 · 事件循环与宏/微任务 · 浏览器渲染流水线(回流/重绘)

JavaScript / TS

闭包与作用域链 · 原型链 · Promise 与 async/await 执行顺序 · 深浅拷贝 · 防抖节流 · 事件委托 · this 绑定 · 泛型与类型收窄

React

虚拟 DOM 与 Diff · Fiber 架构 · Hooks 原理与闭包陷阱 · 批处理与并发特性 · 受控/非受控组件 · 合成事件 · key 的作用 · 状态管理选型

Node / 后端

事件循环六阶段 · 单线程如何应对并发 · 流与背压 · 中间件机制 · 进程守护与集群 · JWT vs Session · REST 幂等 · 限流算法(固定/滑动窗口、令牌桶、漏桶)

数据库

索引数据结构为何是 B+ 树 · 最左前缀 · 聚簇 vs 非聚簇索引 · MVCC 实现 · 隔离级别与幻读 · 慢 SQL 优化思路 · 分库分表与全局 ID(雪花算法)

系统设计

如何设计一个短链/秒杀/IM/抢红包/限流系统 · 一致性哈希 · 缓存三大问题 · 分布式锁 · 分布式 ID · CAP 与 BASE · 消息队列如何保证不丢不重

Python

GIL 与多线程 · 生成器与迭代器 · 装饰器/上下文管理器 · 深浅拷贝 · 可变默认参数 · asyncio 原理与事件循环 · 元类与描述符 · 内存管理与垃圾回收(引用计数+分代)

JVM / Java

类加载与双亲委派 · 内存区域与 OOM 场景 · GC 算法与 G1/ZGC · 线程池参数与拒绝策略 · synchronized vs ReentrantLock(AQS)· volatile 与 happens-before · ThreadLocal 内存泄漏 · Spring 循环依赖与事务失效

答题框架(系统设计题通用):① 澄清需求与量级(DAU、QPS、存储量、一致性要求)→ ② 估算容量 → ③ 画顶层架构 → ④ 定 API 与数据模型 → ⑤ 深入核心难点(如 ID 生成、缓存、热点、幂等)→ ⑥ 讨论瓶颈与扩展方案 → ⑦ 提容错、监控、降级。能把权衡讲清楚比给出「标准答案」更重要。

写在最后

全栈的终点不是「什么都会写」,而是能在不确定性中做出合理取舍,并对结果负责。技术会过时,但以下能力不会:拆解复杂问题的能力、快速定位根因的能力、把模糊需求变成可交付方案的能力、以及与人和协作的能力。

保持好奇,保持怀疑,把每一次线上事故都变成下一次的底气。