面试专栏04-SpringCloud

三、SpringCloud

一、分布式系统的组成

  • 微服务SpringBoot
  • 注册中心/配置中心Spring Cloud Alibaba Nacos
  • 网关Spring Cloud Gateway
  • 远程调用Spring Cloud OpenFeign
  • 服务熔断Spring Cloud Alibaba Sentinel
  • 分布式事务Spring Cloud Alibaba Seata

二、获取所有服务列表

    @Resource
    DiscoveryClient discoveryClient;

    @Test
    void discoveryClientTest(){
        // 获取所有服务的名字
        List<String> services = discoveryClient.getServices();
        services.forEach(System.out::println);

        // 获取服务的实例(参数是服务名字)
        services.forEach((name->{
            List<ServiceInstance> instances = discoveryClient.getInstances(name);
            instances.forEach(i -> {
                String instanceId = i.getInstanceId();
                String host = i.getHost();
                int port = i.getPort();
                String serviceId = i.getServiceId();
                URI uri = i.getUri();
                String scheme = i.getScheme();
                System.out.println("实例id:"+instanceId+
                        "\n主机:"+host+
                        "\n端口:"+port+
                        "\n服务id:"+serviceId+
                        "\nuri:"+uri+
                        "\nscheme:"+scheme);
                System.out.println();
            });
        }));
    }

三、调用远程服务的三种方式

第一种:直接调用,自己选择服务器
    // 基础:没有负载均衡的获取远程服务
    private Product getProductRemote(Long productId) {
        // 获取远程的服务器列表
        List<ServiceInstance> instances = discoveryClient.getInstances("service-product");
        // 获取第一个服务器实例
        ServiceInstance instance = instances.get(0);
        // 构建请求URL
        String url = "http://" + instance.getHost() + ":" + instance.getPort() + "/product/" + productId;
        log.info("远程请求URL:{}", url);
        // 发送请求
        return restTemplate.getForObject(url, Product.class);
    }
第二种:使用LoadBalancerClient
    // 进阶2:完成负载均衡的获取远程服务
    private Product getProductRemoteWithLoadBalance(Long productId) {
        // 获取远程的服务器列表 -- (负载均衡的获取)
        ServiceInstance instance = loadBalancerClient.choose("service-product");
        // 构建请求URL
        String url = "http://" + instance.getHost() + ":" + instance.getPort() + "/product/" + productId;
        log.info("远程请求URL:{}", url);
        // 发送请求
        return restTemplate.getForObject(url, Product.class);
    }
第三种:使用@LoadBalanced
    // 进阶3:直接在 config 中 OrderServiceConfig 类,创建restTemplate对象时,指定使用负载均衡,那末,在调用的时候直接就会
    // 使用负载均衡的方式来获取远程服务
    private Product getProductRemoteWithLoadBalanceAnnotation(Long productId) {
        // 构建请求URL,直接写微服务的名字就好了,不用写具体的地址和端口,
        // 因为在 config 中 OrderServiceConfig 类,创建restTemplate对象时,指定使用负载均衡,
        // service-product 会被动态替换为负载均衡器的地址和端口
        String url = "http://service-product/product/" + productId;
        log.info("远程请求URL:{}", url);
        // 发送请求
        return restTemplate.getForObject(url, Product.class);
    }

四、注册中心宕机情况

注册中心宕机,分为两种情况:

第一种:如果注册中心是在服务注册后且执行了远程调用后宕机的,那末,由于远程调用过,因此会存在缓存,不会每次都请求注册中心获取最新的服务
列表而是使用缓存中的服务列表,只有当注册中心更新时,才会从注册中心重新获取的服务列表。如果注册中心宕机,那末,也不影响从缓存中获取请求地址

第二种:如果注册中心是在服务注册前或调用前就宕机的,那末,由于注册中心还没启动,因此第一次远程调用会请求注册中心,而此时的注册中心处于
宕机状态,因此会报错。

五、配置中心

1、配置中心的依赖
        <!--配置中心的依赖-->
        <dependency>
            <groupId>com.alibaba.cloud</groupId>
            <artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>
        </dependency>
