Spring Cloud从零开始:微服务入门指南

需求别再变
2025-12-14 02:05
阅读 250

上周五晚上十一点,我窝在MacBook前盯着屏幕发呆。咖啡已经凉了第三杯,窗外下着雨,屋里的猫正一脸嫌弃地看着我——毕竟我又没按时给它铲屎。而此刻的我,正在被一个Spring Cloud配置问题折磨得想直接格式化硬盘。

事情是这样的:我们团队最近接了个新项目,要重构一个老旧的单体应用。产品经理信誓旦旦地说“这次绝对不加需求”,结果第二天就甩过来三个PRD文档。更惨的是,后端老哥因为受不了天天半夜被叫起来救火,跳槽去了一家Web3公司(听说现在每天在写Solidity)。于是这口锅……哦不,这个机会,就落到了我头上。

作为一个平时主要写Rust、偶尔用Node.js搞点小工具的人,突然让我上手Spring Cloud,属实有点懵。但没办法,谁让咱还在还房贷呢?而且听HR说,掌握微服务架构的Java工程师在市场上很吃香,简历通过率能提高30%。为了未来的跳槽机会,也得硬着头皮上啊!

为什么是Spring Cloud?

说实话,在决定技术栈之前,我和CTO(其实就是老板兼前端)吵了一架。他觉得用JavaScript全家桶+Serverless就够了,毕竟我们之前的小项目都是这么搞的。但我坚持要用Java + Spring Cloud,理由很简单:

  1. 稳定性:我们的核心业务对数据一致性要求很高,不能像某些NFT平台那样“丢了就丢了”。
  2. 生态成熟:Spring Cloud有一整套经过生产验证的解决方案,不像某些新兴框架,文档都还没写完就出2.0了。
  3. 求职加分:看看招聘网站就知道,大厂的后端岗位基本都要求熟悉微服务架构,尤其是Spring Cloud相关经验。

最终老板被我说服了(可能是因为我答应周末加班),于是就有了这篇踩坑实录。

环境搭建:别被Maven吓跑

首先得承认,作为一个习惯了Cargo和npm的人,第一次看到pom.xml里密密麻麻的dependency,我真的有点头皮发麻。不过Spring Initializr救了我一命,直接在线生成项目骨架,连版本兼容性都帮你搞定了。

<!-- 这是我用Spring Initializr生成的基础依赖 -->
<dependencies>
    <dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-starter-netflix-eureka-server</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
</dependencies>

<dependencyManagement>
    <dependencies>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-sync-cloud-dependencies</artifactId>
            <version>Hoxton.SR12</version>
            <type>pom</type>
            <scope>import</scope>
        </dependency>
    </dependencies>
</dependencyManagement>

注意那个dependencyManagement块,这是Spring Cloud的版本管理魔法。千万别自己乱配版本号,否则等着看各种NoSuchMethodError吧。我上次就是因为手贱改了个版本,导致Eureka注册失败,debug到凌晨三点。

服务注册与发现:Eureka实战

微服务的第一步就是服务注册中心。虽然现在Consul、Zookeeper也很流行,但对于Java生态来说,Eureka还是最省心的选择。

创建一个Eureka Server超级简单:

@SpringBootApplication
@EnableEurekaServer
public class RegistryApplication {
    public static void main(String[] args) {
        SpringApplication.run(RegistryApplication.class, args);
    }
}

然后在application.yml里配置一下:

server:
  port: 8761

eureka:
  client:
    register-with-eureka: false
    fetch-registry: false
  server:
    enable-self-preservation: false

这里有个坑:register-with-eureka: false一定要设,否则Eureka会尝试把自己注册到自己,然后无限循环。我就在这栽过跟头,日志刷屏到磁盘爆满。

其他服务要注册进来也很简单,加上@EnableDiscoveryClient注解,然后配置Eureka地址:

eureka:
  client:
    service-url:
      defaultZone: http://localhost:8761/eureka/

启动后访问http://localhost:8761,就能看到所有注册的服务了。比Kubernetes的dashboard友好多了(运维同事别打我)。

服务间调用:Feign vs RestTemplate

服务拆分后,最大的问题就是服务间怎么通信。Spring Cloud提供了两种主流方式:

