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复制// 获取锁,value存唯一标识
String requestId = UUID.randomUUID().toString();
Boolean locked = redisTemplate.opsForValue().setIfAbsent("lock:order:" + orderId, requestId, Duration.ofSeconds(5));
// 释放锁时校验是不是自己的
if (requestId.equals(redisTemplate.opsForValue().get("lock:order:" + orderId))) {
redisTemplate.delete("lock:order:" + orderId);
}
这个方案解决了锁误删问题,删除前先校验value是不是自己的,但删除操作不是原子性的,极端情况下还是会出问题。
方案四:Redisson分布式锁
java复制RLock lock = redissonClient.getLock("lock:order:" + orderId);
try {
// 尝试加锁,最多等待3秒,锁自动释放时间30秒
if (lock.tryLock(3, 30, TimeUnit.SECONDS)) {
// 业务逻辑
}
} finally {
if (lock.isHeldByCurrentThread()) {
lock.unlock();
}
}
Redisson解决了一个大难题:锁自动续期。它内部有个看门狗机制,默认情况下锁的有效期是30秒,业务没执行完,它会自动帮你续期到业务结束,避免锁提前过期。这是我认为生产环境最靠谱的方案。
3.3 Redisson看门狗机制原理与源码解析
看门狗(WatchDog)机制是Redisson的精髓。它的工作流程是这样的:客户端拿到锁之后,会启动一个定时任务,每隔10秒(锁过期时间的三分之一)检查一次锁是否还被当前线程持有,如果持有,就把过期时间重置为30秒。
这个机制保证了一个核心原则:只要业务没执行完,锁就不会被提前释放。如果当前实例宕机了,看门狗任务也就停了,锁会在30秒后自动过期释放,不会造成死锁。
注意:使用Redisson时,如果指定了leaseTime(锁自动释放时间),看门狗机制就不会生效。所以能不指定leaseTime就不指定,让看门狗帮你兜底。
3.4 分布式锁的常见面试题汇总
面试中分布式锁的高频考点我整理了一下,基本跑不出这几个问题:
- Redis分布式锁的缺陷是什么?主从切换时锁丢失怎么办?
- Redisson看门狗机制是怎么实现的?底层原理是什么?
- 什么是可重入锁?Redisson怎么实现可重入?
- 分布式锁和事务放在一起使用时,锁释放和事务提交的顺序怎么处理?
- ZooKeeper分布式锁和Redis分布式锁的区别和适用场景?
其中锁和事务的顺序问题很多人会忽略。正确的做法是:先提交事务,再释放锁。如果先释放锁再提交事务,释放锁的瞬间其他线程就进来了,但前一个事务还没提交,数据还是旧值,一样会产生并发问题。
4. 消息队列:异步解耦、削峰填谷与最终一致性
4.1 消息队列的三大核心作用拆解
消息队列不是银弹,但它能解决三类非常典型的问题:
第一,异步解耦。以订单系统为例,用户下单后需要发送短信通知、增加积分、更新物流信息。同步调用的话,每个环节都要等待响应,接口耗时可能从50ms飙到800ms。通过MQ把消息发出去,下游服务异步消费,接口响应时间大大缩短,服务间耦合度也降低了。
第二,削峰填谷。秒杀场景下瞬时流量可能达到平时的几十倍,直接打到数据库肯定扛不住。MQ作为缓冲层,先把请求接收下来,下游按自己的消费能力慢慢处理,避免了流量尖峰对系统的冲击。
第三,最终一致性。跨服务的分布式事务不好处理,通过MQ实现最终一致性是业界常用方案。订单服务本地事务提交后发消息,库存服务消费消息扣减库存,整个过程是异步的,但最终两边数据能达到一致。
4.2 RocketMQ核心概念与顺序消息、延迟消息实现
RocketMQ是阿里开源的中间件,在国内互联网公司用得非常多。它的核心概念包括:Producer(生产者)、Consumer(消费者)、Broker(消息服务器)、Topic(主题)、MessageQueue(消息队列)、ConsumerGroup(消费组)。
实际业务中两个能力特别常用:
顺序消息:比如订单状态变更(待支付、已支付、已发货、已完成),必须按照顺序消费,顺序乱了业务就错了。RocketMQ实现顺序消息的方式是生产者端把同一订单的消息发送到同一个MessageQueue,消费者端用单线程消费这个队列里的消息。
java复制// 生产者端:用订单ID作为选择key,保证同一订单的消息进同一个队列
SendResult sendResult = producer.send(message, (mqs, msg, arg) -> {
Long orderId = (Long) arg;
int index = (int) (orderId % mqs.size());
return mqs.get(index);
}, orderId);
延迟消息:比如订单支付超时自动关闭,通常延迟30分钟处理。RocketMQ支持18个级别的延迟消息,但如果我们需要的延迟时间不在预设级别里,就要用定时消息方案去实现:
java复制// 发送延迟消息,延迟级别18对应2小时
Message message = new Message("ORDER_TOPIC", tags, body);
message.setDelayTimeLevel(18);
producer.send(message);
如果业务需要任意时间的延迟,一个更通用的方案是:先投递到延迟队列,同时用定时任务扫描,到了指定时间再把消息投递到真正的业务队列。
4.3 Kafka的适用场景与Spring Boot整合实战
Kafka和RocketMQ各有擅长领域。Kafka吞吐量极高,在日志收集、用户行为埋点、大数据计算链路中几乎是标配。它和Spring Boot整合非常简单:
java复制@Configuration
public class KafkaConfig {
@Bean
public ProducerFactory<String, String> producerFactory() {
Map<String, Object> props = new HashMap<>();
props.put(ProducerConfig.BOOTSTRAP_SERVERS_CONFIG, "localhost:9092");
props.put(ProducerConfig.KEY_SERIALIZER_CLASS_CONFIG, StringSerializer.class);
props.put(ProducerConfig.VALUE_SERIALIZER_CLASS_CONFIG, StringSerializer.class);
// 开启幂等
props.put(ProducerConfig.ENABLE_IDEMPOTENCE_CONFIG, true);
return new DefaultKafkaProducerFactory<>(props);
}
@Bean
public KafkaTemplate<String, String> kafkaTemplate() {
return new KafkaTemplate<>(producerFactory());
}
}
发送消息和消费消息的代码也很直白:
java复制// 发送消息到topic,key用于分区路由
kafkaTemplate.send("USER_LOGIN_TOPIC", userId, jsonString);
// 消费消息
@KafkaListener(topics = "USER_LOGIN_TOPIC", groupId = "login-log-group")
public void onLogin(String message) {
// 处理消息
}
4.4 消息队列重复消费问题与幂等设计
消息重复消费是MQ使用中最常见的问题。原因很多:生产者重试导致重复消息、消费者处理完还没来得及提交offset服务就挂了、消费者端重复投递等等。MQ无法保证消息只被消费一次,只能靠消费端做幂等处理。
幂等处理的常用方案有三种:
- 唯一ID判重:每条消息带上业务唯一ID,消费时先去Redis查询这个ID是否已处理过,处理过就直接返回
- 数据库唯一约束:利用数据库表的唯一索引,重复插入直接捕获异常跳过
- 状态机校验:订单状态从“待支付”到“已支付”是单向流转,重复消费时状态不匹配,直接丢弃
我实际项目中最常用的是Redis判重 + 数据库唯一约束双保险:
java复制@Override
public void onMessage(OrderMessage message) {
String key = "msg:processed:" + message.getMsgId();
// 尝试写入,写入成功说明第一次处理,写入失败说明重复消息
Boolean first = redisTemplate.opsForValue().setIfAbsent(key, "1", Duration.ofMinutes(10));
if (Boolean.FALSE.equals(first)) {
log.warn("重复消息,直接跳过:{}", message.getMsgId());
return;
}
// 业务处理
processOrder(message);
}
4.5 Redis Stream拉取模式实战
Redis Stream是Redis 5.0引入的消息队列能力,轻量级场景下完全可以替代MQ,省去维护一套独立中间件的成本。它支持消费者组、消息确认、消息持久化,基本满足了业务对消息队列的核心诉求。
使用Spring Boot整合Redis Stream时,拉取模式的核心代码如下:
java复制// 生产者:创建stream并添加消息
redisTemplate.opsForStream().add(
ObjectRecord.create("ORDER_STREAM", orderMessage)
);
// 消费者:手动拉取消息
List<MapRecord<String, Object, Object>> messages = redisTemplate.opsForStream()
.read(Consumer.from("order-group", "consumer-1"),
StreamReadOptions.empty().count(10).block(Duration.ofSeconds(5)),
StreamOffset.create("ORDER_STREAM", ReadOffset.lastConsumed())
);
这里有一个关键点:消费者拉取消息后,一定要手动确认(ack),否则消息会一直停留在pending列表里,下次拉取还会重复获取。同时要做好异常处理,消息处理失败时不要把消息重新放回队列死循环,可以记录到死信列表人工处理。
5. 限流熔断:防止系统雪崩的最后一道防线
5.1 限流的四种算法与适用场景对比
限流算法是后端开发的基础功,面试必问。四种主流算法各有优劣:
| 算法 | 原理 | 优点 | 缺点 | 适用场景 |
|---|---|---|---|---|
| 固定窗口 | 单位时间窗口内计数,超过阈值就拒绝 | 实现简单 | 窗口临界点突发流量 | 简单场景 |
| 滑动窗口 | 细分子窗口,滑动统计 | 解决临界突发问题 | 内存占用稍高 | 接口限流 |
| 漏桶算法 | 请求先进桶,恒定速率流出 | 流量整形平滑 | 无法应对突发流量 | 保护下游系统 |
| 令牌桶算法 | 匀速往桶里放令牌,请求必须拿到令牌才能执行 | 允许一定突发流量 | 实现稍复杂 | 接口限流首选 |
我自己最常用的是令牌桶算法,因为它在限制平均速率的同时允许一定程度的突发,用户体验更好。Google Guava的RateLimiter就是令牌桶的经典实现:
java复制// 每秒生成5个令牌
RateLimiter rateLimiter = RateLimiter.create(5.0);
if (rateLimiter.tryAcquire()) {
// 执行业务逻辑
} else {
// 返回"系统繁忙,请稍后重试"
}
5.2 Sentinel核心概念与流控规则配置实战
Sentinel是阿里开源的流量防卫组件,能处理限流、熔断、系统保护三类问题。相比Hystrix,Sentinel的限流能力更强,而Hystrix已经进入维护模式,新项目建议直接选Sentinel。
Sentinel整合Spring Boot非常简单,引入依赖后在方法上加@SentinelResource注解就行:
java复制@Service
public class OrderService {
// 设置资源名和兜底方法
@SentinelResource(value = "createOrder", blockHandler = "createOrderBlock", fallback = "createOrderFallback")
public Order createOrder(OrderCreateRequest request) {
return doCreateOrder(request);
}
// 限流/熔断时进入这个方法
public Order createOrderBlock(OrderCreateRequest request, BlockException ex) {
throw new BizException("系统繁忙,请稍后重试");
}
// 业务异常时进入这个方法
public Order createOrderFallback(OrderCreateRequest request, Throwable t) {
throw new BizException("下单服务异常,请联系客服");
}
}
流控规则可以直接在Sentinel控制台上配置。基本流控参数有三个核心维度:QPS阈值、流控模式(直接/关联/链路)、流控效果(快速失败/预热/排队等待)。
这里说一个实践经验:流控阈值不能拍脑袋定,一定要基于压测数据。比如这个接口压测下来单机QPS上限是800,那生产环境限流阈值设置在600是比较稳妥的,留出一定的系统冗余。
5.3 熔断降级的触发条件与动态配置策略
熔断机制解决的是服务调用链雪崩问题。A服务调用B服务,B服务调用C服务,C服务挂了,如果A、B不知道及时熔断,就会拿有限的线程资源去等一个注定失败的响应,最终所有服务都拖垮。
熔断器有三个状态:CLOSED(关闭)、OPEN(打开)、HALF_OPEN(半开)。正常情况下是关闭状态,失败率达到阈值后熔断器打开,快速失败;经过一段冷却时间后进入半开状态,放少量请求试探服务是否恢复,恢复了就关闭熔断器,没恢复就继续打开。
Sentinel的熔断配置核心参数如下:
java复制@Configuration
public class SentinelRuleConfig {
@PostConstruct
public void initRules() {
// 熔断规则
List<DegradeRule> rules = new ArrayList<>();
DegradeRule rule = new DegradeRule();
rule.setResource("createOrder");
// 慢调用比例:RT超过200ms的请求比例超过阈值就熔断
rule.setGrade(RuleConstant.DEGRADE_GRADE_RT);
rule.setCount(200);
rule.setTimeWindow(10);
rule.setStatIntervalMs(1000);
rules.add(rule);
DegradeRuleManager.loadRules(rules);
}
}
这个规则的意思是:统计1秒内的请求,平均响应时间超过200ms,熔断器打开10秒,10秒内所有请求快速失败,10秒后进入半开状态试探恢复。
5.4 热点参数限流与系统自适应保护
Sentinel还有两个高阶能力很实用。一个是热点参数限流,针对某个接口的热点参数做精细化限流。比如orderId为某个大V的商品ID,单个商品维度限流100 QPS,其他商品维度限流1000 QPS。
另一个是系统自适应保护,从整体维度而非单个接口维度保护系统。可以设置系统总QPS阈值、总线程数阈值、CPU使用率阈值、Load阈值,当系统整体负载过高时,自动对流量做兜底处理。这在复杂的微服务架构下非常有用,单接口限流已经不够了,必须以系统为单位做全局保护。
6. 微服务框架实践:若依微服务版本部署与问题排查
6.1 若依微服务版本架构分析与模块拆解
若依(RuoYi)是国内非常流行的Java后台开发框架,它的微服务版本基于Spring Cloud Alibaba技术栈,包含了权限管理、系统监控、代码生成等完整功能。对于学习微服务架构或者快速搭建企业级项目,若依微服务版本是一个很好的参考实现。
它的核心模块包括:ruoyi-gateway(网关)、ruoyi-auth(认证中心)、ruoyi-system(系统模块)、ruoyi-job(定时任务)、ruoyi-file(文件服务),以及公共模块ruoyi-api和ruoyi-common。
整体请求链路是:
code复制客户端 -> Nginx -> Gateway网关 -> 认证中心校验Token -> 转发到业务服务 -> 返回结果
6.2 若依微服务版本的启动步骤与关键配置
若依微服务版本的启动步骤分为三步:
第一步,启动基础设施。需要Redis、MySQL、Nacos。Nacos是服务注册中心和配置中心,必须先启动,否则其他服务无法注册。
第二步,导入数据库脚本。在sql目录下找到对应的初始化脚本,创建数据库并导入。注意不同模块可能有独立数据库,需要分别导入。
第三步,按依赖顺序启动服务。我建议的顺序是:先启动Nacos确认正常,再启动gateway网关,然后是auth认证中心,最后是system等业务模块。
这里有个新手容易踩的坑:Nacos必须设置为开发环境可写模式,否则配置文件无法持久化,导致服务启动后找不到配置文件眉毛一把抓。修改Nacos的application.properties,设置nacos.core.auth.enabled=false(开发环境)或者正确配置认证。
6.3 若依微服务开发中的常见问题清单
我汇总了使用过程中经常遇到的问题和解决方案:
问题一:服务启动但注册不上Nacos
检查服务的application.yml中Nacos地址配置是否正确,以及本机和Nacos的网络是否通。如果用的是本地Nacos,确认是否关闭了防火墙。
问题二:微服务之间调用认证失败
检查各个服务是否配置了正确的sentinel和seata依赖,以及是否在网关层正确放行了白名单路径。若依默认会拦截所有请求,需要在网关配置中把登录接口加入白名单。
问题三:代码生成器生成的前端代码无法正常联调
大概率是前端请求的baseURL没有修改,或者后端接口路径和前端代码中配置的路径不一致。检查前端的request.js和env配置。
问题四:分布式事务不生效
若依微服务版本默认集成了Seata,但Seata需要单独部署server端,还需要在每个业务数据库中建立undo_log表。如果启用了Seata但没正确配置,事务回滚就会失效。
6.4 MinIO国产替代与文件服务改造方案
若依微服务器版本默认支持本地存储和MinIO对象存储,但在国内企业项目中,越来越多的团队在调研国产化替换方案。MinIO本身是使用Go语言编写的高性能对象存储,完全兼容S3协议。
它的部署方式非常轻量:
bash复制# docker方式快速启动MinIO
docker run -p 9000:9000 -p 9001:9001 \
-e "MINIO_ROOT_USER=admin" \
-e "MINIO_ROOT_PASSWORD=admin123456" \
-v /data/minio:/data \
minio/minio server /data --console-address ":9001"
如果在做国产化改造,可以关注几家兼容S3协议的国产对象存储服务,他们大多提供和MinIO相同的API接口,改造起来非常平滑,只需要修改文件服务模块的配置即可无损迁移。
7. 常见问题与排查技巧实录
7.1 消息队列堆积问题排查
线上系统最怕的就是MQ消息堆积。消费者处理不过来,磁盘占用飙升,消息延迟越来越严重。排查思路我总结为四步:
- 看消费速度:通过MQ控制台查看消费者TPS,确认是不是个别消费者卡住了
- 看下游依赖:消费慢通常不是消费者代码慢,而是它调用的下游服务(数据库、Redis、第三方API)变慢了
- 看逻辑异常:消费者处理消息时遇到异常一直重试,消息一直消费不了
- 看队列数量:topic下队列数少于消费者并发数时,部分消费者会空闲
解决消息堆积的持久方案是消费端水平扩容:增加消费者实例数,让每个实例分担更少的消息。但如果队列数小于消费者数,消费者是收不到消息的,需要先增加队列数。
7.2 分布式锁失效问题定位
分布式锁失效的场景我在不同项目里遇到过好几次。最典型的是Redis主从切换导致的锁丢失。当前实例获取到锁,还没来得及同步给从节点,主节点挂了,从节点升级成主节点,但锁数据丢了。另一个新节点就能获取同一把锁,导致并发问题。
解决思路是使用Redisson的RedLock多节点锁,或者采用ZooKeeper锁,因为ZooKeeper写入是强一致的,不存在主从切换丢数据的问题。当然RedLock也有争议,但结合具体业务场景和团队基础设施,选择适合自己的方案即可。
7.3 限流误伤正常流量怎么办
限流配置太激进,正常用户也会被误伤。遇到这个问题,我会从几个方向去调:
- 检查是否所有接口都用了同一套阈值,有没有对核心链路和非核心链路做差异化配置
- 检查限流维度,比如本应该按用户维度限流,结果配成了按IP维度限流,同机房的正常用户全被挡住了
- 检查流控效果,长时间让请求排队等待可能拖垮接口响应时间,不如直接快速失败并让前端做重试
我个人的经验是:限流后一定要配合降级返回默认值,而不是直接抛异常。比如首页推荐列表挂了,限流后可以返回一个简单的兜底列表,用户感知不强,系统又能稳定运行。
7.4 微服务链路追踪与日志排查
微服务架构下排查问题比单机系统难一个量级。一个请求要经过网关、认证、多个业务服务、MQ、数据库,任何一环出现问题,单看每个服务的日志很难定位。
接入了Spring Cloud Sleuth + Zipkin之后,每个请求会生成一个全局TraceID,跨服务传递,通过Zipkin可以查看整个调用的链路耗时和每节点状态。
另一个实用技巧是:统一日志格式,每个服务打印日志时带上TraceID和业务流水号。定位问题时,直接根据订单号搜索日志,就能把所有相关日志串联起来,效率提升非常多。
8. 实操心得与个人经验总结
这套后端工程与微服务实战内容覆盖的范围相当宽,但所有技术点最终的指向是一致的:在分布式环境下,让系统保持高可用、高并发、数据一致。我反复演练这些方案之后,最大的感触是技术选型永远没有“最好”,只有“最适合”。
我个人强烈建议,学这块内容的时候,不要只停留在“会用”层面。比如用Redisson就把看门狗源码读一遍,用RocketMQ就把消息刷盘机制和ack机制彻底搞明白。我见过太多人写分布式锁只写个SETNX就交差,真要问他锁过期了怎么办、主从切换丢锁怎么办,一下就答不上来。这种深度在面试中一秒钟就会被识别出来。
最后再分享一个经验技巧:做这套实战的时候,最好把Sentinel、Redisson、RocketMQ整合到同一个项目里,用一套完整的业务场景把它们串起来。比如设计一个秒杀系统:Sentinel在前面做流量防护,Redisson保证库存扣减不超卖,RocketMQ异步处理订单创建和发券逻辑。这样一个场景把四个核心模块全部覆盖,学习成本更低,理解也更立体。等这套链路跑通了,再去单点深入研究每个中间件的源码和细节,整个后端工程能力就能真正上一个台阶。
