TOC
CHAT

Spring Cloud微服务开发:基础知识

微服务(microservice)是一种软件架构风格,它是以专注于单一职责的很多小型项目为基础,组合出复杂的大型应用。

相关操作:服务拆分、远程调用、服务治理、请求路由、身份认证、配置管理、服务保护、分布式事务、异步通信、消息可靠性、延迟消息、分布式搜索、倒排索引、数据聚合

详细教程:飞书文档

1 基本概念

1.1 单体架构与微服务架构

单体架构:将业务的所有功能集中在一个项目中开发,打成一个包部署。

  • 优点:架构简单;部署成本低
  • 缺点:团队协作成本高;系统发布效率低;系统可用性差
  • 总结:单体架构适合开发功能相对简单,规模较小的项目。

微服务架构:在服务化思想指导下的一套最佳实践架构方案。服务化指把单体架构中的功能模块拆分为多个独立项目。

  • 特点:粒度小;团队自治;服务自治

1.2 Spring Cloud

Spring Cloud(官网:spring.io/projects/spring-cloud)是目前国内使用最广泛的微服务框架,集成了各种微服务功能组件,并基于SpringBoot实现了这些组件的自动装配,从而提供了良好的开箱即用体验。

  • 服务注册发现:Eureka、Nacos、Consul
  • 服务远程调用:OpenFeign、Dubbo
  • 服务链路监控:Zipkin、Sleuth
  • 统一配置管理:SpringCloudConfig、Nacos
  • 统一网关路由:SpringCloudGateway、Zuul
  • 流控、降级、保护:Hystix、Sentinel

2 服务拆分

2.1 拆分方式

适合拆分的两种情况:

  • 创业型项目:先采用单体架构,快速开发,快速试错。随着规模扩大,逐渐拆分。
  • 确定的大型项目:资金充足,目标明确,可以直接选择微服务架构,避免后续拆分的麻烦。

拆分的目标:

  1. 高内聚:每个微服务的职责要尽量单一,包含的业务相互关联度高、完整度高。
  2. 低耦合:每个微服务的功能要相对独立,尽量减少对其它微服务的依赖。

两种拆分方式:

  • 纵向拆分:按照业务模块来拆分
  • 横向拆分:抽取公共服务,提高复用性

两种工程结构:

  • 独立Project
  • Maven聚合

2.2 远程调用

Spring提供了RestTemplate工具,可以方便地实现Http请求的发送。使用步骤如下:

  1. 注入RestTemplate到Spring容器
  2. 发起远程调用

3 服务治理

3.1 注册中心原理

服务治理中的三个角色:

  • 服务提供者:暴露服务接口,供其它服务调用
  • 服务消费者:调用其它服务提供的接口
  • 注册中心:记录并监控微服务各实例状态,推送服务变更信息

服务提供者会在启动时注册自己信息到注册中心,消费者可以从注册中心订阅和拉取服务信息。

服务提供者通过心跳机制向注册中心报告自己的健康状态,当心跳异常时注册中心会将异常服务剔除,并通知订阅了该服务的消费者。

消费者可以通过负载均衡算法,从多个实例中选择一个。

3.2 Nacos

Nacos是目前国内企业中占比最多的注册中心组件,是阿里巴巴的产品,目前已经加入SpringcloudAlibaba中。

详见:github.com/alibaba/nacos

3.3 服务注册与服务发现

服务注册:

  1. 引入nacos discovery依赖
  2. 配置Nacos地址

服务发现:消费者需要连接nacos以拉取和订阅服务,因此服务发现的前两步与服务注册是一样,后面再加上服务调用即可:

  1. 引入nacos discovery依赖
  2. 配置nacos地址
  3. 服务发现

3.4 OpenFeign

OpenFeign(官方地址:github.com/OpenFeign/feign)是一个声明式的http客户端,是SpringCloud在Eureka公司开源的Feign基础上改造而来。作用:基于SpringMVC的常见注解,使得能够优雅的实现http请求的发送。

3.4.1 快速入门

OpenFeign已经被SpringCloud自动装配,实现起来非常简单:

  1. 引入依赖,包括OpenFeign和负载均衡组件SpringCloudLoadBalancer
  2. 通过@EnableFeignClients注解,启用OpenFeign功能
  3. 编写FeignClient
  4. 使用FeignClient,实现远程调用

3.4.2 连接池

OpenFeign对Http请求做了优雅的伪装,但其底层发起http请求依赖于其它的框架。这些框架可自行选择,包括
以下三种:

  1. HttpURLConnection:默认实现,不支持连接池
  2. Apache HttpClient:支持连接池
  3. OKHttp:支持连接池

具体源码可参考FeignBlockingLoadBalancerClient类中的delegate成员变量。

