Skip to content

微服务项目

前置知识

在阅读本章前,你需要了解:Java基础语法、Spring Boot基础、RESTful API概念

为什么需要微服务架构?

想象你正在维护一个庞大的电商系统,所有功能都塞在一个庞大的“单体”应用里:商品管理、订单处理、用户服务…代码越来越复杂,一次小改动都可能引发大范围的影响,导致部署变得缓慢且风险高。更严重的是,团队协作也变得低效,每个开发组都在争抢同一个庞大的代码库。

这时,微服务架构(Microservices)派上用场了。它帮你把大系统拆分成一组小而独立的服务,每个服务负责自己的业务领域,可以独立开发、测试、部署。服务之间通过轻量级通信(通常是HTTP/REST或消息队列)协作。这样一来,开发速度提升,系统更灵活抗风险,技术栈升级也更简单。

不过,微服务带来了新的挑战:服务如何拆分?它们之间怎么通信?配置管理怎么办?服务如何被发现?这些是我们这章要解决的核心问题。


服务拆分

什么是服务拆分?

服务拆分就是把一个大应用切分成多个小服务,每个服务聚焦于单一业务能力。换句话说,我们把不同职责像“拼图”一样拆开,让它们独立运行和演进。

为什么需要拆分?

  • 降低服务复杂度,更易维护
  • 让团队可以并行开发
  • 支持独立部署,减少影响面
  • 便于灵活选型技术栈

简单示例:将用户管理拆成独立服务

假设之前的单体应用UserService和OrderService混在一起,现在把用户管理单独做成UserService

java
package com.example.userservice;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.web.bind.annotation.*;

import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

@SpringBootApplication
@RestController
@RequestMapping("/users")
public class UserServiceApplication {

    // 简单内存存储用户数据
    private final Map<String, String> users = new ConcurrentHashMap<>();

    public static void main(String[] args) {
        SpringApplication.run(UserServiceApplication.class, args);
    }

    @PostMapping("/{id}")
    public String addUser(@PathVariable String id, @RequestBody String name) {
        users.put(id, name);
        return "User added";
    }

    @GetMapping("/{id}")
    public String getUser(@PathVariable String id) {
        return users.getOrDefault(id, "User not found");
    }
}

这段代码做了什么:

  1. 搭建了一个Spring Boot服务,监听/users接口。
  2. 用内存存储模拟用户数据。
  3. 提供添加用户和查询用户的接口。

我们刚刚实现了把“用户管理”服务拆出来,代码清晰且职责单一。

小结

  • 服务职责单一是拆分的基础
  • 简单HTTP接口实现服务独立访问

服务间通信

拆完服务,关键问题来了:它们如何互相“聊”?

两种通信方式

  1. 同步通信(通常用REST API或gRPC)

    • 请求方发起调用,等待响应
    • 简单直观,但请求阻塞,耦合较紧
  2. 异步通信(消息队列、事件驱动)

    • 使用消息中间件,解耦服务
    • 扩展性好,但实现复杂度较高

先从同步通信入手,大家最熟悉,也最容易理解。

代码示例:订单服务调用用户服务获取用户名

订单服务先调用用户服务接口,获取用户名后才能处理订单。

java
package com.example.orderservice;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.client.RestTemplate;

@SpringBootApplication
@RestController
@RequestMapping("/orders")
public class OrderServiceApplication {

    private final RestTemplate restTemplate = new RestTemplate();

    public static void main(String[] args) {
        SpringApplication.run(OrderServiceApplication.class, args);
    }

    @GetMapping("/{orderId}/username/{userId}")
    public String getOrderUserName(@PathVariable String orderId, @PathVariable String userId) {
        // 调用用户微服务,获取用户名
        String userServiceUrl = "http://localhost:8081/users/" + userId;
        String userName = restTemplate.getForObject(userServiceUrl, String.class);

        return "Order " + orderId + " belongs to user: " + userName;
    }
}

这段代码做了什么:

  1. 创建OrderService服务。
  2. 使用Spring的RestTemplate同步调用用户服务接口(假设用户服务跑在8081端口)。
  3. 返回订单和对应用户名。

小结:

  • 微服务间通信关键在地址和接口规范
  • 需要确保调用的服务可用

配置中心

