SpringCloud小白入门+项目搭建

本文章主要是将SpringCloud开发使用的工具进行介绍,以及SpringCloud项目的展出。欢迎来访的批评指正🌹🌹

.

项目的源代码:https://github.com/Gaonavak/SpringCloud

.

如果可以的话给一下关注和点赞,谢谢支持🫡🫡

一、SpringCloud 简介

核心思想

  • Spring Cloud 的核心思想是将大型应用拆分为多个微服务,每个模块可以由不同的团队独立开发,最终通过路由进行服务调用和转发。

Maven配置

  • 父 POM

    • 使用 Maven 构建父 POM 文件,利用 dependencyManagement 来管理子模块的依赖版本,确保所有模块使用一致的依赖版本,从而提高兼容性和维护性。
    • 在父 POM 中的 <modules> 标签下列出所有子模块。
  • 公共模块

    • 创建公共模块时,不需要启动类,只需定义工具类、接口等,其他子模块可以通过 Maven 引用该模块的依赖

模块间通信

  • 不同模组之间是通过 openfeign 工具进行互通,调用模块其需要在其项目中创建一个接口,并且需要在该接口上面添加@FeignClient(value = "xxx"),x为被调用模块的名称。
    • 在其控制层中使用时通过DI其接口,进而在接口中调用调用对应的接口方法。
    • 接口中的内容就是xxx模块中控制层中的方法签名;

常用的中间体

  • Redis:用于缓存和数据存储,可提高系统性能。
  • Nacos:作为服务发现和配置管理工具,支持动态配置和服务注册。
  • Sentinel/Dubbo:用于服务熔断和流量控制,确保系统的稳定性。
  • RabbitMQ/Kafka:用于异步消息处理和事件驱动架构,提升系统的解耦性和响应速度。
  • Spring Cloud Gateway:用于API网关,集中管理路由和过滤器,提供统一的入口。
  • Spring Security 进行认证和授权,保护微服务的访问安全。
  • 任务调度Quartz、Elastic Job:用于定时执行任务,管理定时任务和批量处理,确保任务的可靠执行。

二、Nacos 注册中心

定义

Nacos 是一个开源的服务注册与配置管理中心,旨在帮助开发者简化微服务架构中的服务发现和配置管理。它提供可视化界面,支持动态配置和热重载,从而提高开发效率。为了启用动态更新功能,需要在启动类上添加 @RefreshScope 注解。

作用

  • 服务发现:Nacos 允许服务实例注册,并使其他服务能够发现这些实例,简化了服务间的调用。
  • 动态配置管理:通过 Nacos,开发者可以在运行时修改配置,而无需重启服务,极大提高了系统的灵活性。
  • 负载均衡 :结合 Nacos 的实例管理,支持轮询等多种负载均衡策略,确保请求均匀分配到各个实例。
    • 心跳机制:Nacos 通过心跳任务监控服务实例的健康状态。

配置

  • 子模块application.yml配置:以下是子模块的通用配置内容,包含 Nacos 的地址和配置文件的名称。(其中的import中的需要在nacos的可视化平台进行创建)

    spring:
    application:
    name: gateway-service
    config:
    import: nacos:gateway-service-dev.yml
    cloud:
    nacos:
    discovery:
    server-addr: localhost:8848
    config:
    server-addr: localhost:8848
    file-extension: yml

  • 子模块 pom.xml 配置依赖

xml 复制代码
        <dependency>
            <groupId>com.alibaba.cloud</groupId>
            <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
        </dependency>

        <dependency>
            <groupId>com.alibaba.cloud</groupId>
            <artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>
        </dependency>
  • nacos平台配置(跟子模块的配置一样,只是将其内容换到nacos的平台进行设置)
yml 复制代码
server:
  port: 9083

三、Gateway路由搭建

定义

Gateway 路由的作用是作为统一的网关,为前端提供公共 IP,避免在访问不同模块时需要使用不同的 IP 地址。通过 Gateway,客户端只需访问一个入口点,简化了服务调用。

作用

  • 统一入口:为微服务提供一个统一的访问入口,简化客户端的调用。
  • 负载均衡:通过集成负载均衡器,确保请求均匀分配到多个服务实例。
  • 路由管理:可以根据请求路径、方法等条件进行路由转发,灵活配置路由规则。
  • 流量控制:结合 Sentinel,可以实现流量控制和熔断,保障系统的稳定性。

配置和依赖

  • 搭建 Gateway 路由需要在 pom.xml 中添加以下依赖:

    <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-gateway</artifactId> </dependency> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-loadbalancer</artifactId> </dependency>
  • 其 Gateway 在 nacos 中的 yml 配置