2、导入配置中心的配置文件

在导入配置中心的依赖后,在微服务的配置文件中导入配置文件

spring:
  config:
    import: nacos:service-order.yml # 从 nacos 的配置中心导入配置文件
3、在配置中心创建配置文件

注意:配置文件的 Data ID 就是导入时指定的文件名: service-order.yml(注意有后缀)

4、添加自动刷新的注解,启用自动刷新配置文件,当配置文件更新时
@RefreshScope // 表示对于nacos配置中心的配置文件的变化,会重新加载配置文件(自动刷新更新配置的值)
@RestController
public class OrderController {}
5、不检查配置文件的引入

如果引入了配置中心的依赖,但是没有在配置中导入配置文件,那末,在启动时会报错,因此需要在配置文件中添加以下配置,
来禁用检查配置文件的引入。

配置的声明规则,先导入优先,后声明优先,先用项目中的配置,再用配置中心的配置,因此如果配置相同
则优先使用项目中的配置。
报错信息:

Connected to the target VM, address: '127.0.0.1:62555', transport: 'socket'
16:49:36.515 [main] ERROR org.springframework.boot.diagnostics.LoggingFailureAnalysisReporter -- 

***************************
APPLICATION FAILED TO START
***************************

Description:

No spring.config.import property has been defined

Action:

Add a spring.config.import=nacos: property to your configuration.
    If configuration is not required add spring.config.import=optional:nacos: instead.
    To disable this check, set spring.cloud.nacos.config.import-check.enabled=false.

Disconnected from the target VM, address: '127.0.0.1:62555', transport: 'socket'

解决办法:增加一个配置信息,禁用检查配置文件的引入

spring.cloud.nacos.config.import-check.enabled=false
6、配置中心的数据隔离

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

六、OpenFeign 声明式远程调用

1、依赖引入

注意:前提要引入 loadbalancer 的依赖,因为是默认的负载均衡。

        <!--OpenFeign的依赖-->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-openfeign</artifactId>
        </dependency>
2、开启OpenFeign的功能
@SpringBootApplication
@EnableFeignClients // 开启OpenFeign的功能
public class ServiceOrderApplication {}
3、编写远程调用的接口
@FeignClient(value = "service-product") // 指定远程调用的服务的名字
public interface ProductClient {
    @GetMapping("/product/{productId}") // 指定远程调用的服务的路径
    Product getProduct(@PathVariable("productId") Long productId); // 指定远程调用的服务的方法

    @GetMapping("/productWhitToken/{productId}") // 指定远程调用的服务的路径
    Product getProduct(@PathVariable("productId") Long productId, @RequestHeader("Authorization") String token);
}   
4、调用远程服务
    // 注入远程调用的接口,直接调用即可
    @Resource
    ProductClient productClient;

补充:不仅可以调用注册中心的微服务,还可以使用第三方的AIP,只需要指明
url 路径即可。例如:

   // 通过 url 路径调用第三方的API
   @FeignClient(value = "remote-service",url = "http://www.baidu.com")
   public interface ProductClient {
      @GetMapping("/{wd}") // 指定远程调用的服务的路径
      Product getProduct(@PathVariable("wd") String word); // 指定远程调用的服务的方法
   }
5、Feign 的日志配置
  • 在配置文件中指定远程调用所在的类 或 包。
      logging:
          level:
          dz.cn.remote: debug
    
  • 在配置类中,引入 bean 对象,指定日志的级别
      @Configuration
      public class FeignConfig {
          @Bean
          Logger.Level feignLoggerLevel(){
              return Logger.Level.FULL;
          }
      }
    
6、Feign 的超时控制

当某个链路中的一个远程调用的服务出现卡顿时,可能导致整个链路都出现卡顿,如果这个时候
有大量的请求同时访问,那末可能导致整个服务器的资源耗尽,导致服务雪崩。

  • 连接超时(connectTimeout):默认连接超时 10 秒,默认返回读取信息
  • 读超时(readTimeout):读取超时 60 秒