你可能发现,服务间调用时,地址硬编码了(刚才写了http://localhost:8081)。在真实场景中,微服务实例多、IP动态变化,如何管理地址和配置呢?

为什么需要配置中心?

  • 集中管理各服务配置,避免散落在代码里
  • 动态更新配置,服务实时感知变动
  • 简化环境切换(开发、测试、生产)

典型方案

Spring Cloud Config 是常见的配置中心技术。它支持把配置放到Git仓库,微服务启动时拉取或监听配置变化。

简单示例:Spring Cloud Config Server配置

java
package com.example.configserver;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.config.server.EnableConfigServer;

@SpringBootApplication
@EnableConfigServer  // 启用配置服务
public class ConfigServerApplication {
    public static void main(String[] args) {
        SpringApplication.run(ConfigServerApplication.class, args);
    }
}

application.yml (配置Git仓库位置)

yaml
server:
  port: 8888

spring:
  cloud:
    config:
      server:
        git:
          uri: https://github.com/example/config-repo  # 配置仓库地址

服务启动后,会暴露配置接口,其他微服务可以通过配置客户端动态获取。

这段代码做了什么:

  1. 启动配置服务器,变身配置服务的中枢。
  2. 配置仓库地址(一般放公共配置文件)。

配置客户端示例(UserService):

yaml
spring:
  application:
    name: userservice
  cloud:
    config:
      uri: http://localhost:8888  # 指向配置中心

UserService启动时会从配置中心拉取配置,实现统一管理。


服务发现

如果没有硬编码地址,服务如何找到彼此?答案是“服务发现”(Service Discovery)。

服务发现原理

微服务启动时,把自己注册到服务注册中心;调用方从注册中心查询可用服务列表,然后发起调用。流行的服务注册中心有Eureka、Consul、Zookeeper等。

代码示例:使用Eureka实现服务注册与发现

  1. 启动Eureka Server:
java
package com.example.eurekaserver;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.eureka.server.EnableEurekaServer;

@SpringBootApplication
@EnableEurekaServer  // 启用Eureka服务注册中心
public class EurekaServerApplication {
    public static void main(String[] args) {
        SpringApplication.run(EurekaServerApplication.class, args);
    }
}

application.yml:

yaml
server:
  port: 8761

eureka:
  client:
    register-with-eureka: false
    fetch-registry: false
  1. UserService作为Eureka客户端注册

用依赖和配置连接Eureka:

yaml
spring:
  application:
    name: userservice
eureka:
  client:
    service-url:
      defaultZone: http://localhost:8761/eureka/

UserService主类用@EnableEurekaClient注解开启:

java
package com.example.userservice;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.eureka.EnableEurekaClient;

@SpringBootApplication
@EnableEurekaClient  // 注册进Eureka
public class UserServiceApplication {
    public static void main(String[] args) {
        SpringApplication.run(UserServiceApplication.class, args);
    }
}
  1. OrderService同理,启动时从Eureka查询UserService位置。

调用服务示例(替换之前的硬编码地址):

java
String userServiceUrl = "http://userservice/users/" + userId;

使用RestTemplate或Feign客户端,Spring Cloud会自动通过Eureka解析服务名为具体实例地址。


⚠️ 常见陷阱

  • 服务拆分过细,导致管理复杂
    拆分服务虽然好,但过度拆分会让系统变成“分布式的单体”,管理调用链变得凌乱,部署压力大。建议遵循业务边界拆分,一开始不要追求极致细粒度。

  • 缺乏配置中心导致环境切换困难
    硬编码配置会让测试环境和生产环境维护成本高。一定要提前规划配置中心实现动态管理。

  • 服务发现后调用失败无降级方案
    没有设计熔断器和重试机制时,调用失败很可能导致系统雪崩。建议配合Hystrix或Resilience4j实现容错。


💡 实战建议

  • 先用单体开发快速迭代,核心流程稳定后再拆分微服务。
  • 使用配置中心统一管理环境配置,避免环境差异。
  • 服务注册发现配合负载均衡,提升可用性和扩展性。
  • 针对关键服务设计异步通信,减少同步调用等待。
  • 加入链路追踪(如Zipkin),帮助调试分布式调用流程。

🔍 深入理解

微服务拆分和领域驱动设计(DDD)

虽然这里我们介绍了简单拆分思路,但微服务拆分的更科学方法是基于领域驱动设计,识别领域边界(Bounded Context),围绕业务能力拆分。让服务自然对应业务子域,避免功能交叉和数据耦合。


实战应用

假设你在开发一家网上书店,从单体应用迁移到微服务架构。你先将用户管理和订单处理拆成两个服务。订单服务要实时根据用户信息决定订单优惠。于是:

  • 用户服务注册到Eureka
  • 订单服务通过Eureka查找用户服务接口
  • 两服务配置都从配置中心加载,支持一键切换测试/生产环境

经过测试你发现调用链中如果用户服务宕机,订单服务也挂了。这促使你为调用增加了熔断和异步消息机制,提升系统弹性。


小结

  • 微服务通过拆分职责清晰的小服务,提升开发效率和扩展性
  • 服务间通信有同步和异步两种模式,初学推荐先掌握同步调用
  • 配置中心集中管理配置,实现动态环境切换
  • 服务发现帮助动态定位服务实例,减少硬编码
  • 实际项目中避免过度拆分和无容错设计,提升系统稳定性

希望通过这一章的学习,你对微服务的核心组成和实践流程有了立体的认识。它不是一蹴而就的,需要我们一步步摸索和调整。放心,我还会陪你一起挖掘更深的微服务秘密!