Zzhaozhao的学习笔记-SpringCloud

71 阅读7分钟

SpringCloud的学习笔记

每周一句:万物自有节奏,忙有价值,闲有滋味

1.openFegin

  1. 使用步骤

    1. 引入依赖

          <!--openFeign-->
              <dependency>
                  <groupId>org.springframework.cloud</groupId>
                  <artifactId>spring-cloud-starter-openfeign</artifactId>
              </dependency>
              <!--负载均衡器-->
              <dependency>
                  <groupId>org.springframework.cloud</groupId>
                  <artifactId>spring-cloud-starter-loadbalancer</artifactId>
              </dependency>
      
    2. 在启动类上加入注解:@EnableFeignClients

    3. 创建接口

      @FeignClient("item-service") // 要掉用的服务名
      public interface ItemClient {
      ​
          @GetMapping("/items")
          public List<ItemDTO> queryItemByIds(@RequestParam("ids") Collection<Long> ids);
      }
      
    4. 使用:直接注入到要用的类中

        @Autowired
          private  final ItemClient itemClient;
      
  2. 连接池

    1. 引入依赖

      <!--OK http 的依赖 -->
      <dependency>
        <groupId>io.github.openfeign</groupId>
        <artifactId>feign-okhttp</artifactId>
      </dependency>
      
    2. 开启连接池

      feign:
        okhttp:
          enabled: true # 开启OKHttp功能
      
  3. 推荐配置

    1. 新建模块用于存放要暴露的接口,将client和要使用的实体类放进来

    2. 在要用的模块中引入

    3. 在启动类上扫描路径

      @EnableFeignClients(basePackages = "com.hmall.api.cilent")
      
  4. 日志

    1. 级别:

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

      1. 创建配置类

        public class DefaultFeignConfig {
        ​
            @Bean
            public Logger.Level feignLoggerLevel() {
                return Logger.Level.FULL;
            }
        }
        
      2. 全局配置

        @EnableFeignClients(basePackages = "com.hmall.api.cilent", defaultConfiguration = DefaultFeignConfig.class)
        
      3. 效果

        12:42:10:381 DEBUG 60301 --- [nio-8082-exec-1] c.h.cart.mapper.CartMapper.selectList    : ==>  Preparing: SELECT id,user_id,item_id,num,name,spec,price,image,create_time,update_time FROM cart WHERE (user_id = ?)
        12:42:10:394 DEBUG 60301 --- [nio-8082-exec-1] c.h.cart.mapper.CartMapper.selectList    : ==> Parameters: 1(Long)
        12:42:10:408 DEBUG 60301 --- [nio-8082-exec-1] c.h.cart.mapper.CartMapper.selectList    : <==      Total: 1
        12:42:10:433 DEBUG 60301 --- [nio-8082-exec-1] com.hmall.api.cilent.ItemClient          : [ItemClient#queryItemByIds] ---> GET http://item-service/items?ids=100000006163 HTTP/1.1
        12:42:10:433 DEBUG 60301 --- [nio-8082-exec-1] com.hmall.api.cilent.ItemClient          : [ItemClient#queryItemByIds] ---> END HTTP (0-byte body)
        12:42:10:525 DEBUG 60301 --- [nio-8082-exec-1] com.hmall.api.cilent.ItemClient          : [ItemClient#queryItemByIds] <--- HTTP/1.1 200  (92ms)
        12:42:10:526 DEBUG 60301 --- [nio-8082-exec-1] com.hmall.api.cilent.ItemClient          : [ItemClient#queryItemByIds] connection: keep-alive
        12:42:10:526 DEBUG 60301 --- [nio-8082-exec-1] com.hmall.api.cilent.ItemClient          : [ItemClient#queryItemByIds] content-type: application/json
        12:42:10:526 DEBUG 60301 --- [nio-8082-exec-1] com.hmall.api.cilent.ItemClient          : [ItemClient#queryItemByIds] date: Sat, 10 Aug 2024 04:42:10 GMT
        12:42:10:526 DEBUG 60301 --- [nio-8082-exec-1] com.hmall.api.cilent.ItemClient          : [ItemClient#queryItemByIds] keep-alive: timeout=60
        12:42:10:526 DEBUG 60301 --- [nio-8082-exec-1] com.hmall.api.cilent.ItemClient          : [ItemClient#queryItemByIds] transfer-encoding: chunked
        12:42:10:526 DEBUG 60301 --- [nio-8082-exec-1] com.hmall.api.cilent.ItemClient          : [ItemClient#queryItemByIds] 
        12:42:10:526 DEBUG 60301 --- [nio-8082-exec-1] com.hmall.api.cilent.ItemClient          : [ItemClient#queryItemByIds] [{"id":"100000006163","name":"巴布豆(BOBDOG)柔薄悦动婴儿拉拉裤XXL码80片(15kg以上)","price":67100,"stock":10000,"image":"https://m.360buyimg.com/mobilecms/s720x720_jfs/t23998/350/2363990466/222391/a6e9581d/5b7cba5bN0c18fb4f.jpg!q70.jpg.webp","category":"拉拉裤","brand":"巴布豆","spec":"{}","sold":11,"commentCount":33343434,"isAD":false,"status":2}]
        12:42:10:526 DEBUG 60301 --- [nio-8082-exec-1] com.hmall.api.cilent.ItemClient          : [ItemClient#queryItemByIds] <--- END HTTP (371-byte body)
        

2.Gateway

  1. 使用

    1. 引入pom

       <!--网关-->
              <dependency>
                  <groupId>org.springframework.cloud</groupId>
                  <artifactId>spring-cloud-starter-gateway</artifactId>
              </dependency>
              <!--nacos discovery-->
              <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-loadbalancer</artifactId>
              </dependency>
      
    2. 配置yaml

      server:
        port: 8080
      spring:
        application:
          name: gateway
        cloud:
          nacos:
            server-addr: 127.0.0.1:8848
          gateway:
            routes:
              - id: item # 路由规则id,自定义,唯一
                uri: lb://item-service # 路由的目标服务,lb代表负载均衡,会从注册中心拉取服务列表
                predicates: # 路由断言,判断当前请求是否符合当前规则,符合则路由到目标服务
                  - Path=/items/**,/search/** # 这里是以请求路径作为判断规则
      
  2. 自定义过滤器

    1. GatewayFilter:路由过滤器,作用于任意指定的路由;默认不生效,要配置到路由后生效

    2. GlobalFilter:全局过滤器,作用范围是所有的路由;声明后自动动生效

    3. 代码

      /**
       * @author Zzhaozhao
       * @date 2024/8/11
       * @description 过滤器
       * @apiNote
       */
      @Component
      public class MyGlobalFilter implements GlobalFilter, Ordered {
          @Override
          public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
              ServerHttpRequest request = exchange.getRequest();
              HttpHeaders headers = request.getHeaders();
              System.out.println(headers);
              return chain.filter(exchange);
          }
      
          @Override
          public int getOrder() {
              return 0;
          }
      }
      
      /**
       * @author Zzhaozhao
       * @date 2024/8/11
       * @description 过滤器
       * @apiNote
       */
      @Component
      public class MyGatewayFilterFactory extends AbstractGatewayFilterFactory<MyGatewayFilterFactory.Config> {
          @Override
          public GatewayFilter apply(Config config) {
              return new OrderedGatewayFilter((exchange, chain) -> {
                  System.out.println(config.getA());
                  return chain.filter(exchange);
              },1);
          }@Data
          public static class Config{
              private String A;
              private String B;
              private String C;
          }
      
          @Override
          public List<String> shortcutFieldOrder() {
              return List.of("a","b","c");
          }
      
          public MyGatewayFilterFactory(){
              super(Config.class);
          }
      }
      

3.登陆校验拦截器

  1. 在gateway模块添加拦截器

    @Component
    @RequiredArgsConstructor
    public class AuthGlobalFilter implements GlobalFilter, Ordered {
    
        private final JwtTool jwtTool;
    
        private final AuthProperties authProperties;
    
        private final AntPathMatcher antPathMatcher = new AntPathMatcher();
    
        @Override
        public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
            // 1.获取请求路径
            ServerHttpRequest request = exchange.getRequest();
    
            // 2.判断是否需要拦截
            if (isFilter(request.getPath().toString())) {
                return chain.filter(exchange);
            }
    
            // 3.获取token
            HttpHeaders headers = request.getHeaders();
            String token = null;
            List<String> list = headers.get("authorization");
            if (!CollectionUtils.isEmpty(list)) {
                token = list.get(0);
            }
    
            // 4.校验token
            Long userId = null;
            try {
                userId = jwtTool.parseToken(token);
            } catch (UnauthorizedException e) {
                ServerHttpResponse response = exchange.getResponse();
                response.setStatusCode(HttpStatus.UNAUTHORIZED);
                return response.setComplete();
            }
    
            // 5.获取用户信息传递下去
    
            String user = userId.toString();
            exchange.mutate()
                    .request(builder -> builder.header("user-info", user))
                    .build();
    
            return chain.filter(exchange);
        }
    
        /**
         * 判断是否为过滤的地址
         *
         * @param path
         * @return
         */
        private boolean isFilter(String path) {
            for (String excludePath : authProperties.getExcludePaths()) {
                if (antPathMatcher.match(excludePath, path)) {
                    return true;
                }
            }
            return false;
        }
    
        @Override
        public int getOrder() {
            return 0;
        }
    }
    
  2. 配置白名单

    hm:
      jwt:
        location: classpath:hmall.jks
        alias: hmall
        password: hmall123
        tokenTTL: 30m
      auth:
        excludePaths:
          - /search/**
          - /users/login
          - /items/**
          - /hi
    

4.微服务获取用户

  1. 获取存入网关请求里面的用户信息通过拦截

    public class UserInfoInterceptor implements HandlerInterceptor {
    
        @Override
        public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
            String userInfo = request.getHeader("user-info");
            if(StrUtil.isNotBlank(userInfo)){
                UserContext.setUser(Long.parseLong(userInfo));
            }
            return true;
        }
    
        @Override
        public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
            UserContext.removeUser();
        }
    }
    
  2. 编写配置类加载拦截器

    @Configuration
    @ConditionalOnClass(DispatcherServlet.class)
    public class MvcConfig implements WebMvcConfigurer {
    
        @Override
        public void addInterceptors(InterceptorRegistry registry) {
            registry.addInterceptor(new UserInfoInterceptor());
        }
    }
    
  3. 要注意的是,这个配置类默认是不会生效的,因为它所在的包是com.hmall.common.config,与其它微服务的扫描包不一致,无法被扫描到,因此无法生效。

    基于SpringBoot的自动装配原理,我们要将其添加到resources目录下的META-INF/spring.factories文件中

    org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
      com.hmall.common.config.MyBatisConfig,\
      com.hmall.common.config.JsonConfig,\
      com.hmall.common.config.MvcConfig
    
  4. 在openfeign中创建bean将用户信息放入请求头中

      @Bean
        public RequestInterceptor userInfoInterceptor(){
            return requestTemplate -> {
                String user = UserContext.getUser().toString();
                if(StrUtil.isNotBlank(user)){
                    requestTemplate.header("user-info",user);
                }
            };
        }
    

5.配置管理

  1. 配置共享:抽取公公共配置到nacos

    spring:
      datasource:
        url: jdbc:mysql://${hm.db.host:127.0.0.1}:${hm.db.port:3306}/${hm.db.database}?useUnicode=true&characterEncoding=UTF-8&autoReconnect=true&serverTimezone=Asia/Shanghai
        driver-class-name: com.mysql.cj.jdbc.Driver
        username: ${hm.db.un:root}
        password: ${hm.db.pw:123}
    mybatis-plus:
      configuration:
        default-enum-type-handler: com.baomidou.mybatisplus.core.handlers.MybatisEnumTypeHandler
      global-config:
        db-config:
          update-strategy: not_null
          id-type: auto
    
  2. 引入pom

    <!--nacos配置管理-->
            <dependency>
                <groupId>com.alibaba.cloud</groupId>
                <artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>
            </dependency>
            <!--读取bootstrap文件-->
            <dependency>
                <groupId>org.springframework.cloud</groupId>
                <artifactId>spring-cloud-starter-bootstrap</artifactId>
            </dependency>
    
  3. 创建bootstrap.yaml文件

    spring:
      application:
        name: cart-service # 服务名称
      profiles:
        active: dev
      cloud:
        nacos:
          server-addr: 127.0.0.1:8848 # nacos地址
          config:
            file-extension: yaml # 文件后缀名
            shared-configs: # 共享配置
              - dataId: shared-jdbc.yaml # 共享mybatis配置
              - dataId: shared-log.yaml # 共享日志配置
              - dataId: shared-swagger.yaml # 共享日志配置
    
  4. 修改application.yaml的配置

    server:
      port: 8082
    feign:
      okhttp:
        enabled: true # 开启OKHttp连接池支持
    hm:
      swagger:
        title: 购物车服务接口文档
        package: com.hmall.cart.controller
      db:
        database: hm-cart
    

6.配置热更新

  1. 创建配置类

    @Data
    @Component
    @ConfigurationProperties(prefix = "hm.cart")
    public class CartProperties {
    ​
        private Integer maxCartCount;
    ​
    }
    
  2. nacos创建配置,命名规则为服务名-环境.后缀

    hm:
      cart:
        maxCartCount: 10 # 购物车商品数量上限
    

7.雪崩问题

  1. 原因

    1. 微服务相互调用,服务提供者出现故障或阻塞
    2. 服务调用者没有做好异常处理,导致自身故障
    3. 调用链中的所有服务级联失败,导致整个集群故障
  2. 解决思路

    1. 尽量避免服务出现阻塞或故障

      1. 保障代码的健壮性
      2. 保证网络畅通
      3. 能应对较高的并发
    2. 服务调用者做好远程调用异常的后备方案,避免故障扩散

  3. 解决方案

    1. 请求限流:限制访问微服务的请求的并发量,避免服务因流量激增出现故障
    2. 线程隔离:也叫舱壁模式,模拟船舱隔板的防水原理。通过限定每个业务能使用的线程数量而将故障业务隔离,避免故障扩散。但是还是会有很多的请求进行访问故障的线程也会造成服务阻塞,那么就需要服务熔断
    3. 服务熔断:由断路器统计请求的异常比例或慢调用比例,如果超出阈值则会熔断该业务,则拦截该接口的请求,熔断期间,所有请求快速失败,全走回掉函数

sentinel

  1. 使用

    1. 下载jarjar

    2. 控制台输入命令

      java -Dserver.port=8090 -Dcsp.sentinel.dashboard.server=localhost:8090 -Dproject.name=sentinel-dashboard -jar sentinel-dashboard.jar
      
    3. 在项目的pom文件中引入依赖

      <!--sentinel-->
      <dependency>
          <groupId>com.alibaba.cloud</groupId> 
          <artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
      </dependency>
      
    4. 在yaml中引入

      spring:
        cloud:
          sentinel:
            transport:
              dashboard: localhost:8090
            http-method-specify: true # 开启请求方式前缀
      
  2. 请求限流

    1. 在簇点链路后面点击流控按钮,即可对其做限流配置