spring:
  cloud:
    openfeign:
      client:
        config:
          default: # 默认配置,如果没有指定,直接使用默认配置
            connect-timeout: 1000
            read-timeout: 3000
          service-product: # 单独配置
            connect-timeout: 3000 # 连接超时时间
            read-timeout: 5000 # 读取超时时间
7、Feign 的重试机制

远程调用失败后,还可以进行多次尝试,如果某次成功,则返回ok,否则依然返回错误。
需要向Spring中注入重试机制的配置类。

    // 配置重试次数,也可以传参数:间隔时间,最大间隔时间,重试次数,
    // 注意每次的间隔时间是递增的,默认是 100 毫秒,下一次请求为上一次间隔时间的 1.5 倍
    // 但是不超过指定的最大间隔时间
    @Bean
    Retryer retryer(){
        return new Retryer.Default(); // 默认值为:this(100L, TimeUnit.SECONDS.toMillis(1L), 5);
    }
8、Feign 的请求拦截器

在远程调用服务之前,可以对请求内容进行拦截修改,例如:添加请求头,添加请求参数等。

@Component // 将该类注入到Spring容器中,这样就会自动扫描,不需要在配置文件中配置
public class XTokenRequestInterceptor implements RequestInterceptor {
    /**
     * 请求拦截器
     * @param requestTemplate 请求模板(请求信息)
     */
    @Override
    public void apply(RequestTemplate requestTemplate) {
        System.out.println("拦截器调用");

        // 添加请求头
        requestTemplate.header("X-Token", UUID.randomUUID().toString());
        // 添加请求体
        // requestTemplate.body(byte[] data, Charset charset);
        // 添加查询信息
        requestTemplate.query("hh",  "hhh");
    }
}
9、Feign 的Fallback 兜底返回

当远程调用失败时,可以进行兜底返回,例如:返回一个默认的响应信息,或者返回一个错误码。

注意:兜底返回需要一个熔断配置,需要引入熔断的依赖,以及开启熔断功能。

# 开启熔断机制
feign:
sentinel:
enabled: true
// 1、创建一个类,实现远程调用的接口,并实现其中的方法。
@Component
public class ProductFeignClientFallback implements ProductFeignClient {
  @Override
  public Product getProductById(Long id, String token) {
    Product product = new Product();
    product.setId(id);
    product.setProductName("iPhone 15-" + id);
    product.setPrice(new BigDecimal("12.5"));
    product.setNum((int) (id + 10));
    return product;
  }
}

// 2、在远程调用接口中,指明兜底回调类 fallback = ProductFeignClientFallback.class
@FeignClient(value = "service-product",fallback = ProductFeignClientFallback.class)
public interface ProductFeignClient {
  @GetMapping("/product/{id}")
  Product getProductById(@PathVariable("id") Long id,@RequestHeader("Authorization") String token);
}

七、Sentinel 流量控制

1、Sentinel 的基本工作原理

随着微服务的流行,服务和服务之间的稳定性变得越来越重要。Spring Cloud Alibaba Sentinel 以流量为切入点,从流量控制
流量路由、熔断降级、系统自适应过载保护、热点流量防护等多个维度保护服务的稳定性。

  • 定义资源:
    • 主流框架自动适配(web Servlet、Dubbo、SpringCloud)所有 web 接口均为资源
    • 编程式:SphU API
    • 声明式:@SentinelResource 注解
  • 定义规则:
    • 流量控制
    • 熔断降级
    • 系统保护
    • 来源访问控制
    • 热点参数限流
2、Sentinel 启动

在服务运行的机器中,通过命令行启动 sentinel,的 jar 包。

spring:
  cloud:
    sentinel:
      transport:
        dashboard: localhost:8080 # 指定 sentinel 控制台的 ip 和端口

注意:

  • 放行端口:
    • sentinel 启动时,首先会占用一个端口,后面会为每个引入的模块分配一个 端口, 端口号默认从 8719 开始,后面一次递增,因此服务器一定要开启
    • 8719 等后面的端口。
  • 公网 ip:
    • 由于阿里云服务器只能连接访问到公网 ip,因此,如果在服务其上 ping 自己的内网 ip,是 ping 不通的。
