从“手艺人”的笨功夫出发,用真实项目场景重构认知:不谈空泛理论,只讲落地细节。库存管理、订单系统、高并发、服务降级、链路追踪……在这里,每个技术点都带着血和汗的教训。
立即开启实战之旅 →别被“架构模式”吓退——真正的成长,始于第一个能跑通的接口。
刚搭好 Spring Boot 的脚手架,我第一反应不是翻文档,而是直接扔进 IDEA 敲代码。你可能会说这太“野路子”,但实战中——能跑通的代码才是活代码。
我见过太多人把项目做得像论文一样精致,结果上线那天就像孤儿院——没人管、没人懂、没人修。真正的高手,懂得用“笨办法”打基础:日志打满、断言前置、异常显性化。
别老跟我提“面向对象”要么“面向过程”——那是给硕士论文预备的。在真实业务里,我更喜欢:数组当数组,字符串当字符串。
以一个简易电商订单系统为例,初期我们只实现:用户下单 → 库存扣减 → 订单生成 → 支付回调。但上线后发现,高并发下库存超卖频发。
解决方案不是立刻上分布式事务,而是先做三件事:
SELECT ... FOR UPDATE 锁行,而非锁表很多开发者以为库存就是简单减一,但真实场景中,以下问题频发:
我见过一个项目,需求评审后花两周画 UML 图、画类图、画时序图……最后上线时,需求早已过时。
真正的节奏应该是:
在千万级订单量面前,优雅的模型不如一行高效的数据处理。
别迷信“面向对象万能论”——在高频调用的接口中,性能优先于抽象。
比如秒杀活动中的“用户抢购记录”,初期用 List 存储所有用户ID,但压测发现内存爆炸——每个请求都创建新 List。
优化方案:改用 ConcurrentHashMap<Long, Boolean>,值只存 true 表示已抢购。
库存管理中,别用对象封装再操作——直接用 Map<String, Integer> 存 SKU-ID 与库存映射,查询效率提升 3 倍以上。
订单创建后,需发短信、扣库存、记日志——这些非核心操作,别同步执行!改用 LinkedBlockingQueue 或 RocketMQ 异步消费。
初期:所有操作同步执行,用户反馈“下单慢”
优化1:库存扣减改用 Redis 预减 + Lua 脚本保证原子性
优化2:日志、短信异步化,主流程耗时从 850ms → 120ms
优化3:引入事件总线(Spring Event),解耦更彻底
稳定性不是“不出错”,而是“错了也能扛住”。
很多项目在 Controller 层用 ThreadLocal 存用户信息,但忘记 remove()——导致线程复用时,A 用户看到 B 用户数据!
@Around 切面中统一 clear(),避免遗漏。
库存扣减时,若用 SELECT ... FOR UPDATE,记得:
① 必须在事务中执行;
② 索引字段必须加索引(否则锁表);
③ 避免长事务导致锁等待。
别让错误在深层调用中才抛出!在方法入口处就校验参数:空值、范围、状态合法性。
好的日志应包含:
• 时间戳(精确到毫秒)
• 请求ID(全链路追踪)
• 用户ID(可选脱敏)
• 关键参数
• 操作结果(成功/失败/异常)
• 耗时
示例:[2024-05-10 14:23:01.567] [req-abc123] [user=8827] createOrder skuId=1001 qty=2 cost=120ms SUCCESS
当服务挂了,别让用户看到“500 错误”——用降级策略守住体验底线。
微信/支付宝回调时,必须做三件事:
① 查本地订单状态(是否已支付)
② 校验签名
③ 更新状态 + 发通知
核心接口必须配置熔断策略:
• 请求失败率 > 50% → 熔断 30 秒
• 每秒请求数 > 100 → 限流(拒绝或排队)
/api/order/create别信直觉——用数据定位瓶颈,才是工程师的科学精神。
iostat -x 1 查看 %util)活动前,用 INFO memory 查 Redis 内存使用。若发现 used_memory_peak_human 接近上限,立即扩容或优化数据结构。
String 存("100"),而非 Hash——减少元数据开销。
开启 MySQL 慢查询日志(slow_query_log=ON),定位耗时 > 1s 的 SQL。
常见问题:
• 未走索引(EXPLAIN 查看)
• JOIN 表过多
• SELECT 导致回表
接入 Prometheus + Grafana,监控以下核心指标:
• 请求 QPS / P99 / P999
• 错误率(HTTP 5xx)
• JVM 堆内存使用
• 数据库连接池活跃数
• Redis 命中率
日志显示:订单创建接口 P99 = 1200ms,错误率 5%
通过 Arthas 监控,发现 orderMapper.insert 耗时 800ms+
数据库索引缺失 + 事务过大(包含 3 个表操作)
P99 = 180ms,错误率 < 0.1%
哪怕张三重复点击 10 次“提交”,系统也必须只生成一张订单。
在支付记录表中加唯一索引:UNIQUE KEY uk_transaction_no (transaction_no),重复回调时数据库自动拒绝。
库存更新时加版本号:UPDATE stock SET stock=stock-1, version=version+1 WHERE id=1001 AND version=5,更新失败则重试。
用户删文件 ≠ 对象存储立即删——这背后藏着数据一致性难题。
对象存储(如 OSS/S3)采用 CC(并发控制)机制:删除后,若已有请求正在读取,仍需返回旧数据。直接物理删除会导致:
• 用户看到 404 错误
• 下载链接失效(但用户可能已保存链接)
is_deleted TINYINT(1) DEFAULT 0WHERE is_deleted = 0在对象存储控制台配置:
• 前缀:deleted/(或用标签 status=deleted)
• 过期时间:30 天后自动删除
这样,即使数据库标记为删除,OSS 仍保留文件 30 天——既满足业务需求,又规避合规风险。
deleted/ 目录,并更新数据库 storage_path。
好代码自己会说话——日志要能定位到“第几行代码出错”。
log.info("用户登录: {}", user);status=PAID、code=STOCK_NOT_ENOUGHcost=120ms线上问题定位神器:
• 监控方法耗时:monitor -c 5 com.example.OrderController createOrder
• 查看方法入参:watch com.example.OrderController createOrder '{params, returnObj}'
| 级别 | 用途 | 示例 |
|---|---|---|
| ERROR | 系统异常(需人工介入) | 数据库连接失败、支付回调签名错误 |
| WARN | 异常但可恢复 | 库存不足、用户重复提交 |
| INFO | 关键业务节点 | 订单创建成功、用户登录 |
| DEBUG | 开发调试 | SQL 执行详情、缓存命中情况 |
别等用户发现bug——在测试环境把问题都暴露出来。
重点关注:
• CPU 使用率(>80% 需优化)
• 内存泄漏(GC 频繁?)
• 数据库连接池耗尽
• 错误率是否 > 0.1%
top + htop + jstat -gcutil 1000