OpenFeign整合OKHttp的步骤如下:

  1. 引入依赖
  2. 开启连接池功能

3.4.3 最佳实践

当定义的FeignClient不在SpringBootApplication的扫描包范围时,这些FeignClient无法使用。有两种方式解决:

  1. 方式一:指定FeignClient所在包
  2. 方式二:指定FeignClient字节码

3.4.4 日志输出

OpenFeign仅在FeignClient所在包的日志级别为DEBUG时才会输出日志,其日志级别有4级:

  • NONE: 不记录任何日志信息【默认】
  • BASIC:仅记录请求的方法,URL以及响应状态码和执行时间
  • HEADERS:在BASIC的基础上,额外记录了请求和响应的头信息
  • FULL:记录所有请求和响应的明细,包括头信息、请求体、元数据。

由于Feign默认的日志级别为NONE,故默认看不到请求日志。

要自定义日志级别需要声明一个类型为Logger.Level的Bean,在其中定义日志级别。但此时这个Bean并未生效,要想配置某个FeignClient的日志,可以在@FeignClient注解中声明。如果想要全局配置,让所有FeignClient都按照这个日志配置,则需要在@EnableFeignClients注解中声明。


4 网关

网关:网络的关口,负责请求的路由、转发、身份校验。

在SpringCloud中网关的实现包括两种:

  1. Spring Cloud Gateway
    • Spring官方出品
    • 基于WebFlux响应式编程
    • 无需调优即可获得优异性能
  2. Netfilx Zuul
    • Netflix出品
    • 基于Servlet的阻塞式编程
    • 需要调优才能获得与SpringCloudGateway类似的性能

4.1 网关路由

步骤:

  1. 创建新模块
  2. 引入网关依赖
  3. 编写启动类
  4. 配置路由规则
spring:
    cloud:
        gateway:
            routes:
                - id: item  # 路由规则id,自定义,唯一
                  uri: lb://item-service    # 路由目标微服务,lb表示负载均衡
                  predicates:   # 路由断言,判断请求是否符合规则,符合则路由到目标
                   - Path=/item/**
                - id: xx
                  uri: lb://xx-service
                  predicates:
                   - Path=/xx/**

网关路由对应的lava类型是RouteDefinition,其中常见的属性有:

  1. id:路由唯一标示
  2. uri:路由目标地址
  3. predicates:路由断言,判断请求是否符合当前路由。
    • Spring提供了12种基本的RoutePredicateFactory实现(详见Spring Cloud官网)
  4. filters:路由过滤器,对请求或响应做特处理。
    • 网关中提供了33种路由过滤器,每种过滤器都有独特的作用。

4.2 网关登录校验

网关请求处理流程:

  1. 路由映射器
    • HandlerMapping的默认实现是RoutePredicateHandlerMapping
    • HandlerMapping根据请求找到匹配的路由井存入上下文,然后把请求交给webHandler处理
  2. 请求处理器
    • 默认实现是FilteringWebHandler,顾名思义是一个过滤器处理器。它会加载网关中配置的多个过滤器,放入集合并排序,形成过滤器链。然后依次执行这些过滤器。
  3. Netty路由过滤器
    • 负责将请求转发到微服务,当微服务返回结果后存入上下文
  4. 过滤器
    • 过滤器内部包含两部分逻辑——pre和post,分别会在请求路由到微服务之前和之后执行。
    • 当所有Filter的pre逻辑都依次顺序执行通过后,请求才会被路由到微服务,否则会被拦截,后续过滤器不再执行。
    • 微服务返回结果后,再倒序执行Filter的post逻辑

网关过滤器有两种,分别为:

  • GatewayFilter:路由过滤器,作用于任意指定的路由;默认不生效,要配置到路由后生效。
  • GlobalFilter:全局过滤器,作用范围是所有路由;声明后自动生效。

自定义GlobalFilter比较简单,直接实现GlobalFilter接口即可:

@Component
public class MyGlobalFilter implements GlobalFilter, Ordered {
    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        // 1. 获取请求
        ServerHttpRequest request = exchange.getRequest();
        // 2. 过滤器业务处理
        HttpHeaders headers = request.getHeaders();
        System.out.println("headers = " + headers);
        // 3. 放行
        return chain.filter(exchange);
    }

    @Override
    public int getOrder() {
        // 过滤器执行顺序:值越小,优先级越高
        return 0;
    }
}
  • filter方法中的两个参数:
    1. ServerWebExchange exchange:请求上下文,包含整个过滤器链内共享数据,例如request、response等。
    2. GatewayFilterChain chain:过滤器链。当前过滤器执行完后,调用过滤器链中的下一个过滤器。

发表评论