yml 复制代码
server:
  port: 9998
spring:
  cloud:
    gateway:
      default-filters:
        - AddRequestHeader=origin,gateway # 添加名为origin的请求头,值为gateway
      globalcors:
        cors-configurations:
          '[/**]': # 匹配所有请求
            allowedOrigins: "*" #跨域处理 允许所有的域
            allowedMethods: # 支持的方法
              - GET
              - POST
              - PUT
              - DELETE
      discovery:
        locator:
          enabled: true #开启注册中心路由功能
      routes:  # 路由
        - id: order-service #路由ID,没有固定要求,但是要保证唯一,建议配合服务名
          uri: lb://order-service # 匹配提供服务的路由地址
          predicates: # 断言
            - Path=/order/** # 断言,路径相匹配进行路由
        - id: point-service #路由ID,没有固定要求,但是要保证唯一,建议配合服务名
          uri: lb://point-service # 匹配提供服务的路由地址
          predicates: # 断言
            - Path=/point/** # 断言,路径相匹配进行路由
        - id: user-service #路由ID,没有固定要求,但是要保证唯一,建议配合服务名
          uri: lb://user-service # 匹配提供服务的路由地址
          predicates: # 断言
            - Path=/user/** # 断言,路径相匹配进行路由

关于 Gateway 和 Nginx 的区别

  • Gateway:通常是微服务架构中用于API管理的组件,负责路由、负载均衡、认证、限流等功能。它可以处理内部服务间的通信和外部请求。
  • Nginx:是一个高性能的反向代理服务器和负载均衡器。除了处理静态文件外,Nginx 还可以用于流量控制、SSL 终端、缓存等。

四、Sentinel 检测中心

定义

Sentinel 是阿里巴巴开源的一个轻量级流量控制组件,旨在帮助开发者在分布式系统中实现流量控制、熔断、降级等功能,以保障系统的稳定性和高可用性。它通过监控服务的 QPS(每秒请求数)、响应时间等指标,动态调整流量策略。

流控规则

QPS(Queries Per Second)是衡量系统处理请求能力的重要指标。Sentinel 允许开发者设定 QPS 限流规则,当请求数超过设定的阈值时,自动进行流量控制,防止系统过载。通过监控 QPS,开发者可以及时发现并处理流量异常问题。

  • 阀值类型:限制流量的标准,可以是固定值或动态生成的值。
    • QPS
    • 并发线程数
  • 单机阀值:针对单个服务实例的流量限制,如 1000 QPS、1000个线程
  • 集群:针对单个服务实例的流量限制,如 1000 QPS、1000个线程
  • 流控模式:
    • 直接:直接对请求进行限流。
    • 关联:多个资源间的关联流控。
    • 链路:根据服务调用链的情况进行流控。
  • 流控效果:
    • 快速失败:一旦超过阈值,立即返回错误。(即在查询访问期间,最多可以只有单机阀值个查询,其他的就返回错误)
    • Warm Up :在流量增长时逐渐增加流量限制。
      • 预热时长:指在流量增加的过程中,系统所需的时间段。在这个时间段内,流量限制会逐渐上升,而不是立即达到最大值。
    • 排队等待 :将请求排队,直到流量恢复正常。
      • 超时时长:超过多少时间后,对应的查询就是失败。

熔断

熔断是指在系统出现故障时,主动中断对某个服务的调用,以避免错误请求进一步影响系统的稳定性。Sentinel 提供了熔断机制,当请求失败率超过一定比例时,将自动断开对该服务的请求,直到系统恢复正常。熔断的主要目的是保护系统,避免因个别服务的故障导致整个系统崩溃。

  • 熔断策略
    • 慢调用比例:定义在一定时间窗口内,慢请求占总请求的比例。若该比例超过配置值,则触发熔断。例如,设置为 20%,意味着如果在统计窗口内,20% 以上的请求响应时间超过设定的最大 RT,则触发熔断。
    • 异常比例:定义在一定时间窗口内,失败请求占总请求的比例。当请求失败率超过此比例时,触发熔断。例如,设置为 10%,表示如果 10% 以上的请求失败,则进行熔断。
    • 异常数:在统计窗口内,请求失败的绝对数量。当失败次数超过此值时触发熔断。例如,设置为 50 表示如果在统计窗口内失败的请求数超过 50 次,则触发熔断。
  • 其他配置
    • 最大 RT:最大响应时间阈值。如果请求的响应时间超过此值,将被视为慢请求。可设置为 500 ms。
    • 比例阀值:熔断触发的比例阈值。可以设置为 30%,表示当请求失败率达到 30% 时触发熔断。
    • 熔断时长:熔断持续的时间。设置为 60 秒,在此期间系统将拒绝所有对该服务的请求。
    • 最小请求数:触发熔断所需的最小请求数。只有当请求数超过该值时,熔断策略才会生效。例如,设置为 100,意味着在统计窗口内至少需要 100 次请求。
    • 统计时长:统计熔断条件的时间窗口。设置为 1 分钟,表示在过去 1 分钟内统计请求的成功率和失败率。

热点

热点问题是指某个特定的资源或服务在短时间内接收到大量请求,可能导致系统过载。Sentinel 的热点规则可以识别并控制这种情况,允许开发者设定特定资源的流量控制策略,以确保系统的稳定性。例如,可以对特定的 URL 或 API 接口设置流量限制,避免瞬时流量带来的压力。

  • 限流模式 :通常使用 QPS 模式,即以每秒请求数(QPS)为单位对热点资源进行流量控制。
  • 参数索引:用于识别请求中的具体参数,例如,可以根据某个查询参数(如用户 ID 或商品 ID)进行流控。开发者可以设定请求的特定参数作为热点索引,以便更精确地控制流量。
  • 单机阀值:针对单个服务实例的流量限制。例如,设置单机阀值为 100 QPS,意味着该服务实例在单位时间内最多处理 100 个请求。
  • 统计窗口时长:用于统计流量的时间窗口,通常设置为 1 秒、5 秒或 1 分钟等。例如,设置为 10 秒表示在过去的 10 秒内统计请求数,以判断是否触发流量控制。

授权规则

授权规则用于控制哪些请求可以访问特定的服务或资源。Sentinel 允许开发者定义细粒度的授权规则,根据请求的来源、请求参数等进行流量控制。通过配置授权规则,可以实现对某些用户或请求的优先级管理,从而提高系统的安全性和稳定性。

  • 流控应用:指定哪些服务或资源需要应用授权规则。例如,可以针对某个特定的微服务或 API 接口配置流控规则,以确保只有符合条件的请求可以访问。
  • 授权类型
    • 白名单:允许访问的请求来源。可以根据 IP 地址、用户 ID 或其他标识符来设置白名单。只有在白名单中的请求才能成功访问被保护的资源。例如,设置特定的用户 ID(如 12345)为白名单,只有该用户的请求会被允许通过。
    • 黑名单:禁止访问的请求来源。类似于白名单,可以根据 IP 地址、用户 ID 或其他标识符来设置黑名单。黑名单中的请求将被拒绝访问。例如,设置某些已知的恶意 IP 为黑名单,这些 IP 的请求将直接被拒绝。:

五、JWT鉴权使用

定义

JWT(JSON Web Token)是一种开放标准(RFC 7519),用于在网络应用环境中安全地传递信息。JWT 可以用于用户身份验证和信息交换,具有良好的安全性和传输效率。

作用

  • 用户身份验证:通过 JWT,用户登录后会得到一个 token,后续请求中携带该 token 以证明用户身份。
  • 信息安全性:JWT 可以通过数字签名(如 HMAC 或 RSA)确保信息未被篡改。
  • 无状态:JWT 是自包含的,服务器无需保存会话状态,减轻服务器负担

配置和使用

  • 一般将其书写在一个公共模块里面,并且用静态方法进行设置对应的配置
java 复制代码
public class JWTutil {

    // 定义用户名常量
    private static final String UserName = "admin";

    // 定义JWT签名的密钥
    private static final String key = "token_key";

    // 定义token的过期时间,单位为毫秒(这里设置为1分钟)
    public static final int token_time_out = 1000 * 60;

    // 生成JWT token的方法
    public static String getToken(String userId) {
        // 创建JWT生成器
        JWTCreator.Builder jwtBuilder = JWT.create();

        // 创建头部信息的Map
        Map<String, Object> headers = new HashMap<>();

        // 设置头部的类型为JWT
        headers.put("type", "jwt");
        // 设置算法为HS256
        headers.put("alg", "hs256");

        // 生成token,包含头部、载荷和签名
        String token = jwtBuilder
            // 设置JWT的头部
            .withHeader(headers)
            // 添加自定义声明,包含用户ID
            .withClaim("userId", userId)
            // 设置token的过期时间
            .withExpiresAt(new Date(System.currentTimeMillis() + token_time_out))
            // 设置token的签发时间
            .withIssuedAt(new Date(System.currentTimeMillis()))
            // 设置token的发行者
            .withIssuer(UserName)
            // 使用指定的密钥和算法进行签名
            .sign(Algorithm.HMAC256(key));

        // 返回生成的token
        return token;
    }
}
  • 使用的依赖:
xml 复制代码
  <!-- jwt -->
  <dependency>
      <groupId>com.auth0</groupId>
      <artifactId>java-jwt</artifactId>
  </dependency>
  • 拦截器的设置:
java 复制代码
@Component
public class GlobalFilterConfig implements GlobalFilter, Ordered {
    //application.yml配置文件中,设置token在redis中的过期时间
    @Value("${config.redisTimeout}")
    private Long redisTimeout;

    @Autowired
    private RedisTemplate redisTemplate;

    private static final String HEADER_NAME = "Acess-Token";

    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        System.out.println("============过滤器============");

        // 获取请求对象
        ServerHttpRequest request = exchange.getRequest();
        // 获取响应对象
        ServerHttpResponse response = exchange.getResponse();
        // 获取请求地址
        String url = request.getURI().getPath();
        // 获取token信息
        String token = request.getHeaders().getFirst(HEADER_NAME);

        // 判断是否为白名单请求,以及一些内置不需要验证的请求。(登录请求也包含其中)。
        // 如果当前请求中包含token令牌不为空的时候,也会继续验证Token的合法性,这样就能保证
        // Token中的用户信息被业务接口正常访问到了。而如果当token为空的时候,白名单的接口可以
        // 被网关直接转发,无需登录验证。当然被转发的接口,也无法获取到用户的token身份数据了。
        if (this.shouldNotFilter(url)) {
            return chain.filter(exchange);
        }
        if (StringUtils.isEmpty(token)) {
            return unAuthorize(exchange);
        }
        //验证redis中是否存在token
        if (!redisTemplate.hasKey(token)) {
            return unAuthorize(exchange);
        }

        //验证通过,刷新token过期时间
        redisTemplate.expire(token, redisTimeout, TimeUnit.SECONDS);
        String userId = String.valueOf(redisTemplate.opsForValue().get(token));
        System.out.println("============登录用户id:" + userId + "============");
        //把新的 exchange放回到过滤链
        ServerHttpRequest newRequest = request.mutate().header(HEADER_NAME, token).build();
        ServerWebExchange newExchange = exchange.mutate().request(newRequest).build();
        return chain.filter(newExchange);

    }
    // 返回未登录的自定义错误
    private Mono<Void> unAuthorize(ServerWebExchange exchange) {
        // 设置错误状态码为401
        exchange.getResponse().setStatusCode(HttpStatus.UNAUTHORIZED);
        // 设置返回的信息为JSON类型
        exchange.getResponse().getHeaders().setContentType(MediaType.APPLICATION_JSON);
        // 自定义错误信息
        String errorMsg = "{\"error\": \"" + "用户未登录或登录超时,请重新登录" + "\"}";
        // 将自定义错误响应写入响应体
        return exchange.getResponse().writeWith(Mono.just(exchange.getResponse().bufferFactory().wrap(errorMsg.getBytes())));
    }
    /**
     * 判断当前请求URL是否为白名单地址,以及一些内置的不用登录的接口,
     * @param url 请求的url。
     * @return 是返回true,否返回false。
     */
    private boolean shouldNotFilter(String url) {
       if (url.startsWith("/user/login")) {
           return true;
       }
       return false;
    }
相关推荐
Piper蛋窝3 小时前
深入 Go 语言垃圾回收:从原理到内建类型 Slice、Map 的陷阱以及为何需要 strings.Builder
后端·go
Bug退退退1235 小时前
RabbitMQ 高级特性之死信队列
java·分布式·spring·rabbitmq
六毛的毛6 小时前
Springboot开发常见注解一览
java·spring boot·后端
AntBlack6 小时前
拖了五个月 ,不当韭菜体验版算是正式发布了
前端·后端·python
31535669136 小时前
一个简单的脚本,让pdf开启夜间模式
前端·后端
uzong6 小时前
curl案例讲解
后端
一只叫煤球的猫7 小时前
真实事故复盘:Redis分布式锁居然失效了?公司十年老程序员踩的坑
java·redis·后端
大鸡腿同学8 小时前
身弱武修法:玄之又玄,奇妙之门
后端
轻语呢喃10 小时前
JavaScript :字符串模板——优雅编程的基石
前端·javascript·后端
MikeWe10 小时前
Paddle张量操作全解析:从基础创建到高级应用
后端