Spring Cloud从零开始:微服务入门踩坑实录
上周五晚上九点半,深圳南山科技园的写字楼里还亮着不少灯。我一边啃着凉掉的猪脚饭,一边盯着IDEA里一堆红色报错发呆——这已经是我连续第三天调试Spring Cloud配置了。作为一个普通一本CS专业的大四学生,刚拿到offer还在等入职,本来以为毕业设计搞完就能躺平,结果导师突然说:“你不是要去腾讯系公司吗?那你得懂点微服务啊。”于是我就这么被“赶鸭子上架”,开始了和Spring Cloud相爱相杀的日子。
为什么非要搞微服务?
说实话,一开始我对微服务有点抗拒。单体应用多香啊,一个jar包打天下,本地跑起来嗖嗖的。但现实是骨感的:我们组最近在重构一个老电商系统,产品经理画了个巨复杂的架构图,说什么“要支持未来百万级用户”、“要能快速迭代新功能”。运维大哥也在旁边补刀:“你们这个单体应用每次上线都要停机半小时,老板都快骂死了。”
没办法,只能硬着头皮上。不过说真的,现在回头看,微服务虽然初期折腾,但一旦搭好了,确实爽。比如我们现在可以独立部署订单服务、商品服务、用户服务,再也不用因为改了个小bug就让整个系统停机了。
环境搭建:第一个坑就差点把我劝退
我选择的是Spring Cloud Alibaba这套组合拳,主要是考虑到阿里系在国内生态比较成熟,文档也相对丰富。理论上来说,就是引入几个starter,配几个注解的事儿。但实际上...
<!-- pom.xml 关键依赖 -->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
<version>2021.1</version>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
看起来很简单对吧?但问题来了:版本兼容性!
我一开始随便抄了个网上的demo,结果启动的时候直接报错:
java.lang.NoSuchMethodError: org.springframework.boot.web.servlet.ServletRegistrationBean.addUrlMappings
查了半天才发现是Spring Boot 2.6.x和某些Spring Cloud版本不兼容。最后我不得不降级到Spring Boot 2.4.13,这才勉强跑起来。建议大家直接去Spring Cloud官方兼容性表格查清楚再动手,别像我一样盲目开干。
服务注册与发现:Nacos真香
搞定基础环境后,就是服务注册与发现了。我用了Nacos,主要是因为它有可视化界面,对于我这种新手很友好。
启动Nacos也很简单:
# 下载nacos-server后
sh bin/startup.sh -m standalone
然后在每个微服务的application.yml里加上:
spring:
cloud:
nacos:
discovery:
server-addr: localhost:8848
这时候我就遇到第二个坑了:服务名大小写问题!
我一开始把服务名叫成OrderService,结果在Feign调用的时候死活找不到服务。后来才发现Nacos默认会把服务名转成小写!所以最好统一用小写加中划线的命名规范,比如order-service。
说到Feign,这里也有个坑。默认情况下Feign是不支持GET请求带复杂对象参数的,必须手动开启:
feign:
httpclient:
enabled: true
client:
config:
default:
connectTimeout: 5000
readTimeout: 5000
而且记得在启动类上加@EnableFeignClients,不然Feign客户端根本不会被扫描到。这些细节看似简单,但新手很容易栽跟头。
配置中心:告别配置文件满天飞
以前做单体应用的时候,最烦的就是不同环境的配置文件。开发环境一套、测试环境一套、生产环境又一套,经常改着改着就搞混了。
现在用了Nacos Config,所有配置都集中管理了:
spring:
cloud:
nacos:
config:
server-addr: localhost:8848
file-extension: yaml
namespace: your-namespace-id # 别忘了区分环境!
但是这里有个超级大坑:配置刷新!
我一开始以为改了Nacos里的配置,服务会自动刷新。结果发现根本不会!必须在需要动态刷新的Bean上加@RefreshScope注解:
@Component
@RefreshScope
public class DynamicConfig {
@Value("${app.feature-toggle}")
private String featureToggle;
// getter...
}
而且要注意,@RefreshScope只对Spring Bean生效,如果你是在Configuration类里直接用@Value注入配置,那是不会刷新的!
网关:Zuul还是Gateway?
说到网关,我纠结了很久。Zuul是Netflix的老将,Gateway是Spring自家的新秀。最后我还是选了Gateway,主要是性能更好,而且和Spring Boot集成更紧密。
基本配置:
spring:
cloud:
gateway:
routes:
- id: user-service
uri: lb://user-service # lb表示负载均衡
predicates:
- Path=/api/user/**
这里要注意lb://前缀,它会自动从注册中心获取服务实例进行负载均衡。不过我第一次配置的时候忘了加lb://,直接写服务名,结果网关报404,调试了半天才发现是URI格式错了。
另外,Gateway的过滤器链也要小心。我曾经在一个全局过滤器里写了日志记录逻辑,结果因为异常处理没做好,导致整个请求链路挂掉。现在我的建议是:过滤器里一定要做好异常兜底!
@Component
public class LoggingFilter implements GlobalFilter {
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
try {
// 记录请求日志
log.info("Request: {}", exchange.getRequest().getPath());
return chain.filter(exchange);
} catch (Exception e) {
log.error("Logging filter error", e);
return chain.filter(exchange); // 即使出错也要继续执行
}
}
}
熔断降级:Hystrix已死,Sentinel当立
Netflix的Hystrix已经停止维护了,所以现在主流选择是阿里开源的Sentinel。配置起来也不复杂:
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
</dependency>
spring:
cloud:
sentinel:
transport:
dashboard: localhost:8080
port: 8719
启动Sentinel Dashboard:
java -Dserver.port=8080 -Dcsp.sentinel.dashboard.server=localhost:8080 -jar sentinel-dashboard.jar
Sentinel的规则配置可以通过代码或者Dashboard界面进行。我个人更喜欢用代码方式,因为可以版本控制:
@PostConstruct
public void initFlowRules() {
List<FlowRule> rules = new ArrayList<>();
FlowRule rule = new FlowRule();
rule.setResource("getUserById");
rule.setGrade(RuleConstant.FLOW_GRADE_QPS);
rule.setCount(20); // QPS阈值设为20
rules.add(rule);
FlowRuleManager.loadRules(rules);
}
不过要注意,Sentinel默认是懒加载的,也就是说只有当资源被访问过之后,规则才会生效。所以在压测的时候要先预热一下。
数据库设计:分布式事务怎么办?
微服务最大的痛点之一就是数据一致性。以前单体应用里一个@Transactional就能搞定的事,现在分布在多个服务里就麻烦了。
我们现在的做法是:
- 强一致性场景:用Seata(阿里开源的分布式事务框架)
- 最终一致性场景:用消息队列+本地事务表
Seata配置稍微复杂一点,需要额外部署TC(Transaction Coordinator)服务:
seata:
enabled: true
application-id: ${spring.application.name}
tx-service-group: my_tx_group
service:
vgroup-mapping:
my_tx_group: default
registry:
type: nacos
nacos:
server-addr: localhost:8848
然后在需要分布式事务的方法上加@GlobalTransactional注解:
@GlobalTransactional
public void createOrder(OrderDTO orderDTO) {
// 调用库存服务扣减库存
inventoryService.decreaseStock(orderDTO.getProductId(), orderDTO.getQuantity());
// 创建订单
orderRepository.save(order);
}
不过Seata也有性能损耗,所以不是所有场景都适合。一般我们只在核心业务流程中使用,比如下单、支付这种。
生产环境踩过的那些坑
说了这么多理论,分享几个我在模拟生产环境时踩的真实坑:
坑1:服务雪崩
上周测试的时候,因为某个下游服务响应慢,导致上游服务线程池被打满,进而引发连锁反应。解决方案是在所有Feign客户端都加上超时配置:
feign:
client:
config:
default:
connectTimeout: 1000
readTimeout: 3000
同时配合Sentinel做熔断,当错误率达到一定阈值就直接熔断,避免拖垮整个系统。
坑2:配置中心连不上
有一次我把应用部署到测试环境,结果启动失败,报错说连不上Nacos。查了半天才发现是网络策略问题——测试环境的安全组没开放8848端口!所以部署前一定要和运维确认好网络连通性。
坑3:日志追踪断了
微服务之间调用频繁,如果没有全链路追踪,排查问题简直是噩梦。我们后来集成了Sleuth + Zipkin:
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-sleuth</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-sleuth-zipkin</artifactId>
</dependency>
这样每个请求都会有一个traceId贯穿所有服务,排查问题效率提升了不少。
关于Replit Agent、Go和Devin的思考
最近技术圈很火的几个东西:Replit Agent、Go语言、还有那个号称AI软件工程师的Devin。作为一个即将入职的新人,我也在思考这些新技术对传统Java微服务开发的影响。
说实话,我觉得短期内Spring Cloud这套体系还是主流。毕竟企业级应用看重的是稳定性、生态成熟度和人才储备。不过我也在业余时间学了点Go,发现用Go写微服务确实轻量很多,启动速度快,内存占用少。
至于Replit Agent和Devin这类AI编程工具,我觉得它们更像是辅助工具。比如我现在写配置文件的时候,会用Copilot帮我生成基础模板,但具体的业务逻辑和异常处理还是要自己来。AI可以帮助我们提高效率,但不能替代程序员的判断力。
写在最后
从零开始搭建Spring Cloud微服务,真的是痛并快乐着。每一个报错、每一个配置项背后都有它的故事。但现在回过头看,这些踩过的坑都成了宝贵的经验。
给准备入坑的朋友们几点建议:
- 版本兼容性一定要查清楚,别盲目抄demo
- 监控告警要尽早接入,别等问题上了生产才后悔
- 文档记录要做好,不然三个月后自己都看不懂
- 渐进式改造,不要一上来就想把所有功能都加上
虽然过程很痛苦,但看到自己的微服务架构跑起来,各个服务和谐共处,那种成就感还是很爽的。就像我们组老大说的:“微服务不是银弹,但学会了绝对不吃亏。”
对了,顺便安利一下,深圳这边腾讯、字节、Shopee都在招微服务相关的人才,如果大家也在学习这块,不妨多交流。反正我现在每天边听周杰伦边写代码,感觉生活还挺充实的。
(完)

评论 0