文章详情

短信预约-IT技能 免费直播动态提醒

请输入下面的图形验证码

提交验证

短信预约提醒成功

如何使用Spring Cloud构建微服务架构?

2023-06-05 06:09

关注

微服务架构模式的核心在于如何识别服务的边界,设计出合理的微服务。

如何使用Spring Cloud构建微服务架构?

但如果要将微服务架构运用到生产项目上,并且能够发挥该架构模式的重要作用,则需要微服务框架的支持。

如何使用Spring Cloud构建微服务架构?

在 Java 生态圈,目前使用较多的微服务框架就是集成了包括 Netflix OSS 以及 Spring Cloud。

它包括:

其中又包括:

Eureka:服务治理组件,包含服务注册中心、服务注册与发现。

Hystrix:容器管理组件,实现断路器模式,倘若依赖的服务出现延迟或故障,则提供强大的容错功能。

Ribbon:客户端负载均衡的服务调用组件。

Feign:基于 Ribbon 和 Hystrix 的声明式服务调用组件。

Zuul:网关组件,提供智能路由、访问过滤等功能。

Archaius:外部化配置组件。

服务治理

当一个系统的微服务数量越来越多的时候,我们就需要对服务进行治理,提供统一的服务注册中心,然后在其框架下提供发现服务的功能。

这样就避免了对多个微服务的配置,以及微服务之间以及与客户端之间的耦合。

Spring Cloud Eureka 是对 Netflix Eureka 的包装,用以实现服务注册与发现。

Eureka 服务端即服务注册中心,支持高可用配置。它依托强一致性提供良好的服务实例可用性,并支持集群模式部署。

Eureka 客户端则负责处理服务的注册与发现。客户端服务通过 annotation 与参数配置的方式,嵌入在客户端应用程序代码中。

在运行应用程序时,Eureka 客户端向注册中心注册自身提供的服务,并周期性地发送心跳更新它的服务租约。

搭建服务注册中心

服务注册中心是一个独立部署的服务(你可以认为它也是一个微服务),所以需要单独为它创建一个项目,并在 pom.xml 中添加 Eureka 的依赖:

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

创建 Spring Boot Application:

@EnableEurekaServer @SpringBootApplication public class Application {     public static void main(String[] args) {         new SpringApplicationBuilder(Application.class).web(true).run(args);     } }

注册服务提供者

要让自己编写的微服务能够注册到 Eureka 服务器中,需要在服务的 Spring Boot Application 中添加 @EnableDiscoveryClient 注解,如此才能让 Eureka 服务器发现该服务。

当然,pom.xml 文件中也需要添加相关依赖:

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

同时,我们还需要为服务命名,并指定地址。这些信息都可以在 application.properties 配置文件中配置:

spring.application.name=demo-service  eureka.client.serviceUrl.defaultZone=http://localhost:1111/eureka/

说明:Spring 更推荐使用 yml 文件来维护系统的配置,yml 文件可以体现出配置节的层次关系,表现力比单纯的 key-value 形式更好。

如果结合使用后面讲到的 Spring Cloud Config,则客户端的配置文件必须命名为 bootstrap.properties 或者 bootstrap.yml。

与上述配置相同的 yml 文件配置为:

spring:   application:     name: demo-service  eureka:   client:     serviceUrl:        defaultZone: http://localhost:1111/eureka/

服务发现与消费

在微服务架构下,许多微服务可能会扮演双重身份:

注册在 Eureka Server 中的微服务可能会被别的服务消费。此时,就相当于在服务中创建另一个服务的客户端,并通过 RestTemplate 发起对服务的调用。

为了更好地提高性能,可以在服务的客户端引入 Ribbon,作为客户端负载均衡。

现在假定我们要为 demo-service 创建一个服务消费者 demo-consumer。该消费者自身也是一个 Spring Boot 微服务,同时也能够被 Eureka 服务器注册。

这时,就需要在该服务的 pom.xml 中添加 Eureka 与 Ribbon 的依赖:

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