3、Sentinel 流控资源分类
  • 默认情况下,Sentinel 会自动为所有 web 接口生成资源,例如:/order/create。
  • 如果需要手动指定资源,则需要使用 @SentinelResource 注解。
1、Web 接口

原理:通过拦截器进行拦截

// 继承 BlockExceptionHandler 接口,其实就是利用拦截器进行判断拦截
@Component
public class MyExceptionHandler implements BlockExceptionHandler {

    // 用于将对象转化为jason字符串,springMVC自带的
    private ObjectMapper objectMapper = new ObjectMapper();

    @Override
    public void handle(HttpServletRequest httpServletRequest,
                       HttpServletResponse httpServletResponse,
                       String resourceName, BlockException e) throws Exception {
        httpServletResponse.setContentType("application/json;charset=UTF-8");
        PrintWriter printWriter = httpServletResponse.getWriter();
        // 自定义 web 限流返回信息
        R ok = R.ok(500, resourceName + "资源的流量超出限制", e);
        String json = objectMapper.writeValueAsString(ok);
        printWriter.print(json);
    }
}
2、@SentinelResource 注解

原理:SentinelResourceAspect 切面来进行,只要是添加了 @SentinelResource 注解的接口,就会有这个切面
切面内部的异常处理逻辑是:

  • 先判断:blockHandler 是否有值
  • 如果没有值,则在判断 fallback 是否有值
  • 如果没有值,则判断 defaultFallback 是否有值
  • 如果都没有值,则直接 throw 抛出给 SpringBoot 处理,可以通过全局异常处理捕获
    @Override
@SentinelResource(value = "createOrder",blockHandler = "createOrderFallback")
public Order createOrder(Long productId, Long userId) {
        // 正常逻辑        
        return order;
}

// 限流异常处理的兜底数据 BlockException 不是 Exception
public Order createOrderFallback(Long productId, Long userId, BlockException e) {
        Order order = new Order();
        order.setId(0L);
        order.setTotalAmount(new BigDecimal("0.0"));
        order.setUserId(userId);
        order.setNickName("未知用户");
        order.setAddress("错误:" + e.getClass());
        return order;
}
3、OpenFeign 远程调用

如果出现限流控制,则会返回 OpenFeign 的远程调用在定义时的兜底回调,没有指定,则使用 SpringBoot 的异常处理

4、SphU 硬编码

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

3、常用的控制规则
1、流控规则

针对来源:default,表示所有来源,可以指定 ip 或者 serviceId
阀值类型:QPS:每秒调用次数
是否集群:可以配置每个服务器的流量控制
流控模式:

  • 直接策略:直接限流,例如:当 QPS 超过阈值时,直接限流

  • 链路策略:额可以指定入口资源,如果是普通的 creat 来的,不做限流,如果是 seckill 过来的都限流

  • 关联策略:当该资源的关联资源请求量大时,限制该资源 \

注意:只有流控效果为快速失败

流控效果:

  • 快速失败:直接限流,多余的丢弃
  • Warm Up(预热):指定限流的最大值,当有大量请求时,会默认先让一部分(1/3)通过,然后慢慢增加,直到达到最大值
  • 匀速排队:会按照QPS的指定数在 1s 上的等分,比如:QPS = 2 ,每秒 2 个请求,每500毫秒一个请求,因此指定的 QPS < 1000
    同时,对于其他的请求会进项排队等待,但是有超时时间,默认为 20s,20s 后,也会丢弃
