Spring Cloud系列教程 | 第十五篇:Spring WebFlux和Spring Cloud的反应式微服务

推荐 Spring Boot/Cloud 视频:

我已经在一年前的文章Reactive microservices with Spring 5中描述了Spring反应支持。当时Spring WebFlux项目一直在积极开发中,现在在Spring 5正式发布之后,值得看看它的当前版本。此外,我们将尝试将我们的反应式微服务置于Spring Cloud生态系统中,其中包含诸如Eureka服务发现,Spring Cloud Commons负载平衡@LoadBalanced以及使用Spring Cloud Gateway(也基于WebFlux和Netty)的API网关等元素。我们还将通过示例来检查Spring对NoSQL数据库的反应支持Spring Data Reactive Mongo项目。

这是一个图,说明了我们的示例系统的体系结构,包括两个微服务,发现服务器,网关和MongoDB数据库。源代码在 spring-cloud-webflux-exampleGitHub上提供。

在这里插入图片描述
让我们描述创建上述系统的方法的进一步步骤。

步骤1.使用Spring WebFlux构建响应式应用程序

要为项目启用库Spring WebFlux,我们应该spring-boot-starter-webflux在依赖项中包含starter 。它包括一些依赖库,如Reactor或Netty服务器。

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-webflux</artifactId>
</dependency>


REST控制器看起来非常类似于为同步Web服务定义的控制器。唯一的区别在于返回对象的类型。而不是单个对象,我们返回类的实例Mono,而不是列表,我们返回类的实例Flux。感谢Spring Data Reactive Mongo,我们不必再在存储库bean上调用所需的方法。

@RestController
public class AccountController {
 
    private static final Logger LOGGER = LoggerFactory.getLogger(AccountController.class);
 
    @Autowired
    private AccountRepository repository;
 
    @GetMapping("/customer/{customer}")
    public Flux findByCustomer(@PathVariable("customer") String customerId) {
        LOGGER.info("findByCustomer: customerId={}", customerId);
        return repository.findByCustomerId(customerId);
    }
 
    @GetMapping
    public Flux findAll() {
        LOGGER.info("findAll");
        return repository.findAll();
    }
 
    @GetMapping("/{id}")
    public Mono findById(@PathVariable("id") String id) {
        LOGGER.info("findById: id={}", id);
        return repository.findById(id);
    }
 
    @PostMapping
    public Mono create(@RequestBody Account account) {
        LOGGER.info("create: {}", account);
        return repository.save(account);
    }
 
}

步骤2.使用Spring Data Reactive Mongo将应用程序与数据库集成

应用程序和数据库之间的集成实现也非常简单。首先,我们需要spring-boot-starter-data-mongodb-reactive在项目依赖项中包含starter 。

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-mongodb-reactive</artifactId>
</dependency>

包含启动器后,将自动启用对响应式Mongo存储库的支持。下一步是使用ORM映射声明实体。以下类也作为响应返回AccountController。

@Document
public class Account {
 
    @Id
    private String id;
    private String number;
    private String customerId;
    private int amount;
 
    ...
 
}

最后,我们可以创建扩展的存储库接口ReactiveCrudRepository。它遵循Spring Data JPA实现的模式,并提供了一些CRUD操作的基本方法。它还允许使用名称定义方法,这些方法会自动映射到查询。与标准Spring Data JPA存储库相比,唯一的区别在于方法签名。对象用Mono和包裹Flux。

public interface AccountRepository extends ReactiveCrudRepository {
 
    Flux findByCustomerId(String customerId);
 
}

在这个例子中,我使用Docker容器在本地运行MongoDB。因为我使用Docker Toolkit在Windows上运行Docker,所以Docker机器的默认地址是192.168.99.100。这是application.yml文件中数据源的配置。

spring:
  data:
    mongodb:
      uri: mongodb://192.168.99.100/test
      

步骤3.使用Eureka启用服务发现

与Spring Cloud集成Eureka与同步REST微服务非常相似。要启用发现客户端,我们应首先spring-cloud-starter-netflix-eureka-client在项目依赖项中包含启动器

<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>