RestTemplate + Ribbon

@Bean
@LoadBalanced
public RestTemplate restTemplate() {
    return new RestTemplate();
}

// 使用
String result = restTemplate.getForObject("http://user-service/users/1", String.class);

注意@LoadBalanced注解,这是开启客户端负载均衡的关键。没有它,你就得手动处理服务实例列表。

Feign声明式调用(推荐)

@FeignClient(name = "user-service")
public interface UserClient {
    @GetMapping("/users/{id}")
    User getUser(@PathVariable("id") Long id);
}

// 使用
@Autowired
private UserClient userClient;

User user = userClient.getUser(1L);

Feign的代码看起来就像调用本地方法一样,简直不要太爽。而且它内置了Ribbon的负载均衡,还能集成Hystrix做熔断。

不过要注意,Feign默认用的是JDK动态代理,性能不如直接用OkHttp。如果QPS很高,建议切换:

feign:
  httpclient:
    enabled: false
  okhttp:
    enabled: true

配置中心:告别配置文件地狱

以前每个服务都要维护自己的application.yml,改个数据库密码要改十几个文件。现在有了Spring Cloud Config,终于可以统一管理了。

Config Server配置:

@SpringBootApplication
@EnableConfigServer
public class ConfigServerApplication {
    public static void main(String[] args) {
        SpringApplication.run(ConfigServerApplication.class, args);
    }
}
spring:
  cloud:
    config:
      server:
        git:
          uri: https://github.com/yourname/config-repo
          username: your-username
          password: your-token

客户端只需要指定Config Server地址:

spring:
  application:
    name: user-service
  cloud:
    config:
      uri: http://localhost:8888

这样,所有配置都从Git仓库拉取,还能利用Git的版本控制。再也不用担心测试环境配置被误推到生产了(虽然我还是干过这种事)。

网关:Zuul vs Gateway

API网关是微服务的门面,负责路由、鉴权、限流等。Spring Cloud有两个选择:

特性 Zuul 1.x Spring Cloud Gateway
性能 同步阻塞 异步非阻塞(Reactor)
集成 Netflix生态 Spring生态原生支持
学习成本 中等
社区活跃度 已停止维护 活跃

显然应该选Gateway。配置示例:

