Spring Boot 2.0,Eureka和Spring Cloud Config Gateway Sleuth快速构建微服务
我的博客上有很多关于Spring Boot和Spring Cloud的微服务的文章。本文的主要目的是简要概述这些框架提供的最重要的组件,这些组件可帮助您创建微服务。本文涉及的主题是:
在我们进入源代码之前,让我们看一下下图。它说明了我们的示例系统的架构。我们有三个独立的微服务,它们在服务发现中注册自己,从配置服务中获取属性并相互通信。整个系统隐藏在API网关之后。
目前,Spring Cloud的最新版本是 Finchley.M9。此版本spring-cloud-dependencies应声明为依赖项管理的pom.xml:
org.springframework.cloud
spring-cloud-dependencies
Finchley.M9
pom
import
现在,让我们考虑采取进一步措施,以便使用Spring Cloud创建基于微服务的工作系统。我们将从Configuration Server开始。
要为应用程序启用Spring Cloud Config功能,请首先包括spring-cloud-config-server项目依赖项。
org.springframework.cloud
spring-cloud-config-server
然后在应用程序启动期间启用运行嵌入式配置服务器的 @EnableConfigServer注解
@SpringBootApplication
@EnableConfigServer
public class ConfigApplication {
public static void main(String[] args) {
new SpringApplicationBuilder(ConfigApplication.class).run(args);
}
}
默认情况下,Spring Cloud Config Server将配置数据存储在Git存储库中。这在生产模式下是非常好的选择,但对于样本目的文件系统后端就足够了。从配置服务器开始真的很容易,因为我们可以将所有属性放在类路径中。Spring Cloud Config默认搜索以下位置内的属性源:classpath:/, classpath:/config, file:./, file:./config。
我们将所有的财产来源放在里面src/main/resources/config。YAML文件名将与服务名称相同。例如,发现服务的YAML文件将位于此处:src/main/resources/config/discovery-service.yml。
最后两件重要的事情。如果您想使用文件系统后端启动配置服务器,则可以激活Spring Boot配置文件本机。可以通过–spring.profiles.active=native在应用程序引导期间设置参数来实现。我还通过在文件中设置属性将默认配置服务器端口(8888)更改为8061。server.portbootstrap.yml
重要的是配置服务器。现在,所有其他应用程序(包括发现服务)都需要添加spring-cloud-starter-config依赖项才能启用配置客户端。我们还必须包括依赖spring-cloud-starter-netflix-eureka-server。
org.springframework.cloud
spring-cloud-starter-netflix-eureka-server
然后,您应该在应用程序启动期间通过@EnableEurekaServer在主类上设置注解来启用运行嵌入式发现服务器。
@SpringBootApplication
@EnableEurekaServer
public class DiscoveryApplication {
public static void main(String[] args) {
new SpringApplicationBuilder(DiscoveryApplication.class).run(args);
}
}
应用程序必须从配置服务器获取属性源。客户端所需的最小配置是应用程序名称和配置服务器的连接设置。
spring:
application:
name: discovery-service
cloud:
config:
uri: http://localhost:8088
正如我已经提到的,配置文件discovery-service.yml应放在config-service模块内。但是,需要对下面显示的配置说几句话。我们已将Eureka运行端口从默认值(8761)更改为8061。对于独立的Eureka实例,我们必须禁用注册和获取注册表。
server:
port: 8061
eureka:
instance:
hostname: localhost
client:
registerWithEureka: false
fetchRegistry: false
serviceUrl:
defaultZone: http://${eureka.instance.hostname}:${server.port}/eureka/
现在,当您使用嵌入式Eureka服务器启动应用程序时,您应该看到以下日志。
成功启动应用程序后,您可以访问地址为http:// localhost:8061 /的 Eureka Dashboard 。
我们的微服务在启动期间执行了一些操作。它需要从config-service发现服务中获取配置,注册自身,公开HTTP API并自动生成API文档。要启用所有这些机制,我们需要包含一些依赖项pom.xml。要启用配置客户端,我们应该包括起动spring-cloud-starter-config。在包含spring-cloud-starter-netflix-eureka-client和注释主类后, 将为微服务启用Discovery客户端 @EnableDiscoveryClient。要强制Spring Boot应用程序生成API文档,我们应该包含 springfox-swagger2依赖项并添加注解 @EnableSwagger2。
以下是为我的示例微服务定义的完整依赖项列表。
org.springframework.cloud
spring-cloud-starter-netflix-eureka-client
org.springframework.cloud
spring-cloud-starter-config
org.springframework.boot
spring-boot-starter-web
io.springfox
springfox-swagger2
2.8.0
这里是使用Discovery Client和Swagger2进行微服务的主要应用程序类。
@SpringBootApplication
@EnableDiscoveryClient
@EnableSwagger2
public class EmployeeApplication {
public static void main(String[] args) {
SpringApplication.run(EmployeeApplication.class, args);
}
@Bean
public Docket swaggerApi() {
return new Docket(DocumentationType.SWAGGER_2)
.select()
.apis(RequestHandlerSelectors.basePackage("pl.piomin.services.employee.controller"))
.paths(PathSelectors.any())
.build()
.apiInfo(new ApiInfoBuilder().version("1.0").title("Employee API").description("Documentation Employee API v1.0").build());
}
...
}
应用程序必须从远程服务器获取配置,因此我们应该只提供bootstrap.yml具有服务名称和服务器URL的文件。实际上,这是Config First Bootstrap方法的示例,当应用程序首先连接到配置服务器并从远程属性源获取发现服务器地址时。还有Discovery First Bootstrap,其中从发现服务器获取配置服务器地址。
spring:
application:
name: employee-service
cloud:
config:
uri: http://localhost:8088
没有太多配置设置。这是存储在远程服务器上的应用程序配置文件。它仅存储HTTP运行端口和Eureka URL。但是,我也将文件employee-service-instance2.yml放在远程配置服务器上。它为应用程序设置了不同的HTTP端口,因此您可以基于远程属性在本地运行同一服务的两个实例。现在,您可以在应用程序启动期间传递参数后运行employee-service端口9090的第二个实例spring.profiles.active=instance2。使用默认设置,您将在端口8090上启动微服务。
server:
port: 9090
eureka:
client:
serviceUrl:
defaultZone: http://localhost:8061/eureka/
这是实现REST控制器类的代码。它提供了添加新员工和使用不同过滤器搜索员工的实现:
@RestController
public class EmployeeController {
private static final Logger LOGGER = LoggerFactory.getLogger(EmployeeController.class);
@Autowired
EmployeeRepository repository;
@PostMapping
public Employee add(@RequestBody Employee employee) {
LOGGER.info("Employee add: {}", employee);
return repository.add(employee);
}
@GetMapping("/{id}")
public Employee findById(@PathVariable("id") Long id) {
LOGGER.info("Employee find: id={}", id);
return repository.findById(id);
}
@GetMapping
public List findAll() {
LOGGER.info("Employee find");
return repository.findAll();
}
@GetMapping("/department/{departmentId}")
public List findByDepartment(@PathVariable("departmentId") Long departmentId) {
LOGGER.info("Employee find: departmentId={}", departmentId);
return repository.findByDepartment(departmentId);
}
@GetMapping("/organization/{organizationId}")
public List findByOrganization(@PathVariable("organizationId") Long organizationId) {
LOGGER.info("Employee find: organizationId={}", organizationId);
return repository.findByOrganization(organizationId);
}
}
我们的第一个微服务已经创建并启动。现在,我们将添加其他相互通信的微服务。下图图示了三个样品微服务之间的通信流:organization-service,department-service和employee-service。微服务organization-service收集(GET /organization/{organizationId}/with-employees)或没有员工(GET /organization/{organizationId}))department-service和部门员工列表的部门列表,而不直接从不同部门划分employee-service。微服务department-service可以收集分配给特定部门的员工列表。
在该方案中两个以上描述 organization-service和department-service具有本地化其他微服务并与它们进行通信。这就是为什么我们需要为这些模块添加额外的依赖性:spring-cloud-starter-openfeign。Spring Cloud Open Feign是一个声明性REST客户端,它使用Ribbon客户端负载均衡器与其他微服务进行通信。
org.springframework.cloud
spring-cloud-starter-openfeign
Open Feign的替代解决方案是Spring RestTemplatewith @LoadBalanced。然而,Feign提供了更加优雅的定义客户端的方式,所以我更喜欢它而不是RestTemplate。在包含所需的依赖项后,我们还应该使用@EnableFeignClients注释启用Feign客户端。
@SpringBootApplication
@EnableDiscoveryClient
@EnableFeignClients
@EnableSwagger2
public class OrganizationApplication {
public static void main(String[] args) {
SpringApplication.run(OrganizationApplication.class, args);
}
...
}
现在,我们需要定义客户端的接口。因为organization-service与其他两个微服务通信,我们应该创建两个接口,每个微服务一个。每个客户端的界面都应注明@FeignClient。注释中的一个字段是必需的 - name。此名称应与服务发现中注册的目标服务的名称相同。这是调用端点GET /organization/{organizationId}公开的客户端的接口employee-service。
@FeignClient(name = "employee-service")
public interface EmployeeClient {
@GetMapping("/organization/{organizationId}")
List findByOrganization(@PathVariable("organizationId") Long organizationId);
}
内部可用的第二个客户端接口organization-service调用两个端点department-service。第一个GET /organization/{organizationId}返回组织只有可用部门列表,而第二个GET /organization/{organizationId}/with-employees返回相同的数据集,包括分配给每个部门的列表员工。
@FeignClient(name = "department-service")
public interface DepartmentClient {
@GetMapping("/organization/{organizationId}")
public List findByOrganization(@PathVariable("organizationId") Long organizationId);
@GetMapping("/organization/{organizationId}/with-employees")
public List findByOrganizationWithEmployees(@PathVariable("organizationId") Long organizationId);
}
最后,我们必须将Feign客户端的bean注入REST控制器。现在,我们可以调用的方法中定义DepartmentClient和EmployeeClient,这相当于调用REST端点。
@RestController
public class OrganizationController {
private static final Logger LOGGER = LoggerFactory.getLogger(OrganizationController.class);
@Autowired
OrganizationRepository repository;
@Autowired
DepartmentClient departmentClient;
@Autowired
EmployeeClient employeeClient;
...
@GetMapping("/{id}")
public Organization findById(@PathVariable("id") Long id) {
LOGGER.info("Organization find: id={}", id);
return repository.findById(id);
}
@GetMapping("/{id}/with-departments")
public Organization findByIdWithDepartments(@PathVariable("id") Long id) {
LOGGER.info("Organization find: id={}", id);
Organization organization = repository.findById(id);
organization.setDepartments(departmentClient.findByOrganization(organization.getId()));
return organization;
}
@GetMapping("/{id}/with-departments-and-employees")
public Organization findByIdWithDepartmentsAndEmployees(@PathVariable("id") Long id) {
LOGGER.info("Organization find: id={}", id);
Organization organization = repository.findById(id);
organization.setDepartments(departmentClient.findByOrganizationWithEmployees(organization.getId()));
return organization;
}
@GetMapping("/{id}/with-employees")
public Organization findByIdWithEmployees(@PathVariable("id") Long id) {
LOGGER.info("Organization find: id={}", id);
Organization organization = repository.findById(id);
organization.setEmployees(employeeClient.findByOrganization(organization.getId()));
return organization;
}
}
Spring Cloud Gateway是一个相对较新的Spring Cloud项目。它构建于Spring Framework 5,Project Reactor和Spring Boot 2.0之上。它需要Spring Boot和Spring Webflux提供的Netty运行时。这是Spring Cloud Netflix Zuul的替代品,它是迄今为止唯一一个为微服务提供API网关的Spring Cloud项目。
API网关在模块内部实现gateway-service。首先,我们应该将启动器包含spring-cloud-starter-gateway在项目依赖项中。
org.springframework.cloud
spring-cloud-starter-gateway
我们还需要启用发现客户端,因为gateway-service它与Eureka集成以便能够执行到下游服务的路由。Gateway还将公开我们的示例微服务公开的所有端点的API规范。这就是我们在网关上启用Swagger2的原因。
@SpringBootApplication
@EnableDiscoveryClient
@EnableSwagger2
public class GatewayApplication {
public static void main(String[] args) {
SpringApplication.run(GatewayApplication.class, args);
}
}
Spring Cloud Gateway提供了三个用于配置的基本组件:路由,谓词和过滤器。Route是网关的基本构建块。它包含目标URI以及已定义谓词和过滤器的列表。Predicate负责匹配来自传入HTTP请求的任何内容,例如标头或参数。过滤器可以在将请求和响应发送到下游服务之前和之后修改它们。可以使用配置属性设置所有这些组件。我们将使用为我们的示例微服务定义的路由创建并放置在confiration服务器文件gateway-service.yml上。
但首先,我们应该通过将property设置spring.cloud.gateway.discovery.locator.enabled为true来启用与路由的发现服务器的集成。然后我们可以继续定义路线规则。我们使用Path Route Predicate Factory来匹配传入的请求,使用RewritePath GatewayFilter Factory来修改请求的路径,使其适应下游服务公开的格式。uri参数指定在发现服务器中注册的目标服务的名称。我们来看看以下路由定义。例如,为了organization-service在路径下的网关上可用/organization/,我们应该定义谓词Path=/organization/,然后/organization从路径中去除前缀,因为目标服务在路径下公开/**。基于uri值的Eureka获取目标服务的地址lb://organization-service。
spring:
cloud:
gateway:
discovery:
locator:
enabled: true
routes:
- id: employee-service
uri: lb://employee-service
predicates:
- Path=/employee/**
filters:
- RewritePath=/employee/(?.*), /$\{path}
- id: department-service
uri: lb://department-service
predicates:
- Path=/department/**
filters:
- RewritePath=/department/(?.*), /$\{path}
- id: organization-service
uri: lb://organization-service
predicates:
- Path=/organization/**
filters:
- RewritePath=/organization/(?.*), /$\{path}
每个带有注释的Spring Boot微服务都会@EnableSwagger2在路径下公开Swagger API文档/v2/api-docs。但是,我们希望将该文档放在单一位置 - 在API网关上。为了实现它,我们需要SwaggerResourcesProvider在gateway-service模块内部提供bean实现 接口。该bean负责定义Swagger资源的列表存储位置,应由应用程序显示。以下是SwaggerResourcesProvider基于Spring Cloud Gateway配置属性从服务发现中获取所需位置的实现。
不幸的是,SpringFox Swagger仍然不支持Spring WebFlux。这意味着如果你将SpringFox Swagger依赖项包含在项目应用程序中将无法启动…我希望很快就能获得对WebFlux的支持,但现在我们必须使用Spring Cloud Netflix Zuul作为网关,如果我们想运行嵌入式Swagger2就可以了。
我创建proxy-service了基于Netflix Zuul的gateway-service基于Spring Cloud Gateway 的替代API网关模块。这是一个内置SwaggerResourcesProvider实现的bean proxy-service。它使用ZuulPropertiesbean将路由定义动态加载到bean中。
@Configuration
public class ProxyApi {
@Autowired
ZuulProperties properties;
@Primary
@Bean
public SwaggerResourcesProvider swaggerResourcesProvider() {
return () -> {
List resources = new ArrayList();
properties.getRoutes().values().stream()
.forEach(route -> resources.add(createResource(route.getServiceId(), route.getId(), "2.0")));
return resources;
};
}
private SwaggerResource createResource(String name, String location, String version) {
SwaggerResource swaggerResource = new SwaggerResource();
swaggerResource.setName(name);
swaggerResource.setLocation("/" + location + "/v2/api-docs");
swaggerResource.setSwaggerVersion(version);
return swaggerResource;
}
}
这是我们的示例微服务系统的Swagger UI,地址为http:// localhost:8060 / swagger-ui.html。
我们来看看下图中可见的系统架构。我们将从organization-service观点来讨论它。在开始organization-service连接到config-service地址localhost:8088 (1)之后可用。基于远程配置设置,它可以在Eureka (2)中注册。当organization-service外部客户端通过地址localhost:8060下可用的网关(3)调用端点时,请求被转发到基于来自服务发现(4)的条目的实例。然后在Eureka (5)中查找地址,并调用其端点(6)。最后organization-serviceorganization-servicedepartment-servicedepartment-service从中呼叫来自employee-service。作为负载的两个可用实例之间平衡请求employee-service通过织带 (7)
我们来看看地址为http:// localhost:8061的Eureka Dashboard 。在那里注册了四个微服务实例:单个实例organization-service和department-service,以及两个实例employee-service。
现在,让我们调用端点http:// localhost:8060 / organization / 1 / with-departments-and-employees。
使用Spring Cloud Sleuth关联不同微服务之间的日志非常简单。实际上,您唯一需要做的就是为spring-cloud-starter-sleuth每个微服务和网关的依赖项添加启动器。
org.springframework.cloud
spring-cloud-starter-sleuth
为了澄清,我们将默认日志格式更改为: %d{yyyy-MM-dd HH:mm:ss} ${LOG_LEVEL_PATTERN:-%5p} %m%n。以下是我们的三个示例微服务生成的日志。[]Spring Cloud Stream生成的大括号内有四个条目。对我们来说最重要的是第二个条目,它表示在traceId系统边缘为每个传入的HTTP请求设置一次。
Spring Boot 2.0,Eureka和Spring Cloud Config Gateway Sleuth快速构建微服务
“随着微服务架构的发展,Spring Cloud 使用得越来越广泛。驰狼课堂 Spring Boot 快速入门,Spring Boot 与Spring Cloud 整合,docker+k8s,大型电商商城等多套免费实战教程可以帮您真正做到快速上手,将技术点切实运用到微服务项目中。”