然后在主应用类 ConosumerApplication 中注入 RestTemplate,并引入 @LoadBalanced 注解开启客户端负载均衡:

@EnableDiscoveryClient @SpringBootApplication public class ConsumerApplication {     @Bean     @LoadBalanced     RestTemplate restTemplate() {         return new RestTemplate();     }     public static void main(String[] args) {         SpringApplication.run(ConsumerApplication.class, args)     } }

假设消费 demo-service 的客户端代码写在 demo-consumer 服务的其中一个 Controller 中:

@RestController public class ConsumerController {     @Autowired     RestTemplate restTemplate;      @RequestMapping(value = "/demo-consumer", method = RequestMethod.Get)     public String helloConsumer() {         return restTemplate.getForEntity("http://demo-service/demo", String.class).getBody();      } }

通过 RestTemplate 就可以发起对 demo-service 的消费调用。

声明式服务调用

通过 Ribbon 和 Hystrix 可以实现对微服务的调用以及容错保护,但 Spring Cloud 还提供了另一种更简单的声明式服务调用方式,即 Spring Cloud Feign。

Feign 实际上就是对 Ribbon 与 Hystrix 的进一步封装。通过 Feign,我们只需创建一个接口并用 annotation 的方式配置,就可以完成对服务供应方的接口(REST API)绑定。

假设我们有三个服务:

服务之间的依赖关系如下图所示:

如何使用Spring Cloud构建微服务架构?

要使用 Feign 来完成声明式的服务调用,需要在作为调用者的服务中创建 Client。

Client 通过 Eureka Server 调用注册的对应服务,这样可以解除服务之间的耦合。

结构如下图所示:

如何使用Spring Cloud构建微服务架构?

为了使用 Feign,需要对应微服务的 pom.xml 文件中添加如下依赖:

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

同时,还需要在被消费的微服务 Application 中添加 @EnableFeignClients 注解。

例如在 Statistics 服务的应用程序类中:

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

由于 Account 服务需要调用 Statistics 服务,因此需要在 Account 服务项目中增加对应的 Client 接口:

@FeignClient(name = "statistics-service") public interface StatisticsServiceClient {      @RequestMapping(method = RequestMethod.PUT, value = "/statistics/{accountName}", consumes = MediaType.APPLICATION_JSON_UTF8_VALUE)     void updateStatistics(@PathVariable("accountName") String accountName, Account account);  }

StatisticsServiceClient 接口的 updateStatistics() 方法会调用 URI 为 /statistics/{accountName} 的 REST 服务,且 HTTP 动词为 put。

这个服务对应的就是 Statistics Service 中 StatisticsController 类中的 saveStatistics() 方法:

@RestController public class StatisticsController {      @Autowired     private StatisticsService statisticsService;      @RequestMapping(value = "/{accountName}", method = RequestMethod.PUT)     public void saveStatistics(@PathVariable String accountName, @Valid @RequestBody Account account) {         statisticsService.save(accountName, account);     } }

在 Account 服务中,如果要调用 Statistics 服务,都应该通过 StatisticsServiceClient 接口进行调用。

例如,Account 服务中的 AccountServiceImpl 要调用 updateStatistics() 方法,就可以在该类的实现中通过 @autowired 注入 StatisticsServiceClient 接口:

@Service public class AccountServiceImpl implements AccountService {     @Autowired     private StatisticsServiceClient statisticsClient;      @Autowired     private AccountRepository repository;      @Override     public void saveChanges(String name, Account update) {          //...         statisticsClient.updateStatistics(name, account);     } }

Notification 服务对 Account 服务的调用如法炮制。

服务容错保护

在微服务架构中,微服务之间可能存在依赖关系,例如 Notification Service 会调用 Account Service,Account Service 调用 Statistics Service。

真实产品中,微服务之间的调用会更加寻常。倘若上游服务出现了故障,就可能会因为依赖关系而导致故障的蔓延,最终导致整个系统的瘫痪。

Spring Cloud Hystrix 通过实现断路器(Circuit Breaker)模式以及线程隔离等功能,实现服务的容错保护。

仍然参考前面的例子,现在系统的微服务包括:

假设上游服务可能会出现故障,为保证系统的健壮性,需要在下游服务中加入容错包含功能。

首先需要在 demo-consumer 服务中添加对 Hystrix 的依赖:

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

然后在 demo-consumer 的应用程序类中加入 @EnableCircuitBreaker 开启断路器功能:

@EnableCircuitBreaker @EnableDiscoveryClient @SpringBootApplication public class ConsumerApplication {     @Bean     @LoadBalanced     RestTemplate restTemplate() {         return new RestTemplate();     }     public static void main(String[] args) {         SpringApplication.run(ConsumerApplication.class, args)     } }

注意:Spring Cloud 提供了 @SpringCloudApplication 注解简化如上代码。该注解事实上已经包含了前面所述的三个注解。

@SpringCloudApplication 注解的定义如下所示:

@Target(ElementType.TYPE) @Retention(RetentionPolicy.RUNTIME) @Documented @Inherited @SpringBootApplication @EnableDiscoveryClient @EnableCircuitBreaker public @interface SpringCloudApplication {}

接下来,需要引入一个新的服务类来封装 Hystrix 提供的断路器保护功能,主要是定义当故障发生时需要执行的回调逻辑,即代码中指定的 fallbackMethod:

@Service public class ConsumerService {     @Autowired     RestTemplate restTemplate;      @HystrixCommand(fallbackMethod = "consumerFallback")     public String consume() {         return restTemplate.getForEntity("http://demo-service/demo", String.class).getBody();      }      public String consumerFallback() {         return "error";     } }  @RestController public class ConsumerController {     @Autowired     ConsumerService consumerService;      @RequestMapping(value = "/demo-consumer", method = RequestMethod.Get)     public String helloConsumer() {         return consumerService.consume();      } }

服务监控

微服务架构将服务的粒度分解的足够细,这使得它在保证服务足够灵活、足够独立的优势下,也带来了管理和监控上的挑战,服务与服务之间的依赖也变得越来越复杂。因此,对服务健康度和运行指标的监控就变得非常重要。

Hystrix 提供了 Dashboard 用以监控 Hystrix 的各项指标信息。为了监控整个系统的微服务,我们需要为 Hystrix Dashboard 建立一个 Spring Boot 微服务。

在该服务项目的 pom 文件中,添加如下依赖:

<dependency>     <groupId>org.springframework.cloud</groupId>     <artifactId>spring-cloud-starter-hystrix</artifactId> </dependency> <dependency>     <groupId>org.springframework.cloud</groupId>     <artifactId>spring-cloud-starter-hystrix-dashboard</artifactId> </dependency> <dependency>     <groupId>org.springframework.cloud</groupId>     <artifactId>spring-cloud-starter-actuator</artifactId> </dependency>

服务的 Application 类需要添加 @EnableHystrixDashboard,以启用 Hystrix Dashboard 功能。

同时,可能需要根据实际情况修改 application.properties 配置文件,例如选择可用的端口号等。

如果要实现对集群的监控,则需要加入 Turbine。

API 网关

理论上,客户端可以直接向每个微服务直接发送请求。但是这种方式是存在挑战和限制的,调用者需要知道所有端点的地址,分别对每一段信息执行 http 请求,然后将结果合并到客户端。

一般而言,针对微服务架构模式的系统,采用的都是前后端分离的架构。为了明显地隔离开前端与后端的边界,我们通常可以专门为前端的消费者定义更加粗粒度的 Open Service。

这些 Open Service 是对外的 RESTful API 服务,可以通过 F5、Nginx 等网络设备或工具软件实现对各个微服务的路由与负载均衡,并公开给外部的客户端调用(注意,内部微服务之间的调用并不需要通过 Open Service)。

这种对外公开的 Open Service 通常又被称为边缘服务(edge service)。

如果这些 Open Service 需要我们自己去开发实现并进行服务的运维,在系统规模不断增大的情况下,会变得越来越困难。

例如,当增加了新的微服务又或者 IP 地址发生变动时,都需要运维人员手工维护这些路由规则与服务实例列表。

又例如针对所有垂直分隔的微服务,不可避免存在重用的横切关注点,例如用户身份认证、授权或签名校验等机制。

我们不能在所有微服务中都去添加这些相同的功能,因为这会造成横切关注点的冗余。

解决的办法是引入 API 网关(API Gateway)。它是系统的单个入口点,用于通过将请求路由到适当的后端服务或者通过调用多个后端服务并聚合结果来处理请求。

此外,它还可以用于认证、insights、压力测试、金丝雀测试(canary testing)、服务迁移、静态响应处理和主动变换管理。

Spring Cloud 为 API 网关提供的解决方案就是 Spring Cloud Zuul,它是对 Netflix Zuul 的包装。

路由规则与服务实例维护

Zuul 解决路由规则与服务实例维护的方法是通过 Spring Cloud Eureka。

API Gateway 自身就是一个 Spring Boot 服务,该服务自身被注册为 Eureka 服务治理下的应用,同时它会从 Eureka 中获得所有其他微服务的实例信息。

这样的设计符合 DRY 原则,因为 Eureka 已经维护了一套服务实例信息,Zuul 直接重用了这些信息,无需人工介入。

对于路由规则,Zuul 默认会将服务名作为 ContextPath 创建路由映射,基本上这种路由映射机制就可以满足微服务架构的路由需求。

倘若需要一些特殊的配置,Zuul 也允许我们自定义路由规则,可以通过在 API 网关的 Application 类中创建 PatternServiceRouteMapper 来定义自己的规则。

横切关注点

诸如授权认证、签名校验等业务逻辑本身与微服务应用所要处理的业务逻辑没有直接关系,我们将这些可能横跨多个微服务的功能称为“横切关注点”。这些横切关注点往往会作为“装饰”功能在服务方法的前后被调用。

Spring Cloud Zuul 提供了一套过滤器机制,允许开发者创建各种过滤器,并指定哪些规则的请求需要执行哪个过滤器。

自定义的过滤器继承自 ZuulFilter 类。例如我们要求客户端发过来的请求在路由之前需要先验证请求中是否包含 accessToken 参数。

如果有就进行路由,否则就拒绝,并返回 401 Unauthorized 错误,则可以定义 AccessFilter 类:

public class AccessFilter extends ZuulFilter {     private static Logger log = LoggerFactory.getLogger(AccessFilter.class);      @Override     public String filterType() {         return "pre"     }      @Override     public int filterOrder() {         return 0;     }      @Override     public boolean shouldFilter() {         return true;     }      @Override     public Object run() {         RequestContext ctx = RequestContext.getCurrentContext();         HttpServletRequest request = ctx.getRequest();          log.info("send {} request to {}", request.getMethod(), request.getRequestURL().toString());          Object accessToken = request.getParameter("accessToken");         if (accessToken == null) {             log.warn("access token is empty");             ctx.setSendZuulResponse(false);             ctx.setResponseStatusCode(401);             return null;         }         log.info("access token ok");         return null;     } }

要让该自定义过滤器生效,还需要在 Zuul 服务的 Application 中创建具体的 Bean:

@EnableZuulProxy @SpringCloudApplication public class ZuulApplication {     public static void main(String[] args) {         new SpringApplicatonBuilder(ZuulApplication.class).web(true).run(args);     }      @Bean     public AccessFilter accessFilter() {         return new AccessFilter();     } }

Zuul 一共提供了四种过滤器:

下图来自官网,它展现了客户端请求到达 Zuul API 网关的生命周期与过滤过程:

如何使用Spring Cloud构建微服务架构?

通过 starter 添加 Zuul 的依赖时,自身包含了 spring-cloud-starter-hystrix 与 spring-cloud-starter-ribbon 模块的依赖,因此 Zuul 自身就拥有线程隔离与断路器的服务容错功能,以及客户端负载均衡。

但是,倘若我们使用 path 与 url 的映射关系来配置路由规则,则路由转发的请求并不会采用 HystrixCommand 来包装,因而这类路由是没有服务容错与客户端负载均衡作用的。

所以在使用 Zuul 时,应尽量使用 path 和 serviceId 的组合对路由进行配置。

分布式配置中心

为什么要引入一个分布式配置中心?一个微服务就需要至少一个配置文件,怎么管理分散在各个微服务中的配置文件呢?如果微服务采用的是不同的技术栈,如何来统一微服务的配置呢?

微服务是部署在不同的节点中,显然我们无法在单机中实现对分布式节点的配置管理。这就是引入 Spring Cloud Config 的目的。

Spring Cloud Config 提供了服务端和客户端支持。服务端是一个独立的微服务,同样可以注册到 Eureka 服务器中。

每个需要使用分布式配置中心的微服务都是 Spring Cloud Config 的客户端。

Spring Cloud Config 默认实现基于 Git 仓库,既可以进行版本管理,还可以通过本地 Git 库起到缓存作用。

Spring Cloud Config 不限于基于 Spring Cloud 开发的系统,而是可以用于任何语言开发的程序,并支持自定义实现。

配置中心服务端

Spring Cloud Config Server 作为配置中心服务端,提供如下功能:

建立一个 Config 服务,需要添加如下依赖:

<dependency>     <groupId>org.springframework.cloud</groupId>     <artifactId>spring-cloud-config-server</artifactId> </dependency>

服务的 Application 类需要添加 @EnableConfigServer 注解:

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

配置服务的基本信息和 Git 仓库的信息放在 application.yml 文件中:

spring:   cloud:     config:       server:         git:             uri: http://localhost/workspace/springcloud-demo             username: user             password: password  server:   port: 8888  security:   user:     password: ${CONFIG_SERVICE_PASSWORD}

Git 库与配置服务

在 Config 服务中配置了 Git 服务器以及 Git 库的信息后,我们就可以在 Git 库中提交配置文件。

存储在Git 库中配置文件的名字以及分支名(默认为 master 分支)会组成访问 Config 服务的 URI。

假设有一个服务为 Notification 服务,则它在配置中心服务端的配置文件为 notification-dev.yml,内容如下:

devMode:  true spring:     application:         name: notification     jdbc:         host: localhost         port: 3306         user: root         password: 123456 logging:     file: demo

配置中心客户端

需要读取配置中心服务端信息的微服务都是配置中心的客户端,为了能够读取配置服务端的信息,这些微服务需要:

例如,Account 服务的配置是由 Spring Cloud Config 进行管理的。在它的资源目录下,提供了 bootstrap.yml 配置文件,内容如下所示:

spring:   application:     name: account-service   cloud:     config:       uri: http://config:8888       fail-fast: true       password: ${CONFIG_SERVICE_PASSWORD}       username: user

注意,该配置文件除了配置了该 Account 服务应用的 name 之外,主要是支持该应用获得配置服务端的信息。

微服务自身的配置信息则统一放到配置中心服务端的文件中,并由 Git 库进行管理。

例如,Account 服务的详细配置在配置中心服务端的 account-dev.yml 文件中:

security:   oauth3:     client:       clientId: account-service       clientSecret: ${ACCOUNT_SERVICE_PASSWORD}       accessTokenUri: http://auth-service:5000/uaa/oauth/token       grant-type: client_credentials       scope: server  spring:   data:     mongodb:       host: account-mongodb       username: user       password: ${MONGODB_PASSWORD}       database: piggymetrics       port: 27017  server:   context-path: /accounts   port: 6000

Spring Cloud Config 通过 Git 实现分布式的配置管理。当配置中心服务端的配置信息发生变更时,各个作为配置客户端的微服务会向 Git 库提交 pull 更新,获得最新的配置信息。

当然,Spring Cloud Config 还可以使用 SVN 库进行配置管理,也支持简单的本地文件系统的存储方式。

此时需要将 spring.profiles.active 设置为 native,并设置搜索配置文件的路径。如果不配置路径,默认在 src/main/resources 目录下搜索。

如下配置文件:

spring:   cloud:     config:       server:         native:           search-locations: classpath:/shared   profiles:     active: native

搜索路径放在 classpath 下的 shared 目录下,那么在代码中,目录就是 resources/shared。

如果使用本地文件系统管理配置文件,则无法支持分布式配置管理以及版本管理,因此在生产系统下,还是推荐使用 Git 库的方式。

总结

在实施微服务时,我们可以将微服务视为两个不同的边界:

所有的微服务都会通过 Eureka 来完成微服务的注册与发现。一个典型的基于 Spring Cloud 的微服务架构如下所示:

如何使用Spring Cloud构建微服务架构?

微服务的集成可以通过 Feign+Ribbon 以 RESTful 方式实现通信,也可以基于 RPC 方式(可以结合 Protocol Buffer)完成服务之间的通信,甚至可以通过发布事件与订阅事件的机制。

事件机制可以使微服务之间更加松散耦合。这时,我们可以引入 RabbitMQ 或 Kafka 来做到服务与服务之间的解耦。

事件机制是异步和非阻塞的,在某些业务场景下,它的性能会更加的好。Spring Cloud 也提供了相关的组件 Spring Cloud Stream 来支持这种事件机制。

对于微服务之间的协作,到底选择 Feign 这种 REST 方式、事件机制或者 RPC 方式,取决于业务场景是否需要同步方式,还是异步方式;是高性能高并发,还是普通方式;是要求彻底解耦,还是做到一般的松散耦合。

我们需要针对实际情况作出实际的判断,作出正确的选择。没有谁坏谁好之分,而是看谁更加的适合。

作者:张逸

简介:架构编码实践者,IT 文艺工作者,大数据平台架构师,兼爱 OO 与 FP,热衷于编程语言学习与技艺提升,致力于将主流领域驱动设计与函数式编程、响应式编程以及微服务架构完美结合。他的个人微信公众号为「逸言」,个人博客:http://zhangyi.xyz。

阅读原文内容投诉

免责声明:

① 本站未注明“稿件来源”的信息均来自网络整理。其文字、图片和音视频稿件的所属权归原作者所有。本站收集整理出于非商业性的教育和科研之目的,并不意味着本站赞同其观点或证实其内容的真实性。仅作为临时的测试数据,供内部测试之用。本站并未授权任何人以任何方式主动获取本站任何信息。

② 本站未注明“稿件来源”的临时测试数据将在测试完成后最终做删除处理。有问题或投稿请发送至: 邮箱/279061341@qq.com QQ/279061341

软考中级精品资料免费领

  • 历年真题答案解析
  • 备考技巧名师总结
  • 高频考点精准押题
  • 2024年上半年信息系统项目管理师第二批次真题及答案解析(完整版)

    难度     813人已做
    查看
  • 【考后总结】2024年5月26日信息系统项目管理师第2批次考情分析

    难度     354人已做
    查看
  • 【考后总结】2024年5月25日信息系统项目管理师第1批次考情分析

    难度     318人已做
    查看
  • 2024年上半年软考高项第一、二批次真题考点汇总(完整版)

    难度     435人已做
    查看
  • 2024年上半年系统架构设计师考试综合知识真题

    难度     224人已做
    查看

相关文章

发现更多好内容

猜你喜欢

AI推送时光机
位置:首页-资讯-后端开发
咦!没有更多了?去看看其它编程学习网 内容吧
首页课程
资料下载
问答资讯