diff --git a/questions/cap-theorem.md b/questions/cap-theorem.md new file mode 100644 index 0000000..0d946b1 --- /dev/null +++ b/questions/cap-theorem.md @@ -0,0 +1,439 @@ +# CAP 理论和 BASE 理论 + +## 问题 + +1. 什么是 CAP 理论?CAP 三者为什么不可兼得? +2. 什么是 BASE 理论? +3. CP、AP、AP 架构分别适用于什么场景? +4. Zookeeper、Eureka、Nacos、Consul 分别是 CP 还是 AP? +5. 在实际项目中如何权衡一致性、可用性、分区容错性? + +--- + +## 标准答案 + +### 1. CAP 理论 + +#### **定义** + +CAP 是指分布式系统中的三个核心指标: + +| 指标 | 说明 | 示例 | +|------|------|------| +| **Consistency(一致性)** | 所有节点在同一时间看到相同的数据 | 写入后,所有节点立即读取到新数据 | +| **Availability(可用性)** | 系统持续提供服务,每个请求都能得到响应 | 即使部分节点故障,系统仍能响应 | +| **Partition Tolerance(分区容错性)** | 系统在网络分区时仍能继续运行 | 节点间网络断开,系统仍能工作 | + +--- + +#### **CAP 定理** + +**一个分布式系统最多只能同时满足两项**: + +``` +┌─────────────────────────────────────┐ +│ CAP 三选二 │ +│ │ +│ CA ────┐ │ +│ │ │ +│ ╱ │ ╲ │ +│ ╱ │ ╲ │ +│ ╱ │ ╲ │ +│ ●────────●──────● │ +│ C P A │ +│ │ +│ CP AP CA │ +└─────────────────────────────────────┘ +``` + +**为什么?**(证明) +``` +场景:两个节点 N1、N2,网络分区(P) + +情况 1:保证 C(一致性) +├─ N1 写入数据 +└─ 为了保证一致性,N2 必须拒绝读取(牺牲 A) + +情况 2:保证 A(可用性) +├─ N1 写入数据 +└─ 为了保证可用性,N2 返回旧数据(牺牲 C) + +结论:在有分区(P)的情况下,C 和 A 无法同时满足 +``` + +--- + +#### **CP、AP、CA 架构** + +**1. CP(一致性 + 分区容错)** + +**特点**: +- 保证数据一致性 +- 分区时部分节点不可用 + +**适用场景**: +- 金融系统(转账、支付) +- 库存系统(超卖不可接受) + +**代表系统**: +- Zookeeper(CP) +- HBase(CP) +- Redis Cluster(CP,主从切换时短暂不可用) + +**示例**: +```java +// Zookeeper 写入流程 +client.setData("/node", data); +// 等待大多数节点确认 +// 如果网络分区,部分节点无法写入 +``` + +--- + +**2. AP(可用性 + 分区容错)** + +**特点**: +- 保证系统可用 +- 分区时可能读到脏数据 + +**适用场景**: +- 社交媒体(点赞、评论) +- 内容分发(CDN) +- 用户行为统计 + +**代表系统**: +- Cassandra(AP) +- DynamoDB(AP) +- Eureka(AP) +- DNS(AP) + +**示例**: +```java +// Cassandra 写入 +session.execute("INSERT INTO users (id, name) VALUES (1, 'Alice')"); +// 写入成功立即返回 +// 数据可能尚未复制到其他节点(最终一致性) +``` + +--- + +**3. CA(一致性 + 可用性)** + +**注意**:**在分布式系统中,CA 不存在**(因为网络分区不可避免)。 + +**CA 存在于**: +- 单机系统(RDBMS) +- 传统关系型数据库(MySQL、PostgreSQL) + +--- + +### 2. BASE 理论 + +#### **定义** + +BASE 是对 CAP 中 AP 方案的补充,通过**牺牲强一致性**来获得**高可用性**。 + +| 指标 | 说明 | 示例 | +|------|------|------| +| **Basically Available(基本可用)** | 系统出现故障时,允许损失部分可用性 | 秒杀时拒绝部分请求 | +| **Soft state(软状态)** | 允许数据存在中间状态 | 订单状态:待支付 → 已支付 | +| **Eventually consistent(最终一致性)** | 数据最终会达到一致状态 | 支付后 1 秒内到账 | + +--- + +#### **BASE vs ACID** + +| 特性 | ACID(传统数据库) | BASE(NoSQL) | +|------|-------------------|---------------| +| **一致性** | 强一致性(立即) | 最终一致性(延迟) | +| **可用性** | 可能(锁、事务) | 高(无锁、异步) | +| **隔离性** | 严格(锁) | 松散 | +| **持久性** | 强 | 弱(可能丢失) | + +--- + +#### **最终一致性的实现** + +**1. 读时修复(Read Repair)**: +```java +// 读取时检查一致性 +public User getUser(Long userId) { + // 从多个节点读取 + User user1 = node1.get(userId); + User user2 = node2.get(userId); + + // 发现不一致,修复 + if (!user1.equals(user2)) { + // 使用版本号或时间戳决定哪个更新 + User latest = user1.getVersion() > user2.getVersion() ? user1 : user2; + + // 异步修复旧数据 + asyncRepair(latest); + + return latest; + } + + return user1; +} +``` + +--- + +**2. 写时修复(Write Repair)**: +```java +// 写入时同步到所有节点 +public void saveUser(User user) { + // 写入主节点 + masterNode.save(user); + + // 异步同步到从节点 + for (Node slave : slaves) { + CompletableFuture.runAsync(() -> { + slave.save(user); + }); + } +} +``` + +--- + +**3. 异步复制**: +``` +主节点收到写入 + ↓ +立即返回成功 + ↓ +异步复制到从节点 + ↓ +最终一致 +``` + +--- + +### 3. 主流注册中心对比 + +#### **Zookeeper(CP)** + +**特点**: +- 保证一致性(ZAB 协议) +- Leader 挂了会重新选举(期间不可用) + +**适用场景**: +- 需要强一致性 +- 对可用性要求不高(如配置中心) + +**示例**: +```java +// Zookeeper 注册 +zk.create("/services/order/192.168.1.10:8080", data, ZooDefs.Ids.OPEN, NodeMode.EPHEMERAL); +``` + +--- + +#### **Eureka(AP)** + +**特点**: +- 保证可用性 +- 客户端缓存注册信息 +- 网络分区时仍能提供服务 + +**适用场景**: +- 对可用性要求高 +- 可容忍短暂的服务发现不准确 + +**示例**: +```java +// Eureka 注册 +@Bean +public EurekaInstanceBeanBean eurekaInstanceBean(InetAddress inetAddress) { + EurekaInstanceBeanBean bean = new EurekaInstanceBeanBean(); + bean.setHostname(inetAddress.getHostAddress()); + bean.setAppName("order-service"); + bean.setNonSecurePort(8080); + return bean; +} +``` + +--- + +#### **Nacos(AP + CP)** + +**特点**: +- 支持 AP 和 CP 切换 +- 默认 AP(临时实例) +- 可配置 CP(持久化实例) + +**配置**: +```yaml +# Nacos 配置 +spring: + cloud: + nacos: + discovery: + server-addr: localhost:8848 + ephemeral: true # true=AP, false=CP +``` + +--- + +#### **Consul(CP)** + +**特点**: +- 保证一致性(Raft 协议) +- 支持 KV 存储 +- 支持健康检查 + +**适用场景**: +- 服务发现 +- 配置中心 +- 分布式锁 + +--- + +### 4. 实际项目应用 + +#### **场景 1:订单系统(CP)** + +**需求**: +- 不能超卖 +- 库存数据必须准确 + +**方案**: +``` +1. 使用分布式锁(Redis、Zookeeper) +2. 数据库使用强一致性事务 +3. 库存扣减使用串行化 +``` + +**代码**: +```java +@Transactional +public void createOrder(Order order) { + // 1. 获取分布式锁 + RLock lock = redissonClient.getLock("product:" + order.getProductId()); + lock.lock(); + + try { + // 2. 查询库存 + Product product = productMapper.selectById(order.getProductId()); + if (product.getStock() < order.getQuantity()) { + throw new BusinessException("库存不足"); + } + + // 3. 扣减库存 + product.setStock(product.getStock() - order.getQuantity()); + productMapper.updateById(product); + + // 4. 创建订单 + orderMapper.insert(order); + + } finally { + lock.unlock(); + } +} +``` + +--- + +#### **场景 2:社交点赞(AP)** + +**需求**: +- 高并发点赞 +- 允许点赞数短暂不准确 + +**方案**: +``` +1. 先更新缓存(Redis) +2. 异步同步到数据库 +3. 最终一致(1 秒内) +``` + +**代码**: +```java +public void like(Long userId, Long postId) { + // 1. 更新缓存(立即返回) + redisTemplate.opsForSet().add("post:" + postId + ":likes", userId); + + // 2. 异步更新数据库 + CompletableFuture.runAsync(() -> { + likeMapper.insert(new Like(userId, postId)); + }); +} + +public Long getLikeCount(Long postId) { + // 1. 先查缓存 + Long count = redisTemplate.opsForSet().size("post:" + postId + ":likes"); + + // 2. 如果缓存不存在,查数据库 + if (count == null || count == 0) { + count = likeMapper.countByPostId(postId); + redisTemplate.opsForValue().set("post:" + postId + ":count", count, 1, TimeUnit.HOURS); + } + + return count; +} +``` + +--- + +#### **场景 3:库存同步(最终一致性)** + +**需求**: +- 多个仓库库存同步 +- 允许短暂不一致 + +**方案**: +``` +1. 使用消息队列(RocketMQ) +2. 事务消息保证不丢失 +3. 消费者重试保证最终一致 +``` + +**代码**: +```java +// 1. 发送事务消息 +@Transactional +public void updateStock(Long productId, int quantity) { + // 更新数据库 + productMapper.updateStock(productId, quantity); + + // 发送事务消息 + Message msg = new Message("StockTopic", "StockUpdate", + JSON.toJSONString(new StockUpdateEvent(productId, quantity)).getBytes()); + rocketMQTemplate.sendMessageInTransaction(msg, null); +} + +// 2. 消费消息 +@RocketMQMessageListener(topic = "StockTopic", consumerGroup = "stock-consumer") +public class StockConsumer implements RocketMQListener { + @Override + public void onMessage(StockUpdateEvent event) { + // 同步到其他仓库 + warehouseService.syncStock(event.getProductId(), event.getQuantity()); + } +} +``` + +--- + +### 5. 阿里 P7 加分项 + +**深度理解**: +- 理解 CAP 理论的局限性(如网络延迟、时钟问题) +- 理解各种一致性协议(Paxos、Raft、ZAB) +- 理解分布式事务的权衡 + +**实战经验**: +- 有设计 CP 或 AP 系统的经验 +- 有处理数据不一致问题的经验 +- 有最终一致性调优的经验 + +**架构能力**: +- 能根据业务特点选择合适的架构 +- 能设计混合架构(部分 CP、部分 AP) +- 能设计数据修复和补偿机制 + +**技术选型**: +- 了解各种注册中心和存储系统的 CAP 特性 +- 能根据业务特点选择合适的技术 +- 有分布式系统设计经验 diff --git a/questions/concurrentHashMap.md b/questions/concurrentHashMap.md new file mode 100644 index 0000000..601ceee --- /dev/null +++ b/questions/concurrentHashMap.md @@ -0,0 +1,476 @@ +# ConcurrentHashMap 原理 + +## 问题 + +1. ConcurrentHashMap 在 JDK 1.7 和 1.8 中的实现有什么区别? +2. ConcurrentHashMap 如何保证线程安全? +3. ConcurrentHashMap 的 size() 方法是如何实现的? +4. ConcurrentHashMap 和 Hashtable、Collections.synchronizedMap 的区别? +5. 在实际项目中如何选择线程安全的 Map? + +--- + +## 标准答案 + +### 1. JDK 1.7 vs 1.8 + +#### **JDK 1.7:分段锁** + +**结构**: +``` +ConcurrentHashMap + ↓ +Segment[](分段数组,默认 16 个) + ↓ +HashEntry[](每个 Segment 有自己的 HashEntry 数组) + ↓ +HashEntry(键值对) +``` + +**特点**: +- **分段锁**:每个 Segment 独立加锁(ReentrantLock) +- **并发度**:默认 16(Segment 数量) +- **粒度**:Segment 级别 + +**获取锁流程**: +```java +// 1. 计算 Segment 索引 +int hash = hash(key.hashCode()); +int segmentIndex = (hash >>> segmentShift) & segmentMask; + +// 2. 获取 Segment +Segment segment = segments[segmentIndex]; + +// 3. 加锁 +segment.lock(); +try { + // 操作 HashEntry[] +} finally { + segment.unlock(); +} +``` + +--- + +#### **JDK 1.8:CAS + synchronized** + +**结构**: +``` +ConcurrentHashMap + ↓ +Node[] + TreeNode[](数组 + 链表 + 红黑树) + ↓ +Node / TreeNode +``` + +**特点**: +- **CAS + synchronized**:CAS 失败后使用 synchronized +- **粒度**:Node 级别(更细) +- **并发度**:理论上无限制(实际受限于数组大小) + +**核心改进**: +| 特性 | JDK 1.7 | JDK 1.8 | +|------|---------|---------| +| **锁机制** | ReentrantLock(分段) | CAS + synchronized(Node 级别) | +| **锁粒度** | Segment 级别 | Node 级别(更细) | +| **并发度** | 默认 16 | 理论无限制 | +| **查询** | 需要加锁(不强一致) | 无锁(volatile) | +| **红黑树** | 不支持 | 支持(链表长度 ≥ 8) | + +--- + +### 2. JDK 1.8 实现原理 + +#### **核心数据结构** + +```java +public class ConcurrentHashMap { + // 数组 + transient volatile Node[] table; + + // 数组(扩容时使用) + private transient volatile Node[] nextTable; + + // 基础计数器值 + private transient volatile long baseCount; + + // 控制位(sizeCtl < 0:初始化或扩容;-1:正在初始化;<-1:扩容线程数) + private transient volatile int sizeCtl; +} +``` + +--- + +#### **Node 节点** + +```java +static class Node implements Map.Entry { + final int hash; + final K key; + volatile V val; + volatile Node next; // 链表下一个节点 +} +``` + +**注意**:`val` 和 `next` 都是 `volatile`,保证可见性。 + +--- + +#### **TreeNode(红黑树节点)** + +```java +static final class TreeNode extends Node { + TreeNode parent; // 父节点 + TreeNode left; // 左子节点 + TreeNode right; // 右子节点 + TreeNode prev; // 前驱节点 + boolean red; // 颜色(红黑树) +} +``` + +**转换条件**: +- 链表 → 红黑树:链表长度 ≥ 8 **且** 数组长度 ≥ 64 +- 红黑树 → 链表:红黑树节点数 ≤ 6 + +--- + +### 3. 核心 API 源码解析 + +#### **put() 方法** + +```java +public V put(K key, V value) { + return putVal(key, value, false); +} + +final V putVal(K key, V value, boolean onlyIfAbsent) { + if (key == null || value == null) throw new NullPointerException(); + + // 1. 计算哈希值(扰动函数,减少哈希冲突) + int hash = spread(key.hashCode()); + int binCount = 0; + + // 2. 无限循环(CAS + 重试) + for (Node[] tab = table;;) { + Node f; int n, i, fh; + + // 2.1 初始化数组(延迟初始化) + if (tab == null || (n = tab.length) == 0) + tab = initTable(); + + // 2.2 计算索引位置 + else if ((f = tabAt(tab, i = (n - 1) & hash)) == null) { + // 位置为空,CAS 插入 + if (casTabAt(tab, i, null, new Node(hash, key, value, null))) + break; // CAS 成功,退出 + } + + // 2.3 扩容中(MOVED = -1) + else if ((fh = f.hash) == MOVED) + tab = helpTransfer(tab, f); // 帮助扩容 + + // 2.4 位置不为空(链表或红黑树) + else { + V oldVal = null; + synchronized (f) { // 加锁(Node 级别) + if (tabAt(tab, i) == f) { + // 链表 + if (fh >= 0) { + binCount = 1; + for (Node e = f;; ++binCount) { + K ek; + // 键已存在,更新值 + if (e.hash == hash && + ((ek = e.key) == key || + (ek != null && key.equals(ek)))) { + oldVal = e.val; + if (!onlyIfAbsent) + e.val = value; + break; + } + + Node pred = e; + // 到达链表尾部,插入新节点 + if ((e = e.next) == null) { + pred.next = new Node(hash, key, + value, null); + break; + } + } + } + // 红黑树 + else if (f instanceof TreeBin) { + Node p; + binCount = 2; + if ((p = ((TreeBin)f).putTreeVal(hash, key, + value)) != null) { + oldVal = p.val; + if (!onlyIfAbsent) + p.val = value; + } + } + } + } + + // 2.5 链表转红黑树 + if (binCount != 0) { + if (binCount >= TREEIFY_THRESHOLD) + treeifyBin(tab, i); // TREEIFY_THRESHOLD = 8 + if (oldVal != null) + return oldVal; + break; + } + } + } + + // 3. 增加元素个数(LongAdder) + addCount(1L, binCount); + return null; +} +``` + +--- + +#### **get() 方法** + +```java +public V get(Object key) { + Node[] tab; Node e, p; int n, eh; K ek; + int h = spread(key.hashCode()); + + // 1. 数组不为空且索引位置不为空 + if ((tab = table) != null && (n = tab.length) > 0 && + (e = tabAt(tab, (n - 1) & h)) != null) { + + // 2. 第一个节点匹配 + if ((eh = e.hash) == h) { + if ((ek = e.key) == key || (ek != null && key.equals(ek))) + return e.val; + } + + // 3. 红黑树 + else if (eh < 0) + return (p = e.find(h, key)) != null ? p.val : null; + + // 4. 链表遍历 + while ((e = e.next) != null) { + if (e.hash == h && + ((ek = e.key) == key || (ek != null && key.equals(ek)))) + return e.val; + } + } + + return null; +} +``` + +**注意**: +- **无锁读取**:整个 `get()` 方法无锁 +- **volatile 可见性**:`Node.val` 是 `volatile`,保证读到最新值 + +--- + +#### **size() 方法** + +**问题**:如何高效统计元素个数? + +**方案**:**LongAdder**(分段计数) + +```java +public int size() { + long n = sumCount(); + return ((n < 0L) ? 0 : + (n > (long)Integer.MAX_VALUE) ? Integer.MAX_VALUE : + (int)n); +} + +final long sumCount() { + // 计算所有 CounterCell 的和 + CounterCell[] as = counterCells; + long sum = baseCount; + if (as != null) { + for (CounterCell a : as) + if (a != null) + sum += a.value; + } + return sum; +} +``` + +**LongAdder 原理**: +``` +LongAdder + ↓ +baseCount(基础值) + ↓ +CounterCell[](计数器数组,避免 CAS 竞争) + ↓ +多线程更新时,随机选择一个 CounterCell 更新 + ↓ +统计时,baseCount + 所有 CounterCell 的值 +``` + +**为什么高效?** +- 高并发时,多线程更新不同的 CounterCell(无竞争) +- 统计时才累加(牺牲实时性换取性能) + +--- + +### 4. ConcurrentHashMap vs 其他 Map + +#### **对比表** + +| 特性 | HashMap | Hashtable | Collections.synchronizedMap | ConcurrentHashMap | +|------|---------|-----------|----------------------------|-------------------| +| **线程安全** | ❌ 否 | ✅ 是 | ✅ 是 | ✅ 是 | +| **锁机制** | 无 | synchronized | synchronized | CAS + synchronized | +| **锁粒度** | - | 整个表 | 整个表 | Node 级别 | +| **并发度** | 无 | 低 | 低 | 高 | +| **迭代器** | Fail-Fast | Fail-Safe | Fail-Safe | Fail-Safe | +| **null 键值** | 允许 | 不允许 | 不允许 | 不允许 | + +--- + +#### **详细对比** + +**1. Hashtable(不推荐)** + +```java +// Hashtable 的 put 方法(整个表加锁) +public synchronized V put(K key, V value) { + // ... +} +``` + +**问题**: +- **锁粒度太大**:整个表加锁 +- **并发度低**:同一时刻只有一个线程能操作 + +--- + +**2. Collections.synchronizedMap()** + +```java +Map map = Collections.synchronizedMap(new HashMap<>()); +``` + +**原理**: +```java +// 内部使用 mutex(Object)加锁 +public V put(K key, V value) { + synchronized (mutex) { // 整个 Map 加锁 + return m.put(key, value); + } +} +``` + +**问题**: +- **锁粒度太大**:整个 Map 加锁 +- **迭代器需要手动加锁**: +```java +Map map = Collections.synchronizedMap(new HashMap<>()); + +// 遍历需要手动加锁 +synchronized (map) { + for (Map.Entry entry : map.entrySet()) { + // ... + } +} +``` + +--- + +**3. ConcurrentHashMap(推荐)** + +```java +Map map = new ConcurrentHashMap<>(); +``` + +**优点**: +- **锁粒度小**:Node 级别 +- **并发度高**:理论上无限制 +- **迭代器无需加锁**:Fail-Safe(弱一致迭代器) + +--- + +### 5. 实际项目应用 + +#### **场景 1:本地缓存** + +```java +@Component +public class LocalCache { + private final ConcurrentHashMap cache = new ConcurrentHashMap<>(); + + public void put(String key, Object value) { + cache.put(key, value); + } + + public Object get(String key) { + return cache.get(key); + } + + public void remove(String key) { + cache.remove(key); + } +} +``` + +--- + +#### **场景 2:计数器** + +```java +@Component +public class CounterService { + private final ConcurrentHashMap counters = new ConcurrentHashMap<>(); + + public void increment(String key) { + counters.computeIfAbsent(key, k -> new LongAdder()).increment(); + } + + public long get(String key) { + LongAdder adder = counters.get(key); + return adder != null ? adder.sum() : 0; + } +} +``` + +--- + +#### **场景 3:去重表** + +```java +@Component +public class DeduplicationService { + private final ConcurrentHashMap dedupTable = new ConcurrentHashMap<>(); + + public boolean isDuplicate(String id) { + return dedupTable.putIfAbsent(id, true) != null; + } +} +``` + +--- + +### 6. 阿里 P7 加分项 + +**深度理解**: +- 理解 `ConcurrentHashMap` 的扩容机制(多线程协同扩容) +- 理解 `LongAdder` 的实现原理(Cell 数组 + CAS) +- 理解 `ConcurrentHashMap` 的弱一致迭代器 + +**实战经验**: +- 有使用 `ConcurrentHashMap` 解决并发问题的经验 +- 有 `ConcurrentHashMap` 性能调优的经验 +- 有处理 `ConcurrentHashMap` 相关线上问题的经验 + +**架构能力**: +- 能根据业务特点选择合适的 Map 实现 +- 能设计高性能的并发数据结构 +- 有分布式缓存的设计经验 + +**技术选型**: +- 了解 `ConcurrentSkipListMap`(跳表实现) +- 了解 Guava 的 `LocalCache`(Caffeine) +- 能根据场景选择本地缓存或分布式缓存 diff --git a/questions/design-patterns.md b/questions/design-patterns.md new file mode 100644 index 0000000..ef90946 --- /dev/null +++ b/questions/design-patterns.md @@ -0,0 +1,1047 @@ +# 设计模式 + +## 问题 + +1. 什么是设计模式?设计模式的六大原则是什么? +2. 单例模式的实现方式有哪些?如何保证线程安全? +3. 工厂模式和抽象工厂模式的区别是什么? +4. 观察者模式的应用场景有哪些?Spring 中哪些地方用到了观察者模式? +5. 策略模式如何解决 if-else 过多的问题? +6. 装饰器模式和代理模式的区别是什么? +7. 在实际项目中如何使用设计模式? + +--- + +## 标准答案 + +### 1. 设计模式的六大原则(SOLID + 1) + +#### **单一职责原则(Single Responsibility Principle)** + +**定义**:一个类只负责一个职责。 + +**示例**: +```java +// ❌ 违反单一职责 +class UserService { + public void saveUser(User user) { } + public void sendEmail(User user) { } + public void log(User user) { } +} + +// ✅ 符合单一职责 +class UserService { + public void saveUser(User user) { } +} + +class EmailService { + public void sendEmail(User user) { } +} + +class LogService { + public void log(User user) { } +} +``` + +--- + +#### **开闭原则(Open-Closed Principle)** + +**定义**:对扩展开放,对修改关闭。 + +**示例**: +```java +// ❌ 每次新增类型需要修改代码 +class OrderService { + public double calculateDiscount(String type, double price) { + if (type.equals("VIP")) { + return price * 0.8; + } else if (type.equals("SVIP")) { + return price * 0.7; + } + // 新增类型需要修改这里 + return price; + } +} + +// ✅ 使用策略模式 +interface DiscountStrategy { + double calculate(double price); +} + +class VipDiscount implements DiscountStrategy { + @Override + public double calculate(double price) { + return price * 0.8; + } +} + +class OrderService { + private Map strategyMap = new HashMap<>(); + + public void registerStrategy(String type, DiscountStrategy strategy) { + strategyMap.put(type, strategy); + } + + public double calculateDiscount(String type, double price) { + DiscountStrategy strategy = strategyMap.get(type); + return strategy != null ? strategy.calculate(price) : price; + } +} +``` + +--- + +#### **里氏替换原则(Liskov Substitution Principle)** + +**定义**:子类可以替换父类出现在父类能够出现的任何地方。 + +**示例**: +```java +// ❌ 违反里氏替换 +class Bird { + public void fly() { + System.out.println("Flying"); + } +} + +class Penguin extends Bird { + @Override + public void fly() { + throw new UnsupportedOperationException("企鹅不会飞"); + } +} + +// ✅ 符合里氏替换 +class Bird { } + +class FlyingBird extends Bird { + public void fly() { + System.out.println("Flying"); + } +} + +class Penguin extends Bird { + public void swim() { + System.out.println("Swimming"); + } +} +``` + +--- + +#### **接口隔离原则(Interface Segregation Principle)** + +**定义**:客户端不应该依赖它不需要的接口。 + +**示例**: +```java +// ❌ 接口过于臃肿 +interface Animal { + void fly(); + void swim(); + void run(); +} + +// ✅ 拆分接口 +interface Flyable { + void fly(); +} + +interface Swimmable { + void swim(); +} + +interface Runnable { + void run(); +} + +class Duck implements Flyable, Swimmable, Runnable { + @Override + public void fly() { } + + @Override + public void swim() { } + + @Override + public void run() { } +} +``` + +--- + +#### **依赖倒置原则(Dependency Inversion Principle)** + +**定义**:高层模块不应该依赖低层模块,两者都应该依赖抽象。 + +**示例**: +```java +// ❌ 高层依赖低层 +class OrderService { + private MySQLDatabase database = new MySQLDatabase(); // 直接依赖具体实现 + + public void saveOrder(Order order) { + database.save(order); + } +} + +// ✅ 依赖抽象 +interface Database { + void save(Object obj); +} + +class MySQLDatabase implements Database { + @Override + public void save(Object obj) { + // MySQL 实现 + } +} + +class OrderService { + private final Database database; + + public OrderService(Database database) { + this.database = database; // 依赖注入 + } + + public void saveOrder(Order order) { + database.save(order); + } +} +``` + +--- + +#### **迪米特法则(Law of Demeter / Least Knowledge Principle)** + +**定义**:一个对象应该对其他对象有最少的了解。 + +**示例**: +```java +// ❌ 违反迪米特法则 +class Customer { + private Order order; + + public void printOrderDetails() { + System.out.println(order.getItem().getProduct().getName()); + // Customer → Order → Item → Product(了解太多) + } +} + +// ✅ 符合迪米特法则 +class Customer { + private Order order; + + public void printOrderDetails() { + System.out.println(order.getProductName()); + } +} + +class Order { + private Item item; + + public String getProductName() { + return item.getProduct().getName(); + } +} +``` + +--- + +### 2. 单例模式 + +#### **实现方式** + +##### **方式 1:饿汉式(线程安全)** + +```java +public class Singleton { + private static final Singleton INSTANCE = new Singleton(); + + private Singleton() { } + + public static Singleton getInstance() { + return INSTANCE; + } +} +``` + +**优点**:简单、线程安全 +**缺点**:类加载时初始化,可能浪费资源 + +--- + +##### **方式 2:懒汉式(线程不安全)** + +```java +public class Singleton { + private static Singleton instance; + + private Singleton() { } + + public static Singleton getInstance() { + if (instance == null) { + instance = new Singleton(); + } + return instance; + } +} +``` + +**问题**:线程不安全 + +--- + +##### **方式 3:懒汉式 + synchronized(线程安全但性能差)** + +```java +public class Singleton { + private static Singleton instance; + + private Singleton() { } + + public static synchronized Singleton getInstance() { + if (instance == null) { + instance = new Singleton(); + } + return instance; + } +} +``` + +**问题**:每次调用都需要加锁,性能差 + +--- + +##### **方式 4:双重检查锁定(推荐)** + +```java +public class Singleton { + // volatile 禁止指令重排序 + private static volatile Singleton instance; + + private Singleton() { } + + public static Singleton getInstance() { + if (instance == null) { // 第一次检查(无锁) + synchronized (Singleton.class) { + if (instance == null) { // 第二次检查(有锁) + instance = new Singleton(); + } + } + } + return instance; + } +} +``` + +**为什么需要 volatile?** +```java +instance = new Singleton(); +// 分三步: +// 1. 分配内存 +// 2. 初始化对象 +// 3. 将 instance 指向内存 + +// 指令重排序后可能是:1 → 3 → 2 +// 线程 A 执行了 1、3,线程 B 发现 instance 不为 null,但对象未初始化! +``` + +--- + +##### **方式 5:静态内部类(推荐)** + +```java +public class Singleton { + private Singleton() { } + + private static class Holder { + private static final Singleton INSTANCE = new Singleton(); + } + + public static Singleton getInstance() { + return Holder.INSTANCE; + } +} +``` + +**优点**: +- 延迟加载(调用 getInstance 时才初始化) +- 线程安全(类加载机制保证) +- 性能好(无锁) + +--- + +##### **方式 6:枚举(最佳实践)** + +```java +public enum Singleton { + INSTANCE; + + public void doSomething() { + // 业务方法 + } +} + +// 使用 +Singleton.INSTANCE.doSomething(); +``` + +**优点**: +- 线程安全(JVM 保证) +- 防止反射攻击 +- 防止序列化破坏 + +--- + +#### **单例模式的问题** + +**1. 反射破坏单例**: +```java +Constructor constructor = Singleton.class.getDeclaredConstructor(); +constructor.setAccessible(true); +Singleton instance1 = constructor.newInstance(); +Singleton instance2 = constructor.newInstance(); +// instance1 != instance2(单例被破坏) +``` + +**解决**: +```java +private Singleton() { + if (Holder.INSTANCE != null) { + throw new RuntimeException("不允许创建多个实例"); + } +} +``` + +**2. 序列化破坏单例**: +```java +Singleton instance1 = Singleton.getInstance(); +ByteArrayOutputStream bos = new ByteArrayOutputStream(); +ObjectOutputStream oos = new ObjectOutputStream(bos); +oos.writeObject(instance1); + +ObjectInputStream ois = new ObjectInputStream( + new ByteArrayInputStream(bos.toByteArray()) +); +Singleton instance2 = (Singleton) ois.readObject(); +// instance1 != instance2(单例被破坏) +``` + +**解决**: +```java +public class Singleton implements Serializable { + private Singleton() { } + + private static class Holder { + private static final Singleton INSTANCE = new Singleton(); + } + + // 反序列化时返回单例 + private Object readResolve() { + return Holder.INSTANCE; + } +} +``` + +--- + +### 3. 工厂模式 + +#### **简单工厂** + +```java +// 产品接口 +interface Payment { + void pay(int amount); +} + +// 具体产品 +class Alipay implements Payment { + @Override + public void pay(int amount) { + System.out.println("支付宝支付:" + amount); + } +} + +class WechatPay implements Payment { + @Override + public void pay(int amount) { + System.out.println("微信支付:" + amount); + } +} + +// 工厂类 +class PaymentFactory { + public static Payment createPayment(String type) { + switch (type) { + case "alipay": + return new Alipay(); + case "wechat": + return new WechatPay(); + default: + throw new IllegalArgumentException("不支持支付方式"); + } + } +} + +// 使用 +Payment payment = PaymentFactory.createPayment("alipay"); +payment.pay(100); +``` + +**问题**:新增支付方式需要修改工厂类(违反开闭原则) + +--- + +#### **工厂方法** + +```java +// 工厂接口 +interface PaymentFactory { + Payment createPayment(); +} + +// 具体工厂 +class AlipayFactory implements PaymentFactory { + @Override + public Payment createPayment() { + return new Alipay(); + } +} + +class WechatPayFactory implements PaymentFactory { + @Override + public Payment createPayment() { + return new WechatPay(); + } +} + +// 使用 +PaymentFactory factory = new AlipayFactory(); +Payment payment = factory.createPayment(); +payment.pay(100); +``` + +**优点**:符合开闭原则(新增支付方式无需修改代码) + +--- + +#### **抽象工厂** + +**场景**:跨平台 UI 工具包 + +```java +// 抽象产品 +interface Button { + void render(); +} + +interface Checkbox { + void render(); +} + +// 具体产品(Windows 风格) +class WindowsButton implements Button { + @Override + public void render() { + System.out.println("Windows 按钮"); + } +} + +class WindowsCheckbox implements Checkbox { + @Override + public void render() { + System.out.println("Windows 复选框"); + } +} + +// 具体产品(Mac 风格) +class MacButton implements Button { + @Override + public void render() { + System.out.println("Mac 按钮"); + } +} + +class MacCheckbox implements Checkbox { + @Override + public void render() { + System.out.println("Mac 复选框"); + } +} + +// 抽象工厂 +interface GUIFactory { + Button createButton(); + Checkbox createCheckbox(); +} + +// 具体工厂 +class WindowsFactory implements GUIFactory { + @Override + public Button createButton() { + return new WindowsButton(); + } + + @Override + public Checkbox createCheckbox() { + return new WindowsCheckbox(); + } +} + +class MacFactory implements GUIFactory { + @Override + public Button createButton() { + return new MacButton(); + } + + @Override + public Checkbox createCheckbox() { + return new MacCheckbox(); + } +} + +// 使用 +GUIFactory factory = new WindowsFactory(); +Button button = factory.createButton(); +Checkbox checkbox = factory.createCheckbox(); +button.render(); +checkbox.render(); +``` + +--- + +### 4. 观察者模式 + +#### **原理** + +定义对象间的一对多依赖关系,当一个对象状态改变时,所有依赖它的对象都得到通知并自动更新。 + +--- + +#### **Java 实现** + +```java +// 观察者接口 +interface Observer { + void update(String message); +} + +// 被观察者(主题) +interface Subject { + void attach(Observer observer); + void detach(Observer observer); + void notifyObservers(); +} + +// 具体主题 +class MessageQueue implements Subject { + private List observers = new ArrayList<>(); + private String message; + + @Override + public void attach(Observer observer) { + observers.add(observer); + } + + @Override + public void detach(Observer observer) { + observers.remove(observer); + } + + @Override + public void notifyObservers() { + for (Observer observer : observers) { + observer.update(message); + } + } + + public void setMessage(String message) { + this.message = message; + notifyObservers(); + } +} + +// 具体观察者 +class EmailNotifier implements Observer { + @Override + public void update(String message) { + System.out.println("发送邮件:" + message); + } +} + +class SMSNotifier implements Observer { + @Override + public void update(String message) { + System.out.println("发送短信:" + message); + } +} + +// 使用 +MessageQueue queue = new MessageQueue(); +queue.attach(new EmailNotifier()); +queue.attach(new SMSNotifier()); + +queue.setMessage("新订单创建"); +``` + +--- + +#### **Spring 事件机制(观察者模式)** + +```java +// 1. 定义事件 +class OrderCreatedEvent extends ApplicationEvent { + private final Order order; + + public OrderCreatedEvent(Object source, Order order) { + super(source); + this.order = order; + } + + public Order getOrder() { + return order; + } +} + +// 2. 定义监听器 +@Component +class OrderEmailListener { + @EventListener + public void handleOrderCreated(OrderCreatedEvent event) { + System.out.println("发送邮件:" + event.getOrder()); + } +} + +@Component +class OrderSMSListener { + @EventListener + public void handleOrderCreated(OrderCreatedEvent event) { + System.out.println("发送短信:" + event.getOrder()); + } +} + +// 3. 发布事件 +@Service +class OrderService { + @Autowired + private ApplicationEventPublisher eventPublisher; + + public void createOrder(Order order) { + // 保存订单 + orderRepository.save(order); + + // 发布事件 + eventPublisher.publishEvent(new OrderCreatedEvent(this, order)); + } +} +``` + +--- + +### 5. 策略模式 + +#### **场景:计算不同类型的折扣** + +```java +// 策略接口 +interface DiscountStrategy { + double calculate(double price); +} + +// 具体策略 +class VipDiscount implements DiscountStrategy { + @Override + public double calculate(double price) { + return price * 0.8; + } +} + +class SvipDiscount implements DiscountStrategy { + @Override + public double calculate(double price) { + return price * 0.7; + } +} + +class NormalDiscount implements DiscountStrategy { + @Override + public double calculate(double price) { + return price; + } +} + +// 上下文 +class OrderService { + private Map strategyMap = new HashMap<>(); + + @PostConstruct + public void init() { + strategyMap.put("VIP", new VipDiscount()); + strategyMap.put("SVIP", new SvipDiscount()); + strategyMap.put("NORMAL", new NormalDiscount()); + } + + public double calculatePrice(String userType, double price) { + DiscountStrategy strategy = strategyMap.get(userType); + if (strategy == null) { + throw new IllegalArgumentException("不支持的用户类型"); + } + return strategy.calculate(price); + } +} + +// 使用 +OrderService orderService = new OrderService(); +double price = orderService.calculatePrice("VIP", 100); +``` + +--- + +#### **Spring 实现策略模式** + +```java +// 策略接口 +public interface PaymentStrategy { + void pay(int amount); + + // 获取策略类型 + String getType(); +} + +// 具体策略(标注为 Spring Bean) +@Component +public class AlipayStrategy implements PaymentStrategy { + @Override + public void pay(int amount) { + System.out.println("支付宝支付:" + amount); + } + + @Override + public String getType() { + return "alipay"; + } +} + +@Component +public class WechatPayStrategy implements PaymentStrategy { + @Override + public void pay(int amount) { + System.out.println("微信支付:" + amount); + } + + @Override + public String getType() { + return "wechat"; + } +} + +// 策略工厂 +@Service +public class PaymentService { + private final Map strategyMap; + + // 构造器注入所有策略 + public PaymentService(List strategies) { + this.strategyMap = strategies.stream() + .collect(Collectors.toMap( + PaymentStrategy::getType, + Function.identity() + )); + } + + public void pay(String type, int amount) { + PaymentStrategy strategy = strategyMap.get(type); + if (strategy == null) { + throw new IllegalArgumentException("不支持的支付方式"); + } + strategy.pay(amount); + } +} +``` + +--- + +### 6. 装饰器模式 vs 代理模式 + +#### **装饰器模式** + +**目的**:动态地给对象添加功能 + +**示例**: +```java +// 组件接口 +interface Coffee { + double cost(); +} + +// 具体组件 +class SimpleCoffee implements Coffee { + @Override + public double cost() { + return 10; + } +} + +// 装饰器 +class MilkDecorator implements Coffee { + private final Coffee coffee; + + public MilkDecorator(Coffee coffee) { + this.coffee = coffee; + } + + @Override + public double cost() { + return coffee.cost() + 2; // 加奶 2 元 + } +} + +class SugarDecorator implements Coffee { + private final Coffee coffee; + + public SugarDecorator(Coffee coffee) { + this.coffee = coffee; + } + + @Override + public double cost() { + return coffee.cost() + 1; // 加糖 1 元 + } +} + +// 使用 +Coffee coffee = new SimpleCoffee(); +coffee = new MilkDecorator(coffee); // 加奶 +coffee = new SugarDecorator(coffee); // 加糖 +System.out.println(coffee.cost()); // 10 + 2 + 1 = 13 +``` + +--- + +#### **代理模式** + +**目的**:控制对对象的访问 + +**静态代理**: +```java +// 接口 +interface OrderService { + void createOrder(Order order); +} + +// 真实对象 +class OrderServiceImpl implements OrderService { + @Override + public void createOrder(Order order) { + System.out.println("创建订单:" + order); + } +} + +// 代理对象 +class OrderServiceProxy implements OrderService { + private final OrderService orderService; + + public OrderServiceProxy(OrderService orderService) { + this.orderService = orderService; + } + + @Override + public void createOrder(Order order) { + // 前置增强 + System.out.println("权限检查"); + + // 调用真实对象 + orderService.createOrder(order); + + // 后置增强 + System.out.println("日志记录"); + } +} + +// 使用 +OrderService proxy = new OrderServiceProxy(new OrderServiceImpl()); +proxy.createOrder(new Order()); +``` + +--- + +**动态代理(JDK)**: +```java +class ProxyHandler implements InvocationHandler { + private final Object target; + + public ProxyHandler(Object target) { + this.target = target; + } + + @Override + public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { + // 前置增强 + System.out.println("权限检查"); + + // 调用真实对象 + Object result = method.invoke(target, args); + + // 后置增强 + System.out.println("日志记录"); + + return result; + } +} + +// 使用 +OrderService realService = new OrderServiceImpl(); +OrderService proxyService = (OrderService) Proxy.newProxyInstance( + realService.getClass().getClassLoader(), + realService.getClass().getInterfaces(), + new ProxyHandler(realService) +); +proxyService.createOrder(new Order()); +``` + +--- + +#### **对比** + +| 特性 | 装饰器模式 | 代理模式 | +|------|-----------|---------| +| **目的** | 增强功能 | 控制访问 | +| **关注点** | 功能扩展 | 访问控制 | +| **透明性** | 对客户端透明 | 客户端可能知道代理存在 | +| **示例** | Java I/O Streams | Spring AOP | + +--- + +### 7. 实际项目应用 + +#### **Spring 中的设计模式** + +1. **工厂模式**:`BeanFactory`、`ApplicationContext` +2. **单例模式**:Spring Bean(默认单例) +3. **代理模式**:AOP、事务管理 +4. **观察者模式**:ApplicationEvent、ApplicationListener +5. **策略模式**:`Resource`(不同资源加载策略) +6. **模板方法模式**:`JdbcTemplate`、`RestTemplate` +7. **适配器模式**:`HandlerAdapter` +8. **责任链模式**:Filter Chain、Interceptor Chain + +--- + +### 8. 阿里 P7 加分项 + +**深度理解**: +- 理解设计模式的适用场景和权衡 +- 理解设计模式与重构的关系 +- 理解设计模式在开源框架中的应用 + +**实战经验**: +- 有使用设计模式重构代码的经验 +- 有设计模式误用的经验和教训 +- 能根据业务特点选择合适的设计模式 + +**架构能力**: +- 能设计可扩展、可维护的架构 +- 能识别代码中的坏味道并重构 +- 能在团队中推广设计模式最佳实践 diff --git a/questions/distributed-lock.md b/questions/distributed-lock.md new file mode 100644 index 0000000..70b859d --- /dev/null +++ b/questions/distributed-lock.md @@ -0,0 +1,456 @@ +# 分布式锁 + +## 问题 + +1. 什么是分布式锁?为什么需要分布式锁? +2. 如何用 Redis 实现分布式锁? +3. Redis 分布式锁有哪些坑?如何解决? +4. Zookeeper 如何实现分布式锁? +5. Redis 分布式锁和 Zookeeper 分布式锁的区别? +6. Redisson 是如何实现分布式锁的? +7. 在实际项目中如何使用分布式锁? + +--- + +## 标准答案 + +### 1. 为什么需要分布式锁 + +#### **场景** + +**单机锁(synchronized、ReentrantLock)**: +```java +@Service +public class OrderService { + private synchronized void createOrder(Order order) { + // 单机环境下有效 + // 分布式环境下无效(不同 JVM) + } +} +``` + +**问题**: +- 单机锁只对单个 JVM 有效 +- 分布式环境下,多个实例的锁互不排斥 +- 需要跨 JVM、跨机器的锁机制 + +--- + +### 2. Redis 分布式锁 + +#### **基本实现** + +```java +public class RedisDistributedLock { + + private final StringRedisTemplate redisTemplate; + + // 加锁 + public boolean lock(String key, String value, long expireTime) { + // SET key value NX PX expireTime + Boolean success = redisTemplate.opsForValue() + .setIfAbsent(key, value, expireTime, TimeUnit.MILLISECONDS); + + return success != null && success; + } + + // 释放锁 + public boolean unlock(String key, String value) { + // Lua 脚本:保证原子性 + String luaScript = + "if redis.call('get', KEYS[1]) == ARGV[1] then " + + " return redis.call('del', KEYS[1]) " + + "else " + + " return 0 " + + "end"; + + DefaultRedisScript script = new DefaultRedisScript<>(luaScript, Long.class); + Long result = redisTemplate.execute(script, Collections.singletonList(key), value); + + return result != null && result == 1; + } +} +``` + +--- + +#### **使用示例** + +```java +@Service +public class OrderService { + + @Autowired + private RedisDistributedLock lock; + + public void createOrder(Order order) { + String lockKey = "order:" + order.getProductId(); + String lockValue = UUID.randomUUID().toString(); + + try { + // 加锁(过期时间 30 秒) + boolean locked = lock.lock(lockKey, lockValue, 30000); + + if (!locked) { + throw new BusinessException("系统繁忙,请稍后再试"); + } + + // 执行业务逻辑 + // 1. 查询库存 + // 2. 扣减库存 + // 3. 创建订单 + + } finally { + // 释放锁 + lock.unlock(lockKey, lockValue); + } + } +} +``` + +--- + +### 3. Redis 分布式锁的坑 + +#### **坑 1:锁超时问题** + +**场景**: +``` +线程 A 获取锁(设置 30 秒过期) +线程 A 执行业务(耗时 50 秒) +30 秒后,锁自动过期 +线程 B 获取锁 +线程 A 执行完毕,释放锁(把线程 B 的锁释放了!) +``` + +**解决方案:看门狗(Watchdog)** + +**原理**: +- 启动后台线程 +- 定期检查锁是否还存在 +- 如果存在,续期(延长过期时间) + +**Redisson 实现**: +```java +RLock lock = redisson.getLock("myLock"); +lock.lock(); // 自动续期(默认 30 秒) + +try { + // 业务逻辑 +} finally { + lock.unlock(); +} +``` + +--- + +#### **坑 2:主从切换导致锁丢失** + +**场景**: +``` +线程 A 在 Master 获取锁 +锁未同步到 Slave +Master 宕机,Slave 升级为 Master +线程 B 在新 Master 上获取锁(冲突!) +``` + +**解决方案:Redlock(Redis 分布式锁算法)** + +**原理**: +- 向多个独立的 Redis 节点请求加锁 +- 大多数节点加锁成功才算成功 + +```java +// Redisson Redlock +RLock lock1 = redisson1.getLock("myLock"); +RLock lock2 = redisson2.getLock("myLock"); +RLock lock3 = redisson3.getLock("myLock"); + +RedissonRedLock redLock = new RedissonRedLock(lock1, lock2, lock3); + +try { + redLock.lock(); + // 业务逻辑 +} finally { + redLock.unlock(); +} +``` + +--- + +#### **坑 3:释放锁时误删** + +**场景**: +``` +线程 A 获取锁 +线程 A 执行时间过长,锁超时过期 +线程 B 获取锁 +线程 A 执行完毕,释放锁(删除了线程 B 的锁!) +``` + +**解决方案:Lua 脚本 + 唯一标识** + +```lua +-- Lua 脚本(原子操作) +if redis.call('get', KEYS[1]) == ARGV[1] then + return redis.call('del', KEYS[1]) +else + return 0 +end +``` + +**Java 实现**: +```java +public boolean unlock(String key, String value) { + String luaScript = + "if redis.call('get', KEYS[1]) == ARGV[1] then " + + " return redis.call('del', KEYS[1]) " + + "else " + + " return 0 " + + "end"; + + DefaultRedisScript script = new DefaultRedisScript<>(luaScript, Long.class); + Long result = redisTemplate.execute(script, Collections.singletonList(key), value); + + return result != null && result == 1; +} +``` + +--- + +### 4. Zookeeper 分布式锁 + +#### **原理** + +利用 Zookeeper 的**临时顺序节点**: + +``` +/lock + /lock/node-0000000001 (客户端 1) + /lock/node-0000000002 (客户端 2) + /lock/node-0000000003 (客户端 3) +``` + +**流程**: +1. 客户端创建临时顺序节点 +2. 获取所有子节点,判断自己是否是序号最小的 +3. 如果是最小节点,获取锁 +4. 如果不是,监听前一个节点的删除事件 + +--- + +#### **Curator 实现** + +```java +@Component +public class ZkDistributedLock { + + private final InterProcessMutex lock; + + public ZkDistributedLock(CuratorFramework curatorFramework) { + this.lock = new InterProcessMutex(curatorFramework, "/lock"); + } + + public void acquire() { + try { + lock.acquire(); + } catch (Exception e) { + throw new RuntimeException("获取锁失败", e); + } + } + + public void release() { + try { + lock.release(); + } catch (Exception e) { + throw new RuntimeException("释放锁失败", e); + } + } +} +``` + +**使用**: +```java +@Service +public class OrderService { + + @Autowired + private ZkDistributedLock lock; + + public void createOrder(Order order) { + try { + lock.acquire(); + + // 业务逻辑 + + } finally { + lock.release(); + } + } +} +``` + +--- + +### 5. Redis vs Zookeeper + +| 特性 | Redis | Zookeeper | +|------|-------|-----------| +| **性能** | 高(内存操作) | 低(磁盘 + ZAB 协议) | +| **可靠性** | 中(可能丢锁) | 高(CP 一致性) | +| **实现复杂度** | 简单 | 复杂 | +| **获取锁方式** | 轮询 | Watcher 通知(事件驱动) | +| **锁释放** | 超时自动释放 | 会话结束自动释放 | +| **适用场景** | 高并发、对一致性要求不高 | 严格一致性要求 | + +--- + +### 6. Redisson 原理 + +#### **看门狗(自动续期)** + +```java +RLock lock = redisson.getLock("myLock"); +lock.lock(); // 默认 leaseTime = -1(启用看门狗) +``` + +**原理**: +``` +1. 加锁成功,启动看门狗线程 +2. 看门狗每 10 秒检查一次 +3. 如果锁还存在,续期 30 秒 +4. 客户端宕机,会话结束,看门狗停止,锁自动过期 +``` + +--- + +#### **公平锁** + +```java +RLock fairLock = redisson.getFairLock("myLock"); +fairLock.lock(); +``` + +**原理**: +- 加锁时,添加到队列尾部 +- 按照请求顺序获取锁 +- 性能比非公平锁低 + +--- + +#### **读写锁** + +```java +RReadWriteLock rwLock = redisson.getReadWriteLock("myLock"); +rwLock.readLock().lock(); // 读锁(共享) +rwLock.writeLock().lock(); // 写锁(独占) +``` + +--- + +### 7. 实际项目应用 + +#### **场景 1:秒杀系统** + +```java +@Service +public class SeckillService { + + @Autowired + private RedissonClient redisson; + + public void seckill(Long userId, Long productId) { + RLock lock = redisson.getLock("seckill:" + productId); + + try { + // 尝试加锁(最多等待 3 秒,锁自动释放时间 10 秒) + boolean locked = lock.tryLock(3, 10, TimeUnit.SECONDS); + + if (!locked) { + throw new BusinessException("抢购人数过多,请稍后再试"); + } + + // 1. 查询库存 + Integer stock = redisTemplate.opsForValue().get("stock:" + productId); + if (stock == null || stock <= 0) { + throw new BusinessException("库存不足"); + } + + // 2. 扣减库存 + redisTemplate.opsForValue().decrement("stock:" + productId); + + // 3. 创建订单 + createOrder(userId, productId); + + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + throw new BusinessException("系统异常"); + } finally { + if (lock.isHeldByCurrentThread()) { + lock.unlock(); + } + } + } +} +``` + +--- + +#### **场景 2:定时任务分布式锁** + +```java +@Component +public class ScheduledTask { + + @Autowired + private RedissonClient redisson; + + @Scheduled(cron = "0 */5 * * * ?") // 每 5 分钟 + public void execute() { + RLock lock = redisson.getLock("scheduled-task"); + + try { + // 只有一个实例执行 + boolean locked = lock.tryLock(0, 5, TimeUnit.MINUTES); + + if (!locked) { + return; // 其他实例正在执行 + } + + // 执行任务 + doTask(); + + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + } finally { + if (lock.isHeldByCurrentThread()) { + lock.unlock(); + } + } + } +} +``` + +--- + +### 8. 阿里 P7 加分项 + +**深度理解**: +- 理解 CAP 理论在分布式锁中的体现 +- 理解 Redis 的 ZSet 实现延迟队列的原理 +- 理解 ZAB 协议和 ZK 的一致性保证 + +**实战经验**: +- 有处理分布式锁死锁的经验 +- 有分布式锁性能优化的经验 +- 有分布式锁监控的经验 + +**架构能力**: +- 能设计高可用的分布式锁方案 +- 能设计分布式锁的容灾方案 +- 能设计分布式锁的降级方案 + +**技术选型**: +- 了解 Redis、Zookeeper、Etcd 等多种分布式锁实现 +- 能根据业务特点选择合适的方案 +- 有自研分布式锁的经验 diff --git a/questions/docker.md b/questions/docker.md new file mode 100644 index 0000000..e69de29 diff --git a/questions/dubbo.md b/questions/dubbo.md new file mode 100644 index 0000000..e69de29 diff --git a/questions/elastic-search.md b/questions/elastic-search.md new file mode 100644 index 0000000..e69de29 diff --git a/questions/interview-skills.md b/questions/interview-skills.md new file mode 100644 index 0000000..c244c27 --- /dev/null +++ b/questions/interview-skills.md @@ -0,0 +1,148 @@ +# 面试技巧和职业规划 + +## 问题 + +1. 如何准备技术面试? +2. 面试中如何回答项目经验? +3. 如何进行技术深度学习? +4. P7 级别的要求是什么? +5. 如何准备系统设计题? + +--- + +## 标准答案 + +### 1. 面试准备清单 + +**基础复习(1-2 周)**: +- [ ] Java 基础(集合、并发、JVM) +- [ ] 数据库(索引、事务、锁) +- [ ] Redis(数据结构、持久化、集群) +- [ ] 消息队列(Kafka、RocketMQ) +- [ ] 分布式事务、缓存、限流 + +**算法刷题(2-4 周)**: +- [ ] LeetCode Hot 100 +- [ ] 剑指 Offer +- [ ] 关注:数组、链表、树、动态规划 + +**系统设计(持续)**: +- [ ] 设计短链接系统 +- [ ] 设计秒杀系统 +- [ ] 设计微博 Feed 流 +- [ ] 设计分布式 ID 生成器 + +--- + +### 2. 项目经验回答(STAR 法则) + +**STAR**: +- **S**ituation(情境):项目背景 +- **T**ask(任务):你的职责 +- **A**ction(行动):你做了什么 +- **R**esult(结果):取得了什么成果 + +**示例**: +``` +S(情境): +某电商双11大促,订单峰值 QPS 达到 10 万,原有系统无法支撑。 + +T(任务): +我负责订单系统的性能优化,目标是将响应时间从 2s 降至 200ms。 + +A(行动): +1. 分析瓶颈:发现数据库查询是主要瓶颈(80% 时间) +2. 优化 SQL:添加索引,将慢查询从 100ms 降至 5ms +3. 引入缓存:使用 Redis 缓存热点数据,命中率 95% +4. 异步处理:非核心逻辑(发邮件、发短信)异步化 +5. 压测验证:使用 JMeter 压测,逐步优化参数 + +R(结果): +- 响应时间从 2s 降至 150ms(目标 200ms) +- 成功支撑双11峰值 QPS 10 万 +- 系统稳定性 99.99% +``` + +--- + +### 3. 技术深度学习 + +**如何深入?**: +``` +1. 看源码(Spring、MyBatis、Redis) +2. 读文档(官方文档、RFC) +3. 写博客(输出倒逼输入) +4. 做分享(团队内、技术社区) +5. 参与开源(GitHub、PR) +``` + +**推荐资源**: +- 书籍:《深入理解 Java 虚拟机》、《MySQL 技术内幕》 +- 博客:美团技术博客、阿里技术公众号 +- 视频:TED、Google I/O +- 论文:SIGMOD、VLDB(数据库) + +--- + +### 4. P7 级别要求 + +**硬技能**: +- 精通一门语言(Java、Go、Python) +- 深入理解某一领域(数据库、消息队列、分布式) +- 有系统设计能力 +- 有性能优化经验 + +**软技能**: +- 技术影响力(博客、分享、开源) +- 团队协作(Code Review、技术方案评审) +- 问题解决能力(线上故障排查) +- 技术规划能力(技术选型、架构演进) + +--- + +### 5. 系统设计题答题思路 + +**5 步法**: +``` +1. 需求澄清 + - 功能需求(做什么) + - 非功能需求(QPS、数据量、一致性) + - 约束条件(预算、时间、人力) + +2. 容量估算 + - QPS(每秒请求数) + - 存储容量(数据量、增长速度) + - 带宽需求 + +3. 核心设计 + - 架构图(画出关键组件) + - 数据模型(ER 图、表设计) + - 核心流程(时序图) + +4. 瓶颈分析 + - 可能的瓶颈(单点、数据库、缓存) + - 解决方案(集群、分片、缓存) + +5. 扩展性 + - 如何扩容(水平扩展、垂直扩展) + - 如何保证高可用(主从、多机房) +``` + +--- + +### 6. 阿里 P7 加分项 + +**技术广度**: +- 对多个技术栈有了解(不限于 Java) +- 对新技术保持敏感(云原生、Service Mesh) +- 有跨领域经验(后端、大数据、运维) + +**技术深度**: +- 精通某一领域(分布式系统、数据库) +- 有开源贡献或技术博客 +- 有技术演讲或培训经验 + +**业务理解**: +- 理解业务价值(技术服务业务) +- 有产品思维(从用户角度思考) +- 有商业意识(成本、ROI) diff --git a/questions/java-concurrent.md b/questions/java-concurrent.md new file mode 100644 index 0000000..d6cb479 --- /dev/null +++ b/questions/java-concurrent.md @@ -0,0 +1,208 @@ +# Java 并发编程基础 + +## 问题 + +1. 进程和线程的区别? +2. 创建线程有哪些方式? +3. synchronized 关键字的原理? +4. volatile 关键字的作用? +5. CAS(Compare And Swap)的原理和 ABA 问题? +6. ThreadLocal 的原理和内存泄漏问题? + +--- + +## 标准答案 + +### 1. 进程 vs 线程 + +| 特性 | 进程 | 线程 | +|------|------|------| +| **资源** | 独立内存、文件描述符 | 共享进程资源 | +| **通信** | IPC(管道、消息队列、共享内存) | 共享内存 | +| **上下文切换** | 慢(需保存更多状态) | 快 | +| **开销** | 大 | 小 | + +--- + +### 2. 创建线程的方式 + +**方式 1:继承 Thread** +```java +class MyThread extends Thread { + @Override + public void run() { + System.out.println("Thread running"); + } +} +new MyThread().start(); +``` + +**方式 2:实现 Runnable** +```java +class MyRunnable implements Runnable { + @Override + public void run() { + System.out.println("Runnable running"); + } +} +new Thread(new MyRunnable()).start(); +``` + +**方式 3:实现 Callable(有返回值)** +```java +class MyCallable implements Callable { + @Override + public String call() throws Exception { + return "Callable result"; + } +} +FutureTask future = new FutureTask<>(new MyCallable()); +new Thread(future).start(); +String result = future.get(); +``` + +--- + +### 3. synchronized 原理 + +**字节码层面**: +```java +// 同步方法 +public synchronized void method() { } +// 字节码:ACC_SYNCHRONIZED + +// 同步代码块 +synchronized (object) { } +// 字节码:monitorenter、monitorexit +``` + +**对象头**: +``` +Mark Word(32 位 JVM) +┌────────────┬────────────┬──────────────┐ +│ 锁状态 │ 29 位或2位 │ 是否是偏向锁 │ +├────────────┼────────────┼──────────────┤ +│ 无锁 │ 对象 Hash │ 01 │ +│ 偏向锁 │ 线程 ID │ 01 │ +│ 轻量级锁 │ 栈中 Lock Record | 00 │ +│ 重量级锁 │ 管程指针 │ 10 │ +└────────────┴────────────┴──────────────┘ +``` + +**锁升级**: +``` +无锁 → 偏向锁 → 轻量级锁(CAS 自旋) → 重量级锁(阻塞) +``` + +--- + +### 4. volatile 关键字 + +**作用**: +1. **保证可见性**(JMM,主内存与工作内存) +2. **禁止指令重排序** + +**示例**: +```java +private volatile boolean running = true; + +public void stop() { + running = false; // 立即对所有线程可见 +} +``` + +**不能保证原子性**: +```java +private volatile int count = 0; + +// ❌ 非线程安全 +count++; // read → modify → write(非原子操作) + +// ✅ 线程安全 +synchronized (this) { + count++; +} +``` + +--- + +### 5. CAS 和 ABA 问题 + +**CAS(Compare And Swap)**: +```java +// Unsafe.compareAndSwapInt(object, offset, expect, update) +// 如果当前值 == 期望值,则更新为 update + +AtomicInteger atomic = new AtomicInteger(0); +atomic.compareAndSet(0, 1); // CAS 操作 +``` + +**ABA 问题**: +``` +线程 A:读取值 A +线程 B:A → B → A +线程 A:CAS 成功(不知道值已变化) +``` + +**解决:版本号(AtomicStampedReference)**: +```java +AtomicStampedReference ref = new AtomicStampedReference<>(100, 0); + +int oldStamp = ref.getStamp(); +ref.compareAndSet(100, 101, oldStamp, oldStamp + 1); // 版本号也参与 CAS +``` + +--- + +### 6. ThreadLocal + +**原理**: +```java +public void set(T value) { + Thread t = Thread.currentThread(); + ThreadLocalMap map = t.threadLocals; + if (map != null) + map.set(this, value); // key 是 ThreadLocal 对象,value 是值 + else + createMap(t, value); +} +``` + +**内存泄漏**: +```java +// ❌ 可能导致内存泄漏 +private static ThreadLocal data = new ThreadLocal<>(); + +public void process() { + data.set(new byte[1024 * 1024]); // 1MB + // 未调用 remove() +} +``` + +**原因**: +- ThreadLocal 是弱引用,但 value 是强引用 +- 线程不销毁,value 不会回收 + +**解决**: +```java +try { + data.set(new byte[1024 * 1024]); + // 业务逻辑 +} finally { + data.remove(); // 必须 +} +``` + +--- + +### 7. 阿里 P7 加分项 + +**深度理解**: +- 理解 JMM(Java 内存模型) +- 理解 happens-before 原则 +- 理解 synchronized 的优化(偏向锁、轻量级锁) + +**实战经验**: +- 有并发问题的排查经验 +- 有性能优化经验(减少锁竞争) +- 有死锁的排查和解决经验 diff --git a/questions/java-memory.md b/questions/java-memory.md new file mode 100644 index 0000000..e69de29 diff --git a/questions/jvm-gc.md b/questions/jvm-gc.md new file mode 100644 index 0000000..b4d8f02 --- /dev/null +++ b/questions/jvm-gc.md @@ -0,0 +1,324 @@ +# JVM 和垃圾回收 + +## 问题 + +1. JVM 内存模型是怎样的? +2. 垃圾回收有哪些算法?G1 和 CMS 的区别? +3. 什么是 Stop-The-World?如何减少 STW 时间? +4. 常见的 OOM 及解决方案? +5. JVM 参数如何调优? +6. 如何分析 GC 日志? + +--- + +## 标准答案 + +### 1. JVM 内存模型 + +#### **运行时数据区** + +``` +┌─────────────────────────────────────┐ +│ 方法区(Method Area) │ +│ (类信息、常量、静态变量、JIT 代码) │ +└─────────────────────────────────────┘ +┌─────────────────────────────────────┐ +│ 堆(Heap) │ +│ (对象实例、数组) │ +│ ┌──────────┐ ┌──────────┐ │ +│ │ 新生代 │ │ 老年代 │ │ +│ │ Eden │ │ │ │ +│ │ S0 │ │ │ │ +│ │ S1 │ │ │ │ +│ └──────────┘ └──────────┘ │ +└─────────────────────────────────────┘ +┌─────────────────────────────────────┐ +│ JVM 栈(Java Stack) │ +│ (栈帧:局部变量、操作数栈、返回地址) │ +└─────────────────────────────────────┘ +┌─────────────────────────────────────┐ +│ 本地方法栈(Native Stack) │ +│ (Native 方法调用) │ +└─────────────────────────────────────┘ +┌─────────────────────────────────────┐ +│ 程序计数器(PC Register) │ +│ (当前执行的字节码指令) │ +└─────────────────────────────────────┘ +``` + +--- + +### 2. 垃圾回收算法 + +#### **标记-清除(Mark-Sweep)** + +**步骤**: +1. 标记存活对象 +2. 清除未标记对象 + +**问题**: +- 产生内存碎片 +- 标记和清除效率都不高 + +--- + +#### **复制(Copying)** + +**步骤**: +1. 将存活对象复制到另一块内存 +2. 清空当前内存 + +**优点**: +- 无内存碎片 +- 简单高效 + +**缺点**: +- 内存利用率低(浪费一半) + +**适用**:新生代(Eden + S0 + S1) + +--- + +#### **标记-整理(Mark-Compact)** + +**步骤**: +1. 标记存活对象 +2. 将存活对象向一端移动 +3. 清理边界外的内存 + +**优点**: +- 无内存碎片 + +**缺点**: +- 移动对象成本高 + +**适用**:老年代 + +--- + +#### **分代收集(Generational)** + +**原理**: +- **弱分代假说**:大多数对象朝生夕灭 +- **新生代**:复制算法(Eden : S0 : S1 = 8 : 1 : 1) +- **老年代**:标记-整理或标记-清除 + +--- + +### 3. 垃圾收集器 + +#### **Serial 收集器** + +**特点**:单线程,STW + +**适用**:客户端应用、小内存 + +```bash +-XX:+UseSerialGC +``` + +--- + +#### **Parallel 收集器** + +**特点**:多线程,STW + +**适用**:后台任务、批处理 + +```bash +-XX:+UseParallelGC +``` + +--- + +#### **CMS(Concurrent Mark Sweep)** + +**特点**:并发收集,低停顿 + +**步骤**: +1. 初始标记(STW) +2. 并发标记 +3. 重新标记(STW) +4. 并发清除 + +**问题**: +- CPU 敏感 +- 浮动垃圾 +- 内存碎片 + +```bash +-XX:+UseConcMarkSweepGC +``` + +--- + +#### **G1(Garbage First)** + +**特点**: +- **可预测停顿**:用户指定停顿时间 +- **分区回收**:将堆划分为多个 Region +- **优先回收垃圾多的 Region** + +**适用**:大堆内存(> 6GB)、多核 CPU + +```bash +-XX:+UseG1GC +-XX:MaxGCPauseMillis=200 # 目标停顿时间 +``` + +--- + +#### **ZGC(Z Garbage Collector)** + +**特点**: +- 并发整理(无内存碎片) +- 停顿时间 < 10ms +- 支持 TB 级堆内存 + +**适用**:超大内存、低延迟要求 + +```bash +-XX:+UseZGC +``` + +--- + +### 4. OOM 及解决方案 + +#### **Java.lang.OutOfMemoryError: Java heap space** + +**原因**:堆内存不足 + +**解决**: +```bash +# 增加堆内存 +-Xms4g -Xmx4g + +# 使用 G1 收集器 +-XX:+UseG1GC +``` + +--- + +#### **Java.lang.OutOfMemoryError: Metaspace** + +**原因**:方法区(元空间)不足 + +**解决**: +```bash +# 增加元空间大小 +-XX:MetaspaceSize=256m +-XX:MaxMetaspaceSize=512m +``` + +--- + +#### **Java.lang.OutOfMemoryError: GC overhead limit exceeded** + +**原因**:GC 频繁,但回收内存少 + +**解决**: +- 增加堆内存 +- 优化代码(减少对象创建) + +--- + +#### **Java.lang.StackOverflowError** + +**原因**:栈深度过大(递归太深) + +**解决**: +```bash +# 增加栈大小 +-Xss2m +``` + +--- + +### 5. JVM 参数调优 + +#### **常用参数** + +```bash +# 堆内存 +-Xms4g # 初始堆大小 +-Xmx4g # 最大堆大小 +-Xmn2g # 新生代大小 +-XX:MetaspaceSize=256m +-XX:MaxMetaspaceSize=512m + +# GC 选择 +-XX:+UseG1GC +-XX:MaxGCPauseMillis=200 + +# GC 日志 +-Xlog:gc*:file=/tmp/gc.log:time,tags:filecount=5,filesize=10m + +# OOM 时自动 Dump +-XX:+HeapDumpOnOutOfMemoryError +-XX:HeapDumpPath=/tmp/ +``` + +--- + +#### **调优步骤** + +``` +1. 监控 GC 频率和停顿时间 +2. 分析 GC 日志 +3. 调整堆内存和新生代比例 +4. 选择合适的 GC 收集器 +5. 优化代码(减少对象创建) +6. 压测验证 +``` + +--- + +### 6. GC 日志分析 + +#### **工具** + +- **GCViewer**:可视化分析 +- **GCeasy**:在线分析(https://gceasy.io) +- **阿里 Arthas**:线上诊断 + +--- + +#### **GC 日志示例** + +``` +[GC (Allocation Failure) [PSYoungGen: 2048K->512K(2560K)] 2048K->1024K(9216K), 0.0023456 secs] +[Full GC (Ergonomics) [PSYoungGen: 512K->0K(2560K)] [ParOldGen: 512K->512K(6656K)] 1024K->512K(9216K), [Metaspace: 5120K->5120K(1056768K)], 0.0234567 secs] +``` + +**解读**: +- **GC**:Minor GC +- **Full GC**:Major GC +- **Allocation Failure**:新生代不足 +- **Ergonomics**:自适应调整 +- **2048K->512K(2560K)**:回收前 2048K,回收后 512K,总容量 2560K +- **0.0023456 secs**:停顿时间 + +--- + +### 7. 阿里 P7 加分项 + +**深度理解**: +- 理解 G1 的 Mixed GC 和 Region 设计 +- 理解 ZGC 的染色指针和读屏障 +- 理解 JVM 的 JIT 编译和逃逸分析 + +**实战经验**: +- 有处理线上 OOM 问题的经验 +- 有 GC 参数调优的经验 +- 有 GC 日志分析和优化经验 + +**架构能力**: +- 能设计高吞吐量或低延迟的 JVM 方案 +- 能设计 JVM 监控和告警体系 +- 能制定 JVM 调优规范 + +**技术选型**: +- 了解各种 GC 收集器的适用场景 +- 了解 JDK 8/11/17/21 的 GC 改进 +- 有 GraalVM 等新技术的使用经验 diff --git a/questions/linux-commands.md b/questions/linux-commands.md new file mode 100644 index 0000000..e69de29 diff --git a/questions/lock-mechanism.md b/questions/lock-mechanism.md new file mode 100644 index 0000000..88aea6a --- /dev/null +++ b/questions/lock-mechanism.md @@ -0,0 +1,1909 @@ +# 数据库锁机制面试指南 + +## 1. 行锁、表锁、页锁 + +### 行锁(Row Lock) + +**定义**:锁定数据库表的某一行或多行数据 + +**特点**: +- 锁粒度最细,并发性能最好 +- 开销大,需要更多的锁资源 +- 适合高并发场景 + +**InnoDB 行锁类型**: +1. **记录锁**:锁定单行记录 +2. **间隙锁**:锁定间隙,防止幻读 +3. **临键锁**:记录锁+间隙锁的组合 + +**代码示例**: +```sql +-- 记录锁 +SELECT * FROM user WHERE id = 1 FOR UPDATE; + +-- 间隙锁 +SELECT * FROM user WHERE id BETWEEN 1 AND 10 FOR UPDATE; + +-- 临键锁 +SELECT * FROM user WHERE id > 5 FOR UPDATE; +``` + +**Java 实现**: +```java +// 行锁实现示例 +public class RowLockExample { + + @Autowired + private JdbcTemplate jdbcTemplate; + + // 记录锁 + public User getUserWithRecordLock(Long userId) { + String sql = "SELECT * FROM user WHERE id = ? FOR UPDATE"; + return jdbcTemplate.queryForObject(sql, new Object[]{userId}, (rs, rowNum) -> { + User user = new User(); + user.setId(rs.getLong("id")); + user.setName(rs.getString("name")); + user.setAge(rs.getInt("age")); + return user; + }); + } + + // 间隙锁 + public List getUsersWithGapLock(String minAge, String maxAge) { + String sql = "SELECT * FROM user WHERE age BETWEEN ? AND ? FOR UPDATE"; + return jdbcTemplate.query(sql, new Object[]{minAge, maxAge}, (rs, rowNum) -> { + User user = new User(); + user.setId(rs.getLong("id")); + user.setName(rs.getString("name")); + user.setAge(rs.getInt("age")); + return user; + }); + } +} +``` + +### 表锁(Table Lock) + +**定义**:锁定整个表,所有操作都需要获取表级锁 + +**特点**: +- 锁粒度最粗,并发性能最差 +- 开销小,实现简单 +- 适用于读多写少或操作简单的场景 + +**代码示例**: +```sql +-- 表锁 +LOCK TABLES user WRITE; -- 写锁,阻塞其他所有操作 +-- 执行操作 +UNLOCK TABLES; -- 释放锁 + +-- 读锁 +LOCK TABLES user READ; -- 读锁,允许并发读,阻塞写 +-- 执行读操作 +UNLOCK TABLES; -- 释放锁 +``` + +**Java 实现**: +```java +// 表锁实现示例 +public class TableLockExample { + + @Autowired + private DataSource dataSource; + + // 写锁 + public void executeWithWriteLock(Runnable operation) { + try (Connection conn = dataSource.getConnection()) { + // 获取写锁 + conn.createStatement().execute("LOCK TABLES user WRITE"); + + try { + // 执行操作 + operation.run(); + } finally { + // 释放锁 + conn.createStatement().execute("UNLOCK TABLES"); + } + } catch (SQLException e) { + throw new RuntimeException("执行写锁操作失败", e); + } + } + + // 读锁 + public List executeWithReadLock(Runnable operation) { + try (Connection conn = dataSource.getConnection()) { + // 获取读锁 + conn.createStatement().execute("LOCK TABLES user READ"); + + try { + // 执行读操作 + operation.run(); + } finally { + // 释放锁 + conn.createStatement().execute("UNLOCK TABLES"); + } + } catch (SQLException e) { + throw new RuntimeException("执行读锁操作失败", e); + } + return Collections.emptyList(); + } +} +``` + +### 页锁(Page Lock) + +**定义**:锁定数据库表的数据页,介于行锁和表锁之间 + +**特点**: +- 锁粒度居中 +- 开销居中 +- 适用于中等到高并发场景 + +**InnoDB 页锁**: +- InnoDB 默认使用行锁,页锁主要用于某些特定操作 +- 在全表扫描时可能会升级为表锁 + +**代码示例**: +```sql +-- InnoDB 自动管理页锁 +-- 通常不需要手动控制 +-- 全表扫描时可能触发页锁 +SELECT * FROM user WHERE age > 100 FOR UPDATE; +``` + +### 锁粒度对比 + +| 锁类型 | 锁粒度 | 并发性能 | 开销 | 适用场景 | +|--------|--------|----------|------|----------| +| 表锁 | 最粗 | 最差 | 最小 | 简单操作、维护 | +| 页锁 | 中等 | 中等 | 中等 | 中等并发 | +| 行锁 | 最细 | 最好 | 最大 | 高并发、高吞吐 | + +## 2. 共享锁、排他锁 + +### 共享锁(Shared Lock / S Lock) + +**定义**:多个事务可以同时持有共享锁,但不能持有排他锁 + +**特点**: +- 读锁,允许多个事务同时读取 +- 不允许修改数据 +- 用于读多写少的场景 + +**代码示例**: +```sql +-- 共享锁语法 +SELECT * FROM user WHERE id = 1 LOCK IN SHARE MODE; + +-- Java 实现 +public class SharedLockExample { + + @Autowired + private JdbcTemplate jdbcTemplate; + + public User getUserWithSharedLock(Long userId) { + String sql = "SELECT * FROM user WHERE id = ? LOCK IN SHARE MODE"; + return jdbcTemplate.queryForObject(sql, new Object[]{userId}, (rs, rowNum) -> { + User user = new User(); + user.setId(rs.getLong("id")); + user.setName(rs.getString("name")); + user.setAge(rs.getInt("age")); + return user; + }); + } + + public List getUsersWithSharedLock() { + String sql = "SELECT * FROM user WHERE age > 18 LOCK IN SHARE MODE"; + return jdbcTemplate.query(sql, (rs, rowNum) -> { + User user = new User(); + user.setId(rs.getLong("id")); + user.setName(rs.getString("name")); + user.setAge(rs.getInt("age")); + return user; + }); + } +} +``` + +### 排他锁(Exclusive Lock / X Lock) + +**定义**:只有一个事务可以持有排他锁,阻止其他事务获取任何锁 + +**特点**: +- 写锁,独占访问 +- 阻止其他事务读取和修改 +- 用于写操作 + +**代码示例**: +```sql +-- 排他锁语法 +SELECT * FROM user WHERE id = 1 FOR UPDATE; + +-- Java 实现 +public class ExclusiveLockExample { + + @Autowired + private JdbcTemplate jdbcTemplate; + + public User getUserWithExclusiveLock(Long userId) { + String sql = "SELECT * FROM user WHERE id = ? FOR UPDATE"; + return jdbcTemplate.queryForObject(sql, new Object[]{userId}, (rs, rowNum) -> { + User user = new User(); + user.setId(rs.getLong("id")); + user.setName(rs.getString("name")); + user.setAge(rs.getInt("age")); + return user; + }); + } + + public void updateUserWithExclusiveLock(Long userId, String newName) { + String sql = "UPDATE user SET name = ? WHERE id = ?"; + jdbcTemplate.update(sql, newName, userId); + } +} +``` + +### 锁兼容性矩阵 + +| 已有锁 | 请求共享锁 | 请求排他锁 | +|--------|------------|------------| +| 共享锁 | 兼容 | 不兼容 | +| 排他锁 | 不兼容 | 不兼容 | + +**兼容性图解**: +``` +锁兼容性矩阵 + S锁 X锁 + S锁 ✓ ✗ + X锁 ✗ ✗ +``` + +### 锁升级与降级 + +```java +// 锁升级示例 +public class LockUpgradeExample { + + @Autowired + private JdbcTemplate jdbcTemplate; + + // 锁升级:从共享锁升级到排他锁 + public void upgradeLock(Long userId) { + try (Connection conn = jdbcTemplate.getDataSource().getConnection()) { + // 1. 获取共享锁 + conn.setAutoCommit(false); + User user = getUserWithSharedLock(userId); + + // 2. 检查是否需要升级 + if (needUpgrade(user)) { + // 3. 升级为排他锁 + try { + conn.createStatement().execute( + "SELECT * FROM user WHERE id = ? FOR UPDATE"); + + // 4. 执行更新操作 + updateUserWithExclusiveLock(userId, "New Name"); + + conn.commit(); + } catch (SQLException e) { + conn.rollback(); + throw new RuntimeException("锁升级失败", e); + } + } else { + conn.commit(); + } + } catch (SQLException e) { + throw new RuntimeException("锁操作失败", e); + } + } + + // 锁降级示例 + public void downgradeLock(Long userId) { + try (Connection conn = jdbcTemplate.getDataSource().getConnection()) { + conn.setAutoCommit(false); + + // 1. 获取排他锁 + User user = getUserWithExclusiveLock(userId); + + // 2. 降级为共享锁 + try { + conn.createStatement().execute( + "SELECT * FROM user WHERE id = ? LOCK IN SHARE MODE"); + + // 3. 执行读操作 + List users = getUsersWithSharedLock(); + + conn.commit(); + } catch (SQLException e) { + conn.rollback(); + throw new RuntimeException("锁降级失败", e); + } + } catch (SQLException e) { + throw new RuntimeException("锁操作失败", e); + } + } +} +``` + +## 3. 意向锁、间隙锁、临键锁 + +### 意向锁(Intention Lock) + +**定义**:表级锁,表示事务意图获取行级锁 + +**类型**: +1. **意向共享锁(IS)**:事务意图获取某些行的共享锁 +2. **意向排他锁(IX)**:事务意图获取某些行的排他锁 + +**特点**: +- 提高锁效率,避免逐行检查 +- 表级锁与行级锁协同工作 + +**代码示例**: +```sql +-- 意向锁(InnoDB 自动管理) +-- 不需要手动设置 +-- 在获取行锁时自动添加 + +-- 查看意向锁 +SELECT * FROM information_schema.innodb_locks; +``` + +**Java 实现**: +```java +// 意向锁示例 +public class IntentionLockExample { + + @Autowired + private JdbcTemplate jdbcTemplate; + + // 多个事务同时获取行锁,意向锁提高效率 + public void multipleRowLocks() { + ExecutorService executor = Executors.newFixedThreadPool(5); + + for (int i = 0; i < 5; i++) { + final int userId = i + 1; + executor.submit(() -> { + try (Connection conn = jdbcTemplate.getDataSource().getConnection()) { + conn.setAutoCommit(false); + + // 获取行锁(自动添加意向锁) + User user = getUserWithExclusiveLock((long) userId); + + // 处理数据 + Thread.sleep(1000); + + conn.commit(); + } catch (Exception e) { + throw new RuntimeException("行锁操作失败", e); + } + }); + } + + executor.shutdown(); + } +} +``` + +### 间隙锁(Gap Lock) + +**定义**:锁定索引记录之间的间隙,防止其他事务插入数据 + +**特点**: +- 防止幻读 +- 只在 REPEATABLE READ 隔离级别下有效 +- 使用间隙锁锁定范围查询 + +**代码示例**: +```sql +-- 间隙锁示例 +-- 用户表数据:id: 1, 5, 10, 15, 20 + +-- 锁定id=5和id=10之间的间隙 +SELECT * FROM user WHERE id > 5 AND id < 10 FOR UPDATE; +-- 锁定间隙:(5, 10) + +-- 锁定id>10的间隙 +SELECT * FROM user WHERE id > 10 FOR UPDATE; +-- 锁定间隙:(10, ∞) + +-- 防止插入 +-- 其他事务无法在(5, 10)之间插入新记录 +``` + +**Java 实现**: +```java +// 间隙锁示例 +public class GapLockExample { + + @Autowired + private JdbcTemplate jdbcTemplate; + + // 间隙锁防止幻读 + public void preventPhantomRead() { + try (Connection conn = jdbcTemplate.getDataSource().getConnection()) { + conn.setAutoCommit(false); + + // 查询并锁定间隙 + List users = jdbcTemplate.query( + "SELECT * FROM user WHERE age BETWEEN 20 AND 30 FOR UPDATE", + (rs, rowNum) -> { + User user = new User(); + user.setId(rs.getLong("id")); + user.setName(rs.getString("name")); + user.setAge(rs.getInt("age")); + return user; + }); + + // 处理数据 + System.out.println("找到 " + users.size() + " 个用户"); + + // 模拟长时间处理 + Thread.sleep(5000); + + conn.commit(); + + // 其他事务无法在age 20-30之间插入新记录 + } catch (Exception e) { + throw new RuntimeException("间隙锁操作失败", e); + } + } + + // 优化后的范围查询(避免间隙锁) + public void optimizedRangeQuery() { + try (Connection conn = jdbcTemplate.getDataSource().getConnection()) { + conn.setAutoCommit(false); + + // 使用主键查询避免间隙锁 + List users = jdbcTemplate.query( + "SELECT * FROM user WHERE age >= 20 AND age <= 30 FOR UPDATE", + (rs, rowNum) -> { + User user = new User(); + user.setId(rs.getLong("id")); + user.setName(rs.getString("name")); + user.setAge(rs.getInt("age")); + return user; + }); + + conn.commit(); + } catch (Exception e) { + throw new RuntimeException("范围查询失败", e); + } + } +} +``` + +### 临键锁(Next-Key Lock) + +**定义**:记录锁与间隙锁的组合,锁定记录本身及其相邻间隙 + +**特点**: +- 解决幻读问题 +- 在 REPEATABLE READ 隔离级别下默认使用 +- 锁定记录+前一个间隙 + +**代码示例**: +```sql +-- 临键锁示例 +-- 用户表数据:id: 1, 5, 10, 15, 20 + +-- 锁定id=10及其前后间隙 +SELECT * FROM user WHERE id = 10 FOR UPDATE; +-- 锁定:(5, 10] + (10, 15) + +-- 锁定范围查询 +SELECT * FROM user WHERE id <= 10 FOR UPDATE; +-- 锁定:(-∞, 1] + (1, 5] + (5, 10] + (10, 15) +``` + +**Java 实现**: +```java +// 临键锁示例 +public class NextKeyLockExample { + + @Autowired + private JdbcTemplate jdbcTemplate; + + // 临键锁防止幻读 + public void preventPhantomReadWithNextKeyLock() { + try (Connection conn = jdbcTemplate.getDataSource().getConnection()) { + conn.setAutoCommit(false); + + // 查询并获取临键锁 + List users = jdbcTemplate.query( + "SELECT * FROM user WHERE age <= 30 FOR UPDATE", + (rs, rowNum) -> { + User user = new User(); + user.setId(rs.getLong("id")); + user.setName(rs.getString("name")); + user.setAge(rs.getInt("age")); + return user; + }); + + // 处理数据 + System.out.println("找到 " + users.size() + " 个用户"); + + // 防止其他事务插入age<=30的记录 + conn.commit(); + } catch (Exception e) { + throw new RuntimeException("临键锁操作失败", e); + } + } + + // 优化查询避免不必要的锁 + public void optimizedQueryWithNextKeyLock() { + try (Connection conn = jdbcTemplate.getDataSource().getConnection()) { + conn.setAutoCommit(false); + + // 使用主键查询减少锁范围 + User user = jdbcTemplate.queryForObject( + "SELECT * FROM user WHERE id = ? FOR UPDATE", + new Object[]{10L}, + (rs, rowNum) -> { + User u = new User(); + u.setId(rs.getLong("id")); + u.setName(rs.getString("name")); + u.setAge(rs.getInt("age")); + return u; + }); + + conn.commit(); + } catch (Exception e) { + throw new RuntimeException("查询失败", e); + } + } +} +``` + +## 4. 乐观锁、悲观锁 + +### 乐观锁(Optimistic Locking) + +**定义**:假设冲突不常发生,只在更新时检查冲突 + +**实现方式**: +1. **版本号**:使用版本号字段 +2. **时间戳**:使用更新时间 +3. **CAS**:Compare And Swap + +**代码示例**: +```java +// 乐观锁实现 +@Component +public class OptimisticLockService { + + @Autowired + private UserRepository userRepository; + + // 使用版本号实现乐观锁 + @Transactional + public User updateUserWithVersion(Long userId, String newName) { + User user = userRepository.findById(userId) + .orElseThrow(() -> new RuntimeException("用户不存在")); + + // 尝试更新 + int affected = userRepository.updateWithVersion( + userId, newName, user.getVersion()); + + if (affected == 0) { + throw new OptimisticLockException("数据已被其他事务修改"); + } + + return userRepository.findById(userId).orElse(null); + } + + // 使用时间戳实现乐观锁 + @Transactional + public User updateUserWithTimestamp(Long userId, String newName) { + User user = userRepository.findById(userId) + .orElseThrow(() -> new RuntimeException("用户不存在")); + + // 尝试更新 + int affected = userRepository.updateWithTimestamp( + userId, newName, user.getUpdateTime()); + + if (affected == 0) { + throw new OptimisticLockException("数据已被其他事务修改"); + } + + return userRepository.findById(userId).orElse(null); + } +} + +// Repository 实现 +@Repository +public class UserRepository { + + @Autowired + private JdbcTemplate jdbcTemplate; + + // 带版本号的更新 + public int updateWithVersion(Long userId, String newName, Integer version) { + String sql = "UPDATE user SET name = ?, version = version + 1 " + + "WHERE id = ? AND version = ?"; + return jdbcTemplate.update(sql, newName, userId, version); + } + + // 带时间戳的更新 + public int updateWithTimestamp(Long userId, String newName, Timestamp timestamp) { + String sql = "UPDATE user SET name = ?, update_time = NOW() " + + "WHERE id = ? AND update_time = ?"; + return jdbcTemplate.update(sql, newName, userId, timestamp); + } +} +``` + +### 悲观锁(Pessimistic Locking) + +**定义**:假设冲突经常发生,直接加锁 + +**实现方式**: +1. **行锁**:SELECT ... FOR UPDATE +2. **表锁**:LOCK TABLES +3. **间隙锁**:防止幻读 + +**代码示例**: +```java +// 悲观锁实现 +@Component +public class PessimisticLockService { + + @Autowired + private UserRepository userRepository; + + // 使用行锁实现悲观锁 + @Transactional + public User updateUserWithRowLock(Long userId, String newName) { + // 1. 加锁 + User user = userRepository.findByIdWithLock(userId) + .orElseThrow(() -> new RuntimeException("用户不存在")); + + // 2. 更新数据 + user.setName(newName); + user.setUpdateTime(new Date()); + + // 3. 保存(锁在事务提交时释放) + return userRepository.save(user); + } + + // 使用表锁实现悲观锁 + @Transactional + public void updateUserWithTableLock(Long userId, String newName) { + // 1. 获取表锁 + jdbcTemplate.update("LOCK TABLES user WRITE"); + + try { + // 2. 执行更新 + int affected = userRepository.update(userId, newName); + if (affected == 0) { + throw new RuntimeException("更新失败"); + } + + // 3. 提交事务时自动释放锁 + } finally { + // 确保释放锁 + jdbcTemplate.update("UNLOCK TABLES"); + } + } +} + +// Repository 实现 +@Repository +public class UserRepository { + + @Autowired + private JdbcTemplate jdbcTemplate; + + // 带锁的查询 + public Optional findByIdWithLock(Long userId) { + String sql = "SELECT * FROM user WHERE id = ? FOR UPDATE"; + return jdbcTemplate.queryForObject(sql, new Object[]{userId}, + (rs, rowNum) -> { + User user = new User(); + user.setId(rs.getLong("id")); + user.setName(rs.getString("name")); + user.setAge(rs.getInt("age")); + user.setVersion(rs.getInt("version")); + user.setUpdateTime(rs.getTimestamp("update_time")); + return user; + }); + } + + // 普通更新 + public int update(Long userId, String newName) { + String sql = "UPDATE user SET name = ? WHERE id = ?"; + return jdbcTemplate.update(sql, newName, userId); + } +} +``` + +### 乐观锁 vs 悲观锁 + +| 特性 | 乐观锁 | 悲观锁 | +|------|--------|--------| +| 冲突假设 | 冲突少 | 冲突多 | +| 性能 | 读性能好,写有冲突时开销大 | 写性能好,读性能差 | +| 适用场景 | 读多写少,冲突少 | 写多,冲突多 | +| 实现复杂度 | 简单 | 复杂 | +| 锁类型 | 无锁,版本号控制 | 行锁、表锁等 | + +### 优化策略 + +```java +// 锁策略优化 +@Service +public class LockOptimizationService { + + @Autowired + private UserRepository userRepository; + + // 根据业务场景选择锁策略 + public User updateUser(Long userId, String newName) { + // 1. 首先使用乐观锁 + try { + return updateUserWithVersion(userId, newName); + } catch (OptimisticLockException e) { + // 2. 乐观锁冲突,使用悲观锁重试 + return updateUserWithPessimisticLock(userId, newName); + } + } + + // 乐观锁重试机制 + @Retryable(value = OptimisticLockException.class, maxAttempts = 3, backoff = @Backoff(delay = 100)) + public User updateUserWithVersion(Long userId, String newName) { + User user = userRepository.findById(userId) + .orElseThrow(() -> new RuntimeException("用户不存在")); + + int affected = userRepository.updateWithVersion( + userId, newName, user.getVersion()); + + if (affected == 0) { + throw new OptimisticLockException("版本冲突"); + } + + return userRepository.findById(userId).orElse(null); + } + + // 悲观锁实现 + @Transactional + public User updateUserWithPessimisticLock(Long userId, String newName) { + User user = userRepository.findByIdWithLock(userId) + .orElseThrow(() -> new RuntimeException("用户不存在")); + + user.setName(newName); + return userRepository.save(user); + } +} +``` + +## 5. 死锁的产生和解决 + +### 死锁的产生条件 + +**四个必要条件**: +1. **互斥条件**:资源不能共享 +2. **请求和保持**:进程保持资源的同时请求其他资源 +3. **不可剥夺**:资源不能被强制释放 +4. **循环等待**:形成等待环路 + +**死锁示例**: +```java +// 死锁示例 +public class DeadlockExample { + + @Autowired + private JdbcTemplate jdbcTemplate; + + public void createDeadlock() { + // 线程1:获取资源A,等待资源B + new Thread(() -> { + try (Connection conn1 = jdbcTemplate.getDataSource().getConnection()) { + conn1.setAutoCommit(false); + + // 获取用户表的锁 + conn1.createStatement().execute("SELECT * FROM user WHERE id = 1 FOR UPDATE"); + System.out.println("线程1获取了用户表锁"); + + // 等待订单表锁 + Thread.sleep(1000); + + // 尝试获取订单表锁 + conn1.createStatement().execute("SELECT * FROM order WHERE id = 1 FOR UPDATE"); + System.out.println("线程1获取了订单表锁"); + + conn1.commit(); + } catch (Exception e) { + System.out.println("线程1异常: " + e.getMessage()); + } + }).start(); + + // 线程2:获取资源B,等待资源A + new Thread(() -> { + try (Connection conn2 = jdbcTemplate.getDataSource().getConnection()) { + conn2.setAutoCommit(false); + + // 获取订单表的锁 + conn2.createStatement().execute("SELECT * FROM order WHERE id = 1 FOR UPDATE"); + System.out.println("线程2获取了订单表锁"); + + // 等待用户表锁 + Thread.sleep(1000); + + // 尝试获取用户表锁 + conn2.createStatement().execute("SELECT * FROM user WHERE id = 1 FOR UPDATE"); + System.out.println("线程2获取了用户表锁"); + + conn2.commit(); + } catch (Exception e) { + System.out.println("线程2异常: " + e.getMessage()); + } + }).start(); + } +} +``` + +### 死锁检测和监控 + +**1. MySQL 死锁检测**: +```sql +-- 查看死锁信息 +SHOW ENGINE INNODB STATUS; + +-- 查看锁等待 +SELECT * FROM information_schema.innodb_lock_waits; +``` + +**2. 死锁监控实现**: +```java +// 死锁监控 +@Component +public class DeadlockMonitor { + + @Autowired + private JdbcTemplate jdbcTemplate; + + @Scheduled(fixedRate = 60000) // 每分钟监控一次 + public void monitorDeadlocks() { + String sql = "SELECT * FROM information_schema.innodb_lock_waits"; + + try { + List> deadlocks = jdbcTemplate.queryForList(sql); + + if (!deadlocks.isEmpty()) { + // 发送告警 + notificationService.sendAlert("检测到死锁", deadlocks); + + // 记录日志 + log.error("检测到死锁: {}", deadlocks); + } + } catch (Exception e) { + log.error("死锁监控失败", e); + } + } + + // 死锁分析 + public void analyzeDeadlock(String deadlockReport) { + // 解析死锁报告 + DeadlockAnalysis analysis = parseDeadlockReport(deadlockReport); + + // 生成解决方案建议 + List suggestions = generateSuggestions(analysis); + + // 记录分析结果 + log.info("死锁分析结果: {}", suggestions); + } +} +``` + +### 死锁预防策略 + +**1. 锁排序**: +```java +// 锁排序预防死锁 +public class LockOrderService { + + private final Map lockMap = new ConcurrentHashMap<>(); + + // 使用固定的锁顺序 + @Transactional + public void transferMoney(String fromAccount, String toAccount, BigDecimal amount) { + // 1. 按账户ID排序,确保锁顺序一致 + String account1 = fromAccount.compareTo(toAccount) < 0 ? fromAccount : toAccount; + String account2 = fromAccount.compareTo(toAccount) < 0 ? toAccount : fromAccount; + + // 2. 按固定顺序加锁 + synchronized (getLock(account1)) { + synchronized (getLock(account2)) { + // 执行转账 + transfer(fromAccount, toAccount, amount); + } + } + } + + private Object getLock(String account) { + return lockMap.computeIfAbsent(account, k -> new Object()); + } +} +``` + +**2. 超时机制**: +```java +// 超时机制预防死锁 +public class TimeoutLockService { + + @Autowired + private DataSource dataSource; + + @Transactional(timeout = 3) // 事务超时3秒 + public void executeWithTimeout(Runnable operation) { + try { + operation.run(); + } catch (TransactionTimedOutException e) { + // 超时回滚,释放锁 + throw new RuntimeException("操作超时,已回滚", e); + } + } + + // 设置锁等待超时 + public void executeWithLockTimeout(Runnable operation, long timeout) { + try (Connection conn = dataSource.getConnection()) { + conn.setAutoCommit(false); + + // 设置锁等待超时 + conn.createStatement().execute( + "SET innodb_lock_wait_timeout = " + timeout); + + try { + operation.run(); + conn.commit(); + } catch (SQLException e) { + conn.rollback(); + throw new RuntimeException("锁等待超时", e); + } + } catch (SQLException e) { + throw new RuntimeException("数据库操作失败", e); + } + } +} +``` + +**3. 避免嵌套锁**: +```java +// 避免嵌套锁示例 +public class NestedLockService { + + // 不好的示例:嵌套锁 + @Transactional + public void badNestedLock(Long userId, Long orderId) { + // 锁1 + User user = userRepository.findByIdWithLock(userId); + + // 业务逻辑 + processUser(user); + + // 锁2(嵌套锁可能导致死锁) + Order order = orderRepository.findByIdWithLock(orderId); + + processOrder(order); + } + + // 好的示例:统一锁 + @Transactional + public void goodSingleLock(Long userId, Long orderId) { + // 统一获取需要的锁 + List users = userRepository.findUsersByIdsWithLock(Arrays.asList(userId)); + List orders = orderRepository.findOrdersByIdsWithLock(Arrays.asList(orderId)); + + // 处理业务逻辑 + processUsersAndOrders(users, orders); + } +} +``` + +### 死锁恢复策略 + +**1. 自动重试**: +```java +// 自动重试机制 +@Service +public class RetryService { + + @Retryable(value = DeadlockLoserDataAccessException.class, + maxAttempts = 3, + backoff = @Backoff(delay = 100)) + public T executeWithRetry(Callable operation) { + return operation.call(); + } + + // 使用示例 + @Transactional + public User updateUserWithRetry(Long userId, String newName) { + return executeWithRetry(() -> { + User user = userRepository.findByIdWithLock(userId); + user.setName(newName); + return userRepository.save(user); + }); + } +} +``` + +**2. 降级处理**: +```java +// 降级处理策略 +@Service +public class FallbackService { + + @Autowired + private CacheManager cacheManager; + + @Transactional + public User updateUserWithFallback(Long userId, String newName) { + try { + // 正常更新 + User user = userRepository.findByIdWithLock(userId); + user.setName(newName); + return userRepository.save(user); + } catch (Exception e) { + // 降级处理:先更新缓存 + updateCache(userId, newName); + + // 异步更新数据库 + asyncUpdateDatabase(userId, newName); + + // 返回缓存中的数据 + return getCachedUser(userId); + } + } + + private void updateCache(Long userId, String newName) { + Cache cache = cacheManager.getCache("userCache"); + User user = new User(); + user.setId(userId); + user.setName(newName); + cache.put(userId, user); + } + + private User getCachedUser(Long userId) { + Cache cache = cacheManager.getCache("userCache"); + return cache.get(userId, User.class); + } +} +``` + +## 6. 实际项目中的锁使用 + +### 电商系统锁策略 + +**1. 库存管理**: +```java +// 库存管理锁策略 +@Service +public class InventoryService { + + @Autowired + private InventoryRepository inventoryRepository; + + // 乐观锁实现库存扣减 + @Transactional + public boolean reduceStockWithOptimisticLock(Long productId, int quantity) { + Inventory inventory = inventoryRepository.findByProductId(productId) + .orElseThrow(() -> new RuntimeException("商品不存在")); + + // 检查库存 + if (inventory.getStock() < quantity) { + return false; + } + + // 乐观锁更新 + int affected = inventoryRepository.reduceStockWithVersion( + productId, quantity, inventory.getVersion()); + + if (affected == 0) { + // 版本冲突,重试或抛出异常 + throw new OptimisticLockException("库存已被其他订单占用"); + } + + return true; + } + + // 悲观锁实现库存扣减 + @Transactional + public boolean reduceStockWithPessimisticLock(Long productId, int quantity) { + // 悲观锁防止并发扣减 + Inventory inventory = inventoryRepository.findByProductIdWithLock(productId) + .orElseThrow(() -> new RuntimeException("商品不存在")); + + if (inventory.getStock() < quantity) { + return false; + } + + inventory.setStock(inventory.getStock() - quantity); + inventoryRepository.save(inventory); + + return true; + } + + // 批量库存扣减 + @Transactional + public boolean batchReduceStock(List items) { + // 使用事务隔离级别避免死锁 + try { + for (OrderItem item : items) { + boolean success = reduceStockWithOptimisticLock( + item.getProductId(), item.getQuantity()); + if (!success) { + // 回滚整个事务 + throw new RuntimeException("库存不足"); + } + } + return true; + } catch (Exception e) { + throw new RuntimeException("批量扣减库存失败", e); + } + } +} +``` + +**2. 订单处理**: +```java +// 订单处理锁策略 +@Service +public class OrderService { + + @Autowired + private OrderRepository orderRepository; + + @Autowired + private PaymentService paymentService; + + // 订单创建锁 + @Transactional(isolation = Isolation.REPEATABLE_READ) + public Order createOrder(OrderDTO orderDTO) { + // 1. 检查用户和商品状态 + User user = userRepository.findById(orderDTO.getUserId()) + .orElseThrow(() -> new RuntimeException("用户不存在")); + + List products = productRepository.findByIds( + orderDTO.getItems().stream() + .map(OrderItemDTO::getProductId) + .collect(Collectors.toList())); + + // 2. 创建订单 + Order order = new Order(); + BeanUtils.copyProperties(orderDTO, order); + order.setStatus("PENDING"); + order.setCreateTime(new Date()); + + order = orderRepository.save(order); + + // 3. 扣减库存(悲观锁) + for (OrderItem item : order.getItems()) { + boolean success = inventoryService.reduceStockWithPessimisticLock( + item.getProductId(), item.getQuantity()); + if (!success) { + throw new RuntimeException("库存不足"); + } + } + + // 4. 预扣金额(乐观锁) + try { + paymentService.reserveAmount(order.getUserId(), order.getTotalAmount()); + } catch (Exception e) { + // 回滚库存 + rollbackInventory(order.getItems()); + throw e; + } + + return order; + } + + // 订单取消锁 + @Transactional + public void cancelOrder(Long orderId) { + Order order = orderRepository.findByIdWithLock(orderId) + .orElseThrow(() -> new RuntimeException("订单不存在")); + + // 检查订单状态 + if (!"PENDING".equals(order.getStatus())) { + throw new RuntimeException("订单状态不允许取消"); + } + + // 更新订单状态 + order.setStatus("CANCELLED"); + order.setUpdateTime(new Date()); + orderRepository.save(order); + + // 释放库存 + for (OrderItem item : order.getItems()) { + inventoryService.releaseStock(item.getProductId(), item.getQuantity()); + } + + // 释放金额 + paymentService.releaseAmount(order.getUserId(), order.getTotalAmount()); + } +} +``` + +### 社交系统锁策略 + +**1. 点赞功能**: +```java +// 点赞功能锁策略 +@Service +public class LikeService { + + @Autowired + private LikeRepository likeRepository; + + @Autowired + private PostRepository postRepository; + + // 乐观锁实现点赞 + @Transactional + public LikeDTO addLike(Long postId, Long userId) { + // 1. 检查是否已经点赞 + boolean alreadyLiked = likeRepository.existsByPostIdAndUserId(postId, userId); + if (alreadyLiked) { + throw new RuntimeException("已经点赞"); + } + + // 2. 创建点赞记录(乐观锁) + Like like = new Like(); + like.setPostId(postId); + like.setUserId(userId); + like.setCreateTime(new Date()); + + like = likeRepository.save(like); + + // 3. 更新点赞数(乐观锁) + updateLikeCount(postId, 1); + + return convertToDTO(like); + } + + // 批量点赞 + @Transactional + public void batchAddLikes(List postIds, Long userId) { + for (Long postId : postIds) { + addLike(postId, userId); + } + } + + // 分布式锁实现点赞 + @Transactional + public LikeDTO addLikeWithDistributedLock(Long postId, Long userId) { + String lockKey = "like_" + postId + "_" + userId; + + // 获取分布式锁 + try { + boolean locked = redisLock.tryLock(lockKey, 5, TimeUnit.SECONDS); + if (!locked) { + throw new RuntimeException("系统繁忙,请稍后重试"); + } + + // 检查是否已经点赞 + boolean alreadyLiked = likeRepository.existsByPostIdAndUserId(postId, userId); + if (alreadyLiked) { + throw new RuntimeException("已经点赞"); + } + + // 创建点赞记录 + Like like = new Like(); + like.setPostId(postId); + like.setUserId(userId); + like.setCreateTime(new Date()); + + like = likeRepository.save(like); + + // 更新点赞数 + updateLikeCount(postId, 1); + + return convertToDTO(like); + + } finally { + // 释放锁 + redisLock.unlock(lockKey); + } + } +} +``` + +**2. 评论系统**: +```java +// 评论系统锁策略 +@Service +public class CommentService { + + @Autowired + private CommentRepository commentRepository; + + // 间隙锁防止评论重复 + @Transactional(isolation = Isolation.REPEATABLE_READ) + public CommentDTO addComment(CommentDTO commentDTO) { + // 1. 获取帖子间隙锁 + Post post = postRepository.findByIdWithGapLock(commentDTO.getPostId()) + .orElseThrow(() -> new RuntimeException("帖子不存在")); + + // 2. 创建评论 + Comment comment = new Comment(); + BeanUtils.copyProperties(commentDTO, comment); + comment.setCreateTime(new Date()); + + comment = commentRepository.save(comment); + + // 3. 更新帖子统计 + post.setCommentCount(post.getCommentCount() + 1); + postRepository.save(post); + + return convertToDTO(comment); + } + + // 评论回复锁 + @Transactional + public CommentDTO replyComment(CommentDTO replyDTO) { + // 1. 获取父评论锁 + Comment parentComment = commentRepository.findByIdWithLock(replyDTO.getParentId()) + .orElseThrow(() -> new RuntimeException("父评论不存在")); + + // 2. 创建回复 + replyDTO.setParentId(parentComment.getId()); + replyDTO.setPostId(parentComment.getPostId()); + + Comment reply = new Comment(); + BeanUtils.copyProperties(replyDTO, reply); + reply.setCreateTime(new Date()); + + reply = commentRepository.save(reply); + + // 3. 更新统计 + parentComment.setReplyCount(parentComment.getReplyCount() + 1); + commentRepository.save(parentComment); + + return convertToDTO(reply); + } +} +``` + +### 金融系统锁策略 + +**1. 转账服务**: +```java +// 转账服务锁策略 +@Service +public class TransferService { + + @Autowired + private AccountRepository accountRepository; + + // 分布式锁实现转账 + @Transactional(isolation = Isolation.SERIALIZABLE) + public void transferWithDistributedLock(Long fromAccountId, Long toAccountId, BigDecimal amount) { + String lockKey = "transfer_" + Math.min(fromAccountId, toAccountId) + "_" + + Math.max(fromAccountId, toAccountId); + + try { + // 获取分布式锁 + boolean locked = redisLock.tryLock(lockKey, 10, TimeUnit.SECONDS); + if (!locked) { + throw new RuntimeException("转账繁忙,请稍后重试"); + } + + transfer(fromAccountId, toAccountId, amount); + + } finally { + // 释放锁 + redisLock.unlock(lockKey); + } + } + + // 乐观锁实现转账 + @Transactional + public void transferWithOptimisticLock(Long fromAccountId, Long toAccountId, BigDecimal amount) { + // 1. 获取账户信息(带乐观锁) + Account fromAccount = accountRepository.findByIdWithVersion(fromAccountId) + .orElseThrow(() -> new RuntimeException("转出账户不存在")); + + Account toAccount = accountRepository.findByIdWithVersion(toAccountId) + .orElseThrow(() -> new RuntimeException("转入账户不存在")); + + // 2. 检查余额 + if (fromAccount.getBalance().compareTo(amount) < 0) { + throw new RuntimeException("余额不足"); + } + + // 3. 执行转账(乐观锁) + boolean success = transferWithVersion(fromAccount, toAccount, amount); + if (!success) { + throw new RuntimeException("转账失败,可能余额已变动"); + } + } + + private boolean transferWithVersion(Account fromAccount, Account toAccount, BigDecimal amount) { + try { + // 扣减转出账户 + fromAccount.setBalance(fromAccount.getBalance().subtract(amount)); + + // 增加转入账户 + toAccount.setBalance(toAccount.getBalance().add(amount)); + + // 批量更新(乐观锁) + accountRepository.saveAll(Arrays.asList(fromAccount, toAccount)); + + return true; + } catch (OptimisticLockException e) { + // 版本冲突,重试 + return false; + } + } +} +``` + +**2. 余额查询**: +```java +// 余额查询锁策略 +@Service +public class BalanceQueryService { + + @Autowired + private AccountRepository accountRepository; + + // 乐观锁实现余额查询 + @Transactional(readOnly = true) + public BigDecimal getBalance(Long accountId) { + Account account = accountRepository.findById(accountId) + .orElseThrow(() -> new RuntimeException("账户不存在")); + + // 使用缓存优化 + BigDecimal balance = balanceCache.get(accountId); + if (balance == null) { + balance = account.getBalance(); + balanceCache.put(accountId, balance); + } + + return balance; + } + + // 悲观锁实现余额查询 + @Transactional(readOnly = true) + public BigDecimal getBalanceWithLock(Long accountId) { + Account account = accountRepository.findByIdWithLock(accountId) + .orElseThrow(() -> new RuntimeException("账户不存在")); + + return account.getBalance(); + } +} +``` + +## 7. 阿里 P7 加分项 + +### 分布式锁实现 + +**1. Redis 分布式锁**: +```java +// Redis 分布式锁实现 +@Component +public class RedisLock { + + @Autowired + private RedisTemplate redisTemplate; + + private static final String LOCK_PREFIX = "lock:"; + private static final long DEFAULT_EXPIRE_TIME = 30; // 秒 + + /** + * 获取分布式锁 + */ + public boolean tryLock(String lockKey, long waitTime, TimeUnit timeUnit) { + String key = LOCK_PREFIX + lockKey; + long expireTime = DEFAULT_EXPIRE_TIME; + + try { + long startTime = System.currentTimeMillis(); + long waitMillis = timeUnit.toMillis(waitTime); + + while (true) { + // 尝试获取锁 + Boolean success = redisTemplate.opsForValue().setIfAbsent( + key, + Thread.currentThread().getName(), + expireTime, + TimeUnit.SECONDS); + + if (Boolean.TRUE.equals(success)) { + return true; + } + + // 检查是否超时 + if (System.currentTimeMillis() - startTime > waitMillis) { + return false; + } + + // 避免CPU空转 + Thread.sleep(100); + } + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + return false; + } + } + + /** + * 释放分布式锁 + */ + public void unlock(String lockKey) { + String key = LOCK_PREFIX + lockKey; + redisTemplate.delete(key); + } + + /** + * 可重入分布式锁 + */ + private final ConcurrentHashMap lockCountMap = new ConcurrentHashMap<>(); + + public boolean tryReentrantLock(String lockKey, long waitTime, TimeUnit timeUnit) { + Thread currentThread = Thread.currentThread(); + String key = LOCK_PREFIX + lockKey; + + // 检查是否是当前线程持有锁 + Integer count = lockCountMap.get(key); + if (count != null && count > 0) { + lockCountMap.put(key, count + 1); + return true; + } + + // 获取新锁 + if (tryLock(lockKey, waitTime, timeUnit)) { + lockCountMap.put(key, 1); + return true; + } + + return false; + } + + public void releaseReentrantLock(String lockKey) { + String key = LOCK_PREFIX + lockKey; + Integer count = lockCountMap.get(key); + + if (count == null) { + return; + } + + if (count > 1) { + lockCountMap.put(key, count - 1); + } else { + lockCountMap.remove(key); + unlock(lockKey); + } + } +} + +// Redis 分布式锁使用示例 +@Service +public class RedisLockService { + + @Autowired + private RedisLock redisLock; + + @Autowired + private InventoryService inventoryService; + + public boolean reduceStockWithRedisLock(Long productId, int quantity) { + String lockKey = "inventory_" + productId; + + try { + // 获取分布式锁 + boolean locked = redisLock.tryLock(lockKey, 5, TimeUnit.SECONDS); + if (!locked) { + return false; + } + + // 扣减库存 + return inventoryService.reduceStock(productId, quantity); + + } finally { + // 释放锁 + redisLock.unlock(lockKey); + } + } +} +``` + +**2. Zookeeper 分布式锁**: +```java +// Zookeeper 分布式锁实现 +@Component +public class ZookeeperLock { + + @Autowired + private CuratorFramework curatorFramework; + + /** + * 创建分布式锁 + */ + public InterProcessMutex createDistributedLock(String lockPath) { + return new InterProcessMutex(curatorFramework, lockPath); + } + + /** + * 获取锁 + */ + public boolean tryLock(String lockPath, long timeout, TimeUnit unit) { + InterProcessMutex lock = createDistributedLock(lockPath); + + try { + return lock.acquire(timeout, unit); + } catch (Exception e) { + throw new RuntimeException("获取分布式锁失败", e); + } + } + + /** + * 释放锁 + */ + public void releaseLock(String lockPath) { + InterProcessMutex lock = createDistributedLock(lockPath); + + try { + lock.release(); + } catch (Exception e) { + throw new RuntimeException("释放分布式锁失败", e); + } + } +} + +// Zookeeper 分布式锁使用示例 +@Service +public class ZookeeperLockService { + + @Autowired + private ZookeeperLock zookeeperLock; + + @Autowired + private PaymentService paymentService; + + public boolean processPaymentWithZkLock(Long orderId, BigDecimal amount) { + String lockPath = "/payment/lock/" + orderId; + + try { + // 获取分布式锁 + boolean locked = zookeeperLock.tryLock(lockPath, 10, TimeUnit.SECONDS); + if (!locked) { + return false; + } + + // 处理支付 + return paymentService.processPayment(orderId, amount); + + } finally { + // 释放锁 + zookeeperLock.releaseLock(lockPath); + } + } +} +``` + +### 高级锁优化策略 + +**1. 自适应锁策略**: +```java +// 自适应锁策略 +@Service +public class AdaptiveLockService { + + @Autowired + private JdbcTemplate jdbcTemplate; + + @Autowired + private RedisLock redisLock; + + // 根据业务复杂度选择锁策略 + public T executeWithAdaptiveLock(Callable operation, String lockKey, BusinessType type) { + switch (type) { + case SIMPLE: + return executeWithoutLock(operation); + case NORMAL: + return executeWithOptimisticLock(operation); + case COMPLEX: + return executeWithPessimisticLock(operation, lockKey); + case CRITICAL: + return executeWithDistributedLock(operation, lockKey); + default: + return executeWithOptimisticLock(operation); + } + } + + private T executeWithoutLock(Callable operation) { + try { + return operation.call(); + } catch (Exception e) { + throw new RuntimeException("操作失败", e); + } + } + + private T executeWithOptimisticLock(Callable operation) { + int retryCount = 0; + final int maxRetries = 3; + + while (retryCount < maxRetries) { + try { + return operation.call(); + } catch (OptimisticLockException e) { + retryCount++; + if (retryCount >= maxRetries) { + throw new RuntimeException("乐观锁重试次数已用尽", e); + } + // 指数退避 + try { + Thread.sleep(100 * (1 << retryCount)); + } catch (InterruptedException ie) { + Thread.currentThread().interrupt(); + throw new RuntimeException("线程被中断", ie); + } + } + } + throw new RuntimeException("操作失败"); + } + + private T executeWithPessimisticLock(Callable operation, String lockKey) { + try (Connection conn = jdbcTemplate.getDataSource().getConnection()) { + conn.setAutoCommit(false); + + // 获取悲观锁 + conn.createStatement().execute( + "SELECT * FROM business_data WHERE key = '" + lockKey + "' FOR UPDATE"); + + try { + T result = operation.call(); + conn.commit(); + return result; + } catch (Exception e) { + conn.rollback(); + throw e; + } + } catch (SQLException e) { + throw new RuntimeException("悲观锁操作失败", e); + } + } + + private T executeWithDistributedLock(Callable operation, String lockKey) { + try { + boolean locked = redisLock.tryLock(lockKey, 5, TimeUnit.SECONDS); + if (!locked) { + throw new RuntimeException("获取分布式锁失败"); + } + + try { + return operation.call(); + } finally { + redisLock.unlock(lockKey); + } + } catch (Exception e) { + throw new RuntimeException("分布式锁操作失败", e); + } + } +} + +// 业务类型枚举 +public enum BusinessType { + SIMPLE, // 简单操作,不需要锁 + NORMAL, // 普通操作,使用乐观锁 + COMPLEX, // 复杂操作,使用悲观锁 + CRITICAL // 关键操作,使用分布式锁 +} +``` + +**2. 智能锁监控**: +```java +// 智能锁监控 +@Component +public class SmartLockMonitor { + + @Autowired + private MeterRegistry meterRegistry; + + @Autowired + private JdbcTemplate jdbcTemplate; + + // 监控锁使用情况 + @Scheduled(fixedRate = 60000) + public void monitorLockUsage() { + // 1. 监控数据库锁等待 + monitorDatabaseLocks(); + + // 2. 监控分布式锁等待 + monitorDistributedLocks(); + + // 3. 监控锁超时 + monitorLockTimeout(); + } + + private void monitorDatabaseLocks() { + String sql = "SELECT COUNT(*) FROM information_schema.innodb_lock_waits"; + + Integer waitCount = jdbcTemplate.queryForObject(sql, Integer.class); + + if (waitCount != null && waitCount > 0) { + meterRegistry.gauge("database.lock.waits", waitCount); + + // 发送告警 + if (waitCount > 10) { + notificationService.sendAlert("数据库锁等待过多", waitCount); + } + } + } + + private void monitorDistributedLocks() { + // 监控 Redis 锁等待 + Set keys = redisTemplate.keys("lock:*"); + if (keys != null) { + meterRegistry.gauge("distributed.lock.count", keys.size()); + + // 监控锁等待时间 + for (String key : keys) { + String value = redisTemplate.opsForValue().get(key); + if (value != null) { + long ttl = redisTemplate.getExpire(key); + if (ttl > 0) { + meterRegistry.gauge("distributed.lock.ttl", ttl, k -> ttl); + } + } + } + } + } + + private void monitorLockTimeout() { + // 监控锁超时 + String sql = "SELECT COUNT(*) FROM transaction WHERE status = 'timeout'"; + + Integer timeoutCount = jdbcTemplate.queryForObject(sql, Integer.class); + + if (timeoutCount != null && timeoutCount > 0) { + meterRegistry.gauge("transaction.lock.timeout", timeoutCount); + } + } + + // 智能锁推荐 + public LockRecommendation recommendLockStrategy(String tableName, String operation) { + // 1. 分析表特征 + TableFeature feature = analyzeTableFeature(tableName); + + // 2. 分析操作频率 + OperationFrequency frequency = analyzeOperationFrequency(tableName, operation); + + // 3. 基于规则推荐锁策略 + if (feature.isHighWrite() && frequency.isHighFrequency()) { + return new LockRecommendation( + LockType.OPTIMISTIC, + "乐观锁适合高写入频率场景", + 3 // 重试次数 + ); + } else if (feature.isCritical() && frequency.isLowFrequency()) { + return new LockRecommendation( + LockType.PESSIMISTIC, + "悲观锁适合关键数据", + 1 + ); + } else { + return new LockRecommendation( + LockType.ADAPTIVE, + "自适应锁策略", + 3 + ); + } + } +} +``` + +### 总结 + +数据库锁机制是并发控制的核心,需要根据业务场景选择合适的锁策略: + +1. **掌握各种锁类型**:行锁、表锁、页锁、共享锁、排他锁、意向锁、间隙锁、临键锁 +2. **理解锁的兼容性**:锁与锁之间的兼容关系 +3. **掌握乐观锁和悲观锁**:两种锁策略的优缺点和适用场景 +4. **避免死锁**:通过锁排序、超时、避免嵌套锁等方式预防死锁 +5. **实际项目应用**:电商、社交、金融等不同场景的锁策略 +6. **高级特性**:分布式锁、智能锁监控、自适应锁策略 + +在实际项目中,需要平衡性能和一致性,选择合适的锁策略,并持续优化锁的使用。 \ No newline at end of file diff --git a/questions/microservices-gateway.md b/questions/microservices-gateway.md new file mode 100644 index 0000000..f23c4ca --- /dev/null +++ b/questions/microservices-gateway.md @@ -0,0 +1,171 @@ +# API 网关核心原理 + +## 问题 + +1. 什么是 API 网关?为什么需要 API 网关? +2. API 网关的核心功能有哪些? +3. Spring Cloud Gateway 和 Zuul 的区别? +4. 网关的限流、熔断、降级如何实现? +5. 网关的路由和负载均衡策略? + +--- + +## 标准答案 + +### 1. API 网关的作用 + +**核心功能**: +1. **路由转发**:将请求转发到后端服务 +2. **统一鉴权**:集中的认证和授权 +3. **限流熔断**:保护后端服务 +4. **日志监控**:统一的日志和监控 +5. **协议转换**:HTTP → WebSocket、gRPC 等 +6. **灰度发布**:按规则路由流量 + +--- + +### 2. Spring Cloud Gateway 核心概念 + +**三大组件**: +``` +Route(路由) + ↓ +Predicate(断言) + ↓ +Filter(过滤器) +``` + +**示例**: +```yaml +spring: + cloud: + gateway: + routes: + - id: user-service + uri: lb://user-service + predicates: + - Path=/api/users/** + - Header=Authorization, .* + filters: + - StripPrefix=1 + - RequestRateLimiter=10 # 限流:10 QPS +``` + +--- + +### 3. Gateway vs Zuul + +| 特性 | Zuul 1.x | Zuul 2.x | Spring Cloud Gateway | +|------|----------|----------|---------------------| +| **模型** | Servlet 阻塞 | Netty 非阻塞 | Netty 非阻塞 | +| **性能** | 低 | 中 | 高 | +| **Spring** | 集成好 | 集成一般 | 原生支持 | +| **动态路由** | 不支持 | 支持 | 支持 | +| **限流** | 需自研 | 需自研 | 内置(Redis) | + +--- + +### 4. 核心功能实现 + +#### **限流** + +```yaml +spring: + cloud: + gateway: + routes: + - id: rate-limiter + uri: lb://user-service + predicates: + - Path=/api/users/** + filters: + - name: RequestRateLimiter + args: + redis-rate-limiter.replenishRate: 10 # 每秒填充 10 个令牌 + redis-rate-limiter.burstCapacity: 20 # 桶容量 20 +``` + +--- + +#### **熔断(Circuit Breaker)** + +```yaml +spring: + cloud: + gateway: + routes: + - id: circuit-breaker + uri: lb://user-service + predicates: + - Path=/api/users/** + filters: + - name: CircuitBreaker + args: + fallbackUri: forward:/fallback +``` + +--- + +#### **统一鉴权** + +```java +@Component +public class AuthFilter implements GlobalFilter { + @Override + public Mono filter(ServerWebExchange exchange, GatewayFilterChain chain) { + String token = exchange.getRequest().getHeaders().getFirst("Authorization"); + + if (token == null || !validateToken(token)) { + exchange.getResponse().setStatusCode(HttpStatus.UNAUTHORIZED); + return exchange.getResponse().setComplete(); + } + + return chain.filter(exchange); + } +} +``` + +--- + +### 5. 网关高可用 + +**部署架构**: +``` + ┌─────────────┐ + │ 负载均衡 │ + │ (Nginx) │ + └──────┬──────┘ + │ + ┌───────────────┼───────────────┐ + │ │ │ + ┌────▼────┐ ┌────▼────┐ ┌────▼────┐ + │Gateway 1│ │Gateway 2│ │Gateway 3│ + └────┬────┘ └────┬────┘ └────┬────┘ + │ │ │ + └──────────────┼──────────────┘ + │ + ┌──────────────┼──────────────┐ + │ │ │ + ┌────▼────┐ ┌────▼────┐ ┌────▼────┐ + │Service A│ │Service B│ │Service C│ + └─────────┘ └─────────┘ └─────────┘ +``` + +--- + +### 6. 阿里 P7 加分项 + +**深度理解**: +- 理解 WebFlux 的响应式编程模型 +- 理解 Netty 的事件循环 +- 理解限流算法(令牌桶、漏桶) + +**实战经验**: +- 有网关性能调优的经验 +- 有网关灰度发布的经验 +- 有网关监控和故障排查的经验 + +**架构能力**: +- 能设计高可用网关架构 +- 能设计网关的监控体系 +- 能设计网关的动态路由方案 diff --git a/questions/mybatis.md b/questions/mybatis.md new file mode 100644 index 0000000..9ff760b --- /dev/null +++ b/questions/mybatis.md @@ -0,0 +1,177 @@ +# MyBatis 核心原理 + +## 问题 + +1. MyBatis 的核心组件有哪些? +2. MyBatis 的缓存机制(一级缓存、二级缓存)? +3. MyBatis 的插件原理是什么? +4. #{} 和 ${} 的区别是什么? +5. MyBatis 如何处理批量操作? +6. MyBatis 的动态 SQL 是如何实现的? + +--- + +## 标准答案 + +### 1. MyBatis 核心组件 + +``` +Configuration(全局配置) + ↓ +SqlSession(会话) + ↓ +Executor(执行器) + ↓ +StatementHandler(语句处理器) + ↓ +ParameterHandler(参数处理器) + ↓ +ResultSetHandler(结果集处理器) +``` + +--- + +### 2. 缓存机制 + +**一级缓存(SqlSession 级别)**: +```java +SqlSession session = sqlSessionFactory.openSession(); +User user1 = session.selectOne("selectUser", 1); +User user2 = session.selectOne("selectUser", 1); // 从缓存获取 +``` + +**二级缓存(Mapper 级别)**: +```xml + + +``` + +```java +User user1 = session1.selectOne("selectUser", 1); +session1.close(); // 一级缓存清空,写入二级缓存 + +User user2 = session2.selectOne("selectUser", 1); // 从二级缓存获取 +``` + +--- + +### 3. 插件原理 + +**拦截的四大组件**: +- Executor +- StatementHandler +- ParameterHandler +- ResultSetHandler + +**示例**: +```java +@Intercepts({ + @Signature(type = Executor.class, method = "update", args = {MappedStatement.class, Object.class}) +}) +public class SqlInterceptor implements Interceptor { + @Override + public Object intercept(Invocation invocation) throws Throwable { + // 前置逻辑 + Object result = invocation.proceed(); + // 后置逻辑 + return result; + } +} +``` + +--- + +### 4. #{} vs ${} + +| 特性 | #{} | ${} | +|------|-----|-----| +| **预处理** | 是(PreparedStatement) | 否(Statement) | +| **SQL 注入** | 安全 | 不安全 | +| **类型转换** | 自动 | 需手动 | +| **动态表名/列名** | 不支持 | 支持 | + +**示例**: +```xml + +SELECT * FROM users WHERE id = #{id} + + +SELECT * FROM users WHERE id = ${id} + + +SELECT * FROM ${tableName} WHERE id = #{id} +``` + +--- + +### 5. 批量操作 + +**方式 1:SqlSession 批量**: +```java +SqlSession session = sqlSessionFactory.openSession(ExecutorType.BATCH); +try { + UserMapper mapper = session.getMapper(UserMapper.class); + for (User user : users) { + mapper.insert(user); + } + session.commit(); +} finally { + session.close(); +} +``` + +**方式 2:Batch Insert**: +```xml + + INSERT INTO users (name, email) VALUES + + (#{user.name}, #{user.email}) + + +``` + +--- + +### 6. 动态 SQL + +**常用标签**: +```xml + + + +``` + +--- + +### 7. 阿里 P7 加分项 + +**深度理解**: +- 理解 MyBatis 的代理机制(MapperProxy) +- 理解二级缓存的并发问题 +- 理解插件的责任链模式 + +**实战经验**: +- 有处理 MyBatis 性能问题的经验 +- 有自定义插件的经验 +- 有 MyBatis 与 Spring 集成的经验 + +**架构能力**: +- 能设计多数据源方案 +- 能设计分库分表的 MyBatis 方案 +- 能设计 MyBatis 监控体系 diff --git a/questions/nacos.md b/questions/nacos.md new file mode 100644 index 0000000..e69de29 diff --git a/questions/network-tcpip.md b/questions/network-tcpip.md new file mode 100644 index 0000000..d3e2f92 --- /dev/null +++ b/questions/network-tcpip.md @@ -0,0 +1,577 @@ +# TCP/IP 网络协议 + +## 问题 + +1. TCP 和 UDP 的区别是什么? +2. TCP 三次握手和四次挥手的流程是什么?为什么需要三次握手? +3. TCP 如何保证可靠传输? +4. 什么是 SYN 洪水攻击?如何防范? +5. HTTP 和 HTTPS 的区别是什么? +6. HTTP 1.1、2.0、3.0 的演进历程和主要特性 +7. 什么是粘包和拆包?如何解决? +8. 在实际项目中遇到过哪些网络问题? + +--- + +## 标准答案 + +### 1. TCP vs UDP + +#### **对比表** + +| 特性 | TCP | UDP | +|------|-----|-----| +| **连接性** | 面向连接 | 无连接 | +| **可靠性** | 可靠传输 | 不可靠 | +| **顺序** | 保证顺序 | 不保证顺序 | +| **速度** | 较慢 | 快 | +| **流量控制** | 有(滑动窗口) | 无 | +| **拥塞控制** | 有 | 无 | +| **首部开销** | 20-60 字节 | 8 字节 | +| **应用场景** | 文件传输、邮件、HTTP | 视频流、直播、DNS | + +--- + +#### **TCP 特性** + +**面向连接**: +``` +客户端 服务器 + │ │ + │─────── SYN ───────────────→│ 建立连接 + │←────── SYN+ACK ────────────│ + │─────── ACK ───────────────→│ + │ │ + │─────── 数据 ───────────────→│ + │←─────── ACK ───────────────│ + │ │ + │─────── FIN ───────────────→│ 断开连接 + │←─────── ACK ───────────────│ + │←────── FIN ────────────────│ + │─────── ACK ───────────────→│ +``` + +**可靠传输**: +- 确认应答(ACK) +- 超时重传 +- 校验和 +- 序列号 + +**流量控制**: +- 滑动窗口协议 +- 动态调整窗口大小 + +--- + +#### **UDP 特性** + +**无连接、不可靠**: +``` +客户端 服务器 + │ │ + │─────── 数据报 ─────────────→│ (可能丢失) + │─────── 数据报 ─────────────→│ (可能乱序) + │ │ + │ │ +``` + +**优点**: +- 速度快(无连接、无确认) +- 开销小(首部 8 字节) +- 支持一对一、一对多、多对多 + +**应用场景**: +- 视频直播(可容忍丢帧) +- 在线游戏(实时性要求高) +- DNS 查询(请求响应快) +- VoIP(语音通话) + +--- + +### 2. TCP 三次握手和四次挥手 + +#### **三次握手** + +**流程**: +``` +客户端 服务器 + │ │ + │─────── SYN=1, seq=x ──────→│ SYN_SENT + │ │ + │←───── SYN=1, ACK=1, seq=y, ack=x+1 ───│ + │ SYN_RCVD │ + │ │ + │─────── ACK=1, seq=x+1, ack=y+1 ────→│ ESTABLISHED + │ ESTABLISHED │ +``` + +**状态变化**: +``` +客户端:CLOSED → SYN_SENT → ESTABLISHED +服务器:CLOSED → LISTEN → SYN_RCVD → ESTABLISHED +``` + +--- + +#### **为什么需要三次握手?** + +**目的**: +1. **确认双方收发能力**: + - 第一次握手:服务端确认客户端能发 + - 第二次握手:客户端确认服务端能收能发 + - 第三次握手:服务端确认客户端能收 + +2. **防止已失效的连接请求突然又传送到服务端**: + ``` + 场景:客户端发送的第一个连接请求在网络中滞留 + 结果:客户端超时重发,建立连接后,滞留的请求到达 + + 如果只有两次握手: + ─ 客户端发送 SYN + ─ 服务器收到,建立连接,等待数据 + ─ 滞留的 SYN 到达,服务器又建立连接(错误!) + + 三次握手避免: + ─ 服务器收到滞留的 SYN,回复 SYN+ACK + ─ 客户端发现 ack 不对,丢弃(不建立连接) + ``` + +3. **同步初始序列号(ISN)**: + - 双方协商好初始序列号 + - 保证数据的顺序和去重 + +--- + +#### **四次挥手** + +**流程**: +``` +客户端 服务器 + │ │ + │─────── FIN=1, seq=u ──────→│ FIN_WAIT1 + │ │ CLOSE_WAIT + │←─────── ACK=1, seq=v, ack=u+1 ───│ + │ FIN_WAIT2 │ + │ │ + │←─────── FIN=1, ACK=1, seq=w, ack=u+1 ───│ + │ TIME_WAIT │ LAST_ACK + │ │ + │─────── ACK=1, seq=u+1, ack=w+1 ────→│ CLOSED + │ (等待 2MSL) │ +``` + +**状态变化**: +``` +客户端:ESTABLISHED → FIN_WAIT1 → FIN_WAIT2 → TIME_WAIT → CLOSED +服务器:ESTABLISHED → CLOSE_WAIT → LAST_ACK → CLOSED +``` + +--- + +#### **为什么需要四次挥手?** + +**原因**: +- TCP 是**全双工**协议(双向传输) +- 双方都需要关闭发送方向 + +**流程**: +1. 客户端发送 FIN(关闭客户端→服务器方向) +2. 服务器确认 ACK(但服务器可能还有数据要发送) +3. 服务器发送 FIN(关闭服务器→客户端方向) +4. 客户端确认 ACK + +--- + +#### **为什么 TIME_WAIT 状态需要等待 2MSL?** + +**MSL(Maximum Segment Lifetime)**: +- 报文最大生存时间(通常 30 秒 - 2 分钟) +- 2MSL = 1 分钟 - 4 分钟 + +**目的**: +1. **确保最后一个 ACK 能到达**: + - 如果 ACK 丢失,服务器会重传 FIN + - 客户端等待 2MSL,可以重传 ACK + +2. **让旧连接的报文自然消失**: + - 防止旧连接的报文干扰新连接 + - 确保网络中所有旧报文都已消失 + +**问题**: +- 大量 TIME_WAIT 会导致端口资源耗尽 +- 解决:调低 `TIME_WAIT` 时间或端口复用 + +```bash +# Linux 调整 +net.ipv4.tcp_tw_reuse = 1 # 端口复用 +net.ipv4.tcp_tw_recycle = 0 # 关闭快速回收(有坑) +``` + +--- + +### 3. TCP 可靠传输机制 + +#### **核心机制** + +1. **序列号(Sequence Number)**: + - 每个字节都有编号 + - 用于排序和去重 + +2. **确认应答(ACK)**: + - 接收方确认收到的数据 + - ACK = 下一个期望接收的字节序号 + +``` +发送方:seq=1000, len=100 +接收方:ack=1100(期待下一个字节) +``` + +3. **超时重传(RTO)**: + - 发送方启动定时器 + - 超时未收到 ACK,重传数据 + +4. **快速重传**: + - 收到 3 个重复 ACK,立即重传 + - 无需等待超时 + +``` +发送方:seq=1000, len=100 +接收方:期望 1100,但收到 1200(乱序) +接收方:连续发送 ack=1100(3 次) +发送方:收到 3 个重复 ack,快速重传 +``` + +5. **滑动窗口**: + - 流量控制 + - 动态调整发送速率 + +6. **拥塞控制**: + - 慢启动、拥塞避免、快重传、快恢复 + +--- + +### 4. SYN 洪水攻击 + +#### **攻击原理** + +**场景**:攻击者发送大量 SYN 包,但不完成三次握手。 + +``` +攻击者 服务器 + │ │ + ├─── SYN ─────────────────────→│ 分配资源 + ├─── SYN ─────────────────────→│ 分配资源 + ├─── SYN ─────────────────────→│ 分配资源 + ├─── SYN ─────────────────────→│ 分配资源 + │ (不回复 ACK) │ 资源耗尽 + │ │ 无法服务正常用户 +``` + +**后果**: +- 服务器维护大量 `SYN_RCVD` 连接 +- 内存资源耗尽 +- 无法响应正常用户的连接请求 + +--- + +#### **防范措施** + +**1. SYN Cookies**: +```bash +# 开启 SYN Cookies +net.ipv4.tcp_syncookies = 1 +``` + +**原理**: +- 不分配资源 +- 计算 Cookie 并编码在 SYN+ACK 的初始序列号中 +- 客户端回复 ACK 时验证 Cookie + +**2. 增加 SYN 队列**: +```bash +net.ipv4.tcp_max_syn_backlog = 8192 +``` + +**3. 缩短超时时间**: +```bash +net.ipv4.tcp_synack_retries = 2 +``` + +**4. 防火墙**: +- 限制单个 IP 的连接数 +- 检测异常流量并拦截 + +--- + +### 5. HTTP vs HTTPS + +#### **对比** + +| 特性 | HTTP | HTTPS | +|------|------|-------| +| **协议** | 应用层 | 应用层 + 安全层(SSL/TLS) | +| **端口** | 80 | 443 | +| **加密** | 明文传输 | 加密传输 | +| **证书** | 不需要 | 需要 CA 证书 | +| **性能** | 快 | 较慢(TLS 握手) | +| **SEO** | 无优惠 | 搜索引擎优先排名 | + +--- + +#### **HTTPS 工作流程** + +``` +客户端 服务器 + │ │ + │─────── ClientHello ────────────→│ (支持的加密套件) + │ │ + │←────── ServerHello ─────────────│ (选择的加密套件 + 证书) + │←────── Certificate ─────────────│ + │ │ + │─────── ClientKeyExchange ─────→│ (生成随机数) + │─────── ChangeCipherSpec ───────→│ (之后消息加密) + │─────── Finished ───────────────→│ + │ │ + │←────── ChangeCipherSpec ────────│ + │←────── Finished ────────────────│ + │ │ + │══════ 加密通信 ══════════════════│ +``` + +--- + +#### **HTTPS 的安全性** + +1. **数据加密**:防止中间人窃听 +2. **数据完整性**:防止数据被篡改 +3. **身份认证**:防止钓鱼网站 + +--- + +### 6. HTTP 版本演进 + +#### **HTTP/1.0** + +**特点**: +- 每个请求都需要新的 TCP 连接 +- 无状态、无连接 + +**问题**: +- 性能差(频繁建立连接) +- 队头阻塞 + +--- + +#### **HTTP/1.1** + +**改进**: +1. **持久连接**:`Connection: keep-alive` +2. **管道化(Pipelining)**:可发送多个请求 +3. **分块传输**:`Transfer-Encoding: chunked` +4. **缓存**:更强的缓存控制 + +**问题**: +- 队头阻塞仍然存在 +- 请求串行执行 + +--- + +#### **HTTP/2.0** + +**改进**: +1. **二进制协议**:不再是纯文本 +2. **多路复用**:一个 TCP 连接并发多个请求 +3. **头部压缩**:HPACK 算法 +4. **服务端推送**:Server Push + +**多路复用**: +``` +HTTP/1.1: +请求1 → ━━━ 连接1 ━━━ → 响应1 +请求2 → ━━━ 连接2 ━━━ → 响应2 +请求3 → ━━━ 连接3 ━━━ → 响应3 + +HTTP/2.0: +请求1 ┓ +请求2 ┣━━━━ 单连接 ━━━→ ┳ 响应1 +请求3 ┛ ┻ 响应2 + 响应3 +``` + +--- + +#### **HTTP/3.0(QUIC)** + +**改进**: +1. **基于 UDP**:不再是 TCP +2. **解决队头阻塞**:流级别隔离 +3. **更快握手**:0-RTT +4. **连接迁移**:IP 变化不影响连接 + +**架构**: +``` +HTTP/3.0 + ↓ +QUIC(UDP) + ↓ +加密、可靠传输、流控制 +``` + +--- + +### 7. 粘包和拆包 + +#### **问题** + +**TCP 是字节流协议**,无消息边界: + +``` +发送方发送两个包: +┌──────┐ ┌──────┐ +│ 包1 │ │ 包2 │ +└──────┘ └──────┘ + +接收方可能收到: +1. 正常:┌──────┐┌──────┐ +2. 粘包:┌──────┐┌──────┐(两个包粘在一起) +3. 拆包:┌────┐ ┌──┐┌──┐┌────┐(包被拆散) +``` + +--- + +#### **解决方案** + +**1. 固定长度**: +``` +每个包固定 100 字节 + +优点:简单 +缺点:浪费空间(短消息) +``` + +**2. 分隔符**: +``` +每个包以 \n 结束 + +优点:实现简单 +缺点:内容中不能有分隔符 +``` + +**3. 长度字段(推荐)**: +``` +┌────┬──────────┐ +│长度│ 数据 │ +└────┴──────────┘ + +实现:Netty 的 LengthFieldBasedFrameDecoder +``` + +**Netty 示例**: +```java +// 服务端 +public class Server { + public static void main(String[] args) { + EventLoopGroup bossGroup = new NioEventLoopGroup(); + EventLoopGroup workerGroup = new NioEventLoopGroup(); + + try { + ServerBootstrap bootstrap = new ServerBootstrap(); + bootstrap.group(bossGroup, workerGroup) + .channel(NioServerSocketChannel.class) + .childHandler(new ChannelInitializer() { + @Override + protected void initChannel(SocketChannel ch) { + // 解决粘包拆包:长度字段解码器 + ch.pipeline().addLast(new LengthFieldBasedFrameDecoder( + 1024 * 1024, // 最大帧长度 + 4, // 长度字段偏移量 + 4, // 长度字段长度 + 0, // 长度调整值 + 4 // 剥离的字节数 + )); + ch.pipeline().addLast(new MessageHandler()); + } + }); + + bootstrap.bind(8080).sync().channel().closeFuture().sync(); + } finally { + bossGroup.shutdownGracefully(); + workerGroup.shutdownGracefully(); + } + } +} + +// 消息编解码 +public class MessageEncoder extends MessageToByteEncoder { + @Override + protected void encode(ChannelHandlerContext ctx, Message msg, ByteBuf out) { + byte[] data = msg.getBody().getBytes(); + out.writeInt(data.length); // 长度字段 + out.writeBytes(data); // 数据字段 + } +} +``` + +--- + +### 8. 实际项目经验 + +#### **案例 1:TCP 长连接异常断开** + +**问题**: +- 客户端崩溃,服务器未收到 FIN +- 服务器维护大量死连接 + +**解决**: +```java +// 启用 TCP Keep-Alive +socket.setKeepAlive(true); + +// 应用层心跳 +@Scheduled(fixedRate = 30000) +public void sendHeartbeat() { + channel.writeAndFlush(new HeartbeatMessage()); +} + +// 超时断开 +ch.pipeline().addLast(new IdleStateHandler(60, 0, 0, TimeUnit.SECONDS)); +ch.pipeline().addLast(new HeartbeatHandler()); +``` + +--- + +#### **案例 2:HTTPS 性能优化** + +**问题**: +- HTTPS 握手耗时 200ms+ +- 高并发下性能差 + +**解决**: +1. **HTTP/2 多路复用** +2. **TLS False Start**:减少 1 个 RTT +3. **Session Resumption**:恢复会话 +4. **HSTS**:强制 HTTPS,避免重定向 + +--- + +### 9. 阿里 P7 加分项 + +**深度理解**: +- 理解 TCP 的拥塞控制算法( Reno、Cubic、BBR) +- 理解 QUIC 协议的设计原理 +- 理解 TLS 1.3 的改进(0-RTT、加密握手) + +**实战经验**: +- 有处理网络抖动导致的连接不稳定问题 +- 有 TCP 参数调优经验 +- 有 HTTPS 性能优化经验 + +**架构能力**: +- 能设计高性能的网络通信框架 +- 能设计跨地域的网络架构 +- 有网络监控和故障排查经验 + +**技术选型**: +- 了解 gRPC、Thrift 等 RPC 框架 +- 了解 Netty、Mina 等网络框架 +- 能根据业务特点选择合适的协议 diff --git a/questions/nginx.md b/questions/nginx.md new file mode 100644 index 0000000..e69de29 diff --git a/questions/performance-tuning.md b/questions/performance-tuning.md new file mode 100644 index 0000000..443c949 --- /dev/null +++ b/questions/performance-tuning.md @@ -0,0 +1,603 @@ +# 性能优化 + +## 问题 + +1. 性能优化的常见思路和方法有哪些? +2. 如何进行系统性能瓶颈分析? +3. 数据库性能优化有哪些方法? +4. JVM 性能调优的常见问题和解决方案? +5. 接口性能优化有哪些技巧? +6. 如何进行全链路压测? +7. 在实际项目中如何进行性能优化? + +--- + +## 标准答案 + +### 1. 性能优化思路 + +#### **优化原则** + +1. **测量优先**:先测量,后优化 +2. **抓大放小**:优化瓶颈,而非所有地方 +3. **数据驱动**:用数据说话 +4. **权衡取舍**:优化往往带来复杂性 + +--- + +#### **优化层次** + +``` +用户响应时间 + ↓ +前端优化(减少请求、CDN、缓存) + ↓ +网络优化(减少 RTT、压缩) + ↓ +后端优化(代码、算法、并发) + ↓ +数据库优化(索引、分库分表) + ↓ +操作系统优化(TCP、内核参数) + ↓ +硬件优化(CPU、内存、磁盘、网络) +``` + +--- + +### 2. 性能瓶颈分析 + +#### **分析工具** + +**1. CPU 分析**: +```bash +# top 命令 +top -p + +# 查看 CPU 使用率 +%CPU: 80.5 + +# 查看线程 CPU +top -H -p +``` + +**2. 内存分析**: +```bash +# 查看内存使用 +free -h + +# 查看进程内存 +ps aux | grep java + +# JVM 堆内存 +jmap -heap +``` + +**3. 磁盘 I/O**: +```bash +# iostat +iostat -x 1 + +# 查看 I/O 等待 +%iowait: 15.2 # 高说明磁盘瓶颈 +``` + +**4. 网络 I/O**: +```bash +# 查看网络连接 +netstat -anp | grep + +# 查看网络流量 +iftop +``` + +--- + +#### **Java 性能分析工具** + +**1. JProfiler**: +- CPU Profiler +- Memory Profiler +- Thread Profiler + +**2. Arthas(阿里开源)**: +```bash +# 安装 +curl -O https://arthas.aliyun.com/arthas-boot.jar +java -jar arthas-boot.jar + +# 查看 CPU 最高的线程 +dashboard + +# 查看方法调用耗时 +trace com.example.UserService getUser + +# 查看类加载 +sc -d com.example.User +``` + +**3. VisualVM**: +- JDK 自带 +- 可视化界面 + +--- + +### 3. 数据库性能优化 + +#### **优化层次** + +``` +SQL 优化 + ↓ +索引优化 + ↓ +表结构优化 + ↓ +数据库配置优化 + ↓ +架构优化(读写分离、分库分表) +``` + +--- + +#### **SQL 优化** + +**1. 避免 SELECT \***: +```sql +-- ❌ 查询所有列 +SELECT * FROM users WHERE id = 1; + +-- ✅ 只查询需要的列 +SELECT id, name, email FROM users WHERE id = 1; +``` + +**2. 避免 IN 子查询**: +```sql +-- ❌ 子查询 +SELECT * FROM orders WHERE user_id IN (SELECT id FROM users WHERE status = 1); + +-- ✅ JOIN +SELECT o.* FROM orders o +INNER JOIN users u ON o.user_id = u.id +WHERE u.status = 1; +``` + +**3. 批量操作**: +```sql +-- ❌ 单条插入 +INSERT INTO orders (id, user_id) VALUES (1, 100); +INSERT INTO orders (id, user_id) VALUES (2, 100); +INSERT INTO orders (id, user_id) VALUES (3, 100); + +-- ✅ 批量插入 +INSERT INTO orders (id, user_id) VALUES +(1, 100), +(2, 100), +(3, 100); +``` + +**4. 使用 UNION ALL 代替 UNION**: +```sql +-- ❌ UNION(去重,慢) +SELECT name FROM users_a +UNION +SELECT name FROM users_b; + +-- ✅ UNION ALL(不去重,快) +SELECT name FROM users_a +UNION ALL +SELECT name FROM users_b; +``` + +--- + +#### **索引优化** + +**1. 创建合适的索引**: +```sql +-- 为查询条件创建索引 +CREATE INDEX idx_user_id ON orders(user_id); + +-- 为排序创建索引 +CREATE INDEX idx_created_at ON orders(created_at); + +-- 联合索引(注意最左前缀) +CREATE INDEX idx_user_status ON orders(user_id, status); +``` + +**2. 避免索引失效**: +```sql +-- ❌ 使用函数 +SELECT * FROM orders WHERE YEAR(created_at) = 2024; + +-- ✅ 范围查询 +SELECT * FROM orders WHERE created_at >= '2024-01-01' AND created_at < '2025-01-01'; + +-- ❌ 隐式转换 +SELECT * FROM users WHERE phone = 13800138000; -- phone 是 VARCHAR + +-- ✅ 显式转换 +SELECT * FROM users WHERE phone = '13800138000'; + +-- ❌ 前缀模糊查询 +SELECT * FROM users WHERE name LIKE '%张'; + +-- ✅ 后缀模糊查询 +SELECT * FROM users WHERE name LIKE '张%'; +``` + +--- + +#### **表结构优化** + +**1. 垂直拆分**: +```sql +-- 拆分前(大表) +CREATE TABLE users ( + id BIGINT PRIMARY KEY, + name VARCHAR(50), + email VARCHAR(100), + password VARCHAR(100), + intro TEXT, -- 简介(可能很长) + settings JSON, -- 设置 + created_at DATETIME +); + +-- 拆分后 +CREATE TABLE users_base ( + id BIGINT PRIMARY KEY, + name VARCHAR(50), + email VARCHAR(100), + password VARCHAR(100), + created_at DATETIME +); + +CREATE TABLE users_ext ( + user_id BIGINT PRIMARY KEY, + intro TEXT, + settings JSON +); +``` + +**2. 水平拆分**: +```sql +-- 拆分前(单表数据量大) +CREATE TABLE orders ( + id BIGINT PRIMARY KEY, + user_id BIGINT, + amount DECIMAL(10,2), + created_at DATETIME +); -- 1 亿条数据 + +-- 拆分后(按时间分表) +CREATE TABLE orders_2024_01 ( + id BIGINT PRIMARY KEY, + user_id BIGINT, + amount DECIMAL(10,2), + created_at DATETIME +); + +CREATE TABLE orders_2024_02 ( + ... +); +``` + +--- + +### 4. JVM 性能调优 + +#### **常见问题** + +**1. 内存泄漏**: +```java +// ❌ 内存泄漏 +public class Cache { + private static final Map cache = new HashMap<>(); + + public void put(String key, Object value) { + cache.put(key, value); // 永不清理,内存泄漏 + } +} + +// ✅ 使用 Guava Cache +public class Cache { + private static final Cache cache = CacheBuilder.newBuilder() + .expireAfterWrite(10, TimeUnit.MINUTES) + .maximumSize(10000) + .build(); + + public void put(String key, Object value) { + cache.put(key, value); + } +} +``` + +--- + +**2. 频繁 GC**: +```bash +# 查看 GC 情况 +jstat -gcutil 1000 + +# 输出 + S0 S1 E O M CCS YGC YGCT FGC FGCT GCT + 0.00 70.15 80.23 65.40 95.12 90.23 1523 45.236 10 12.345 57.581 + +# YGC:年轻代 GC 次数(正常 1000 次/小时以下) +# FGC:老年代 GC 次数(应该接近 0) +# GCT:总 GC 时间(应该 < 5%) +``` + +**调优**: +```bash +# 调整堆内存大小 +-Xms4g -Xmx4g + +# 调整年轻代大小 +-Xmn2g + +# 使用 G1 收集器(大堆内存推荐) +-XX:+UseG1GC + +# 调整 GC 并行线程 +-XX:ParallelGCThreads=8 +``` + +--- + +**3. CPU 100%**: +```bash +# 查看最耗 CPU 的线程 +top -H -p + +# 转换线程 ID 为 16 进制 +printf "%x" + +# 查看线程堆栈 +jstack | grep -A 20 +``` + +--- + +### 5. 接口性能优化 + +#### **优化技巧** + +**1. 异步处理**: +```java +// ❌ 同步处理(慢) +@PostMapping("/order") +public String createOrder(@RequestBody Order order) { + // 1. 保存订单(100ms) + orderService.save(order); + + // 2. 发送邮件(500ms) + emailService.send(order); + + // 3. 发送短信(300ms) + smsService.send(order); + + // 总耗时:900ms + return "success"; +} + +// ✅ 异步处理(快) +@PostMapping("/order") +public String createOrder(@RequestBody Order order) { + // 1. 保存订单(100ms) + orderService.save(order); + + // 2. 异步发送邮件、短信 + CompletableFuture.runAsync(() -> { + emailService.send(order); + smsService.send(order); + }); + + // 总耗时:100ms + return "success"; +} +``` + +--- + +**2. 缓存**: +```java +// ❌ 每次查询数据库 +public User getUser(Long userId) { + return userMapper.selectById(userId); // 50ms +} + +// ✅ 使用缓存 +@Cacheable(value = "users", key = "#userId") +public User getUser(Long userId) { + return userMapper.selectById(userId); // 第一次 50ms,后续 1ms +} +``` + +--- + +**3. 批量查询**: +```java +// ❌ 循环查询(N+1 问题) +List orders = orderMapper.selectList(); +for (Order order : orders) { + User user = userMapper.selectById(order.getUserId()); // N 次查询 + order.setUser(user); +} + +// ✅ 批量查询 +List orders = orderMapper.selectList(); +Set userIds = orders.stream() + .map(Order::getUserId) + .collect(Collectors.toSet()); +Map userMap = userMapper.selectByIds(userIds); // 1 次查询 +orders.forEach(o -> o.setUser(userMap.get(o.getUserId()))); +``` + +--- + +**4. 连接池优化**: +```yaml +# HikariCP 配置 +spring: + datasource: + hikari: + minimum-idle: 10 # 最小空闲连接 + maximum-pool-size: 50 # 最大连接池大小 + connection-timeout: 30000 # 连接超时时间 + idle-timeout: 600000 # 空闲连接超时 + max-lifetime: 1800000 # 连接最大生命周期 +``` + +--- + +### 6. 全链路压测 + +#### **压测工具** + +**1. JMeter**: +- 图形化界面 +- 支持分布式压测 +- 可录制脚本 + +**2. Locust(Python)**: +```python +from locust import HttpUser, task, between + +class WebsiteUser(HttpUser): + wait_time = between(1, 3) + + @task + def index(self): + self.client.get("/") + + @task(3) + def api(self): + self.client.get("/api/users") +``` + +**运行**: +```bash +locust -f locustfile.py --host=https://example.com +``` + +--- + +#### **压测流程** + +``` +1. 制定压测计划 + - 确定目标(QPS、响应时间) + - 确定场景(秒杀、日常流量) + +2. 编写压测脚本 + - 模拟真实用户行为 + - 准备测试数据 + +3. 执行压测 + - 逐步加压 + - 记录指标 + +4. 分析结果 + - 找出瓶颈 + - 优化后再次压测 + +5. 容量评估 + - 确定系统容量 + - 预留缓冲(如 30%) +``` + +--- + +#### **压测指标** + +| 指标 | 说明 | 目标值 | +|------|------|--------| +| **QPS** | 每秒请求数 | 根据业务需求 | +| **响应时间** | P99 < 200ms | 根据业务需求 | +| **错误率** | 错误请求比例 | < 0.1% | +| **CPU 使用率** | CPU 占用 | < 70% | +| **内存使用率** | 内存占用 | < 80% | +| **TPS** | 每秒事务数 | 根据业务需求 | + +--- + +### 7. 实际项目案例 + +#### **案例 1:接口响应时间从 2s 优化到 100ms** + +**问题**: +- 用户列表接口响应时间 2 秒 +- 数据库查询慢 + +**分析**: +```sql +EXPLAIN SELECT * FROM users WHERE status = 1; +-- type: ALL(全表扫描) +-- rows: 1000000 +``` + +**优化**: +```sql +-- 1. 添加索引 +CREATE INDEX idx_status ON users(status); + +-- 2. 只查询需要的字段 +SELECT id, name, email FROM users WHERE status = 1; + +-- 3. 添加缓存 +@Cacheable("users") +``` + +**结果**:响应时间降至 100ms + +--- + +#### **案例 2:解决频繁 GC 问题** + +**问题**: +- 应用频繁 Full GC +- CPU 使用率 100% + +**分析**: +```bash +# 查看 GC 日志 +jstat -gcutil 1000 + +# FGC 频繁,说明老年代内存不足 +``` + +**优化**: +```bash +# 调整 JVM 参数 +-Xms8g -Xmx8g # 增加堆内存 +-Xmn4g # 调整年轻代大小 +-XX:+UseG1GC # 使用 G1 收集器 +-XX:MaxGCPauseMillis=200 # 设置最大 GC 暂停时间 +``` + +**结果**:Full GC 次数从每小时 10 次降至 0 次 + +--- + +### 8. 阿里 P7 加分项 + +**深度理解**: +- 理解性能优化的底层原理(操作系统、网络、数据库) +- 理解各种性能分析工具的实现原理 +- 理解性能测试的统计学方法 + +**实战经验**: +- 有将接口从秒级优化到毫秒级的经验 +- 有解决线上性能问题的经验 +- 有全链路压测的经验 + +**架构能力**: +- 能设计高性能架构 +- 能设计性能监控体系 +- 能制定性能优化规范 + +**技术选型**: +- 了解各种性能分析工具(JProfiler、Arthas、SkyWalking) +- 了解分布式追踪系统(Zipkin、Jaeger) +- 能根据业务特点制定性能指标 diff --git a/questions/redis-architecture.md b/questions/redis-architecture.md new file mode 100644 index 0000000..e69de29 diff --git a/questions/redis-data-structure.md b/questions/redis-data-structure.md new file mode 100644 index 0000000..f69272c --- /dev/null +++ b/questions/redis-data-structure.md @@ -0,0 +1,215 @@ +# Redis 数据结构 + +## 问题 + +1. Redis 有哪些数据结构?底层实现是什么? +2. String 类型的应用场景? +3. Hash 和 String 的区别? +4. List 的应用场景? +5. Set 和 ZSet 的区别? +6. Bitmap、HyperLogLog、GEO 的应用? + +--- + +## 标准答案 + +### 1. Redis 数据类型 + +| 类型 | 底层实现 | 应用场景 | +|------|---------|----------| +| **String** | SDS | 缓存、计数器、分布式锁 | +| **Hash** | 压缩列表/哈希表 | 对象存储、购物车 | +| **List** | 双向链表/压缩列表 | 消息队列、最新列表 | +| **Set** | 哈希表/整数集合 | 标签、共同关注 | +| **ZSet** | 跳表/哈希表 | 排行榜、延时队列 | +| **Bitmap** | String(位操作) | 签到、在线用户 | +| **HyperLogLog** | String(基数统计) | UV 统计 | +| **GEO** | ZSet(经纬度编码) | 附近的人 | + +--- + +### 2. String(SDS - Simple Dynamic String) + +**结构**: +```c +struct sdshdr { + int len; // 已使用长度 + int free; // 剩余空间 + char buf[]; // 字节数组 +}; +``` + +**优势**: +- O(1) 获取长度 +- 防止缓冲区溢出 +- 减少内存分配次数 + +**应用**: +```bash +# 缓存 +SET user:1001 '{"id":1001,"name":"Alice"}' +GET user:1001 + +# 计数器 +INCR view_count:1001 +DECR stock:1001 + +# 分布式锁 +SET lock:order:1001 "uuid" NX PX 30000 +``` + +--- + +### 3. Hash + +**结构**: +```bash +HSET user:1001 name "Alice" age 25 email "alice@example.com" +HGET user:1001 name +HGETALL user:1001 +``` + +**底层**: +- 字段少(< 512):压缩列表(ziplist) +- 字段多(≥ 512):哈希表(hashtable) + +**应用**: +```java +// 对象存储(推荐 Hash,而非 String) +redisTemplate.opsForHash().putAll("user:1001", Map.of( + "name", "Alice", + "age", "25" +)); + +// 购物车 +redisTemplate.opsForHash().put("cart:1001", "product:1001", "2"); +``` + +--- + +### 4. List + +**结构**: +```bash +LPUSH list:msgs "msg1" "msg2" "msg3" # 左侧插入 +RPOP list:msgs # 右侧弹出 +``` + +**底层**: +- 元素少(< 512):压缩列表(ziplist) +- 元素多(≥ 512):双向链表(linkedlist) +- Redis 3.2+:quicklist(ziplist + linkedlist) + +**应用**: +```bash +# 消息队列 +LPUSH queue:email '{"to":"alice@example.com","subject":"Hello"}' +RPOP queue:email + +# 最新列表 +LPUSH timeline:1001 "post1" "post2" +LRANGE timeline:1001 0 9 # 最新 10 条 +``` + +--- + +### 5. Set vs ZSet + +**Set(无序集合)**: +```bash +SADD tags:article:1001 "java" "redis" "mysql" +SMEMBERS tags:article:1001 +SISMEMBER tags:article:1001 "java" +SINTER tags:user:1001 tags:article:1001 # 交集 +``` + +**底层**: +- 元素少(< 512):整数集合(intset) +- 元素多(≥ 512):哈希表(hashtable) + +--- + +**ZSet(有序集合)**: +```bash +ZADD rank:score 100 "player1" 200 "player2" 150 "player3" +ZREVRANGE rank:score 0 9 WITHSCORES # Top 10 +ZRANK rank:score "player1" # 排名 +ZSCORE rank:score "player1" # 分数 +``` + +**底层**: +- 元素少(< 128):压缩列表(ziplist) +- 元素多(≥ 128):跳表(skiplist) + 哈希表(hashtable) + +--- + +### 6. Bitmap + +**原理**:用 bit 位表示状态(0 或 1) + +```bash +# 签到 +SETBIT sign:2024:02:28:1001 0 1 # 用户 1001 在第 0 天签到 +SETBIT sign:2024:02:28:1001 4 1 # 用户 1001 在第 4 天签到 + +# 统计签到天数 +BITCOUNT sign:2024:02:28:1001 + +# 用户 1001 和 1002 共同签到的天数 +BITOP AND result sign:2024:02:28:1001 sign:2024:02:28:1002 +BITCOUNT result +``` + +--- + +### 7. HyperLogLog + +**用途**:基数统计(不重复元素个数) + +**优点**:内存占用极小(12 KB) + +```bash +PFADD uv:2024:02:28 user:1001 user:1002 user:1003 +PFCOUNT uv:2024:02:28 # 3 + +# 合并多个 HyperLogLog +PFMERGE uv:2024:02:01-28 uv:2024:02:01 uv:2024:02:02 ... +``` + +**误差率**:< 1% + +--- + +### 8. GEO(地理位置) + +```bash +# 添加位置 +GEOADD locations:users 116.404 39.915 "user:1001" # 北京 + +# 查找附近的人(5 km 内) +GEORADIUS locations:users 116.404 39.915 5 km + +# 计算距离 +GEODIST locations:users user:1001 user:1002 +``` + +**底层**:ZSet(经纬度编码为 score) + +--- + +### 9. 阿里 P7 加分项 + +**深度理解**: +- 理解 SDS 和 C 字符串的区别 +- 理解跳表的实现原理 +- 理解压缩列表的优缺点 + +**实战经验**: +- 有选择合适数据类型的经验 +- 有大数据量下的优化经验 +- 有 Redis 内存优化的经验 + +**架构能力**: +- 能设计基于 Redis 的业务方案 +- 能设计 Redis 集群方案 +- 能设计 Redis 监控体系 diff --git a/questions/security-encryption.md b/questions/security-encryption.md new file mode 100644 index 0000000..99d9a63 --- /dev/null +++ b/questions/security-encryption.md @@ -0,0 +1,617 @@ +# 加密与安全 + +## 问题 + +1. 对称加密和非对称加密的区别是什么? +2. Base64 是加密吗?为什么? +3. 什么是数字签名?如何防止数据篡改? +4. HTTPS 中的 TLS 握手过程是怎样的? +5. 什么是中间人攻击?如何防范? +6. SQL 注入和 XSS 攻击的原理和防范措施? +7. 密码存储的最佳实践是什么? +8. 在实际项目中如何保证数据安全? + +--- + +## 标准答案 + +### 1. 对称加密 vs 非对称加密 + +#### **对称加密** + +**特点**:加密和解密使用同一个密钥。 + +``` +加密:明文 + 密钥 → 密文 +解密:密文 + 密钥 → 明文 +``` + +**常见算法**: +- **AES**(Advanced Encryption Standard):推荐 +- **DES**(Data Encryption Standard):已过时(密钥太短) +- **3DES**:DES 的改进,但速度慢 +- **RC4**:已不安全 + +**Java 示例**: +```java +import javax.crypto.Cipher; +import javax.crypto.KeyGenerator; +import javax.crypto.SecretKey; +import java.util.Base64; + +public class AESExample { + public static void main(String[] args) throws Exception { + // 生成密钥 + KeyGenerator keyGen = KeyGenerator.getInstance("AES"); + keyGen.init(256); // AES-256 + SecretKey secretKey = keyGen.generateKey(); + + // 加密 + Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding"); + cipher.init(Cipher.ENCRYPT_MODE, secretKey); + byte[] encrypted = cipher.doFinal("Hello World".getBytes()); + System.out.println("加密:" + Base64.getEncoder().encodeToString(encrypted)); + + // 解密 + cipher.init(Cipher.DECRYPT_MODE, secretKey); + byte[] decrypted = cipher.doFinal(encrypted); + System.out.println("解密:" + new String(decrypted)); + } +} +``` + +**优点**: +- 速度快(比非对称加密快 100-1000 倍) +- 适合加密大量数据 + +**缺点**: +- 密钥分发困难(如何安全地共享密钥?) + +--- + +#### **非对称加密** + +**特点**:加密和解密使用不同的密钥(公钥和私钥)。 + +``` +公钥加密:明文 + 公钥 → 密文 +私钥解密:密文 + 私钥 → 明文 + +或者: +私钥签名:明文 + 私钥 → 签名 +公钥验签:签名 + 公钥 → 验证 +``` + +**常见算法**: +- **RSA**:最常用 +- **ECC**(椭圆曲线加密):密钥更短,速度更快 +- **DSA**:数字签名算法 + +**Java 示例**: +```java +import java.security.*; +import javax.crypto.Cipher; +import java.util.Base64; + +public class RSAExample { + public static void main(String[] args) throws Exception { + // 生成密钥对 + KeyPairGenerator keyGen = KeyPairGenerator.getInstance("RSA"); + keyGen.initialize(2048); + KeyPair keyPair = keyGen.generateKeyPair(); + PublicKey publicKey = keyPair.getPublic(); + PrivateKey privateKey = keyPair.getPrivate(); + + // 公钥加密 + Cipher cipher = Cipher.getInstance("RSA"); + cipher.init(Cipher.ENCRYPT_MODE, publicKey); + byte[] encrypted = cipher.doFinal("Hello World".getBytes()); + System.out.println("加密:" + Base64.getEncoder().encodeToString(encrypted)); + + // 私钥解密 + cipher.init(Cipher.DECRYPT_MODE, privateKey); + byte[] decrypted = cipher.doFinal(encrypted); + System.out.println("解密:" + new String(decrypted)); + } +} +``` + +**优点**: +- 无需安全分发密钥(公钥可以公开) +- 可用于数字签名 + +**缺点**: +- 速度慢(比对称加密慢 100-1000 倍) +- 不适合加密大量数据 + +--- + +#### **混合加密(HTTPS 使用)** + +``` +1. 使用非对称加密协商对称密钥 +2. 使用对称密钥加密数据 +``` + +**优点**: +- 结合两者优点(安全 + 速度) + +--- + +### 2. Base64 是加密吗? + +**答案:不是!** + +**Base64 是编码,不是加密。** + +**目的**: +- 将二进制数据转换为 ASCII 字符串 +- 方便在文本协议中传输(如 HTTP、Email) + +**原理**: +``` +二进制:01001000 0110101 01011011 +Base64:SGVsbG8= + +编码表(64 个字符): +A-Z (26) + a-z (26) + 0-9 (10) + + / (2) = 64 +``` + +**特点**: +- 可逆(可解码) +- 无密钥(任何人都能解码) +- 数据会增大 33% + +**示例**: +```java +import java.util.Base64; + +public class Base64Example { + public static void main(String[] args) { + String original = "Hello World"; + + // 编码 + String encoded = Base64.getEncoder().encodeToString(original.getBytes()); + System.out.println("编码:" + encoded); // SGVsbG8gV29ybGQ= + + // 解码 + String decoded = new String(Base64.getDecoder().decode(encoded)); + System.out.println("解码:" + decoded); // Hello World + } +} +``` + +**错误用法**: +```java +// ❌ 用 Base64 存储密码(不安全) +String password = "123456"; +String encoded = Base64.getEncoder().encodeToString(password.getBytes()); +// 存储到数据库 +// 攻击者可以直接解码! +``` + +--- + +### 3. 数字签名 + +#### **原理** + +数字签名用于验证: +1. **完整性**:数据未被篡改 +2. **身份认证**:发送者确实是声称的人 +3. **不可抵赖**:发送者无法否认发送过 + +**流程**: +``` +发送方(签名): +1. 对原文计算哈希:hash = SHA256(原文) +2. 用私钥加密哈希:signature = RSA_Encrypt(hash, 私钥) +3. 发送:原文 + signature + +接收方(验签): +1. 对原文计算哈希:hash1 = SHA256(原文) +2. 用公钥解密签名:hash2 = RSA_Decrypt(signature, 公钥) +3. 对比 hash1 和 hash2 + - 相同 → 签名有效 + - 不同 → 数据被篡改 +``` + +--- + +#### **Java 实现** + +```java +import java.security.*; +import java.util.Base64; + +public class DigitalSignatureExample { + public static void main(String[] args) throws Exception { + // 生成密钥对 + KeyPairGenerator keyGen = KeyPairGenerator.getInstance("RSA"); + keyGen.initialize(2048); + KeyPair keyPair = keyGen.generateKeyPair(); + PrivateKey privateKey = keyPair.getPrivate(); + PublicKey publicKey = keyPair.getPublic(); + + // 待签名数据 + String data = "重要合同内容"; + + // 签名 + Signature signature = Signature.getInstance("SHA256withRSA"); + signature.initSign(privateKey); + signature.update(data.getBytes()); + byte[] signatureBytes = signature.sign(); + System.out.println("签名:" + Base64.getEncoder().encodeToString(signatureBytes)); + + // 验签 + signature.initVerify(publicKey); + signature.update(data.getBytes()); + boolean isValid = signature.verify(signatureBytes); + System.out.println("验签结果:" + isValid); + } +} +``` + +--- + +#### **实际应用** + +1. **软件签名**:验证软件来源 +2. **代码签名**:防止代码被篡改 +3. **PDF 签名**:电子合同 +4. **JWT(JSON Web Token)**:API 认证 + +```java +// JWT 示例 +String token = Jwts.builder() + .setSubject("user123") + .signWith(Keys.hmacShaKeyFor(secretKey), SignatureAlgorithm.HS256) + .compact(); + +// 验证 JWT +Claims claims = Jwts.parserBuilder() + .setSigningKey(secretKey) + .build() + .parseClaimsJws(token) + .getBody(); +``` + +--- + +### 4. SQL 注入攻击 + +#### **原理** + +攻击者在输入中注入恶意 SQL 代码。 + +**示例**: +```java +// ❌ 不安全的代码(拼接 SQL) +String username = request.getParameter("username"); +String password = request.getParameter("password"); +String sql = "SELECT * FROM users WHERE username = '" + username + "' AND password = '" + password + "'"; + +// 攻击者输入: +username = "admin' OR '1'='1"; +password = "anything" + +// 实际执行的 SQL: +SELECT * FROM users WHERE username = 'admin' OR '1'='1' AND password = 'anything' +// 结果:永远为真,绕过密码验证! +``` + +**危害**: +- 绕过认证 +- 窃取数据 +- 删除数据 +- 提升权限 + +--- + +#### **防范措施** + +**1. 使用预编译语句(PreparedStatement)**: + +```java +// ✅ 安全代码 +String sql = "SELECT * FROM users WHERE username = ? AND password = ?"; +PreparedStatement stmt = connection.prepareStatement(sql); +stmt.setString(1, username); +stmt.setString(2, password); +ResultSet rs = stmt.executeQuery(); +``` + +**原理**:参数化查询,SQL 结构固定,参数不会被解析为 SQL 代码。 + +--- + +**2. 输入验证**: + +```java +// 验证用户名格式 +if (!username.matches("^[a-zA-Z0-9_]{3,20}$")) { + throw new IllegalArgumentException("用户名格式不正确"); +} +``` + +--- + +**3. 最小权限原则**: + +```sql +-- 应用用户只授予必要权限 +GRANT SELECT, INSERT, UPDATE ON app_db.* TO 'app_user'@'localhost'; +-- 不要授予 DROP、ALTER 等危险权限 +``` + +--- + +**4. 使用 ORM**: + +```java +// JPA/Hibernate 自动防止 SQL 注入 +User user = userRepository.findByUsernameAndPassword(username, password); +``` + +--- + +### 5. XSS 攻击(跨站脚本攻击) + +#### **原理** + +攻击者在网页中注入恶意 JavaScript 代码。 + +**示例**: +```html + + +``` + +**危害**: +- 窃取 Cookie +- 会话劫持 +- 重定向到钓鱼网站 +- 篡改网页内容 + +--- + +#### **防范措施** + +**1. 输出转义**: + +```java +// 使用 Spring 的 HTML 转义 +import org.springframework.web.util.HtmlUtils; + +String userInput = ""; +String escaped = HtmlUtils.htmlEscape(userInput); +// 输出:<script>alert('XSS')</script> +``` + +--- + +**2. CSP(Content Security Policy)**: + +```http +# HTTP 响应头 +Content-Security-Policy: default-src 'self'; script-src 'self' 'unsafe-inline' +``` + +--- + +**3. HttpOnly Cookie**: + +```java +// 设置 Cookie 为 HttpOnly(JavaScript 无法访问) +Cookie cookie = new Cookie("session", token); +cookie.setHttpOnly(true); +cookie.setSecure(true); // 仅 HTTPS 传输 +response.addCookie(cookie); +``` + +--- + +**4. 输入验证**: + +```java +// 白名单验证 +if (!comment.matches("[a-zA-Z0-9 \\u4e00-\\u9fa5]+")) { + throw new IllegalArgumentException("评论包含非法字符"); +} +``` + +--- + +### 6. 密码存储 + +#### **错误做法** + +```java +// ❌ 明文存储 +password = "123456"; + +// ❌ Base64 编码(可逆) +password = Base64.getEncoder().encodeToString("123456".getBytes()); + +// ❌ MD5 哈希(易被彩虹表破解) +password = DigestUtils.md5Hex("123456"); // e10adc3949ba59abbe56e057f20f883e +``` + +--- + +#### **正确做法:BCrypt** + +**特点**: +- 自动加盐(Salt) +- 慢哈希(防暴力破解) +- 可调整计算成本 + +```java +import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; + +public class PasswordExample { + public static void main(String[] args) { + BCryptPasswordEncoder encoder = new BCryptPasswordEncoder(); + + // 加密(自动加盐) + String rawPassword = "123456"; + String encodedPassword = encoder.encode(rawPassword); + System.out.println("加密后:" + encodedPassword); + // 输出:$2a$10$N9qo8uLOickgx2ZMRZoMyeIjZAgcfl7p92ldGxad68LJZdL17lhWy + + // 验证 + boolean matches = encoder.matches(rawPassword, encodedPassword); + System.out.println("验证结果:" + matches); // true + } +} +``` + +--- + +#### **密码存储最佳实践** + +1. **使用慢哈希算法**: + - BCrypt(推荐) + - Argon2(最新) + - PBKDF2 + - Scrypt + +2. **每个密码独立的盐**: + ```java + salt = random_bytes(16); + hashed_password = hash(password + salt); + ``` + +3. **调整计算成本**: + ```java + // BCrypt 成本参数(10-12 为宜) + BCryptPasswordEncoder encoder = new BCryptPasswordEncoder(12); + ``` + +4. **不要自己发明加密算法** + +--- + +### 7. HTTPS 完整流程 + +``` +客户端 服务器 + │ │ + │─────── ClientHello ────────────→│ + │ (支持的加密套件、随机数1) │ + │ │ + │←────── ServerHello ─────────────│ + │ (选择的加密套件、随机数2、证书) │ + │←────── Certificate ─────────────│ + │ │ + │─────── 验证证书 ────────────────│ + │ (检查 CA 签名、有效期等) │ + │ │ + │─────── 生成随机数3 ─────────────→│ + │─────── 用公钥加密随机数3 ─────────→│ + │ │ + │ 服务器用私钥解密 + │ 生成会话密钥: + │ master_secret = PRF(随机数1, 随机数2, 随机数3) + │ │ + │←─────── ChangeCipherSpec ────────│ + │←─────── Finished ────────────────│ + │ (用会话密钥加密,证明安全) │ + │ │ + │─────── ChangeCipherSpec ─────────→│ + │─────── Finished ────────────────→│ + │ (用会话密钥加密) │ + │ │ + │══════ 加密通信 ══════════════════│ + │ (使用会话密钥对称加密) │ +``` + +--- + +### 8. 中间人攻击 + +#### **原理** + +攻击者拦截并可能篡改通信双方的通信内容。 + +``` +客户端 攻击者 服务器 + │ │ │ + ├─── HTTPS 请求 ────→│ │ + │ ├─── 伪造请求 ───────→│ + │ │ │ + │←──── 伪造响应 ──────│←──── 真实响应 ──────│ + │←────────────────────│ │ +``` + +--- + +#### **防范措施** + +**1. HTTPS + 证书验证**: +- 验证服务器证书 +- 检查证书链 +- 验证证书有效期 + +**2. HSTS(HTTP Strict Transport Security)**: +```http +Strict-Transport-Security: max-age=31536000; includeSubDomains +``` + +**3. 证书固定(Certificate Pinning)**: +```java +// 移动应用中固定证书 +public class CertificatePinner { + private static final String KNOWN_CERT = "SHA256:AAAAAAAAA..."; + + public void verifyCertificate(X509Certificate cert) { + String certHash = calculateCertificateHash(cert); + if (!certHash.equals(KNOWN_CERT)) { + throw new SSLException("证书不匹配"); + } + } +} +``` + +--- + +### 9. 实际项目安全实践 + +#### **安全检查清单** + +- [ ] 所有用户输入都经过验证和转义 +- [ ] 密码使用 BCrypt 存储且每次加不同的盐 +- [ ] 敏感数据在传输时使用 HTTPS 加密 +- [ ] 敏感数据在存储时使用 AES 加密 +- [ ] API 使用 JWT 或 OAuth2 认证 +- [ ] 实施 CSRF 防护(CSRF Token) +- [ ] 实施 CORS 限制 +- [ ] 设置安全响应头(CSP、X-Frame-Options 等) +- [ ] 定期进行安全审计和渗透测试 +- [ ] 使用依赖扫描工具检查漏洞 + +--- + +### 10. 阿里 P7 加分项 + +**深度理解**: +- 理解各种加密算法的数学原理 +- 理解 TLS 1.3 的改进 +- 理解零知识证明等高级加密技术 + +**实战经验**: +- 有处理线上安全漏洞的经验 +- 有设计安全架构的经验 +- 有加密性能优化的经验 + +**架构能力**: +- 能设计安全的认证和授权体系 +- 能设计数据加密方案 +- 能制定安全开发规范 + +**合规要求**: +- 了解 GDPR、等保等安全合规要求 +- 有安全审计和风险评估经验 diff --git a/questions/spring-boot.md b/questions/spring-boot.md new file mode 100644 index 0000000..ec81866 --- /dev/null +++ b/questions/spring-boot.md @@ -0,0 +1,369 @@ +# Spring Boot 核心原理 + +## 问题 + +1. Spring Boot 的自动配置原理是什么? +2. @SpringBootApplication 注解包含哪些核心注解? +3. Spring Boot 的启动流程是怎样的? +4. 什么是条件注解(@Conditional)? +5. Spring Boot 如何实现配置文件加载? +6. Spring Boot 的健康检查和监控如何实现? +7. Spring Boot 的 Starter 原理是什么? + +--- + +## 标准答案 + +### 1. @SpringBootApplication 注解 + +```java +@SpringBootConfiguration // 1. 配置类 +@EnableAutoConfiguration // 2. 自动配置 +@ComponentScan // 3. 组件扫描 +public @interface SpringBootApplication { +} +``` + +--- + +#### **1. @SpringBootConfiguration** + +```java +@Configuration // 本质上是 @Configuration +public @interface SpringBootConfiguration { +} +``` + +**作用**:标记为配置类(等同于 XML 配置文件) + +--- + +#### **2. @EnableAutoConfiguration** + +```java +@AutoConfigurationPackage // 自动配置包 +@Import(AutoConfigurationImportSelector.class) // 导入自动配置类 +public @interface EnableAutoConfiguration { +} +``` + +**核心**:`AutoConfigurationImportSelector` + +```java +// 加载 META-INF/spring.factories 中的自动配置类 +String[] configurations = SpringFactoriesLoader.loadFactoryNames( + getSpringFactoriesLoaderFactoryClass(), + getBeanClassLoader() +); +``` + +--- + +#### **3. @ComponentScan** + +```java +@ComponentScan( + excludeFilters = { // 排除过滤器 + @Filter(type = FilterType.CUSTOM, classes = TypeExcludeFilter.class), + @Filter(type = FilterType.CUSTOM, classes = AutoConfigurationExcludeFilter.class) + } +) +``` + +**作用**:扫描 `@Component`、`@Service`、`@Repository`、`@Controller` 等注解 + +--- + +### 2. 自动配置原理 + +#### **核心流程** + +``` +1. @EnableAutoConfiguration + ↓ +2. 加载 META-INF/spring.factories + ↓ +3. 根据 @Conditional 条件注解决定是否加载配置 + ↓ +4. 注册 Bean +``` + +--- + +#### **spring.factories 示例** + +```properties +# spring-boot-autoconfigure-2.7.0.jar +org.springframework.boot.autoconfigure.EnableAutoConfiguration=\ +org.springframework.boot.autoconfigure.admin.SpringApplicationAdminJmxAutoConfiguration,\ +org.springframework.boot.autoconfigure.aop.AopAutoConfiguration,\ +org.springframework.boot.autoconfigure.amqp.RabbitAutoConfiguration,\ +... +``` + +--- + +#### **条件注解示例** + +```java +@Configuration +@ConditionalOnClass(DataSource.class) // 类路径存在 DataSource +@ConditionalOnMissingBean(DataSource.class) // 容器中不存在 DataSource Bean +@EnableConfigurationProperties(DataSourceProperties.class) +public class DataSourceAutoConfiguration { + + @Bean + public DataSource dataSource(DataSourceProperties properties) { + // 创建 DataSource + return properties.initializeDataSourceBuilder().build(); + } +} +``` + +--- + +### 3. Spring Boot 启动流程 + +```java +@SpringBootApplication +public class Application { + public static void main(String[] args) { + SpringApplication.run(Application.class, args); + } +} +``` + +--- + +#### **核心步骤** + +``` +1. 创建 SpringApplication + ↓ +2. 准备 Environment + ↓ +3. 打印 Banner + ↓ +4. 创建 ApplicationContext + ↓ +5. 刷新 Context(加载 Bean) + ↓ +6. 调用 Runner(ApplicationRunner、CommandLineRunner) +``` + +--- + +#### **源码分析** + +```java +public SpringApplication(Class... primarySources) { + // 1. 保存主配置类 + this.primarySources = new LinkedHashSet<>(Arrays.asList(primarySources)); + + // 2. 推断 Web 应用类型(SERVLET、REACTIVE、NONE) + this.webApplicationType = WebApplicationType.deduceFromClasspath(); + + // 3. 加载 ApplicationContextInitializer + setInitializers((Collection) getSpringFactoriesInstances(ApplicationContextInitializer.class)); + + // 4. 加载 ApplicationListener + setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class)); + + // 5. 推断主类(main 方法所在类) + this.mainApplicationClass = deduceMainApplicationClass(); +} +``` + +--- + +### 4. 配置文件加载 + +#### **加载顺序** + +``` +1. 命令行参数(--server.port=8081) +2. Java 系统属性(System.getProperties()) +3. 操作系统环境变量 +4. JAR 外部的 application-{profile}.properties +5. JAR 内部的 application-{profile}.properties +6. JAR 外部的 application.properties +7. JAR 内部的 application.properties +8. @PropertySource +9. 默认属性(SpringApplication.setDefaultProperties) +``` + +--- + +#### **配置文件示例** + +```yaml +# application.yml +server: + port: 8080 + +spring: + profiles: + active: dev +--- +spring: + config: + activate: + on-profile: dev + datasource: + url: jdbc:mysql://localhost:3306/dev +--- +spring: + config: + activate: + on-profile: prod + datasource: + url: jdbc:mysql://prod-db:3306/prod +``` + +--- + +### 5. 自定义 Starter + +#### **步骤** + +**1. 创建 autoconfigure 模块**: + +```java +@Configuration +@ConditionalOnClass(HelloService.class) +@EnableConfigurationProperties(HelloProperties.class) +public class HelloAutoConfiguration { + + @Bean + @ConditionalOnMissingBean + public HelloService helloService(HelloProperties properties) { + return new HelloService(properties.getMessage()); + } +} +``` + +**2. 创建配置类**: + +```java +@ConfigurationProperties(prefix = "hello") +public class HelloProperties { + private String message = "Hello World"; + + // getter/setter +} +``` + +**3. 创建 spring.factories**: + +```properties +# META-INF/spring.factories +org.springframework.boot.autoconfigure.EnableAutoConfiguration=\ +com.example.hello.HelloAutoConfiguration +``` + +**4. 使用**: + +```yaml +# application.yml +hello: + message: Hello Spring Boot +``` + +```java +@Autowired +private HelloService helloService; + +helloService.sayHello(); // 输出:Hello Spring Boot +``` + +--- + +### 6. Actuator 监控 + +#### **依赖** + +```xml + + org.springframework.boot + spring-boot-starter-actuator + +``` + +--- + +#### **配置** + +```yaml +management: + endpoints: + web: + exposure: + include: health,info,metrics,prometheus # 暴露端点 + endpoint: + health: + show-details: always # 显示详细信息 +``` + +--- + +#### **常用端点** + +| 端点 | 说明 | 示例 | +|------|------|------| +| **/actuator/health** | 健康检查 | `{"status":"UP"}` | +| **/actuator/info** | 应用信息 | 版本、描述等 | +| **/actuator/metrics** | 指标 | JVM、HTTP 请求等 | +| **/actuator/prometheus** | Prometheus 格式 | 监控数据 | +| **/actuator/env** | 环境变量 | 配置属性 | +| **/actuator/loggers** | 日志配置 | 日志级别 | + +--- + +#### **自定义健康检查** + +```java +@Component +public class DatabaseHealthIndicator implements HealthIndicator { + @Autowired + private DataSource dataSource; + + @Override + public Health health() { + try (Connection conn = dataSource.getConnection()) { + if (conn.isValid(1)) { + return Health.up() + .withDetail("database", "MySQL") + .build(); + } + } catch (SQLException e) { + return Health.down() + .withException(e) + .build(); + } + } +} +``` + +--- + +### 7. 阿里 P7 加分项 + +**深度理解**: +- 理解 Spring Boot 的条件装配机制 +- 理解 SpringApplication 的启动流程 +- 理解 AutoConfigurationImportSelector 的工作原理 + +**实战经验**: +- 有自定义 Starter 的经验 +- 有解决自动配置冲突的经验 +- 有 Spring Boot 性能优化的经验 + +**架构能力**: +- 能设计可复用的 Spring Boot Starter +- 能设计多环境配置方案 +- 能设计微服务监控体系 + +**技术选型**: +- 了解 Spring Boot 2.x vs 3.x 的区别 +- 了解 GraalVM Native Image 的支持 +- 了解 Quarkus、Micronaut 等替代框架 diff --git a/questions/system-design-general.md b/questions/system-design-general.md new file mode 100644 index 0000000..c788334 --- /dev/null +++ b/questions/system-design-general.md @@ -0,0 +1,166 @@ +# 系统设计方法论 + +## 问题 + +1. 系统设计的核心原则是什么? +2. 如何进行需求分析? +3. 如何估算系统容量? +4. 数据模型设计有哪些最佳实践? +5. 如何设计高可用架构? + +--- + +## 标准答案 + +### 1. 系统设计原则 + +**核心原则**: +1. **简单性**(KISS:Keep It Simple, Stupid) +2. **模块化**(高内聚、低耦合) +3. **可扩展性**(水平扩展、垂直扩展) +4. **高可用性**(冗余、故障隔离) +5. **一致性**(CAP 理论权衡) + +--- + +### 2. 需求分析 + +**功能需求**: +- 系统做什么? +- 用户角色有哪些? +- 核心流程是什么? + +**非功能需求**: +- **性能**:QPS、响应时间 +- **可用性**:99.9%、99.99%、99.999% +- **扩展性**:支持多少用户 +- **安全性**:认证、授权、加密 +- **一致性**:强一致、最终一致 + +--- + +### 3. 容量估算 + +**QPS 估算**: +``` +日活用户(DAU):100 万 +平均每人每天请求:20 次 +总 QPS = 100 万 × 20 / 86400 ≈ 231 QPS +峰值 QPS = 231 × 5 ≈ 1155 QPS(假设峰值是平均的 5 倍) +``` + +**存储估算**: +``` +用户数:100 万 +每人平均数据:1 KB +总存储 = 100 万 × 1 KB ≈ 1 GB +每年增长 = 1 GB × 12 = 12 GB +``` + +**带宽估算**: +``` +平均响应大小:10 KB +QPS:1000 +带宽 = 1000 × 10 KB × 8 = 80 Mbps +``` + +--- + +### 4. 数据模型设计 + +**原则**: +1. **范式化**(减少冗余) +2. **反范式化**(提高性能) +3. **分区**(水平、垂直) +4. **索引**(加速查询) + +**示例**: +```sql +-- 范式化(3NF) +CREATE TABLE users ( + id BIGINT PRIMARY KEY, + name VARCHAR(50), + email VARCHAR(100) +); + +CREATE TABLE orders ( + id BIGINT PRIMARY KEY, + user_id BIGINT, + amount DECIMAL(10,2), + created_at DATETIME, + FOREIGN KEY (user_id) REFERENCES users(id) +); + +-- 反范式化(冗余用户名,避免 JOIN) +CREATE TABLE orders_denormalized ( + id BIGINT PRIMARY KEY, + user_id BIGINT, + user_name VARCHAR(50), -- 冗余 + amount DECIMAL(10,2), + created_at DATETIME +); +``` + +--- + +### 5. 高可用架构 + +**冗余设计**: +``` + ┌─────────────┐ + │ 负载均衡 │ (Nginx、HAProxy) + └──────┬──────┘ + │ + ┌─────────┼─────────┐ + │ │ │ + ┌──▼──┐ ┌──▼──┐ ┌──▼──┐ + │App 1│ │App 2│ │App 3│ + └──┬──┘ └──┬──┘ └──┬──┘ + │ │ │ + └─────────┼─────────┘ + │ + ┌──────▼──────┐ + │ 主从数据库 │ + │ Master │ + │ Slave │ + └─────────────┘ +``` + +**故障隔离**: +- 熔断器(Circuit Breaker) +- 限流(Rate Limiter) +- 降级(Fallback) +- 舱舱模式(Bulkhead) + +--- + +### 6. 扩展性设计 + +**垂直扩展(Scale Up)**: +- 升级硬件(CPU、内存、磁盘) +- 简单、快速 +- 有上限(硬件限制) + +**水平扩展(Scale Out)**: +- 增加机器数量 +- 无限扩展 +- 需要架构支持(无状态、数据分片) + +--- + +### 7. 阿里 P7 加分项 + +**架构思维**: +- 能进行技术选型(权衡各种方案) +- 能进行容量规划(预估资源需求) +- 能进行成本控制(性价比优化) + +**设计能力**: +- 能设计高并发架构 +- 能设计高可用架构 +- 能设计可扩展架构 + +**沟通能力**: +- 能清晰地表达设计方案 +- 能进行技术方案评审 +- 能推动方案落地 diff --git a/questions/thread-pool-params.md b/questions/thread-pool-params.md new file mode 100644 index 0000000..c30abd9 --- /dev/null +++ b/questions/thread-pool-params.md @@ -0,0 +1,600 @@ +# 线程池核心参数详解 + +## 问题 + +1. 线程池的核心参数有哪些?各自的作用是什么? +2. 如何合理设置线程池大小? +3. 线程池的拒绝策略有哪些?如何自定义? +4. 线程池如何优雅关闭? +5. 线程池的监控指标有哪些? +6. 在实际项目中如何使用线程池? + +--- + +## 标准答案 + +### 1. 线程池核心参数 + +#### **ThreadPoolExecutor 构造函数** + +```java +public ThreadPoolExecutor( + int corePoolSize, // 核心线程数 + int maximumPoolSize, // 最大线程数 + long keepAliveTime, // 非核心线程空闲存活时间 + TimeUnit unit, // 时间单位 + BlockingQueue workQueue, // 任务队列 + ThreadFactory threadFactory, // 线程工厂 + RejectedExecutionHandler handler // 拒绝策略 +) +``` + +--- + +#### **参数详解** + +**1. corePoolSize(核心线程数)** +- **说明**:即使空闲也保留的线程数 +- **默认值**:创建时无核心线程(任务到达时才创建) +- **预热**:`prestartAllCoreThreads()` 提前创建核心线程 + +```java +ThreadPoolExecutor executor = new ThreadPoolExecutor( + 10, // 核心线程数 + 20, // 最大线程数 + ... +); + +// 预热核心线程 +executor.prestartAllCoreThreads(); +``` + +--- + +**2. maximumPoolSize(最大线程数)** +- **说明**:线程池允许的最大线程数 +- **限制**:`maximumPoolSize >= corePoolSize` +- **动态调整**:运行时可通过 `setMaximumPoolSize()` 调整 + +```java +// 动态调整最大线程数 +executor.setMaximumPoolSize(50); +``` + +--- + +**3. keepAliveTime(非核心线程存活时间)** +- **说明**:非核心线程的空闲存活时间 +- **超时回收**:超过时间后,线程会被回收 +- **允许回收核心线程**:`allowCoreThreadTimeOut(true)` + +```java +ThreadPoolExecutor executor = new ThreadPoolExecutor( + 10, + 20, + 60, // 存活时间 + TimeUnit.SECONDS, + new LinkedBlockingQueue<>(100) +); + +// 允许核心线程超时回收 +executor.allowCoreThreadTimeOut(true); +``` + +--- + +**4. workQueue(任务队列)** + +**常见队列**: + +| 队列 | 特性 | 适用场景 | +|------|------|----------| +| **SynchronousQueue** | 不存储,直接传递 | 高并发、低延迟 | +| **LinkedBlockingQueue** | 无界队列(默认 Integer.MAX_VALUE) | 任务提交频繁 | +| **ArrayBlockingQueue** | 有界队列 | 防止资源耗尽 | +| **PriorityBlockingQueue** | 优先级队列 | 优先级任务 | + +**示例**: +```java +// 1. SynchronousQueue(高并发) +ExecutorService executor1 = new ThreadPoolExecutor( + 10, 20, + 60L, TimeUnit.SECONDS, + new SynchronousQueue() // 无队列,直接传递 +); + +// 2. LinkedBlockingQueue(无界) +ExecutorService executor2 = new ThreadPoolExecutor( + 10, 20, + 60L, TimeUnit.SECONDS, + new LinkedBlockingQueue<>(1000) // 队列长度 1000 +); + +// 3. PriorityBlockingQueue(优先级) +ExecutorService executor3 = new ThreadPoolExecutor( + 10, 20, + 60L, TimeUnit.SECONDS, + new PriorityBlockingQueue<>(100) +); +``` + +--- + +**5. threadFactory(线程工厂)** + +**作用**: +- 设置线程名称(便于排查) +- 设置线程优先级 +- 设置是否为守护线程 + +**示例**: +```java +ThreadFactory namedThreadFactory = new ThreadFactoryBuilder() + .setNameFormat("order-pool-%d") // 线程名称前缀 + .setDaemon(false) // 非守护线程 + .setPriority(Thread.NORM_PRIORITY) + .build(); + +ThreadPoolExecutor executor = new ThreadPoolExecutor( + 10, 20, + 60L, TimeUnit.SECONDS, + new LinkedBlockingQueue<>(100), + namedThreadFactory +); +``` + +--- + +**6. handler(拒绝策略)** + +**内置策略**: + +| 策略 | 说明 | +|------|------| +| **AbortPolicy(默认)** | 抛出异常 | +| **CallerRunsPolicy** | 调用者线程执行 | +| **DiscardPolicy** | 静默丢弃 | +| **DiscardOldestPolicy** | 丢弃最旧的任务 | + +```java +// 自定义拒绝策略 +RejectedExecutionHandler handler = new RejectedExecutionHandler() { + @Override + public void rejectedExecution(Runnable r, ThreadPoolExecutor executor) { + // 记录日志 + log.warn("任务被拒绝: {}", r); + + // 重试(加入队列等待) + if (!executor.isShutdown()) { + try { + executor.getQueue().put(r); + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + } + } + } +}; + +ThreadPoolExecutor executor = new ThreadPoolExecutor( + 10, 20, + 60L, TimeUnit.SECONDS, + new LinkedBlockingQueue<>(100), + handler +); +``` + +--- + +### 2. 线程池工作流程 + +``` +任务提交 + ↓ +核心线程数 < corePoolSize? +├─ 是 → 创建核心线程并执行 +└─ 否 → 继续 + ↓ +队列未满? +├─ 是 → 加入队列 +└─ 否 → 继续 + ↓ +线程数 < maximumPoolSize? +├─ 是 → 创建非核心线程并执行 +└─ 否 → 继续 + ↓ +拒绝策略 +``` + +--- + +### 3. 合理设置线程池大小 + +#### **CPU 密集型任务** + +**特点**:主要消耗 CPU 资源(计算、加密) + +**公式**: +``` +线程数 = CPU 核心数 + 1 +``` + +**原因**: +- CPU 密集型任务不需要太多线程 +- +1 是为了当某线程因页故障等原因暂停时,CPU 不会闲置 + +**示例**: +```java +int cpuCore = Runtime.getRuntime().availableProcessors(); // 8 +int poolSize = cpuCore + 1; // 9 + +ThreadPoolExecutor executor = new ThreadPoolExecutor( + poolSize, // 核心线程数 + poolSize, // 最大线程数 + 0L, TimeUnit.MILLISECONDS, + new LinkedBlockingQueue<>(100) +); +``` + +--- + +#### **IO 密集型任务** + +**特点**:主要等待 IO(网络、磁盘) + +**公式**: +``` +线程数 = CPU 核心数 × (1 + IO 耗时 / CPU 耗时) +``` + +**示例**: +```java +// IO 耗时 / CPU 耗时 = 2(IO 占 2/3,CPU 占 1/3) +int cpuCore = Runtime.getRuntime().availableProcessors(); // 8 +int poolSize = cpuCore * (1 + 2); // 24 + +ThreadPoolExecutor executor = new ThreadPoolExecutor( + cpuCore, // 核心线程数 = CPU 核心数 + poolSize, // 最大线程数 + 60L, TimeUnit.SECONDS, + new LinkedBlockingQueue<>(500) +); +``` + +--- + +#### **通用公式** + +``` +线程数 = CPU 核心数 × 目标 CPU 使用率 × (1 + IO 耗时 / CPU 耗时) +``` + +**参数调整**: +- 目标 CPU 使用率:80% - 90% +- IO / CPU 比例:通过压测获得 + +--- + +### 4. 线程池监控 + +#### **监控指标** + +| 指标 | 说明 | 获取方法 | +|------|------|----------| +| **活跃线程数** | 正在执行任务的线程数 | `getActiveCount()` | +| **已完成任务数** | 历史完成的任务总数 | `getCompletedTaskCount()` | +| **总任务数** | 已完成 + 正在执行 | `getTaskCount()` | +| **队列大小** | 队列中待执行任务数 | `getQueue().size()` | +| **最大线程数** | 历史最大线程数 | `getLargestPoolSize()` | +| **线程池是否关闭** | `isShutdown()` | `isShutdown()` | + +--- + +#### **监控代码** + +```java +@Component +public class ThreadPoolMonitor { + + @Autowired + private Map executorMap; + + @Scheduled(fixedRate = 60000) // 每分钟 + public void monitor() { + executorMap.forEach((name, executor) -> { + ThreadPoolExecutorStats stats = new ThreadPoolExecutorStats(); + stats.setName(name); + stats.setCorePoolSize(executor.getCorePoolSize()); + stats.setMaximumPoolSize(executor.getMaximumPoolSize()); + stats.setActiveCount(executor.getActiveCount()); + stats.setCompletedTaskCount(executor.getCompletedTaskCount()); + stats.setTaskCount(executor.getTaskCount()); + stats.setQueueSize(executor.getQueue().size()); + stats.setLargestPoolSize(executor.getLargestPoolSize()); + + // 上报到监控系统(Prometheus、Grafana) + Metrics.report(stats); + + // 告警判断 + if (executor.getActiveCount() >= executor.getMaximumPoolSize() * 0.8) { + alert("线程池 " + name + " 负载过高"); + } + }); + } +} +``` + +--- + +#### **Actuator 监控(Spring Boot)** + +**依赖**: +```xml + + org.springframework.boot + spring-boot-starter-actuator + +``` + +**配置**: +```yaml +management: + endpoints: + web: + exposure: + include: health,info,metrics + metrics: + export: + prometheus: + enabled: true +``` + +**访问**: +```bash +curl http://localhost:8080/actuator/metrics +curl http://localhost:8080/actuator/metrics/executor.pool.size +``` + +--- + +### 5. 线程池优雅关闭 + +#### **问题** + +不优雅关闭的后果: +- 已提交的任务可能丢失 +- 正在执行的任务可能被中断 + +--- + +#### **shutdown()** + +```java +executor.shutdown(); + +try { + // 等待任务完成 + if (!executor.awaitTermination(60, TimeUnit.SECONDS)) { + // 超时,强制关闭 + executor.shutdownNow(); + } +} catch (InterruptedException e) { + executor.shutdownNow(); + Thread.currentThread().interrupt(); +} +``` + +**特点**: +- 不再接受新任务 +- 等待已提交的任务完成 +- 超时后可调用 `shutdownNow()` 强制关闭 + +--- + +#### **shutdownNow()** + +```java +List unfinishedTasks = executor.shutdownNow(); +``` + +**特点**: +- 不再接受新任务 +- 尝试停止正在执行的任务(通过 `Thread.interrupt()`) +- 返回未执行的任务列表 + +--- + +### 6. Spring 线程池配置 + +#### **配置类** + +```java +@Configuration +public class ThreadPoolConfig { + + @Bean("orderThreadPool") + public ThreadPoolExecutor orderThreadPool() { + return new ThreadPoolExecutor( + 10, // 核心线程数 + 20, // 最大线程数 + 60L, TimeUnit.SECONDS, + new LinkedBlockingQueue<>(100), + new ThreadFactoryBuilder() + .setNameFormat("order-pool-%d") + .build(), + new ThreadPoolExecutor.CallerRunsPolicy() + ); + } + + @Bean("emailThreadPool") + public ThreadPoolExecutor emailThreadPool() { + return new ThreadPoolExecutor( + 5, + 10, + 60L, TimeUnit.SECONDS, + new LinkedBlockingQueue<>(50), + new ThreadFactoryBuilder() + .setNameFormat("email-pool-%d") + .build(), + new ThreadPoolExecutor.CallerRunsPolicy() + ); + } +} +``` + +--- + +#### **使用** + +```java +@Service +public class OrderService { + + @Autowired + @Qualifier("orderThreadPool") + private ThreadPoolExecutor orderThreadPool; + + public void createOrder(Order order) { + // 异步处理 + orderThreadPool.execute(() -> { + // 处理订单 + processOrder(order); + }); + } +} +``` + +--- + +#### **@Async(Spring 异步)** + +**配置**: +```java +@Configuration +@EnableAsync +public class AsyncConfig { + + @Bean("asyncExecutor") + public Executor asyncExecutor() { + ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor(); + executor.setCorePoolSize(10); + executor.setMaxPoolSize(20); + executor.setQueueCapacity(100); + executor.setThreadNamePrefix("async-pool-"); + executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy()); + executor.initialize(); + return executor; + } +} +``` + +**使用**: +```java +@Service +public class EmailService { + + @Async("asyncExecutor") + public void sendEmail(String to, String subject, String body) { + // 异步发送邮件 + mailSender.send(to, subject, body); + } +} +``` + +--- + +### 7. 实际项目经验 + +#### **案例 1:线程池参数调优** + +**问题**: +- 订单接口响应慢 +- CPU 使用率低(30%),线程池队列满 + +**分析**: +```java +// 原配置 +corePoolSize = 5 +maximumPoolSize = 10 +queue = LinkedBlockingQueue(100) +``` + +**问题**: +- 线程数太少,任务堆积在队列 +- 数据库连接池用满,等待连接 + +**优化**: +```java +// 优化后配置 +corePoolSize = 20 // 增加 +maximumPoolSize = 50 // 增加 +queue = LinkedBlockingQueue(500) // 增加 +``` + +**结果**:响应时间从 2s 降至 200ms + +--- + +#### **案例 2:动态线程池** + +**需求**:根据流量动态调整线程池大小 + +**实现**: +```java +@Component +public class DynamicThreadPoolManager { + + private final Map executorMap = new ConcurrentHashMap<>(); + + @PostConstruct + public void init() { + // 定时调整线程池大小 + ScheduledExecutorService scheduler = Executors.newSingleThreadScheduledExecutor(); + scheduler.scheduleAtFixedRate(this::adjustThreadPoolSize, 1, 1, TimeUnit.MINUTES); + } + + private void adjustThreadPoolSize() { + executorMap.forEach((name, executor) -> { + // 获取当前负载 + int activeCount = executor.getActiveCount(); + int maximumPoolSize = executor.getMaximumPoolSize(); + + // 负载 > 80%,扩容 + if (activeCount > maximumPoolSize * 0.8) { + int newSize = Math.min(maximumPoolSize * 2, 100); + executor.setMaximumPoolSize(newSize); + log.info("扩容线程池: {} -> {}", name, newSize); + } + // 负载 < 20%,缩容 + else if (activeCount < maximumPoolSize * 0.2) { + int newSize = Math.max(maximumPoolSize / 2, executor.getCorePoolSize()); + executor.setMaximumPoolSize(newSize); + log.info("缩容线程池: {} -> {}", name, newSize); + } + }); + } +} +``` + +--- + +### 8. 阿里 P7 加分项 + +**深度理解**: +- 理解线程池的状态转换(RUNNING、SHUTDOWN、STOP、TIDYING、TERMINATED) +- 理解 `Worker` 的实现原理(继承 AQS、实现 Runnable) +- 理解线程池的异常处理机制 + +**实战经验**: +- 有线程池参数调优的经验 +- 有处理线程池饱和问题的经验 +- 有线程池监控和告警的经验 + +**架构能力**: +- 能设计动态线程池(根据流量调整) +- 能设计线程池隔离(不同业务独立线程池) +- 能设计线程池监控体系 + +**技术选型**: +- 了解 `ForkJoinPool`(工作窃取线程池) +- 了解 `ScheduledThreadPoolExecutor`(定时任务线程池) +- 了解 `Vert.x`、WebFlux 等响应式框架的线程模型 diff --git a/questions/transaction-isolation.md b/questions/transaction-isolation.md new file mode 100644 index 0000000..1a9944b --- /dev/null +++ b/questions/transaction-isolation.md @@ -0,0 +1,1514 @@ +# 数据库事务隔离级别面试指南 + +## 1. ACID 特性 + +### ACID 四大特性 + +**1. 原子性(Atomicity)** +- 事务是一个不可分割的工作单位 +- 事务中的操作要么全部成功,要么全部失败 +- 需要事务日志(Undo Log)实现 + +**2. 一致性(Consistency)** +- 事务必须使数据库从一个一致性状态变到另一个一致性状态 +- 数据库的完整性约束不被破坏 +- 需要业务逻辑和数据库约束保证 + +**3. 隔离性(Isolation)** +- 多个并发事务之间相互隔离 +- 一个事务的执行不能被其他事务干扰 +- 通过锁机制和MVCC实现 + +**4. 持久性(Durability)** +- 事务一旦提交,其对数据库的修改就是永久性的 +- 即使系统发生故障,修改也不会丢失 +- 通过Redo Log实现 + +### ACID 实现原理 + +```java +// ACID 在数据库中的实现示例 +public class TransactionACIDExample { + + // 1. 原子性实现 + public void atomicTransfer(String fromAccount, String toAccount, BigDecimal amount) { + try { + // 开启事务 + beginTransaction(); + + // 执行转账操作 + deductAmount(fromAccount, amount); + addAmount(toAccount, amount); + + // 提交事务 + commitTransaction(); + + } catch (Exception e) { + // 回滚事务 + rollbackTransaction(); + throw new TransactionException("Transfer failed", e); + } + } + + // 2. 一致性实现 + public void consistentUpdate() { + // 事务前检查 + if (!checkBusinessRule()) { + throw new BusinessRuleException("Business rule violated"); + } + + try { + beginTransaction(); + + // 执行更新 + updateData(); + + // 事务后检查 + if (!checkBusinessRule()) { + rollbackTransaction(); + throw new BusinessRuleException("Business rule violated"); + } + + commitTransaction(); + } catch (Exception e) { + rollbackTransaction(); + throw e; + } + } + + // 3. 隔离性实现 + public void isolatedOperation() { + // 设置隔离级别 + setTransactionIsolation(Isolation.READ_COMMITTED); + + try { + beginTransaction(); + + // 执行操作 + performOperation(); + + commitTransaction(); + } catch (Exception e) { + rollbackTransaction(); + throw e; + } + } + + // 4. 持久性实现 + public void durableOperation() { + try { + beginTransaction(); + + // 执行操作 + performOperation(); + + // 提交到内存 + commitTransaction(); + + // 刷入磁盘 + forceWriteToDisk(); + + } catch (Exception e) { + rollbackTransaction(); + throw e; + } + } +} +``` + +### 事务日志实现 + +**Undo Log(回滚日志)** +```sql +-- Undo Log 结构 +CREATE TABLE undo_log ( + id BIGINT AUTO_INCREMENT, + branch_id VARCHAR(64) NOT NULL, + xid VARCHAR(100) NOT NULL, + rollback_info LONGBLOB NOT NULL, + log_status INT NOT NULL, + create_time DATETIME NOT NULL, + update_time DATETIME NOT NULL, + PRIMARY KEY (id), + UNIQUE KEY ux_undo_log_branch_id (xid, branch_id) +); + +-- Undo Log 示例 +INSERT INTO undo_log VALUES ( + 1, + 'branch_id_001', + 'xid_123456', + '{"before": {"name": "Alice", "age": 25}, "after": {"name": "Alice", "age": 26}}', + 1, + NOW(), + NOW() +); +``` + +**Redo Log(重做日志)** +```sql +-- Redo Log 结构 +CREATE TABLE redo_log ( + id BIGINT AUTO_INCREMENT, + xid VARCHAR(100) NOT NULL, + branch_id VARCHAR(64) NOT NULL, + log_data LONGBLOB NOT NULL, + log_status INT NOT NULL, + create_time DATETIME NOT NULL, + PRIMARY KEY (id), + UNIQUE KEY ux_redo_log_branch_id (xid, branch_id) +); + +-- Redo Log 示例 +INSERT INTO redo_log VALUES ( + 1, + 'xid_123456', + 'branch_id_001', + '{"sql": "UPDATE user SET age = 26 WHERE id = 1", "before": "25", "after": "26"}', + 1, + NOW() +); +``` + +## 2. 四种隔离级别 + +### 读未提交(Read Uncommitted) + +**特点**: +- 最低的隔离级别 +- 可能读到未提交的数据(脏读) +- 性能最好,一致性最差 + +**问题演示**: +```sql +-- 会话1(事务A) +BEGIN; +UPDATE user SET balance = balance - 100 WHERE id = 1; -- 余额:900 + +-- 会话2(事务B) +BEGIN; +SELECT balance FROM user WHERE id = 1; -- 读取到900(脏读) +-- 事务A未提交,事务B读取到未提交的数据 + +-- 会话1(事务A) +ROLLBACK; -- 回滚,余额恢复为1000 + +-- 会话2(事务B) +SELECT balance FROM user WHERE id = 1; -- 余额:1000(数据不一致) +``` + +**MySQL 配置**: +```sql +-- 设置隔离级别 +SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; + +-- 查看当前隔离级别 +SELECT @@transaction_isolation; + +-- 全局设置 +SET GLOBAL transaction_isolation = 'READ-UNCOMMITTED'; +``` + +### 读已提交(Read Committed) + +**特点**: +- 只能读取到已提交的数据 +- 不会出现脏读 +- 可能出现不可重复读 + +**问题演示**: +```sql +-- 会话1(事务A) +BEGIN; +SELECT balance FROM user WHERE id = 1; -- 余额:1000 + +-- 会话2(事务B) +BEGIN; +UPDATE user SET balance = balance - 100 WHERE id = 1; -- 余额:900 +COMMIT; -- 提交 + +-- 会话1(事务A) +SELECT balance FROM user WHERE id = 1; -- 余额:900(不可重复读) +-- 同一事务中两次读取结果不同 +``` + +**MySQL 配置**: +```sql +-- 设置隔离级别 +SET TRANSACTION ISOLATION LEVEL READ COMMITTED; + +-- Oracle、SQL Server 默认隔离级别 +``` + +### 可重复读(Repeatable Read) + +**特点**: +- 同一事务中多次读取数据结果一致 +- 不会出现脏读和不可重复读 +- 可能出现幻读 + +**问题演示**: +```sql +-- 会话1(事务A) +BEGIN; +SELECT * FROM order WHERE user_id = 1 AND status = 'pending'; -- 0条记录 + +-- 会话2(事务B) +BEGIN; +INSERT INTO order (user_id, amount, status) VALUES (1, 100, 'pending'); +COMMIT; -- 提交 + +-- 会话1(事务A) +SELECT * FROM order WHERE user_id = 1 AND status = 'pending'; -- 1条记录(幻读) +-- 同一事务中两次查询结果不同 +``` + +**MySQL 配置**: +```sql +-- 设置隔离级别 +SET TRANSACTION ISOLATION LEVEL REPEATABLE READ; + +-- MySQL 默认隔离级别 +``` + +### 串行化(Serializable) + +**特点**: +- 最高隔离级别 +- 事务串行执行 +- 完全隔离,性能最差 +- 不会出现任何问题 + +**演示**: +```sql +-- 设置隔离级别 +SET TRANSACTION ISOLATION LEVEL SERIALIZABLE; + +-- 事务串行执行 +-- 性能最差,但一致性最好 +``` + +### 隔离级别对比表 + +| 隔离级别 | 脏读 | 不可重复读 | 幻读 | 并发性能 | 适用场景 | +|---------|------|-----------|------|---------|---------| +| 读未提交 | 可能 | 可能 | 可能 | 最高 | 不重要数据 | +| 读已提交 | 不可能 | 可能 | 可能 | 高 | 普通业务 | +| 可重复读 | 不可能 | 不可能 | 可能 | 中 | 重要业务 | +| 串行化 | 不可能 | 不可能 | 不可能 | 低 | 极端重要 | + +## 3. MVCC 原理 + +### MVCC 基本概念 + +**MVCC(Multi-Version Concurrency Control)**: +- 多版本并发控制 +- 通过数据版本号实现并发控制 +- 读写不阻塞 + +**核心组件**: +- **隐藏字段**:`DB_TRX_ID`(事务ID)、`DB_ROLL_PTR`(回滚指针) +- **版本链**:通过回滚指针连接历史版本 +- **Read View**:读视图,用于确定可见性 + +### MVCC 实现原理 + +**1. 数据行结构** +```sql +-- InnoDB 行结构示意图 +┌─────────┬─────────────────┬─────────────────┬─────────────────┐ +| Row Header | Transaction ID | Rollback Pointer | Column Data │ +└─────────┴─────────────────┴─────────────────┴─────────────────┘ +``` + +**2. 版本链构建** +```sql +-- 版本链构建过程 +-- 初始状态 +UPDATE user SET name = 'Alice' WHERE id = 1; -- TRX_ID = 100 +┌─────────┬─────────────────┬─────────────────┬─────────────────┐ +| Row Header | TRX_ID: 100 | NULL | name: 'Alice' │ +└─────────┴─────────────────┴─────────────────┴─────────────────┘ + +-- 更新 +UPDATE user SET name = 'Alice Smith' WHERE id = 1; -- TRX_ID = 200 +┌─────────┬─────────────────┬─────────────────┬─────────────────┐ +| Row Header | TRX_ID: 200 | PTR → TRX_ID:100 | name: 'Alice Smith'│ +└─────────┴─────────────────┴─────────────────┴─────────────────┘ +``` + +**3. Read View 机制** +```java +// Read View 实现 +public class ReadView { + private long creatorTrxId; // 创建ReadView的事务ID + private long[] trxIds; // 活跃事务ID列表 + private long upLimitId; // 最小活跃事务ID + private long lowLimitId; // 最大活跃事务ID + 1 + private long creatorTrxId; // 创建ReadView的事务ID + + public boolean isVisible(long trxId, long rollPointer) { + // 1. 如果当前事务在创建ReadView之后,不可见 + if (trxId >= lowLimitId) { + return false; + } + + // 2. 如果事务在活跃列表中,不可见 + if (isInActiveTrxIds(trxId)) { + return false; + } + + // 3. 如果事务在创建ReadView之前且已提交,可见 + return trxId < upLimitId; + } +} +``` + +### MVCC 工作流程 + +**1. 读操作流程** +```java +// MVCC 读操作 +public class MVCCReader { + public Object read(Object row, long trxId) { + // 1. 创建ReadView + ReadView readView = createReadView(trxId); + + // 2. 从最新版本开始遍历 + while (row != null) { + long rowTrxId = getRowTrxId(row); + + // 3. 判断可见性 + if (readView.isVisible(rowTrxId, getRowRollPointer(row))) { + return row; + } + + // 4. 沿着回滚指针查找历史版本 + row = getHistoricalVersion(row); + } + + // 5. 未找到合适版本,返回null + return null; + } +} +``` + +**2. 写操作流程** +```java +// MVCC 写操作 +public class MVCCWriter { + public void update(Object row, Object newValue, long trxId) { + // 1. 创建新版本 + Object newRow = createNewVersion(row, newValue, trxId); + + // 2. 更新版本链 + Object current = getLatestVersion(row); + setRowRollPointer(current, newRow); + + // 3. 更新数据行 + updateRowData(row, newValue); + } +} +``` + +### MySQL MVCC 实现细节 + +**1. 隐藏字段** +```sql +-- 查看隐藏字段 +SHOW CREATE TABLE user; +-- 输出包含: +-- `DB_TRX_ID` 6 byte +-- `DB_ROLL_PTR` 7 byte +-- `DB_ROW_ID` 6 byte + +-- 隐藏字段含义: +-- DB_TRX_ID: 最近修改该行的事务ID +-- DB_ROLL_PTR: 回滚指针,指向该行的历史版本 +-- DB_ROW_ID: 行ID,6字节 +``` + +**2. Undo Log 结构** +```sql +-- Undo Log 示例 +CREATE TABLE undo_log ( + id BIGINT AUTO_INCREMENT, + branch_id VARCHAR(64) NOT NULL, + xid VARCHAR(100) NOT NULL, + rollback_info LONGBLOB NOT NULL, + log_status INT NOT NULL, + create_time DATETIME NOT NULL, + update_time DATETIME NOT NULL, + PRIMARY KEY (id), + UNIQUE KEY ux_undo_log_branch_id (xid, branch_id) +); + +-- INSERT 操作的 Undo Log +-- 记录插入前的数据(实际上是空,因为插入前没有数据) +-- UPDATE 操作的 Undo Log +-- 记录更新前的数据 +-- DELETE 操作的 Undo Log +-- 记录被删除的数据 +``` + +**3. MVCC 演示** +```sql +-- 创建测试表 +CREATE TABLE user ( + id INT PRIMARY KEY, + name VARCHAR(50), + age INT, + INDEX idx_name (name) +); + +-- 插入初始数据 +INSERT INTO user VALUES (1, 'Alice', 25); + +-- 事务A:可重复读 +START TRANSACTION; +SELECT * FROM user WHERE id = 1; -- 结果:(1, 'Alice', 25) + +-- 事务B:更新数据 +START TRANSACTION; +UPDATE user SET name = 'Alice Smith' WHERE id = 1; +COMMIT; + +-- 事务A:再次查询 +SELECT * FROM user WHERE id = 1; -- 结果:(1, 'Alice', 25) -- 可重复读 +-- 事务A仍然读取到旧版本 + +-- 事务A:提交后再次查询 +COMMIT; +SELECT * FROM user WHERE id = 1; -- 结果:(1, 'Alice Smith', 25) -- 读取到最新版本 +``` + +## 4. 脏读、幻读、不可重复读 + +### 脏读(Dirty Read) + +**定义**:读取到未提交的事务数据 + +**问题场景**: +```java +// 脏读问题演示 +public class DirtyReadExample { + public void dirtyReadDemo() { + // 线程1:事务A + new Thread(() -> { + Connection conn1 = getDataSource().getConnection(); + try { + conn1.setAutoCommit(false); + + // 执行更新 + conn1.createStatement().executeUpdate( + "UPDATE account SET balance = balance - 100 WHERE id = 1"); + + // 模拟长时间处理 + Thread.sleep(5000); + + // 回滚 + conn1.rollback(); + + } catch (Exception e) { + try { + conn1.rollback(); + } catch (SQLException ex) { + ex.printStackTrace(); + } + } + }).start(); + + // 线程2:事务B + new Thread(() -> { + try { + Thread.sleep(1000); // 等待事务A开始 + + Connection conn2 = getDataSource().getConnection(); + conn2.setAutoCommit(false); + + // 读取未提交的数据 + ResultSet rs = conn2.createStatement().executeQuery( + "SELECT balance FROM account WHERE id = 1"); + if (rs.next()) { + double balance = rs.getDouble("balance"); + System.out.println("读取到余额: " + balance); // 900(脏读) + } + + conn2.commit(); + + } catch (Exception e) { + e.printStackTrace(); + } + }).start(); + } +} +``` + +**解决方案**: +- 使用 READ COMMITTED 或更高隔离级别 +- 在数据库层面设置隔离级别 + +### 不可重复读(Non-repeatable Read) + +**定义**:同一事务中多次读取同一数据,结果不同 + +**问题场景**: +```java +// 不可重复读问题演示 +public class NonRepeatableReadExample { + public void nonRepeatableReadDemo() { + // 线程1:事务A + new Thread(() -> { + Connection conn1 = getDataSource().getConnection(); + try { + conn1.setAutoCommit(false); + + // 第一次读取 + ResultSet rs1 = conn1.createStatement().executeQuery( + "SELECT balance FROM account WHERE id = 1"); + if (rs1.next()) { + double balance1 = rs1.getDouble("balance"); + System.out.println("第一次读取余额: " + balance1); // 1000 + } + + // 模拟长时间处理 + Thread.sleep(5000); + + // 第二次读取 + ResultSet rs2 = conn1.createStatement().executeQuery( + "SELECT balance FROM account WHERE id = 1"); + if (rs2.next()) { + double balance2 = rs2.getDouble("balance"); + System.out.println("第二次读取余额: " + balance2); // 900(不可重复读) + } + + conn1.commit(); + + } catch (Exception e) { + try { + conn1.rollback(); + } catch (SQLException ex) { + ex.printStackTrace(); + } + } + }).start(); + + // 线程2:事务B + new Thread(() -> { + try { + Thread.sleep(2000); // 等待事务A第一次读取 + + Connection conn2 = getDataSource().getConnection(); + conn2.setAutoCommit(false); + + // 更新数据 + conn2.createStatement().executeUpdate( + "UPDATE account SET balance = balance - 100 WHERE id = 1"); + + conn2.commit(); + + } catch (Exception e) { + e.printStackTrace(); + } + }).start(); + } +} +``` + +**解决方案**: +- 使用 REPEATABLE READ 或 SERIALIZABLE 隔离级别 +- 使用 MVCC 机制 + +### 幻读(Phantom Read) + +**定义**:同一事务中多次查询,返回的行数不同 + +**问题场景**: +```java +// 幻读问题演示 +public class PhantomReadExample { + public void phantomReadDemo() { + // 线程1:事务A + new Thread(() -> { + Connection conn1 = getDataSource().getConnection(); + try { + conn1.setAutoCommit(false); + + // 第一次查询 + ResultSet rs1 = conn1.createStatement().executeQuery( + "SELECT COUNT(*) FROM account WHERE balance > 500"); + if (rs1.next()) { + int count1 = rs1.getInt(1); + System.out.println("第一次查询结果: " + count1); // 1 + } + + // 模拟长时间处理 + Thread.sleep(5000); + + // 第二次查询 + ResultSet rs2 = conn1.createStatement().executeQuery( + "SELECT COUNT(*) FROM account WHERE balance > 500"); + if (rs2.next()) { + int count2 = rs2.getInt(1); + System.out.println("第二次查询结果: " + count2); // 2(幻读) + } + + conn1.commit(); + + } catch (Exception e) { + try { + conn1.rollback(); + } catch (SQLException ex) { + ex.printStackTrace(); + } + } + }).start(); + + // 线程2:事务B + new Thread(() -> { + try { + Thread.sleep(2000); // 等待事务A第一次查询 + + Connection conn2 = getDataSource().getConnection(); + conn2.setAutoCommit(false); + + // 插入新数据 + conn2.createStatement().executeUpdate( + "INSERT INTO account (id, balance) VALUES (2, 600)"); + + conn2.commit(); + + } catch (Exception e) { + e.printStackTrace(); + } + }).start(); + } +} +``` + +**解决方案**: +- 使用 SERIALIZABLE 隔离级别 +- 使用间隙锁(Gap Lock) + +### 问题解决策略 + +**1. 代码层面解决** +```java +// 使用乐观锁解决并发问题 +public class OptimisticLockExample { + public void updateWithOptimisticLock() { + try { + Connection conn = getDataSource().getConnection(); + conn.setAutoCommit(false); + + // 1. 读取数据 + ResultSet rs = conn.createStatement().executeQuery( + "SELECT * FROM user WHERE id = 1"); + if (rs.next()) { + User user = new User(); + user.setId(rs.getLong("id")); + user.setName(rs.getString("name")); + user.setVersion(rs.getInt("version")); + } + + // 2. 模拟业务处理 + Thread.sleep(1000); + + // 3. 更新数据(包含版本号检查) + int affected = conn.createStatement().executeUpdate( + "UPDATE user SET name = ?, version = version + 1 " + + "WHERE id = ? AND version = ?", + new Object[]{"New Name", 1L, user.getVersion()}); + + if (affected == 0) { + throw new OptimisticLockException("数据已被其他事务修改"); + } + + conn.commit(); + + } catch (Exception e) { + throw new RuntimeException("更新失败", e); + } + } +} + +// 使用悲观锁解决并发问题 +public class PessimisticLockExample { + public void updateWithPessimisticLock() { + try { + Connection conn = getDataSource().getConnection(); + conn.setAutoCommit(false); + + // 1. 加锁 + ResultSet rs = conn.createStatement().executeQuery( + "SELECT * FROM user WHERE id = 1 FOR UPDATE"); + + // 2. 更新数据 + conn.createStatement().executeUpdate( + "UPDATE user SET name = 'New Name' WHERE id = 1"); + + conn.commit(); + + } catch (Exception e) { + try { + conn.rollback(); + } catch (SQLException ex) { + ex.printStackTrace(); + } + throw new RuntimeException("更新失败", e); + } + } +} +``` + +**2. 数据库层面解决** +```sql +-- 设置隔离级别解决并发问题 +-- 1. 避免脏读 +SET TRANSACTION ISOLATION LEVEL READ COMMITTED; + +-- 2. 避免不可重复读 +SET TRANSACTION ISOLATION LEVEL REPEATABLE READ; + +-- 3. 避免幻读 +SET TRANSACTION ISOLATION LEVEL SERIALIZABLE; + +-- 使用锁机制 +-- 1. 共享锁(读锁) +SELECT * FROM user WHERE id = 1 LOCK IN SHARE MODE; + +-- 2. 排他锁(写锁) +SELECT * FROM user WHERE id = 1 FOR UPDATE; +``` + +## 5. 实际项目中的隔离级别选择 + +### 不同业务场景的隔离级别选择 + +**1. 电商系统** +```java +// 电商系统事务管理 +@Configuration +@EnableTransactionManagement +public class EcommerceTransactionConfig { + + @Bean + public PlatformTransactionManager transactionManager(DataSource dataSource) { + return new DataSourceTransactionManager(dataSource); + } + + // 订单服务 - 使用可重复读 + @Service + public class OrderService { + + @Transactional(isolation = Isolation.REPEATABLE_READ) + public Order createOrder(OrderDTO orderDTO) { + // 1. 减库存 + inventoryService.reduceStock(orderDTO.getItems()); + + // 2. 创建订单 + Order order = new Order(); + BeanUtils.copyProperties(orderDTO, order); + order.setStatus("PENDING"); + order.setCreateTime(LocalDateTime.now()); + + orderRepository.save(order); + + // 3. 预扣账户余额 + paymentService.reserveAmount(order.getUserId(), order.getTotalAmount()); + + return order; + } + + @Transactional(isolation = Isolation.READ_COMMITTED) + public void cancelOrder(Long orderId) { + // 读取已提交的数据 + Order order = orderRepository.findById(orderId); + + if ("PENDING".equals(order.getStatus())) { + // 取消订单 + order.setStatus("CANCELLED"); + order.setUpdateTime(LocalDateTime.now()); + orderRepository.save(order); + + // 释放库存和余额 + inventoryService.releaseStock(order.getItems()); + paymentService.releaseAmount(order.getUserId(), order.getTotalAmount()); + } + } + } + + // 支付服务 - 使用串行化 + @Service + public class PaymentService { + + @Transactional(isolation = Isolation.SERIALIZABLE) + public void processPayment(Long orderId) { + // 加锁确保数据一致性 + Order order = orderRepository.findByIdWithLock(orderId); + + // 防止重复支付 + if ("PROCESSING".equals(order.getStatus())) { + throw new PaymentException("订单已在处理中"); + } + + // 处理支付 + order.setStatus("PROCESSING"); + order.setUpdateTime(LocalDateTime.now()); + orderRepository.save(order); + + // 调用支付网关 + paymentGateway.process(order); + + // 更新订单状态 + order.setStatus("PAID"); + order.setPayTime(LocalDateTime.now()); + orderRepository.save(order); + } + } +} +``` + +**2. 金融系统** +```java +// 金融系统事务管理 +@Service +public class FinancialTransactionService { + + @Transactional(isolation = Isolation.SERIALIZABLE) + public void transferMoney(String fromAccount, String toAccount, BigDecimal amount) { + try { + // 1. 加锁账户 + Account from = accountRepository.findByIdForLock(fromAccount); + Account to = accountRepository.findByIdForLock(toAccount); + + // 2. 检查余额 + if (from.getBalance().compareTo(amount) < 0) { + throw new InsufficientBalanceException("余额不足"); + } + + // 3. 执行转账 + from.setBalance(from.getBalance().subtract(amount)); + to.setBalance(to.getBalance().add(amount)); + + // 4. 记录交易 + Transaction transaction = new Transaction(); + transaction.setFromAccount(fromAccount); + transaction.setToAccount(toAccount); + transaction.setAmount(amount); + transaction.setStatus("COMPLETED"); + transaction.setTime(LocalDateTime.now()); + + accountRepository.saveAll(Arrays.asList(from, to)); + transactionRepository.save(transaction); + + } catch (Exception e) { + throw new FinancialException("转账失败", e); + } + } + + @Transactional(isolation = Isolation.READ_COMMITTED) + public BigDecimal getAccountBalance(String accountNumber) { + // 读取已提交的数据 + Account account = accountRepository.findByAccountNumber(accountNumber); + return account.getBalance(); + } +} +``` + +**3. 社交系统** +```java +// 社交系统事务管理 +@Service +public class SocialService { + + @Transactional(isolation = Isolation.REPEATABLE_READ) + public void createPost(PostDTO postDTO) { + // 1. 创建帖子 + Post post = new Post(); + BeanUtils.copyProperties(postDTO, post); + post.setStatus("PUBLISHED"); + post.setCreateTime(LocalDateTime.now()); + + postRepository.save(post); + + // 2. 更新用户统计 + User user = userRepository.findById(post.getUserId()); + user.setPostCount(user.getPostCount() + 1); + user.setLastPostTime(LocalDateTime.now()); + + userRepository.save(user); + } + + @Transactional(isolation = Isolation.READ_COMMITTED) + public List getFeedByUserId(Long userId, int page, int size) { + // 读取已提交的数据 + return postRepository.findByUserIdOrderByCreateTimeDesc(userId, page, size); + } +} +``` + +### 隔离级别调优策略 + +**1. 性能优化** +```java +// 隔离级别性能测试 +@SpringBootTest +public class IsolationLevelPerformanceTest { + + @Autowired + private JdbcTemplate jdbcTemplate; + + @Test + public void testReadUncommittedPerformance() { + // 测试读未提交性能 + long startTime = System.currentTimeMillis(); + + for (int i = 0; i < 10000; i++) { + jdbcTemplate.query("SELECT * FROM user WHERE id = 1", rs -> { + return rs.getString("name"); + }); + } + + long endTime = System.currentTimeMillis(); + System.out.println("READ UNCOMMITTED: " + (endTime - startTime) + "ms"); + } + + @Test + public void testSerializablePerformance() { + // 测试串行化性能 + long startTime = System.currentTimeMillis(); + + for (int i = 0; i < 10000; i++) { + jdbcTemplate.query("SELECT * FROM user WHERE id = 1 FOR UPDATE", rs -> { + return rs.getString("name"); + }); + } + + long endTime = System.currentTimeMillis(); + System.out.println("SERIALIZABLE: " + (endTime - startTime) + "ms"); + } +} +``` + +**2. 动态隔离级别切换** +```java +// 动态隔离级别切换 +@Service +public class DynamicIsolationService { + + @Autowired + private TransactionManager transactionManager; + + public void executeWithIsolation(Runnable operation, Isolation isolation) { + TransactionTemplate template = new TransactionTemplate(transactionManager); + template.setIsolationLevel(isolation); + + template.execute(status -> { + operation.run(); + return null; + }); + } + + // 根据业务场景选择隔离级别 + public void processOrder(Order order) { + if (order.getType() == OrderType.NORMAL) { + // 普通订单使用可重复读 + executeWithIsolation(() -> { + processNormalOrder(order); + }, Isolation.REPEATABLE_READ); + } else if (order.getType() == OrderType.PREMIUM) { + // 高级订单使用串行化 + executeWithIsolation(() -> { + processPremiumOrder(order); + }, Isolation.SERIALIZABLE); + } + } +} +``` + +**3. 事务监控和告警** +```java +// 事务监控 +@Component +public class TransactionMonitor { + + @Autowired + private TransactionManager transactionManager; + + @Scheduled(fixedRate = 60000) // 每分钟监控一次 + public void monitorTransactions() { + // 监控长事务 + List activeTransactions = getActiveTransactions(); + + for (ActiveTransaction tx : activeTransactions) { + if (tx.getDuration() > 300000) { // 5分钟 + monitorService.alert("长事务: " + tx.getSql() + ", 持续时间: " + tx.getDuration()); + } + } + + // 监控死锁 + int deadlockCount = getDeadlockCount(); + if (deadlockCount > 0) { + monitorService.alert("检测到死锁,数量: " + deadlockCount); + } + } + + // 监控隔离级别使用情况 + public Map getIsolationLevelUsage() { + Map result = new HashMap<>(); + + // 查询数据库获取隔离级别使用情况 + String sql = "SELECT variable_name, variable_value " + + "FROM information_schema.global_status " + + "WHERE variable_name IN ('Com_select', 'Com_update', 'Com_delete')"; + + List> stats = jdbcTemplate.queryForList(sql); + + for (Map stat : stats) { + String name = (String) stat.get("variable_name"); + String value = (String) stat.get("variable_value"); + + if ("Com_select".equals(name)) { + result.put(Isolation.READ_COMMITTED, Integer.parseInt(value)); + } else if ("Com_update".equals(name)) { + result.put(Isolation.REPEATABLE_READ, Integer.parseInt(value)); + } else if ("Com_delete".equals(name)) { + result.put(Isolation.SERIALIZABLE, Integer.parseInt(value)); + } + } + + return result; + } +} +``` + +### 最佳实践总结 + +**1. 隔离级别选择原则** +- **READ UNCOMMITTED**:极少使用,主要用于临时性、不重要的数据 +- **READ COMMITTED**:通用场景,Oracle、PostgreSQL默认级别 +- **REPEATABLE READ**:MySQL默认,大多数业务场景适用 +- **SERIALIZABLE**:极端重要的金融场景,性能要求低 + +**2. 事务管理最佳实践** +```java +// 事务管理最佳实践 +@Service +public class BestPracticeService { + + // 1. 合理设置事务边界 + @Transactional + public void processOrder(OrderDTO orderDTO) { + // 单个事务处理完整业务流程 + Order order = createOrder(orderDTO); + processPayment(order); + updateInventory(order); + notifyUser(order); + } + + // 2. 避免长事务 + @Transactional + public void processOrderInParts(OrderDTO orderDTO) { + // 分步骤处理,避免长事务 + Order order = createOrder(orderDTO); + + // 立即提交事务 + commitTransaction(); + + // 异步处理后续流程 + asyncProcessPayment(order); + asyncUpdateInventory(order); + } + + // 3. 正确处理异常 + @Transactional(rollbackFor = {BusinessException.class, RuntimeException.class}) + public void processOrderWithErrorHandling(OrderDTO orderDTO) { + try { + Order order = createOrder(orderDTO); + processPayment(order); + updateInventory(order); + } catch (BusinessException e) { + // 业务异常,记录日志但不回滚 + log.error("业务处理失败", e); + throw e; + } catch (Exception e) { + // 系统异常,回滚 + throw e; + } + } + + // 4. 只读事务优化 + @Transactional(readOnly = true) + public List getUserOrders(Long userId) { + // 只读操作,使用只读事务提高性能 + return orderRepository.findByUserId(userId); + } +} +``` + +**3. 性能优化建议** +- **批量操作**:使用批量插入和更新减少事务数量 +- **异步处理**:耗时操作异步化,避免阻塞主事务 +- **缓存策略**:合理使用缓存减少数据库访问 +- **连接池配置**:优化连接池大小避免连接耗尽 + +## 6. 阿里 P7 加分项 + +### 分布式事务管理 + +**1. Seata 分布式事务** +```java +// Seata 分布式事务配置 +@Configuration +public class SeataConfig { + + @Bean + public GlobalTransactionScanner globalTransactionScanner() { + return new GlobalTransactionScanner( + "demo-service", + "my_test_tx_group"); + } + + @Bean + public XADataSource xaDataSource(DataSource dataSource) { + // 配置XA数据源 + return new AtomikosDataSourceBean(); + } +} + +// Seata 服务调用示例 +@Service +public class OrderServiceWithSeata { + + @Autowired + private OrderRepository orderRepository; + + @Autowired + private InventoryService inventoryService; + + @Autowired + private PaymentService paymentService; + + @GlobalTransactional(timeoutMills = 300000) + public void createOrder(OrderDTO orderDTO) { + // 1. 创建订单 + Order order = createOrder(orderDTO); + + // 2. 减库存 + inventoryService.reduceStock(orderDTO.getItems()); + + // 3. 扣除余额 + paymentService.deductAmount(order.getUserId(), order.getTotalAmount()); + } +} +``` + +**2. RocketMQ 分布式事务** +```java +// RocketMQ 分布式事务消息 +@Service +public class RocketMQTransactionService { + + @Autowired + private RocketMQTemplate rocketMQTemplate; + + public void sendMessageWithTransaction(String topic, String message) { + // 1. 准备本地事务 + Transaction transaction = prepareLocalTransaction(); + + // 2. 发送事务消息 + TransactionSendResult result = rocketMQTemplate.sendMessageInTransaction( + topic, + MessageBuilder.withBody(message.getBytes()).build(), + transaction); + + // 3. 处理结果 + if (result.getSendStatus() != SendStatus.SEND_OK) { + throw new RuntimeException("发送事务消息失败"); + } + } + + @TransactionalEventListener + public void handleTransactionEvent(TransactionEvent event) { + // 处理事务事件 + if (event.isSuccess()) { + commitTransaction(event.getTxId()); + } else { + rollbackTransaction(event.getTxId()); + } + } +} +``` + +### 高级并发控制 + +**1. 乐观锁实现** +```java +// 乐观锁实现 +@Component +public class OptimisticLockExecutor { + + public T executeWithOptimisticLock(Supplier operation, + Supplier fallback, + int maxRetries) { + int retryCount = 0; + RuntimeException lastException = null; + + while (retryCount < maxRetries) { + try { + // 1. 执行操作 + T result = operation.get(); + + // 2. 检查结果 + if (result != null) { + return result; + } + + } catch (OptimisticLockException e) { + lastException = e; + retryCount++; + + // 指数退避 + try { + Thread.sleep(100 * (1 << retryCount)); + } catch (InterruptedException ie) { + Thread.currentThread().interrupt(); + throw new RuntimeException("线程被中断", ie); + } + } + } + + // 重试次数用完,执行回退操作 + return fallback.get(); + } +} + +// 使用示例 +@Service +public class UserService { + + @Autowired + private UserRepository userRepository; + + @Autowired + private OptimisticLockExecutor optimisticLockExecutor; + + public User updateUserWithOptimisticLock(Long userId, String newName) { + return optimisticLockExecutor.executeWithOptimisticLock( + () -> { + User user = userRepository.findById(userId); + if (user == null) { + return null; + } + + user.setName(newName); + user.setVersion(user.getVersion() + 1); + + userRepository.save(user); + return user; + }, + () -> { + // 回退操作 + User user = userRepository.findById(userId); + if (user != null) { + user.setName("Fallback_" + newName); + user.setVersion(user.getVersion() + 1); + userRepository.save(user); + } + return user; + }, + 3 // 最大重试次数 + ); + } +} +``` + +**2. 悲观锁优化** +```java +// 悲观锁优化 +@Service +public class PessimisticLockService { + + @Autowired + private JdbcTemplate jdbcTemplate; + + // 使用间隙锁优化 + public List getUsersWithGapLock(String minAge, String maxAge) { + String sql = "SELECT * FROM user WHERE age BETWEEN ? AND ? FOR UPDATE"; + return jdbcTemplate.query(sql, new Object[]{minAge, maxAge}, + (rs, rowNum) -> { + User user = new User(); + user.setId(rs.getLong("id")); + user.setName(rs.getString("name")); + user.setAge(rs.getInt("age")); + return user; + }); + } + + // 使用Next-Key Lock优化 + public User getUserWithNextKeyLock(Long userId) { + String sql = "SELECT * FROM user WHERE id >= ? LIMIT 1 FOR UPDATE"; + return jdbcTemplate.queryForObject(sql, new Object[]{userId}, + (rs, rowNum) -> { + User user = new User(); + user.setId(rs.getLong("id")); + user.setName(rs.getString("name")); + user.setAge(rs.getInt("age")); + return user; + }); + } +} +``` + +### 性能监控和调优 + +**1. 事务性能监控** +```java +// 事务性能监控 +@Component +public class TransactionPerformanceMonitor { + + @Autowired + private MeterRegistry meterRegistry; + + @Autowired + private TransactionManager transactionManager; + + // 监控事务执行时间 + @Around("execution(* com.example.service..*(..)) && @annotation(transactional)") + public Object monitorTransactionTime(ProceedingJoinPoint joinPoint, Transactional transactional) throws Throwable { + String methodName = joinPoint.getSignature().getName(); + long startTime = System.currentTimeMillis(); + + try { + Object result = joinPoint.proceed(); + + // 记录成功事务 + recordTransactionMetric(methodName, true, System.currentTimeMillis() - startTime); + + return result; + } catch (Exception e) { + // 记录失败事务 + recordTransactionMetric(methodName, false, System.currentTimeMillis() - startTime); + throw e; + } + } + + private void recordTransactionMetric(String methodName, boolean success, long duration) { + // 记录事务指标 + meterRegistry.counter("transaction.count", + "method", methodName, + "success", String.valueOf(success)) + .increment(); + + meterRegistry.timer("transaction.duration", + "method", methodName, + "success", String.valueOf(success)) + .record(duration, TimeUnit.MILLISECONDS); + } + + // 监控隔离级别使用情况 + @Scheduled(fixedRate = 60000) + public void monitorIsolationLevels() { + Map usage = new ConcurrentHashMap<>(); + + // 收集正在执行的事务隔离级别 + collectActiveTransactionIsolationLevels(usage); + + // 上报指标 + usage.forEach((isolation, count) -> { + meterRegistry.gauge("transaction.isolation.count", + "level", isolation.name(), + count); + }); + } +} +``` + +**2. 动态调优策略** +```java +// 动态调优策略 +@Component +public class DynamicOptimizationStrategy { + + @Autowired + private JdbcTemplate jdbcTemplate; + + @Autowired + private TransactionTemplate transactionTemplate; + + // 根据系统负载动态调整隔离级别 + public void adjustIsolationLevelBasedOnLoad() { + SystemLoad load = getSystemLoad(); + + if (load.getCpuUsage() > 80) { + // 高负载时使用较低隔离级别 + setDefaultIsolation(Isolation.READ_COMMITTED); + } else if (load.getMemoryUsage() > 70) { + // 中等负载使用可重复读 + setDefaultIsolation(Isolation.REPEATABLE_READ); + } else { + // 低负载使用串行化 + setDefaultIsolation(Isolation.SERIALIZABLE); + } + } + + // 自动优化事务超时时间 + public void optimizeTransactionTimeout() { + String sql = "SELECT AVG(duration) FROM transaction_metrics WHERE success = 1"; + Double avgDuration = jdbcTemplate.queryForObject(sql, Double.class); + + if (avgDuration != null) { + // 设置超时时间为平均执行时间的2倍 + int timeout = (int) (avgDuration * 2); + setDefaultTransactionTimeout(timeout); + } + } + + // 自适应重试策略 + public T executeWithAdaptiveRetry(Supplier operation, + Class retryException, + int maxRetries) { + int retryCount = 0; + long baseDelay = 100; + + while (retryCount < maxRetries) { + try { + return operation.get(); + } catch (Exception e) { + if (retryException.isInstance(e)) { + retryCount++; + + // 指数退避 + long delay = baseDelay * (1 << retryCount); + try { + Thread.sleep(delay); + } catch (InterruptedException ie) { + Thread.currentThread().interrupt(); + throw new RuntimeException("线程被中断", ie); + } + } else { + throw e; + } + } + } + + throw new RetryExhaustedException("重试次数已用完"); + } +} +``` + +### 总结 + +数据库事务隔离级别是并发控制的核心,需要根据业务场景选择合适的隔离级别: + +1. **理解 ACID 特性**:原子性、一致性、隔离性、持久性 +2. **掌握四种隔离级别**:读未提交、读已提交、可重复读、串行化 +3. **理解 MVCC 机制**:多版本并发控制的基本原理 +4. **识别并发问题**:脏读、不可重复读、幻读 +5. **选择合适隔离级别**:根据业务需求和性能要求 +6. **优化事务性能**:避免长事务、合理使用锁 +7. **监控和调优**:持续监控事务性能,动态调整策略 + +在实际项目中,需要平衡数据一致性和系统性能,选择合适的隔离级别和并发控制策略。 \ No newline at end of file