Spring Cloud从零开始:一个微服务实战者的成长笔记
引子:为什么我决定用Spring Cloud重构项目?

那是一个不算太久远的春天,我们团队接手了一个中型电商平台的升级项目。老系统是典型的单体架构,部署在一台ECS服务器上,数据库是MySQL,前端用Vue 2写的SPA页面,后端基于Spring Boot开发。
当时的问题很突出:
- 每次更新一个模块都要全量发布,风险极大;
- 随着用户增长,订单服务和库存服务经常成为瓶颈;
- 新功能迭代周期越来越长,代码库臃肿不堪;
- 不同业务之间高度耦合,维护成本很高。
老板拍板要做微服务化改造,技术选型的时候,我和几个骨干工程师开了几天会,最终选择了Spring Cloud Alibaba + Nacos + Gateway + Feign + Sentinel的技术栈。说实话,这对我来说也是一次全新的尝试,但事实证明,这次选择非常明智。
这篇文章,我会以第一人称来分享我的真实经验和踩过的坑,希望对正在考虑或者刚开始微服务之路的同学有所帮助。
初探微服务:我们的第一个Spring Cloud项目是如何诞生的?

一开始我们定下了几个目标:
- 逐步拆分、平滑过渡,不能为了拆而拆;
- 保持原有功能的同时进行改造;
- 确保性能不降,甚至要有所提升;
- 运维自动化程度提高,减少人为干预错误。
于是,我们在原Spring Boot的基础上,引入了Spring Cloud Starter Alibaba,并搭建了基础的注册中心——Nacos Server。
第一步:搭建注册中心(Nacos)
我们选择将Nacos作为服务注册与配置中心。最初只是跑在本地的一个Docker容器里:
docker run -d --name nacos-server -p 8848:8848 nacos/nacos-server:latest
然后在各个Spring Boot项目的application.yml中配置服务注册:
spring:
application:
name: order-service
cloud:
nacos:
discovery:
server-addr: 127.0.0.1:8848
启动后就能看到服务自动注册到了Nacos控制台。那一刻,心里莫名有种“我终于开始微服务了”的兴奋感。
真正挑战来了:服务拆分和通信难题
在实际开发中,我们把原来的大系统分成了以下几个核心服务:
- 用户服务(user-service)
- 商品服务(product-service)
- 订单服务(order-service)
- 库存服务(inventory-service)
- 支付服务(payment-service)
每个服务单独打成jar包,通过不同的端口运行。接下来遇到的第一个大问题就是服务间如何通信?
最开始我们直接用RestTemplate调用:
String url = "http://localhost:9001/product/{id}";
Product product = restTemplate.getForObject(url, Product.class, productId);
但在分布式环境下,这种写死的方式显然不可取,而且不具备容错能力。
于是我们引入了Feign Client:
@FeignClient(name = "product-service")
public interface ProductServiceClient {
@GetMapping("/products/{id}")
Product getProductById(@PathVariable("id") Long id);
}
这样一来,Feign会自动通过Ribbon做客户端负载均衡,结合Nacos的服务发现机制,就可以做到动态调用目标服务。
不过,别高兴得太早!
惊天一问:服务雪崩怎么办?
有一次测试环境压测时,我们模拟商品服务挂掉,结果整个订单流程都瘫痪了。因为订单服务依赖商品服务查询价格,而库存服务又依赖订单服务获取下单状态。
这就是典型的服务雪崩效应。
我们当时的应对措施有三步:
- 加熔断限流组件 —— 我们选择了Sentinel。
- 给Feign添加Fallback兜底逻辑。
- 异步+队列解耦部分业务流程(例如库存扣减)。
具体来说,在订单服务中加入Sentinel依赖:
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
</dependency>
在Feign Client接口加上 fallback:
@FeignClient(name = "product-service", fallback = ProductClientFallBack.class)
实现fall back类:
@Component
public class ProductClientFallBack implements ProductServiceClient {
@Override
public Product getProductById(Long id) {
return new Product();
}
}
这样即使商品服务挂了,也能返回默认值或缓存值,避免整个系统崩溃。
路由网关登场:统一入口怎么玩?
当服务越拆越多之后,暴露出来的另一个问题就是:API地址越来越多,前端不知道应该访问哪个服务!
更麻烦的是权限控制、日志记录、IP白名单等功能需要在多个服务重复实现。
这时候我们就引入了Spring Cloud Gateway,作为统一入口层。
Gateway本身支持路由、过滤器链、限流等强大的功能。比如我们可以这样定义路由规则:
spring:
cloud:
gateway:
routes:
- id: user-service
uri: lb://user-service
predicates:
- Path=/api/user/**
filters:
- StripPrefix=1
这样前端只需要访问/api/user/login,就会被自动转发到user-service的/login接口。
我们也实现了自定义的全局过滤器来做身份认证:
public class AuthFilter implements GlobalFilter {
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
String token = exchange.getRequest().getHeaders().getFirst("Authorization");
if (!isValidToken(token)) {
exchange.getResponse().setStatusCode(HttpStatus.UNAUTHORIZED);
return exchange.getResponse().setComplete();
}
return chain.filter(exchange);
}
}
这一块虽然看起来简单,但调试起来还是有不少细节需要注意,比如Token校验失败后的响应格式要统一流程,不能出现有的服务返回JSON、有的服务跳转登录页的情况。
数据库设计:是共享还是独立?
这个问题争论了很久。最后我们采取的是“适度独立”。
什么意思呢?
对于高频率修改、强一致性要求的数据(如库存),我们采用独立数据库,各服务只能通过API交互。
而对于读多写少、低一致性的数据(如分类信息),我们允许一定限度的复制同步,避免频繁跨服务调用。
举个例子:
订单服务需要获取商品的基本信息,如果每次都要调用product-service,效率太低。
于是我们采用了数据冗余策略,在order-service中保存一份product简略信息快照:
@Data
@Entity
public class OrderProductSnapshot {
private String productName;
private BigDecimal price;
private Integer quantity;
}
每当生成新订单时,就保存一次商品快照,这样即便后续商品信息更改,也不会影响历史订单的数据展示。
当然,快照也有缺点,比如占用存储、更新不及时等,但结合业务场景来看,利大于弊。
生产上线那些事:我们是怎么部署和维护的?
微服务拆得再好,部署不妥也是白搭。我们采用的方案是:
- Jenkins做持续集成,打包并上传到私有镜像仓库(Harbor);
- 使用Kubernetes集群管理服务部署;
- 每个服务部署两个Pod,配合健康检查确保高可用;
- 日志集中使用ELK(ElasticSearch + Logstash + Kibana)收集;
- 监控告警接入Prometheus + Grafana;
- 接口文档用Swagger整合所有服务API,方便前后端协作。
其中最头疼的一点是K8s的Service配置,尤其是在早期没有经验的情况下,有时候YAML写错了半天查不出问题。
后来我们整理了一份标准化的模板,包括资源限制、就绪检查、滚动更新策略等,才逐渐顺畅起来。
性能优化:从慢到快的关键几步
微服务化之后,不可避免地带来了额外的网络开销。为了提升性能,我们做了以下几件事:
- 引入缓存机制:Redis用于热点数据缓存(如热销商品、首页内容);
- 异步处理非关键路径:比如发送短信、邮件这些操作,全部丢进RabbitMQ;
- 数据库索引优化和连接池配置调整;
- Feign调用增加超时控制,防止慢请求拖垮整个链路;
- 启用Zipkin做分布式追踪,快速定位性能瓶颈;
- 使用JVM性能分析工具(Arthas)排查内存泄漏和GC问题。
特别说一下Zipkin,它帮助我们发现了好多隐藏很深的延迟问题。
比如说某个服务明明响应很快,但整体链路却很慢,原来是有个下游服务在做批量任务时占用了线程资源,导致后续请求排队。
成果与收获:拆完之后真的更好了吗?
当然!
我们现在基本做到了:
✅ 各个服务完全解耦,独立开发、测试、部署;
✅ 整体系统吞吐量提升了30%以上;
✅ 故障范围可控,不会牵一发而动全身;
✅ 新成员更容易理解系统结构,入职更快上手;
✅ 实现了蓝绿部署、灰度发布的初步能力。
虽然初期投入了不少时间和精力,但从长远来看,这是值得的。
写给读者的经验总结
如果你正在准备微服务化改造或刚刚起步,以下是我在实践过程中积累的一些经验建议:
✅ 从小处试水,别上来就把大系统一顿拆
我们前期是先拆出一个服务,观察它的表现和稳定性,确认没问题后再推进其他模块。盲目拆分会带来巨大的维护成本。
✅ 服务命名要有规范,否则后期根本管不过来
我们当时统一使用{业务模块}-service的格式,如product-service、order-service,便于理解和查找。
✅ 分布式事务是个坑,慎用!推荐事件驱动或补偿机制
我们最早试图用Seata做分布式事务,结果复杂且效率低下。后来改为消息队列+本地事务表的方式,反而稳定多了。
✅ 日志和监控必须一开始就搞定
不然出了问题都不知道去哪查。一定要把日志统一采集,配上调用链追踪,否则你永远不知道请求卡在哪一个环节。
✅ 技术债一定要还,否则迟早反噬
比如有些API设计不合理,当时图省事没改,后面要改就得动很多地方,痛苦不已。
结语:微服务不是银弹,但正确使用可以带来质变
写到这里,我已经回忆起了那段边学边干、熬夜改Bug的日子。虽然累,但现在回头看,那次改造不仅提升了系统的可维护性,也让我自己对微服务的理解有了质的飞跃。
Spring Cloud生态非常庞大,涉及的技术点也非常多。但只要你从实际业务出发,坚持“先解决问题,再完善体系”的思路,就一定能走出一条适合自己的微服务之路。
希望这篇文章能帮你在微服务的道路上走得更稳、更远。欢迎留言交流,一起进步!
作者:一只热爱码代码的程序员 / 前端时间做过Java后端,现在专注微服务架构设计与研发

评论 0