spring:
  cloud:
    gateway:
      routes:
        - id: user-service
          uri: lb://user-service
          predicates:
            - Path=/api/users/**
          filters:
            - StripPrefix=1

这里的lb://表示使用负载均衡,配合前面的Eureka,自动路由到可用实例。

熔断与降级:别让一个服务拖垮整个系统

去年双11期间,我们的支付服务因为第三方接口超时,导致整个订单系统雪崩。从那以后,熔断机制成了标配。

Hystrix虽然经典,但已经进入维护模式。现在推荐用Resilience4j,不过为了快速上手,我还是先用了Hystrix:

@FeignClient(name = "payment-service", fallback = PaymentFallback.class)
public interface PaymentClient {
    @PostMapping("/pay")
    Result pay(@RequestBody PaymentRequest request);
}

@Component
public class PaymentFallback implements PaymentClient {
    @Override
    public Result pay(PaymentRequest request) {
        // 降级逻辑,比如返回"稍后重试"
        return Result.failure("Payment service unavailable, please try later");
    }
}

记得在主类上加@EnableHystrix,然后配置超时时间:

hystrix:
  command:
    default:
      execution:
        isolation:
          thread:
            timeoutInMilliseconds: 3000

数据库设计:每个服务独享数据库

微服务的一个核心原则是数据库隔离。千万不要多个服务共用一个数据库,否则迟早会变成分布式单体。

我们的做法是:

  • 用户服务 → user_db
  • 订单服务 → order_db
  • 商品服务 → product_db

跨服务的数据一致性用Saga模式或者事件驱动来解决。比如用户下单后,订单服务发布"OrderCreated"事件,库存服务监听并扣减库存。

// 订单服务
@Transactional
public void createOrder(Order order) {
    orderRepository.save(order);
    eventPublisher.publishEvent(new OrderCreatedEvent(order.getId()));
}

// 库存服务
@EventListener
public void handleOrderCreated(OrderCreatedEvent event) {
    inventoryService.decrease(event.getOrderId());
}

虽然比直接事务复杂,但保证了服务的独立性。而且用RabbitMQ/Kafka做消息队列,还能天然实现削峰填谷。

生产环境那些事儿

开发环境跑得好好的,上线就出问题?这些坑我都替你踩过了:

1. 健康检查别忽略

Eureka默认用/actuator/health做健康检查,确保你的服务实现了这个端点:

management:
  endpoints:
    web:
      exposure:
        include: health,info
  endpoint:
    health:
      show-details: always

2. 日志聚合很重要

微服务日志分散在各个容器里,没有ELK(Elasticsearch+Logstash+Kibana)根本没法排查问题。我们用Logback把日志输出到JSON格式,方便Filebeat收集。

3. 监控必不可少

Prometheus + Grafana监控各服务的QPS、响应时间、错误率。特别是Feign调用的延迟分布,能帮你快速定位瓶颈。

4. 安全不能少

网关层统一做JWT鉴权,内部服务间用双向TLS加密。别学我,曾经因为没开HTTPS,被安全扫描抓出高危漏洞,差点被通报批评。

对比JavaScript生态:为什么后端还是Java香?

作为经常在JS和Java之间横跳的人,我觉得两者各有千秋:

  • 开发体验:JavaScript(尤其是TypeScript)的开发体验确实更好,热更新快,调试方便。但Java的IDE支持无敌,IntelliJ IDEA的智能提示和重构功能,写起代码来行云流水。

  • 性能:Java在CPU密集型场景优势明显。我们的订单计算服务,用Node.js要500ms,换成Java只要80ms。

  • 生态成熟度:Spring Cloud这套微服务解决方案,比JavaScript生态的类似方案(比如NestJS + Redis + Consul组合)要成熟稳定得多。特别是在企业级应用中,Java的事务管理、连接池、线程模型都经过了充分验证。

当然,如果你只是做个简单的CRUD应用,用Express或者Koa完全够用。但一旦涉及到复杂的业务逻辑、高并发、强一致性,Java的优势就体现出来了。

给求职者的建议

最近帮朋友内推,发现很多候选人简历上写着“熟悉微服务”,但连服务注册发现都说不清楚。如果你想在求职中脱颖而出,建议:

  1. 动手实践:光看教程没用,一定要自己搭一套完整的微服务系统。可以从用户服务+订单服务开始,逐步加入配置中心、网关、熔断等功能。

  2. 理解原理:面试官最喜欢问“Eureka和Zookeeper的区别”、“Feign底层怎么实现的”。这些都要能说出一二三。

  3. 关注生产问题:微服务在生产环境的问题比开发环境复杂得多。了解如何做链路追踪(Sleuth+Zipkin)、如何做灰度发布、如何处理分布式事务,会让你在面试中加分不少。

  4. 不要忽视基础:微服务只是架构层面的东西,底层的JVM调优、MySQL索引优化、Redis缓存策略同样重要。我见过太多人只会背Spring Cloud组件名,结果连HashMap扩容机制都说不清。

最后

折腾了两周,我们的微服务架构终于跑起来了。虽然过程中踩了不少坑,但看到各个服务在Eureka里整齐排列,请求通过Gateway优雅地路由,Feign调用丝般顺滑,心里还是很有成就感的。

现在我已经能一边撸猫一边写Rust,一边用Spring Cloud支撑公司的核心业务了。远程办公的好处就是,没人看到我调试失败时砸键盘的样子(其实是舍不得砸我的MacBook Pro)。

如果你也在学习Spring Cloud,记住:别怕踩坑,每个报错都是成长的机会。实在搞不定就重启电脑(开玩笑的),或者去Stack Overflow搜一下,大概率有人遇到过同样的问题。

最后送大家一句我在工位上贴的座右铭:“微服务不是银弹,但不会微服务,你可能会成为别人的弹药。

好了,我去给猫铲屎了,顺便想想明天怎么说服老板让我用Rust重写其中一个服务……

评论 0

最热最新
暂无评论
匿名用户Lv.1
0
影响力
0
文章
0
粉丝