2、熔断规则
  • 熔断降级:
    如果当前微服务发现自己调用微服务时,总是抛出异常,那末,下一次在调用时,可以直接返回错误信息。
    熔断降级作为自身的保护手段,通常在客户端(调用端)进行配置。
    有利于
    • 切断不稳定调用
    • 快速返回步积压
    • 避免服务雪崩,即由于链路中的某个微服务故障,同时有大量请求到来,导致服务器的资源耗尽,从而导致整个系统崩溃。
  • 熔断开关工作原理:
    • 首先,如果远程调用失败,那末直接返回错误信息,直到达到规定的阈值
    • 断开状态:达到规定的阈值后,熔断开关会处于 断开状态 此时会拒绝所有请求,但是会有规定的断开时间
    • 半闭合状态:如果达到规定的时间后,会进入 半闭合装填 此时,会先尝试放行一个请求,如果请求成功,并且是在合法的范围内(不被认为是慢调用等状态)
      则进入打开状态,如果失败,则进入 断开状态再次重复循环。
  • 熔断开关的阈值规定:
    • 慢调用比例:设置慢时间,在统计时长内,所有请求中,请求时间大于慢时间的比例 大于 慢调用比例,断开
    • 异常比例数:在统计时长内,在统计时长内,所有请求中,异常请求的比例 大于 异常比例数,断开
    • 异常数:在统计时长内,请求的返回的是异常的请求个数 大于 异常数,断开
  • 热点参数限流

