Spring Cloud解决了分布式系统的常见问题。 但是,对于只使用广为人知的整体应用程序工作的人来说,从一开始就跳入一长串为分布式服务设计的模式可能会让人不知所措。 本文将通过实用的方法为您介绍Spring Cloud的基础知识。 完成后,您不仅应该知道如何基于Spring Cloud启动项目,还应该了解为什么需要所有步骤以及它们解决了哪些问题。
1.第一服务
让我们定义一下我们将使用Spring Cloud解决的问题。 该演示的目的是为分布式博客平台奠定基础。
分布式系统的核心组件是服务,它不过是旨在专注于域的特定部分的常规应用程序。 在一个复杂的系统中,可能会有数十种不同的服务,但是为了清楚起见,我们仅从两个示例开始。 第一项服务将照顾作者,而第二项服务将专注于文章。
作者服务
在我们的例子中,作者服务是使用spring-boot-starter-web创建的典型Spring Boot应用程序。 目前,我们不使用Spring Cloud的任何功能。
@SpringBootApplication
public class AuthorServiceApplication {
public static void main(String[] args) {
SpringApplication.run(AuthorServiceApplication.class, args);
}
}
这是一个作者域类,这是我们第一个服务的主要重点。
class Author {
private final Long id;
private final String name;
//…
}
最后,我们创建一个REST控制器,该控制器允许提取所有作者或根据其标识符查找特定作者。
@RestController
class AuthorController {
//…
@GetMapping
List<Author> findAll() {
//…
}
@GetMapping("/{id}")
Author findOne(@PathVariable long id) {
//…
}
}
文章服务
第二项服务类似于上一项。 如果需要代码示例,可以在GitHub存储库中找到它们 。
此步骤的关键是要认识到,我们将域的不同部分分为较小的和松散耦合的应用程序,而不是一个较大的应用程序。 它给我们带来什么? 有许多优点,例如,更简单的可伸缩性,弹性或更快的部署。 如果您需要更多的理论背景,建议您阅读Sam Newman撰写的一本很棒的书,名为Building microservices 。
2.分布式配置
如果您尝试在一台机器上启动这两种服务,则默认的Spring Boot设置将无法实现,因为这两个应用程序都将尝试在端口8080上运行。您可以自定义设置并在每个应用程序的application.properties中选择不同的端口。应用程序,这对于两个服务来说不是问题,但对于许多服务而言,可能会更成问题。
配置服务器
对于复杂的分布式系统,明智的做法是将所有服务的配置都放在一个位置,以简化整个管理过程。 为了适合微服务系统,这些配置将由…另一服务提供。 创建应用程序,并将以下依赖项放入pom.xml中 。
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-config-server</artifactId>
</dependency>
配置服务器中的主要应用程序类与其他应用程序没有太大区别。 唯一的区别是之前添加的Spring Cloud依赖项中的@EnableConfigServer批注,该批注负责为外部配置公开API。
@SpringBootApplication
@EnableConfigServer
public class ConfigServerApplication {
public static void main(String[] args) {
SpringApplication.run(ConfigServerApplication.class, args);
}
}
配置位置
我们将在哪里保留服务的配置? 配置服务器JAR文件中的捆绑属性不是一个灵活的解决方案。 一些外部位置似乎是一个更好的主意。 默认情况下,Spring Cloud使用Git存储库来管理配置。 可以在配置应用程序的application.properties中设置Git服务器的URI地址(以及其他详细信息)。 幸运的是,在我们的演示中,我们不需要单独的Git服务器。 出于测试目的,本地存储库可以正常工作。
server.port=9001
spring.application.name=config-server
spring.cloud.config.server.git.uri=file://${user.home}/config
Git存储库的位置是通过spring.cloud.config.server.git.uri属性设置的。 为了使用真实服务器,应将值更改为不带file:前缀的某些URL。 我们还更改了默认端口,以避免在单台计算机上运行时与其他服务冲突。 此外,该应用程序收到了自己的名称。 目前不需要,但稍后我们将使用该名称作为其他服务中对配置服务器的引用。
配置库
所有服务的配置都将保留在spring.cloud.config.server.git.uri中设置的位置。 现在,我们可以创建两个文件,专门用于以前开发的服务,在其中我们将更改默认端口并分配名称,就像对配置服务器所做的一样。
这是article-service.properties文件的内容。
server.port=9003
spring.application.name=article-service
此时的author-service.properties文件看起来几乎相同。
server.port=9004
spring.application.name=author-service
最后,初始化Git存储库并提交两个创建的配置。
git init
git add .
git commit -m 'Service configs'
3.服务发现
配置服务器已准备就绪,但其他服务仍不知道其存在以及如何获取其配置的方式。 该问题的一种可能解决方案是使用Spring Cloud Config Client将服务直接与服务器连接,该客户端可以添加spring-cloud-starter-config依赖项。 主要缺点是我们必须在每个服务中对配置服务器的地址进行硬编码。 如果此服务的位置将来发生更改,或者我们想提供冗余对等方,则所有服务都需要更新。 在称为“服务发现”的模式下解决了在分布式系统中查找其他服务的问题。
注册表服务器
我们已经为所有配置创建了服务器,这是我们的第一个基础架构服务。 同样,现在我们将开发一个注册表服务器,这是另一种基础结构服务,它将充当分布式系统中所有组件的地址簿。 创建具有以下依赖关系的新应用程序。
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-eureka-server</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-config</artifactId>
</dependency>
</dependencies>
第一个负责公开注册表API。 已经提到了第二个依赖关系,该依赖关系用于连接我们的配置服务器,该服务器还将保存我们正在创建的注册表的配置。 主应用程序类类似于其他Spring Boot应用程序。 我们仅添加@EnableEurekaServer批注以公开注册表API。
@SpringBootApplication
@EnableEurekaServer
public class RegistryServerApplication {
public static void main(String[] args) {
SpringApplication.run(RegistryServerApplication.class, args);
}
}
注册表服务器中最后缺少的是引导程序配置。 配置的主要部分将由配置服务器提供服务,但是我们需要描述如何找到它。 在main / resources目录中创建bootstrap.properties文件,并添加下面显示的行,这些行是配置服务器的地址和用于获取属性的注册表应用程序的名称。
spring.cloud.config.name=registry-server
spring.cloud.config.uri=http://localhost:9001
公开注册表配置
下一步是将配置添加到配置服务器监控的Git存储库中。 创建一个名为Registry-server.properties的文件。 重要的是,文件名必须与注册表服务器应用程序中bootstrap.properties文件中的spring.cloud.config.name属性值匹配。 最低要求的配置如下所示。 不要忘记将更改提交到Git存储库。
spring.application.name=registry-server
server.port=9002
eureka.client.register-with-eureka=false
eureka.client.fetch-registry=false
eureka.client.serviceUrl.defaultZone=http://localhost:9002/eureka/
前两个属性是普通Spring Boot应用程序的典型属性。 其他三个专用于Spring Cloud Eureka Client。 默认情况下,每个Eureka服务器都将尝试连接到其他对等服务器以注册其存在。 在我们的简单演示中,我们只有一个注册表服务器实例,但是在生产解决方案中,您可能会提供此类服务的某些冗余。 我们的注册表服务器不会连接任何东西,这就是为什么我们将默认值更改为false的原因。 注册事件会传播到eureka.client.serviceUrl.defaultZone中列出的所有Eureka服务器,但是尽管只有一种,因为在我们这种情况下,我们仍然需要设置此属性以覆盖默认值。
使用外部配置运行注册表
目前,我们可以同时运行两个服务器,以验证它们是否按预期工作。 由于注册表服务器取决于配置服务器,因此需要首先启动它。 几秒钟后,也可以启动注册表服务器。 如果您没有遗漏任何步骤,则两个应用程序都应运行,日志中没有任何错误。 配置服务器应获取配置并在端口9002上运行。导航到http:// localhost:9002 /后 ,将显示Eureka仪表板,其中包含有关正在运行的实例的一些详细信息。
4.配置服务注册
我们的注册服务与配置服务器连接的事实并不意味着该服务器已注册为服务。 服务的责任是将其状态传达给分布式寄存器。 为了连接到注册表服务,配置服务器需要以下依赖关系。
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-eureka</artifactId>
</dependency>
它将允许我们在配置服务器的主类中使用Eureka客户端注释。
@EnableEurekaClient
public class ConfigServerApplication {
//…
}
最后是注册表服务器的地址,必须将其添加到配置服务器的application.properties中。
eureka.client.serviceUrl.defaultZone=http://localhost:9002/eureka/
此时,您可能开始怀疑服务器之间的通信将如何进行。 注册表服务器需要从配置服务器进行配置,而同时配置服务器希望连接到注册表以通知其存在。 他们应该以什么顺序交流?
实际上,没有任何变化。 您首先启动配置服务器。 每隔几秒钟,它将尝试与注册表服务器连接并在每次失败时打印错误日志。 启动注册表服务器后,它将获取其配置并开始接受注册。 最后,配置服务器将注册,并且不再显示错误日志。 尝试一下以确认它是否按预期工作。
5.配置获取
我们的配置服务器最终可以通过本文开头创建的域服务发现。 首先,进行少量修改,我们需要针对作者和文章服务重复上一段的所有步骤,以允许与注册服务进行通信。 提醒一下,这些步骤是:
- 添加对spring-cloud-starter-eureka的依赖
- 用@EnableEurekaClient注释主类
- 在bootstrap.properties中设置eureka.client.serviceUrl.defaultZone (不在application.properties中 )
这将允许服务与注册表服务器进行通信,但仍不会获取任何配置。 为了使这一过程自动化,我们需要在服务中提供另一个小的依赖项(请注意,注册表服务器中使用了相同的依赖项)。
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-config</artifactId>
</dependency>
最后,我们需要在bootstrap.properties中设置一些其他详细信息。 这是作者服务的示例文件。 类比属性应添加到Article服务。
spring.cloud.config.name=author-service
spring.cloud.config.discovery.service-id=config-server
spring.cloud.config.discovery.enabled=true
spring.cloud.config.name的值必须与配置服务器提供的配置库中的相应属性文件匹配。 第二个属性用于标识在我们的Eureka服务器中注册的配置服务器。 更改的值必须与配置服务器中application.properties中存在的spring.application.name值匹配。 使用最后一个属性,我们启用配置发现过程。
您应该能够验证两个服务是否都在集中式配置中定义的端口上启动。 如果您关闭了配置和发现服务,请先启动它们,然后再运行域服务。 只要所有步骤都正确完成,两个服务都应在配置的端口上响应浏览器。 在另一种情况下,确保您不会错过任何步骤或将代码与存储库中的示例进行比较 。
6.一劳永逸,一劳永逸
目前,我们实际上可以认为我们的基本设置已经准备就绪,但是我们将为难题添加另一部分,并了解分布式系统中使用的另一种模式,即服务网关。 顾名思义,其目的是允许客户端使用单个访问点查找所有服务。 换句话说,网关充当分布式服务的路由器。
网关服务
让我们在此演示中创建最后一个Spring Boot应用程序,并添加下面提到的依赖项。 唯一的新功能是spring-cloud-starter-zuul ,其中包含创建网关所需的类。
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-zuul</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-eureka</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-config</artifactId>
</dependency>
除了以前添加的注释外,应用程序的主类还应使用@EnableZuulProxy声明自己为代理网关。
@SpringBootApplication
@EnableZuulProxy
@EnableEurekaClient
public class GatewayServiceApplication {
public static void main(String[] args) {
SpringApplication.run(GatewayServiceApplication.class, args);
}
}
该应用程序还需要bootstrap.properties文件,该文件的值类似于先前配置的域服务。 这里没有什么新东西需要解释。
spring.cloud.config.name=gateway-service
spring.cloud.config.discovery.service-id=config-server
spring.cloud.config.discovery.enabled=true
eureka.client.serviceUrl.defaultZone=http://localhost:9002/eureka/
配置获取
就像其他常规服务一样,网关依赖于配置服务器管理的Git存储库中存储的配置。 创建一个名为gateway-service.properties的文件,使用以下值设置其内容,然后将更改提交到配置库。
spring.application.name=gateway-service
server.port=8080
zuul.routes.author-service.path=/authors/**
zuul.routes.article-service.path=/articles/**
我们已经知道前两个值。 另外两个更有趣。 在这里,我们定义对于给定的URL模式,到网关的所有请求都应转发到由其名称标识的服务。 请注意,我们不会将网关与这些服务的任何特定主机地址耦合。 网关将使用先前创建的发现服务找到它们。
最终基本设置验证
启动网关之后,应用程序应在端口8080上进行侦听。尝试使用配置的映射访问这两个域服务:
http:// localhost:8080 / articles
http:// localhost:8080 / authors
网关模式使我们能够将API的客户端与运行服务的特定主机分离。 只有网关的地址必须与客户端共享。 网关还可以照顾重复服务的负载平衡,但是让我们将这个话题再留一遍。
7.总结
乍一看,基本的Spring Cloud设置似乎非常复杂,尤其是与典型的整体应用程序基础相比。 还有许多创建系统的构件。 但是,每个组件在设计时都考虑了“单一责任主体”。 我们了解了微服务体系结构中使用的三种基本模式,即服务发现,分布式配置和服务网关。 他们每个人都有一个专用的应用程序,仅专注于单个任务。 划分职责是微服务体系结构的主要要素,即使我们创建的小型演示在实践中也极大地描述了这个想法。
翻译自: https://www.javacodegeeks.com/2017/10/spring-cloud-basic-setup.html