1. 项目概述
1.1 这个项目到底在解决什么问题
做后端开发的朋友应该都有体会,系统一上规模,最先崩的不是业务代码,而是那些“看似简单”的基础设施问题。用户量从几百涨到几万,单机Tomcat扛不住;多个服务实例同时操作同一份数据,库存超卖、订单重复;上游接口响应慢,线程池直接被打满;高峰流量一来,数据库连接数瞬间耗尽。
这套“后端工程&微服务实战”就是围绕这些真实痛点展开的。它不是一个具体的业务系统,而是一整套后端工程化能力的组合演练,核心覆盖四个技术栈:高并发处理、分布式锁、消息队列、限流熔断。这四个东西几乎是现代后端面试和实战中绕不开的硬骨头,也是从“能写CRUD”走向“能设计系统”的分水岭。
如果你正在做微服务架构改造,或者准备大厂后端面试,又或者单纯想搞明白“分布式锁到底怎么实现才靠谱”“消息队列重复消费怎么解决”“Sentinel限流和Hystrix熔断有什么区别”,这套内容值得花时间完整过一遍。
1.2 核心问题拆解与整体技术选型
我把整个工程拆成四个核心模块来看,每个模块背后都对应一类经典问题:
| 模块 | 解决的核心问题 | 关键技术选型 |
|---|---|---|
| 高并发处理 | 单机线程池打满、数据库连接耗尽 | 线程池参数调优、异步化、批量处理 |
| 分布式锁 | 多实例并发操作同一资源导致数据不一致 | Redis分布式锁、Redisson |
| 消息队列 | 服务间异步解耦、削峰填谷、最终一致性 | RocketMQ/Kafka、Redis Stream |
| 限流熔断 | 上游突发流量打垮下游、系统雪崩 | Sentinel、Resilience4j |
选型上我倾向于Spring Cloud Alibaba这套生态,原因后面细说。但更关键的是,这套实战里每个模块都不是孤立存在的,它们之间是互相配合的关系:分布式锁保证数据一致性,消息队列做异步解耦和削峰,限流熔断保护系统不被突发流量打垮,高并发处理能力贯穿始终。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 高并发处理:从线程池到系统瓶颈分析
2.1 线程池参数调优的底层逻辑
高并发场景下,线程池是最基础也是最容易被用错的工具。很多同学线程池参数全靠抄,corePoolSize=10、maxPoolSize=20、queueCapacity=100,看着挺合理,实际一压测就出问题。
我个人的经验是,线程池参数必须结合业务场景来定,核心看三个指标:QPS、单个请求的平均耗时、可接受的排队时间。比如一个订单创建接口,QPS峰值2000,单次请求平均耗时50ms,算下来需要的并发线程数大约在2000 * 0.05 = 100左右。如果核心线程数配10,队列再长也没用,大部分请求会直接排队超时。
另一个容易被忽略的点是拒绝策略。默认的AbortPolicy直接抛异常,在流量高峰时体验极差。我更推荐自定义拒绝策略,把被拒绝的任务写入MQ或本地缓存,后续异步补偿处理,既能保证请求不丢,又能削峰。
2.2 高并发场景下的数据库瓶颈与优化思路
线程池只是第一层防护,真正的瓶颈往往在数据库。2000 QPS打到单库MySQL上,连接池先扛不住,然后就是慢查询拖垮整个服务。
实战中我会分三步走:
- 索引优化:先看慢查询日志,找出全表扫描的SQL,针对where条件、排序字段建立组合索引
- 读写分离:读多写少的业务场景,主库只写,从库只读,分摊压力
- 缓存兜底:热点数据扔进Redis,缓存穿透、击穿、雪崩三个问题单独处理
这里说一个我踩过的坑:Redis缓存更新策略用的是“先删缓存再更新数据库”,结果并发场景下两个请求同时进来,一个读一个写,读请求把旧数据又塞回缓存了,导致数据不一致。后来改成“先更新数据库,再删缓存”,并且给缓存设置合理的过期时间,情况才稳定下来。
2.3 压测工具与瓶颈定位方法
调优不能靠猜,一定要用数据说话。我常用的压测工具是JMeter和wrk。JMeter适合复杂场景,可以模拟多用户并发操作完整业务流程;wrk适合接口级的简单压测,单机就能压出很高的QPS。
压测的核心不只是看QPS和RT,更重要的是观察系统的瓶颈点。CPU打满说明计算密集或者线程上下文切换太频繁;内存飙升说明可能有大对象频繁GC;数据库连接池用完说明SQL执行太慢或者连接没释放。每一步排查都要有对应的监控数据支撑,不然就是盲调。
3. 分布式锁:多实例并发下的数据一致性保障
3.1 为什么需要分布式锁,以及常见的误用场景
单机环境下用synchronized或者Lock就能解决并发问题,但微服务架构下,同一个服务会部署多个实例,每个实例有自己的JVM锁,互不相通。这时候要保证“同一时间只有一个实例能操作同一份数据”,就必须引入分布式锁。
最常见的业务场景是防重复提交:用户快速点击下单按钮,请求被负载均衡分发到不同服务实例,如果不用分布式锁,两个请求就可能同时创建出两条订单记录。还有就是定时任务,分布式环境下多个实例同时执行同一个定时任务,重复处理数据是必然的,必须用分布式锁保证只有一个实例在跑。
3.2 Redis分布式锁的四种经典实现方案演进
分布式锁的方案不少,但最主流的还是基于Redis。我梳理了四种方案,从简单到可靠,大家可以根据业务场景选型:
方案一:SETNX + 手动释放
java复制// 获取锁
Boolean locked = redisTemplate.opsForValue().setIfAbsent("lock:order:" + orderId, "1");
// 释放锁
redisTemplate.delete("lock:order:" + orderId);
这个方案最简单,但问题一堆:获取锁的客户端挂了,锁永远不会释放,形成死锁。所以这个方案只能用于demo演示,绝对不要上生产。
方案二:SETNX + 过期时间
java复制// 获取锁,5秒自动过期
Boolean locked = redisTemplate.opsForValue().setIfAbsent("lock:order:" + orderId, "1", Duration.ofSeconds(5));
解决了死锁问题,但又有新坑:业务执行时间超过5秒,锁自动释放了,另一个线程进来拿到锁,两个线程同时执行,锁就形同虚设。
方案三:SETNX + 过期时间 + 唯一标识
java复制// 获取锁