注意:有熔断和没有熔断的区别
1、没有熔断:在进行远程调用的时候,发现超时或者远程调用的结果错误,那末执行兜底回调(每次都要向远程发请求
2、有熔断:在进行远程调用的时候,出错了,那末执行兜底回调,并在熔断期间内,不会在向远程发送请求,而是直接返回兜底数据

八、gateway 网关

1、引入网关依赖

两个都需要引入,因为通过nacos做服务发现

        <!--引入服务发现-->
        <dependency>
          <groupId>com.alibaba.cloud</groupId>
          <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
        </dependency>

        <!--网关-->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-gateway</artifactId>
        </dependency>
2、predicates 配置

routes 中的 predicates 属性是一个数组,即配置多种匹配规则,如下图所示。同时,对于每种匹配的参数信息,都保存在
RoutePredicateFactory 的实现类中,每个实现类删去后缀,就是规则的名字,而参数都在实现类的 Config 内部类中
如果添加多个匹配规则,那末只有所有的匹配规则都满足了,才会转发请求
例如:

spring:
  cloud:
    gateway:
      routes:
        - id: order-route
          uri: lb://service-order # lb 表示使用负载均衡,会在nacos中查找对应的服务
          predicates:
            - name: Path
              args:
                patterns: /api/order/**
                matchTrailingSlash: true # 是否精确比配,true:/xxx/1 和 /xxx/1/ 都匹配,false: /xxx/1 和 /xxx/1/ 不匹配
            - name: Header # 向请求头中添加参数
              args:
                header: token  # 参数的名字
                regexp: xxxxx  # 参数的值,可以是正则表达式

同样,也可以自定义匹配规则,只需要写的跟 RoutePredicateFactory 的实现类类似即可

在这里插入图片描述

spring:
  cloud:
    gateway:
      routes:
        - id: order-route
          uri: lb://service-order # lb 表示使用负载均衡,会在nacos中查找对应的服务
          predicates:
            - Path=/api/order/**
        - id: product-route
          uri: lb://service-product
          predicates:
            - Path=/api/product/**
#        - id: url-test
#          uri: https://www.baidu.com # uri 可以是网址,既可以转发给其他的网页,直接访问该网址
#          predicates:
#            - Path=/**
        - id: route-test
          uri: http://localhost:8081
          predicates:
            - name: Path

注意:使用的是外部配置文件,需要在默认的配置文件中,包含外部配置文件,外部配置文件的命名规则为 application-{配置文件名}.yml
public List shortcutFieldOrder() {return Arrays.asList(“patterns”, “matchTrailingSlash”);}
表示短格式的参数,参数的顺序必须和这个方法返回的顺序一致,eg:- Path=/api/order/**,true

3、filters 配置

修改转发路径,添加请求头,修改请求参数,修改响应头等

spring:
  cloud:
    gateway:
      routes:
        - id: order-route
          uri: lb://service-order
          predicates:
            - Path=/api/order/**,true
          filters: # 路径重写,匹配 /api/order/** 的请求,将 /api/order/** 删除前缀 /api/order/
            - RewritePath=/api/order/(?<path>.*),/$\{path}
        - id: product-route
          uri: lb://service-product
          predicates:
            - Path=/api/product/**,true
          filters:
            - RewritePath=/api/product/(?<path>.*),/$\{path}
      default-filters: # 默认过滤器,为所有的路由添加
        - AddResponseHeader=default-filter, 默认值

自定义全局过滤器

@Slf4j
@Component // 加入容器后生效
public class RtGlobalFilter implements GlobalFilter, Ordered {
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
ServerHttpRequest request = exchange.getRequest();
ServerHttpResponse response = exchange.getResponse();

        String uri = request.getURI().toString();
        long start = System.currentTimeMillis();
        log.info("请求【{}】开始,时间:{}",uri,start);
        //===================以上是前置逻辑===================


        // 由于是异步的,所以需要使用doFinally,而不是直接写在下边
        Mono<Void> filter = chain.filter(exchange).doFinally(r ->{
            //==================以下是后置逻辑===================
            long end = System.currentTimeMillis();
            log.info("请求【{}】结束,耗时:{}ms",uri,end-start);
        });

        return filter;
    }

    // 顺序,返回的数值越小,优先级越高
    @Override
    public int getOrder() {
        return 0;
    }
}

自定义yml配置的过滤器
还是参考官方的配置类编写,继承 GatewayFilterFactory 或中其中的抽象类
再次实现抽象类的方法,例如继承:AbstractNameValueGatewayFilterFactory 类,这就是传入的是 参数名和参数值 \

开启全局跨域

spring:
  cloud:
    gateway:
      globalcors:
        cors-configurations:
          '[/**]': # 对于所有请求以及请求类型都允许跨域操作
            allowed-origin-patterns: '*'
            allowed-headers: '*'
            allowed-methods: '*'

九、Seata 分布式事务

TC (Transaction Coordinator) 事务协调者:维护全局和分支事务的状态,驱动全局事务提交或回滚
TM (Transaction Manager) 资源管理器:定义全局事务的范围,驱动全局事务提交或回滚
RM (Resource Manager):管理分支事务对应的资源,与 TC 交谈以注册分支事务和提交或回滚分支事务 \

1、Seata 的引入

客户端下载网址:https://seata.apache.org/zh-cn/download/seata-server
依赖的引入 \

        <!--分布式事务-->
        <dependency>
            <groupId>com.alibaba.cloud</groupId>
            <artifactId>spring-cloud-starter-alibaba-seata</artifactId>
        </dependency>

配置文件,所有引入 seata 的服务都需要配置这个文件,指定 seata 的服务器地址 \

service {
  vgroupMapping.default_tx_group = "default"
  default.grouplist = "localhost:8091"
  enableDegrade = false
  disableGlobalTransaction = false
}
2、Seata 的简单使用

在总的事务调用方法上添加 @GlobalTransactional 注解,其他分支事务添加 @Transactional 注解

3、Seata 的原理

通过二个阶段的提交,保证事务的一致性。

首先:每个微服务的事务开始执行,根据更新数据的结果,记录更前的数据和更新后的数据。
其次:单个微服务的事务提交前,先获取全局锁,保证没有其他的事务对数据进行操作。
然后:单个微服务的事务提交,这里已经更新数据库,并向数据库中的 undolog 日志插入了一条数据,用于记录提交前的数据和更新后的数据,并向全局事务报告事务的状态
最后:

  • 如果所有事务的装填全部是成功,那末直接结束全局事务。
  • 如果有一个事务的状态是回滚,那末,对于已经成功的事务,将根据undolog 日志,进行回滚(即再次修改为原来的数据),然后结束全局事务。
4、Seata 模式
  • AT:默认的自动模式
  • XA:自动模式,但是是根据数据库的XA协议进行实现的,利用数据库的事务做到全局事务,即全局事务在完成前,微服务的事务不会真的提交,而是阻塞
  • TCC:手动模式,需要自己实现开启,提交,回滚,而 seata 只是在合适的时候调用这些方法,用于不仅仅是数据库的事务,也可以是广义上的事务
  • SAGA:用于处理长事务
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值