From 943eec2ed78ca9f6efe4e758fe675922069c094f Mon Sep 17 00:00:00 2001 From: CyC2018 Date: Sat, 31 Oct 2026 00:23:19 +0800 Subject: [PATCH] =?UTF-8?q?Update=20=E5=AD=A6=E4=B9=A0=E8=B7=AF=E7=BA=BF?= =?UTF-8?q?=20-=20Java=20=E5=90=8E=E7=AB=AF.md?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 新增订单与支付一天,补充算法题与题解链接,更新延迟消息关单的参考答案。 --- notes/学习路线 - Java 后端.md | 47 +++++++++++++++++++++++++++++------ 1 file changed, 39 insertions(+), 8 deletions(-) diff --git a/notes/学习路线 - Java 后端.md b/notes/学习路线 - Java 后端.md index b9b5a65c..dd3f69ef 100644 --- a/notes/学习路线 - Java 后端.md +++ b/notes/学习路线 - Java 后端.md @@ -81,6 +81,7 @@ * [第 64 天:Docker](#第-64-天docker) * [第 65 天:Kubernetes](#第-65-天kubernetes) * [第 66 天:系统设计题的思路与即时通讯](#第-66-天系统设计题的思路与即时通讯) + * [第 67 天:订单与支付](#第-67-天订单与支付) @@ -180,9 +181,10 @@ | [61](#第-61-天秒杀与抢红包) | 秒杀与抢红包 | 系统设计基础:秒杀系统 | 76. 最小覆盖子串 | | [62](#第-62-天海量数据短链与-feed-流) | 海量数据、短链与 Feed 流 | 系统设计基础:海量数据处理~Feed 流 | 25. K 个一组翻转链表 | | [63](#第-63-天高并发系统的设计) | 高并发系统的设计 | 系统设计基础:高并发系统的设计 | 23. 合并 K 个升序链表 | -| [64](#第-64-天docker) | Docker | Docker:解决的问题~镜像与容器 | 4. 寻找两个正序数组的中位数 | -| [65](#第-65-天kubernetes) | Kubernetes | Docker:Kubernetes | 124. 二叉树中的最大路径和 | -| [66](#第-66-天系统设计题的思路与即时通讯) | 系统设计题的思路与即时通讯 | 系统设计基础:系统设计题的思路~即时通讯 | 72. 编辑距离 | +| [64](#第-64-天docker) | Docker | Docker:解决的问题~镜像与容器 | 32. 最长有效括号 | +| [65](#第-65-天kubernetes) | Kubernetes | Docker:Kubernetes | 4. 寻找两个正序数组的中位数 | +| [66](#第-66-天系统设计题的思路与即时通讯) | 系统设计题的思路与即时通讯 | 系统设计基础:系统设计题的思路~即时通讯 | 124. 二叉树中的最大路径和 | +| [67](#第-67-天订单与支付) | 订单与支付 | 系统设计基础:订单与支付 | 72. 编辑距离 | ## 四、Java 基础 @@ -1636,7 +1638,7 @@ Web 页面请求过程把前几天的内容串了起来。攻击技术中 XSS、 预期:maps 中 r-xp 的可执行文件区域是代码段,之后 rw-p 的是数据段和 BSS,[heap] 紧随其后,1 MB 的 malloc 和共享库在中间的映射区,[stack] 在最高处,再运行一次起始地址都变,上下顺序不变。 -算法(回溯与搜索,中等):[22. 括号生成](https://leetcode.cn/problems/generate-parentheses/)。 +算法(回溯与搜索,中等):[22. 括号生成](https://leetcode.cn/problems/generate-parentheses/),题解见[括号生成](Leetcode%20题解%20-%20搜索.md#16-括号生成)。 明天:第 48 天,I/O 多路复用(Socket:I/O 模型~I/O 复用)。 @@ -1968,7 +1970,7 @@ RPC 一节对比了它和 HTTP 的取舍。雪花算法要注意时钟回拨; 1. 消息队列只保证单个队列(Kafka 的分区)内有序;发送端按业务 Key(如订单号)把同一业务的消息发到同一个队列;接收端一个队列只由一个线程顺序消费,Kafka 中一个分区只分给组内一个消费者,RabbitMQ 用单个消费者或单活跃消费者;生产者重试可能让后发的消息先写入,Kafka 默认开启的幂等生产者用序号避免了这种乱序;分区数改变后同一 key 可能落到别的分区,要预先规划分区数。 2. 先区分生产突增(峰值后可自然消化)还是消费变慢(下游变慢、反复重试、频繁重平衡),先修复消费变慢的原因,失败消息转入死信队列;扩容消费者(Kafka 普通消费者组、RocketMQ 4.x 中超过分区或队列数的消费者会空闲),或批量写库、线程池并行(同一 key 交给同一线程);分区不够时转发到分区更多的新主题,不重要的消息降级跳过;积压接近保留时间(Kafka 默认 7 天)前先转存。 -3. 下单时发一条 30 分钟后投递的延迟消息,到期后消费者用条件更新取消订单(WHERE status = 'UNPAID'),重复或晚到的消息不会取消已支付的订单,要求高的业务再用低频扫描兜底;RocketMQ 4.x 有 30m 延迟级别,5.x 可指定任意时间,RabbitMQ 用 TTL 加死信队列;也可以用 Redis 有序集合,分值为到期时间,后台线程取到期成员,多实例用 ZREM 返回 1 决定由谁处理。 +3. 下单时发一条 30 分钟后投递的延迟消息,到期后消费者用条件更新取消订单(WHERE status = 'UNPAID'),重复或晚到的消息不会取消已支付的订单,要求高的业务再用低频扫描兜底;用户已付款、回调还没到时本地仍是待支付,所以取消前要先向支付平台关单,平台返回已支付就按支付成功处理;RocketMQ 4.x 有 30m 延迟级别,5.x 可指定任意时间,RabbitMQ 用 TTL 加死信队列;也可以用 Redis 有序集合,分值为到期时间,后台线程取到期成员,多实例用 ZREM 返回 1 决定由谁处理。 @@ -2161,7 +2163,7 @@ RPC 一节对比了它和 HTTP 的取舍。雪花算法要注意时钟回拨; 预期:多阶段构建的镜像只含 JRE 和 jar,约 236 MB,在 Maven 镜像中单阶段构建并运行的镜像约 680 MB,多出了 JDK、Maven 和下载的依赖。 -算法(二分查找,困难):[4. 寻找两个正序数组的中位数](https://leetcode.cn/problems/median-of-two-sorted-arrays/),题解见[两个有序数组的中位数](Leetcode%20题解%20-%20二分查找.md#7-两个有序数组的中位数)。 +算法(栈和队列,困难):[32. 最长有效括号](https://leetcode.cn/problems/longest-valid-parentheses/),题解见[最长有效括号](Leetcode%20题解%20-%20动态规划.md#3-最长有效括号)。 明天:第 65 天,Kubernetes(Docker:Kubernetes)。 @@ -2186,13 +2188,13 @@ Kubernetes 重点看 Pod 的创建过程、Service 和 Ingress 的作用,以 -算法(二叉树,困难):[124. 二叉树中的最大路径和](https://leetcode.cn/problems/binary-tree-maximum-path-sum/)。 +算法(二分查找,困难):[4. 寻找两个正序数组的中位数](https://leetcode.cn/problems/median-of-two-sorted-arrays/),题解见[两个有序数组的中位数](Leetcode%20题解%20-%20二分查找.md#7-两个有序数组的中位数)。 明天:第 66 天,系统设计题的思路与即时通讯(系统设计基础:系统设计题的思路~即时通讯)。 ### 第 66 天:系统设计题的思路与即时通讯 -最后两章是开放的设计题。点赞系统演示了回答的顺序:先问清需求、估算规模,再从简单方案出发找瓶颈;即时通讯的重点是消息怎样做到不丢、不重、不乱序,以及一台机器能维持多少个长连接。 +这两章是开放的设计题。点赞系统演示了回答的顺序:先问清需求、估算规模,再从简单方案出发找瓶颈;即时通讯的重点是消息怎样做到不丢、不重、不乱序,以及一台机器能维持多少个长连接。 阅读:《系统设计基础》从[系统设计题的思路](系统设计基础.md#十三系统设计题的思路)读到[即时通讯](系统设计基础.md#十四即时通讯)。 @@ -2215,6 +2217,35 @@ Kubernetes 重点看 Pod 的创建过程、Service 和 Ingress 的作用,以 预期:第二次 INSERT 报错 ERROR 1062 Duplicate entry '...' for key 'video_like.PRIMARY';INSERT IGNORE 不报错,影响 0 行;EXPLAIN 中 type 为 ref,按 user_id 等值查找,扫描的行数约等于这个用户点过赞的视频数。 +算法(二叉树,困难):[124. 二叉树中的最大路径和](https://leetcode.cn/problems/binary-tree-maximum-path-sum/)。 + +明天:第 67 天,订单与支付(系统设计基础:订单与支付)。 + +### 第 67 天:订单与支付 + +本章把条件更新、延迟消息和幂等用在订单与支付上,难点是订单服务和支付平台之间没有共同的事务。超时关单的三步和关单与付款同时发生的时序图要细读;对账是前面各环节都漏掉时的兜底;转账一节回到数据库的行锁,可以和秒杀中防止超卖的条件更新对照着看。 + +阅读:《系统设计基础》的[订单与支付](系统设计基础.md#十五订单与支付)。 + +自测: + +1. 订单超时未支付要自动关闭,关单时用户恰好付了款,怎样保证两边一致?(见[超时关单](系统设计基础.md#超时关单)) +2. 支付回调重复到达或并发到达时,怎样保证只发一次货?(见[支付回调与重复支付](系统设计基础.md#支付回调与重复支付)) +3. A 给 B 转账的同时 B 给 A 转账,怎样避免死锁,又不让余额扣成负数?(见[转账与余额扣减](系统设计基础.md#转账与余额扣减)) + +
+参考要点 + +1. 关单以支付平台为准,分三步:先用条件更新把订单从待支付改为关单中,影响 0 行就按当前状态处理;再调用平台的关单接口,成功或订单已关闭时改为已关闭,在同一个事务中回补库存;关单失败时查单,已支付就按支付成功处理(与回调走同一段代码),订单不存在可以关闭,结果不明保持关单中,由扫描任务稍后重做;只改本地状态的话,用户打开旧的支付页面仍能付款;下单时把同一个截止时间作为 time_expire 传给平台,否则平台默认 7 天内都能支付;回调在关单中到达时仍然有效。 +2. 先验签,核对商户号、订单号、金额与本地订单一致,并确认交易状态是支付成功,再把三件事放在同一个事务中:以交易号为主键插入去重表,重复的通知报 1062 后回滚;用 UPDATE ... WHERE status IN ('UNPAID', 'CLOSING') 改为已支付,影响 1 行才继续;写入发货消息;重复的通知也要应答成功,否则平台会继续重发;先查状态再更新的写法,同一笔通知并发到达 10 次时会发 10 次货。 +3. 按账户 ID 从小到大加锁,两个方向的转账都先锁 id 小的行,就不会互相等待(实验中每 480 笔的死锁从约 70~100 次降为 0),只有涉及同一账户的转账在这一行的锁上排队,不是串行化;余额判断写在条件中:UPDATE account SET balance = balance - 100 WHERE id = 1 AND balance >= 100,影响 0 行说明余额不足、回滚,先读出余额在应用中算好再写回会互相覆盖;客户端生成的请求号作主键,死锁回滚或超时重试也只成功一次;每次变动写流水,用期初余额加流水合计核对当前余额。 + +
+ +动手:阅读[附录 A23](系统设计基础.md#a23-超时关单与支付回调)的输出,说明只改本地状态的写法为什么会出现平台已付、本地已关,先向平台关单、失败只等回调的写法又为什么会有订单停在待支付。 + +预期:只改本地状态的写法,到期时把订单改为已关闭,但平台上的交易仍然可以支付,用户在关单前后付款时就出现平台已付、本地已关(300 轮中二百多轮),只能退款;先向平台关单、失败只等回调的写法,关单因用户已付款而失败后只等回调,回调又有 10% 丢失,订单就停在待支付(300 轮中二十多轮),要靠查单或对账补上;按三步的写法关单失败时查单,300 轮全部一致。 + 算法(动态规划,困难):[72. 编辑距离](https://leetcode.cn/problems/edit-distance/),题解见[编辑距离](Leetcode%20题解%20-%20动态规划.md#2-编辑距离)。 综合题:设计一个秒杀系统:从页面、网关、服务、缓存、消息队列到数据库,每一层分别做什么来扛住流量、防止超卖?出现故障时怎样降级?(见[秒杀系统](系统设计基础.md#六秒杀系统)、[读路径的逐层分流](系统设计基础.md#读路径的逐层分流)、[限流、降级与熔断](系统设计基础.md#限流降级与熔断))