1. 概述
Spring Cloud 是构建健壮云应用的框架。它通过解决分布式环境中常见问题,简化了应用开发过程。
微服务架构旨在简化开发、部署和维护。应用的解耦特性让开发者能专注于单一问题,且改进不会影响系统其他部分。
但采用微服务架构时也会面临新挑战:
- ✅ 外部化配置:确保配置灵活且修改时无需重新构建服务
- ✅ 服务发现:动态定位服务实例
- ✅ 隐藏复杂性:屏蔽部署在不同主机上的服务细节
本文将构建五个微服务:配置服务器、发现服务器、网关服务器、图书服务和评分服务。这些服务构成了云开发的坚实基础,能解决上述挑战。
2. 配置服务器
开发云应用时,维护和分发服务配置是个难题。我们不想在水平扩容时浪费时间配置环境,更不想将配置硬编码到应用中造成安全风险。
解决方案:将所有配置整合到单个 Git 仓库,通过一个应用统一管理所有服务的配置。下面实现一个简单版本。
想了解更多细节和复杂示例,可参考我们的 Spring Cloud 配置 文章。
2.1. 项目搭建
访问 *https://start.spring.io*,选择 Maven 和 Spring Boot 2.7.x。
设置 artifact 为 "config"。在依赖项中搜索 "config server" 并添加。点击生成按钮下载预配置项目。
也可手动创建 Spring Boot 项目并添加依赖:
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.7.8</version>
<relativePath/>
</parent>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>2021.0.7</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
添加配置服务器依赖:
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-config-server</artifactId>
</dependency>
最新版本可在 Maven Central 查找:spring-cloud-dependencies, test, config-server
2.2. Spring 配置
在主类添加注解启用配置服务器:
@SpringBootApplication
@EnableConfigServer
public class ConfigApplication {...}
@EnableConfigServer
将应用转换为配置服务器。
2.3. 配置属性
在 src/main/resources
添加 application.properties
:
server.port=8081
spring.application.name=config
spring.cloud.config.server.git.uri=file://${user.home}/application-config
最关键的配置是 git.uri
参数。当前设置为相对路径,Windows 下通常解析为 c:\Users\{username}\
,Linux/macOS 下为 /Users/{username}/
。该属性指向存储所有其他应用属性文件的 Git 仓库,必要时可设为绝对路径。
⚠️ 提示:Windows 系统需在路径前加 "file:///",Linux/macOS 使用 "file://"。
2.4. Git 仓库
导航到 spring.cloud.config.server.git.uri
定义的文件夹,创建 application-config
目录。进入该目录执行 git init
,初始化 Git 仓库用于存储和跟踪配置文件变更。
2.5. 运行验证
执行 mvn spring-boot:run
启动服务器。应看到如下输出:
Tomcat started on port(s): 8081 (http)
2.6. 引导配置
后续服务需要配置服务器管理其属性。这需要先解决鸡生蛋问题:在每个应用中配置能连接到配置服务器的属性。
这是一个引导过程,每个应用都需要 bootstrap.properties
文件。该文件与 application.properties
类似但有特殊之处:
父级 Spring ApplicationContext
**优先加载 bootstrap.properties
**。这至关重要,因为配置服务器需要通过它来管理 application.properties
中的属性。这个特殊的 ApplicationContext
还负责解密加密的应用属性。
建议保持这两个属性文件分离:bootstrap.properties
用于准备配置服务器,application.properties
存放应用特定属性。技术上虽可将应用属性放在 bootstrap.properties
,但不推荐。
最后,既然配置服务器管理应用属性,为什么还需要 application.properties
?答案是它仍可作为默认值,当配置服务器未提供相应属性时生效。
3. 服务发现
解决了配置问题后,还需要让所有服务能互相发现。我们将设置 Eureka 发现服务器。由于服务可能运行在任何 IP/端口组合上,需要中央地址注册表作为应用地址查询服务。
新服务启动时会与发现服务器通信并注册地址,其他服务可通过该信息发起请求。
想了解更多细节和复杂实现,参考 Spring Cloud Eureka 文章。
3.1. 项目搭建
访问 start.spring.io。设置 artifact 为 "discovery"。添加 "eureka server" 和 "config client" 依赖。生成项目。
或手动创建 Spring Boot 项目,添加依赖:
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-config</artifactId>
</dependency>
依赖包可在 Maven Central 查找:config-client, eureka-server
3.2. Spring 配置
在主类添加 Java 配置:
@SpringBootApplication
@EnableEurekaServer
public class DiscoveryApplication {...}
@EnableEurekaServer
将应用配置为使用 Netflix Eureka 的发现服务器。Spring Boot 会自动检测类路径上的配置依赖,从配置服务器获取配置。
3.3. 配置属性
添加两个属性文件:
首先在 src/main/resources
添加 bootstrap.properties
:
spring.cloud.config.name=discovery
spring.cloud.config.uri=http://localhost:8081
这些属性让发现服务器在启动时查询配置服务器。
然后在 Git 仓库添加 discovery.properties
:
spring.application.name=discovery
server.port=8082
eureka.instance.hostname=localhost
eureka.client.serviceUrl.defaultZone=http://localhost:8082/eureka/
eureka.client.register-with-eureka=false
eureka.client.fetch-registry=false
文件名必须匹配 spring.application.name
属性。
此外,我们告诉服务器运行在默认区域,这与配置客户端的区域设置匹配。还设置服务器不注册到其他发现实例。
生产环境中会部署多个实例提供容错能力,此时 register-with-eureka
应设为 true。
⚠️ 记得将文件提交到 Git 仓库,否则不会被检测到。
3.4. 配置服务器添加依赖
在配置服务器的 POM 文件添加依赖:
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-bootstrap</artifactId>
</dependency>
依赖包可在 Maven Central 查找:eureka-client
在配置服务器的 src/main/resources/application.properties
添加:
eureka.client.region=default
eureka.client.registryFetchIntervalSeconds=5
eureka.client.serviceUrl.defaultZone=http://localhost:8082/eureka/
3.5. 运行验证
使用 mvn spring-boot:run
启动发现服务器。命令行输出应包含:
Fetching config from server at: http://localhost:8081
...
Tomcat started on port(s): 8082 (http)
重启配置服务。成功时输出应类似:
DiscoveryClient_CONFIG/10.1.10.235:config:8081: registering service...
Tomcat started on port(s): 8081 (http)
DiscoveryClient_CONFIG/10.1.10.235:config:8081 - registration status: 204
4. 网关服务器
解决了配置和发现问题后,客户端访问所有应用仍存在困难。
在分布式系统中,需要管理复杂的 CORS 头以允许跨域请求。通过创建网关服务器可解决此问题,它作为反向代理将客户端请求转发到后端服务器。
网关服务器是微服务架构的绝佳实践,它让所有响应来自单一主机,消除 CORS 需求,并为认证等通用问题提供统一处理点。
4.1. 项目搭建
访问 https://start.spring.io。设置 artifact 为 "gateway"。添加 "gateway"、"config client" 和 "eureka discovery client" 依赖。生成项目。
或手动创建 Spring Boot 项目,添加依赖:
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-config</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-gateway</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-bootstrap</artifactId>
</dependency>
依赖包可在 Maven Central 查找:config-client, eureka-client, gateway
4.2. Spring 配置
在主类添加配置:
@SpringBootApplication
@EnableDiscoveryClient
@EnableFeignClients
public class GatewayApplication {...}
4.3. 配置属性
添加两个属性文件:
在 src/main/resources
添加 bootstrap.properties
:
spring.cloud.config.name=gateway
spring.cloud.config.discovery.service-id=config
spring.cloud.config.discovery.enabled=true
spring.cloud.gateway.discovery.locator.enabled=true
spring.cloud.gateway.discovery.locator.lowerCaseServiceId=true
eureka.client.serviceUrl.defaultZone=http://localhost:8082/eureka/
在 Git 仓库添加 gateway.properties
:
spring.application.name=gateway
server.port=8080
eureka.client.region=default
eureka.client.registryFetchIntervalSeconds=5
⚠️ 记得提交仓库变更!
4.4. 运行验证
启动配置和发现服务,等待配置服务注册到发现服务器。若已运行则无需重启。完成后启动网关服务器,它应在 8080 端口启动并注册到发现服务器。控制台输出应包含:
Fetching config from server at: http://10.1.10.235:8081/
...
DiscoveryClient_GATEWAY/10.1.10.235:gateway:8080: registering service...
DiscoveryClient_GATEWAY/10.1.10.235:gateway:8080 - registration status: 204
Tomcat started on port(s): 8080 (http)
常见踩坑:在配置服务器注册到 Eureka 前启动网关。此时会看到日志:
Fetching config from server at: http://localhost:8888
这是配置服务器的默认地址,表明发现服务在配置请求时未获取到地址。等待几秒后重试,配置服务器注册到 Eureka 后问题自动解决。
5. 图书服务
微服务架构中,可根据业务目标自由创建多个应用。工程师通常按领域划分服务,我们将遵循此模式创建图书服务,处理应用中所有图书相关操作。
5.1. 项目搭建
访问 *https://start.spring.io*。设置 artifact 为 "book-service"。添加 "web"、"config client" 和 "eureka discovery client" 依赖。生成项目。
或手动添加依赖:
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-config</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-bootstrap</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
依赖包可在 Maven Central 查找:config-client, eureka-client, web
5.2. Spring 配置
修改主类:
@SpringBootApplication
@EnableDiscoveryClient
@RestController
@RequestMapping("/books")
public class BookServiceApplication {
public static void main(String[] args) {
SpringApplication.run(BookServiceApplication.class, args);
}
private List<Book> bookList = Arrays.asList(
new Book(1L, "Baeldung goes to the market", "Tim Schimandle"),
new Book(2L, "Baeldung goes to the park", "Slavisa")
);
@GetMapping("")
public List<Book> findAllBooks() {
return bookList;
}
@GetMapping("/{bookId}")
public Book findBook(@PathVariable Long bookId) {
return bookList.stream().filter(b -> b.getId().equals(bookId)).findFirst().orElse(null);
}
}
还添加了 REST 控制器和属性文件设置的返回值字段。
添加图书 POJO:
public class Book {
private Long id;
private String author;
private String title;
// 标准 getter/setter
}
5.3. 配置属性
添加两个属性文件:
在 src/main/resources
添加 bootstrap.properties
:
spring.cloud.config.name=book-service
spring.cloud.config.discovery.service-id=config
spring.cloud.config.discovery.enabled=true
spring.cloud.gateway.discovery.locator.enabled=true
spring.cloud.gateway.discovery.locator.lowerCaseServiceId=true
eureka.client.serviceUrl.defaultZone=http://localhost:8082/eureka/
在 Git 仓库添加 book-service.properties
:
spring.application.name=book-service
server.port=8083
eureka.client.region=default
eureka.client.registryFetchIntervalSeconds=5
eureka.client.serviceUrl.defaultZone=http://localhost:8082/eureka/
⚠️ 记得提交仓库变更!
5.4. 运行验证
其他服务启动后启动图书服务。控制台输出应类似:
DiscoveryClient_BOOK-SERVICE/10.1.10.235:book-service:8083: registering service...
DiscoveryClient_BOOK-SERVICE/10.1.10.235:book-service:8083 - registration status: 204
Tomcat started on port(s): 8083 (http)
启动后通过浏览器访问 http://localhost:8080/book-service/books,返回控制器中定义的两本图书的 JSON。注意我们通过网关服务器访问,而非直接访问 8083 端口。
6. 评分服务
类似图书服务,评分服务也是领域驱动服务,处理评分相关操作。
6.1. 项目搭建
访问 https://start.spring.io。设置 artifact 为 "rating-service"。添加 "web"、"config client" 和 "eureka discovery client" 依赖。生成项目。
或手动添加依赖:
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-config</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-bootstrap</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
依赖包可在 Maven Central 查找:config-client, eureka-client, web
6.2. Spring 配置
修改主类:
@SpringBootApplication
@EnableDiscoveryClient
@RestController
@RequestMapping("/ratings")
public class RatingServiceApplication {
public static void main(String[] args) {
SpringApplication.run(RatingServiceApplication.class, args);
}
private List<Rating> ratingList = Arrays.asList(
new Rating(1L, 1L, 2),
new Rating(2L, 1L, 3),
new Rating(3L, 2L, 4),
new Rating(4L, 2L, 5)
);
@GetMapping("")
public List<Rating> findRatingsByBookId(@RequestParam Long bookId) {
return bookId == null || bookId.equals(0L) ? Collections.EMPTY_LIST : ratingList.stream().filter(r -> r.getBookId().equals(bookId)).collect(Collectors.toList());
}
@GetMapping("/all")
public List<Rating> findAllRatings() {
return ratingList;
}
}
添加评分 POJO:
public class Rating {
private Long id;
private Long bookId;
private int stars;
// 标准 getter/setter
}
6.3. 配置属性
添加两个属性文件:
在 src/main/resources
添加 bootstrap.properties
:
spring.cloud.config.name=rating-service
spring.cloud.config.discovery.service-id=config
spring.cloud.config.discovery.enabled=true
spring.cloud.gateway.discovery.locator.enabled=true
spring.cloud.gateway.discovery.locator.lowerCaseServiceId=true
eureka.client.serviceUrl.defaultZone=http://localhost:8082/eureka/
在 Git 仓库添加 rating-service.properties
:
spring.application.name=rating-service
server.port=8084
eureka.client.region=default
eureka.client.registryFetchIntervalSeconds=5
eureka.client.serviceUrl.defaultZone=http://localhost:8082/eureka/
⚠️ 记得提交仓库变更!
6.4. 运行验证
其他服务启动后启动评分服务。控制台输出应类似:
DiscoveryClient_RATING-SERVICE/10.1.10.235:rating-service:8083: registering service...
DiscoveryClient_RATING-SERVICE/10.1.10.235:rating-service:8083 - registration status: 204
Tomcat started on port(s): 8084 (http)
启动后访问 http://localhost:8080/rating-service/ratings/all,返回所有评分的 JSON。注意通过网关服务器访问,而非直接访问 8084 端口。
7. 总结
现在我们已将 Spring Cloud 的各个组件整合成功能完整的微服务应用。这构成了构建更复杂应用的基础。
源代码可在 GitHub 获取。