然后我们必须使用@EnableDiscoveryClient注解启用它。

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

微服务将自动在Eureka注册。对于cource,我们可能运行的不仅仅是每个服务的实例。这是在运行两个实例和单个实例后显示Eureka Dashboard(http:// localhost:8761)如下,您可以参考我之前的文章了解详细信息: 使用Spring Boot 2.0,Eureka和Spring Cloud的微服务快速指南。Eureka服务器可用作模块。account-servicecustomer-servicediscovery-service
在这里插入图片描述

步骤4.使用WebClient的反应式微服务之间的服务间通信

WebClientSpring WebFlux项目实现了跨服务通信。与RestTemplate您相同应该使用Spring Cloud Commons进行注释@LoadBalanced。它使用Netflix OSS Ribbon客户端实现与服务发现和负载平衡的集成。因此,第一步是使用@LoadBalanced注释声明客户端构建器bean 。

@Bean
@LoadBalanced
public WebClient.Builder loadBalancedWebClientBuilder() {
    return WebClient.builder();
}

然后我们可以注入WebClientBuilderREST控制器。与account-service内部实现通信GET /{id}/with-accounts,首先我们使用反应式Spring Data存储库搜索客户实体。它返回对象Mono,而WebClient返回Flux。现在,我们的主要目标是将这些内容合并到发布者,并将单个Mono对象与从中Flux截获的帐户列表一起返回,而不会阻止流。以下代码片段说明了我如何WebClient与其他微服务进行通信,然后将存储库中的响应和结果合并到单个Mono对象。这种合并可能会以更“优雅”的方式完成,因此可以自由地根据您的提案创建推送请求。

@Autowired
private WebClient.Builder webClientBuilder;
 
@GetMapping("/{id}/with-accounts")
public Mono findByIdWithAccounts(@PathVariable("id") String id) {
    LOGGER.info("findByIdWithAccounts: id={}", id);
    Flux accounts = webClientBuilder.build().get().uri("http://account-service/customer/{customer}", id).retrieve().bodyToFlux(Account.class);
    return accounts
            .collectList()
            .map(a -> new Customer(a))
            .mergeWith(repository.findById(id))
            .collectList()
            .map(CustomerMapper::map);
}

步骤5.使用Spring Cloud Gateway构建API网关

Spring Cloud Gateway是最新的Spring Cloud项目之一。它建立在Spring WebFlux之上,因此我们可以将它用作基于反应式微服务的示例系统的网关。与Spring WebFlux应用程序类似,它在嵌入式Netty服务器上运行。要为Spring Boot应用程序启用它,只需在项目中包含以下依赖项。

<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-gateway</artifactId>
</dependency>

们还应该启用发现客户端,以便允许网关获取已注册微服务的列表。但是,没有必要在Eureka中注册网关的应用程序。要禁用注册,您可以将属性设置eureka.client.registerWithEureka为false内部application.yml文件。

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

默认情况下,Spring Cloud Gateway不支持与服务发现集成。要启用它,我们应该将属性设置spring.cloud.gateway.discovery.locator.enabled 为true。现在,应该做的最后一件事是配置路线。Spring Cloud Gateway提供了两种可在路由内配置的组件:过滤器和谓词。谓词用于将HTTP请求与路由匹配,而过滤器可用于在发送下游请求之前或之后修改请求和响应。这是网关的完整配置。它启用服务发现位置,并根据服务注册表中的条目定义两个路由。我们使用Path Route Predicate工厂来匹配传入的请求,以及RewritePath GatewayFilter工厂修改,以使其适应由下游服务公开的格式请求的路径(终点在路径暴露/,而网关揭露他们在路径/account和/customer)。

spring:
  cloud:
    gateway:
      discovery:
        locator:
          enabled: true
      routes:
      - id: account-service
        uri: lb://account-service
        predicates:
        - Path=/account/**
        filters:
        - RewritePath=/account/(?.*), /$\{path}
      - id: customer-service
        uri: lb://customer-service
        predicates:
        - Path=/customer/**
        filters:
        - RewritePath=/customer/(?.*), /$\{path}

步骤6.测试样品系统

在进行一些测试之前,让我们回顾一下我们的样本系统。我们有两个微服务account-service,customer-service它们使用MongoDB作为数据库。微服务customer-service调用端点GET /customer/{customer}暴露account-service。帐户服务的URL取自Eureka。整个示例系统隐藏在网关后面,网关在地址localhost:8090下可用。
现在,第一步是在Docker容器上运行MongoDB。执行以下命令后,Mongo在地址192.168.99.100:27017下可用。

$ docker run -d --name mongo -p 27017:27017 mongo

然后我们可以继续跑步discovery-service。Eureka的默认地址为localhost:8761。您可以使用IDE或仅执行命令来运行它java -jar target/discovery-service-1.0-SNAPHOT.jar。同样的规则适用于我们的样本微服务。但是,account-service需要在两个实例中相乘,因此在使用-Dserver.portVM参数运行第二个实例时,您需要覆盖默认HTTP端口java -jar -Dserver.port=2223 target/account-service-1.0-SNAPSHOT.jar。最后,运行后gateway-service我们可能会添加一些测试数据。

$ curl --header "Content-Type: application/json" --request POST --data '{"firstName": "John","lastName": "Scott","age": 30}' http://localhost:8090/customer
{"id": "5aec1debfa656c0b38b952b4","firstName": "John","lastName": "Scott","age": 30,"accounts": null}
$ curl --header "Content-Type: application/json" --request POST --data '{"number": "1234567890","amount": 5000,"customerId": "5aec1debfa656c0b38b952b4"}' http://localhost:8090/account
{"id": "5aec1e86fa656c11d4c655fb","number": "1234567892","customerId": "5aec1debfa656c0b38b952b4","amount": 5000}
$ curl --header "Content-Type: application/json" --request POST --data '{"number": "1234567891","amount": 12000,"customerId": "5aec1debfa656c0b38b952b4"}' http://localhost:8090/account
{"id": "5aec1e91fa656c11d4c655fc","number": "1234567892","customerId": "5aec1debfa656c0b38b952b4","amount": 12000}
$ curl --header "Content-Type: application/json" --request POST --data '{"number": "1234567892","amount": 2000,"customerId": "5aec1debfa656c0b38b952b4"}' http://localhost:8090/account
{"id": "5aec1e99fa656c11d4c655fd","number": "1234567892","customerId": "5aec1debfa656c0b38b952b4","amount": 2000}

为了测试服务间通信都只是调用端点GET /customer/{id}/with-accounts上gateway-service。它将请求转发给customer-service,然后customer-service通过account-service使用被动方式调用enpoint WebClient。结果如下所示。
在这里插入图片描述

结论

Spring 5和Spring Boot 2.0开始,有一系列可用的方法来构建基于微服务的架构。我们可以使用Spring Cloud Netflix项目进行一对一通信,基于消息代理的消息传递微服务和Spring Cloud Stream的发布/订阅通信模型构建标准同步系统,最后进行异步,反应Spring WebFlux的微服务。本文的主要目的是向您展示如何将Spring WebFlux与Spring Cloud项目结合使用,以便为Spring Boot之上的反应式微服务提供服务发现,负载平衡或API网关等机制。在Spring 5之前,缺乏对反应式微服务的支持是Spring框架的一个缺点,但现在使用Spring WebFlux已不再是这种情况。不仅如此,我们还可以利用Spring对最流行的NoSQL数据库(如MongoDB或Cassandra)的反应支持,并轻松将我们的反应式微服务与同步REST微服务一起放在一个系统中。

源码

源代码在 spring-cloud-webflux-exampleGitHub上提供。

专家推荐

“随着微服务架构的发展,Spring Cloud 使用得越来越广泛。驰狼课堂 Spring Boot 快速入门,Spring Boot 与Spring Cloud 整合,docker+k8s,大型电商商城等多套免费实战教程可以帮您真正做到快速上手,将技术点切实运用到微服务项目中。” 
关注公众号,每天精彩内容,第一时间送达!
在这里插入图片描述

猜你喜欢

转载自blog.csdn.net/qq_43253123/article/details/83017032