SpringCloud

一.Nacos(管理服务)

1.服务注册

①引入 nacos discovery 依赖
bash 复制代码
<!--nacos 服务注册发现-->
<dependency>
    <groupId>com.alibaba.cloud</groupId>
    <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>
②配置Nacos地址
bash 复制代码
spring:
  application:
    name: item-service # 服务名称
  cloud:
    nacos:
      server-addr: 192.168.150.101:8848 # nacos地址

2.服务发现

①引入 nacos discovery 依赖和配置Nacos地址

这两步和上面注册一摸一样,这里略.....

③服务发现(后面有OpenFeign,这个了解一下)
java 复制代码
private final DiscoveryClient discoveryClient;

private void handleCartItems(List<CartVO> vos) {
    // 1.根据服务名称,拉取服务的实例列表
    List<ServiceInstance> instances = discoveryClient.getInstances("item-service");
    // 2.负载均衡,挑选一个实例
    ServiceInstance instance = instances.get(RandomUtil.randomInt(instances.size()));
    // 3.获取实例的IP和端口
    URI uri = instance.getUri();
    // ... 略
}

二.OpenFeign(服务通信)

OpenFeign是一个声明式的http客户端

1.快速入门

①引入依赖
bash 复制代码
  <!--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>
②通过@EnableFeignClients注解,启动OpenFeign功能
java 复制代码
@EnableFeignClients
@SpringBootApplication
public class CartApplication { // ... 略 }
③编写FeignClient客户端
java 复制代码
// 这个接口不用实现,OpenFeign会动态代理帮我们实现
@FeignClient("item-service")  //这个是从nacos的item-service服务中获取实例列表,然后通过负载均衡算法获取一个实例
public interface ItemClient {
    @GetMapping("/items") //告诉OpenFeign去服务实例中调用这个接口,这个接口是get请求,路径是/items
//返回值默认是json格式,转换为java对象       请求参数是ids                  请求数据
    List<ItemDTO> queryItemByIds(@RequestParam("ids") Collection<Long> ids);
    
}
  • @FeignClient("item-service") :声明服务名称

  • @GetMapping :声明请求方式

  • @GetMapping("/items") :声明请求路径

  • @RequestParam("ids") Collection<Long> ids :声明请求参数

  • List<ItemDTO> :返回值类型

④使用FeignClient,实现远程调用
java 复制代码
// 要先依赖注入 ItemClient这个接口,然后在调用
List<ItemDTO> items = itemClient.queryItemByIds(List.of(1,2,3));

2.连接池

前面讲了底层原理,动态代理听不懂。

每次发送请求的时候都是创建一个连接,效率比较低下,所以创建一个连接池效率高一些。

OpenFeign整合OKHttp开始连接池:

①引入依赖
bash 复制代码
<!--ok-http-->
<dependency>
    <groupId>io.github.openfeign</groupId>
    <artifactId>feign-okhttp</artifactId>
</dependency>
②开启连接池功能
bash 复制代码
feign:
  okhttp:
    enabled: true # 开启OKHttp连接池支持

3.最佳实践

两种最佳实践的项目格式:

①每个微服务分为三个模块

好处:当前微服务写好自己给别人调用的接口,别人引入依赖直接使用即可。

弊端:项目结构变复杂了

②储存在一个公共的模块下

弊端:代码耦合度增加了。

4. 日志输出

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

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

使用:

①自定义日志级别需要声明一个类型为Logger.Level的Bean,在其中定义日志级别

java 复制代码
public class DefaultFeignConfig {
    @Bean
    public Logger.Level feignLogLevel(){
        return Logger.Level.FULL;
    }
}

②配置全局,让所有FeignClient 都按照这个日志配置,要在@EnableFeignClient注解中声明:

java 复制代码
@EnableFeignClients(defaultConfiguration = DefaultFeignConfig.class)

一般情况下不用配置,调试的时候在配置。

这两个红色框的没看懂

三.Gateway(路由)

作用:请求的路由,转发,身份校验

Ⅰ.网关路由

1. 快速入门
①创建新模块
②引入网关依赖
bash 复制代码
<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-gateway</artifactId>
</dependency>
③编写启动类
④配置路由规则(重要)
2.路由属性

四个属性含义如下:

  • id:路由的唯一标示

  • predicates:路由断言,其实就是匹配条件

  • filters:路由过滤条件,后面讲

  • uri:路由目标地址,lb://代表负载均衡,从注册中心获取目标微服务的实例列表,并且负载均衡选择一个访问。

①路由断言 predicates:
名称 说明 示例
After 是某个时间点后的请求 - After=2037-01-20T17:42:47.789-07:00America/Denver
Before 是某个时间点之前的请求 - Before=2031-04-13T15:14:47.433+08:00Asia/Shanghai
Between 是某两个时间点之前的请求 - Between=2037-01-20T17:42:47.789-07:00America/Denver, 2037-01-21T17:42:47.789-07:00America/Denver
Cookie 请求必须包含某些cookie - Cookie=chocolate, ch.p
Header 请求必须包含某些header - Header=X-Request-Id, \d+
Host 请求必须是访问某个host(域名) - Host=**.somehost.org,**.anotherhost.org
Method 请求方式必须是指定方式 - Method=GET,POST
Path 请求路径必须符合指定规则 - Path=/red/{segment},/blue/**
Query 请求参数必须包含指定参数 - Query=name, Jack或者- Query=name
RemoteAddr 请求者的ip必须是指定范围 - RemoteAddr=192.168.1.1/24
weight 权重处理
②路由过滤器 filters:
名称 说明 示例
AddRequestHeader 给当前请求添加一个请求头 AddrequestHeader=headerName,headerValue
RemoveRequestHeader 移除请求中的一个请求头 RemoveRequestHeader=headerName
AddResponseHeader 给响应结果中添加一个响应头 AddResponseHeader=headerName,headerValue
RemoveResponseHeader 从响应结果中移除有一个响应头 RemoveResponseHeader=headerName
RewritePath 请求路径重写 RewritePath=/red/(?<segment>.*), /${segment}
StripPrefix 去除请求路径中的 N 段前缀 StripPrefix=1,则路径 /a/b 转发时只保留 /b
...总共33种 ... ...

Ⅱ.网关登录校验

1.自定义拦截器
①GlobalFilter(常用这个)
java 复制代码
@Component
public class MyGlobalFilter implements GlobalFilter , Ordered {

    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        // 登录校验逻辑
        ServerHttpRequest request = exchange.getRequest();
        HttpHeaders headers = request.getHeaders();

        // 放行
        return chain.filter(exchange);
    }

    public int getOrder() {  // 过滤的顺序,数字越大越靠后
        return 0;
    }
}

需要实现GlobalFilter和Ordered接口

ServerWebExchange:网关内部的上下文对象,保存网关共享对象如:request,respone,session,或者一些自定的共享属性。

GatewayFilterChain:当前过滤器执行完后,要调用过滤器链中的下一个过滤器。

Ordered接口作用:是我们自定义的过滤器要在 NettyRoutingFilter 的前面执行,因为NettyRoutingFilter 是用于路由转发到微服务的。

②GatewayFilter(这个写的麻烦一点)

相比GlobalFilter可以指定作用范围 ,还可以配置自定义参数如下

无参数:图片是代码模板

代码实现

java 复制代码
@Component
public class PrintAnyGatewayFilterFactory extends AbstractGatewayFilterFactory<Object> {
    @Override
    public GatewayFilter apply(Object config) {
        return new  OrderedGatewayFilter(new GatewayFilter() {
            @Override
            public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
                // 登录校验逻辑
                System.out.println("print any filter running");
                // 放行
                return chain.filter(exchange);
            }
        },1);
    }
}
java 复制代码
spring:
  cloud:
    gateway:
      default-filters:
        - PrintAny

该类的名称一定要以GatewayFilterFactory为后缀!

有参数:这里只给代码模板,就不给具体代码实现了,因为太多了。

2.实现登录校验
java 复制代码
@Component
@RequiredArgsConstructor
@EnableConfigurationProperties(AuthProperties.class)
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.获取Request
        ServerHttpRequest request = exchange.getRequest();
        // 2.判断是否不需要拦截,
        if(isExclude(request.getPath().toString())){
            // 无需拦截,直接放行
            return chain.filter(exchange);
        }
        // 3.获取请求头中的token
        String token = null;
        List<String> headers = request.getHeaders().get("authorization");
        if (!CollUtils.isEmpty(headers)) {
            token = headers.get(0);
        }
        // 4.校验并解析token
        Long userId = null;
        try {
            userId = jwtTool.parseToken(token);
        } catch (UnauthorizedException e) {
            // 如果无效,拦截
            ServerHttpResponse response = exchange.getResponse();
            response.setRawStatusCode(401);
            return response.setComplete();  // 拦截
        }

        // TODO 5.如果有效,传递用户信息
        System.out.println("userId = " + userId);
        // 6.放行
        return chain.filter(exchange);
    }

    private boolean isExclude(String antPath) {
        for (String pathPattern : authProperties.getExcludePaths()) {
            if(antPathMatcher.match(pathPattern, antPath)){
                return true;    // 如果这个地址在排除拦击的路径里面,直接放即可
            }
        }
        return false;
    }

    @Override
    public int getOrder() {
        return 0;
    }
}
3.网关传递用户

实现思路:

网关过滤器储存用户信息到请求头:

java 复制代码
// 5.如果有效,传递用户信息
String userInfo = userId.toString();
ServerWebExchange swe = exchange.mutate()
        .request(builder -> builder.header("user-info", userInfo))
        .build();

拦截器获取请求头,并储存到ThreadLocal中:

java 复制代码
public class UserInfoInterceptor implements HandlerInterceptor {
    public boolean preHandle(final HttpServletRequest request, final HttpServletResponse response, final Object handler) throws Exception {
        String userInfo = request.getHeader("user-info");
        if(StrUtil.isNotBlank(userInfo)) {
            UserContext.setUser(Long.valueOf(userInfo));
        }
        return true;
    }
    public void afterCompletion(final HttpServletRequest request, final HttpServletResponse response, final Object handler, final Exception ex) throws Exception {
        UserContext.removeUser();
    }
}

然后接下来就是注册拦截器,但是注册完之后还是存在问题,因为代码添加到了common这个模块中,网关也 也引入了common的依赖,但是gateway不是有SpringMVC实现的,所以不能运行拦截器的代码,我们需要条件装配,解决方案:在添加注册拦截器的类上添加

@ConditionalOnClass(DispatcherServlet.class) 注解即可

4.OpenFeign传递用户

使用OpenFegin拦截器。

java 复制代码
public class DefaultFeignConfig {
    @Bean
    public RequestInterceptor userInfoRequestInterceptor() {
        return new RequestInterceptor() {
            public void apply(RequestTemplate template) {
                Long userId = UserContext.getUser();
                if (userId != null) {
                    template.header("user-info", userId.toString());
                }
            }
        };
    }
}

DefaultFeignConfig 这个配置类要在启动类上声明:

复制代码
@EnableFeignClients(basePackages = "com.hmall.api.client",defaultConfiguration = DefaultFeignConfig.class)

basePackages参数:

  • 作用:指定扫描哪些包下的 Feign 客户端接口。Spring 会扫描basePackages指定包及其子包中的所有被@FeignClient注解标注的接口,然后创建这些接口的代理实例,以便在其他组件中可以通过依赖注入的方式使用这些 Feign 客户端来调用远程服务。

defaultConfiguration参数:

  • 作用:指定 Feign 客户端的默认配置类。这个配置类可以用来配置 Feign 客户端的一些通用属性,例如日志级别、解码器、编码器、负载均衡策略等。通过设置defaultConfiguration,可以为所有 Feign 客户端应用相同的配置。

整体代码思路流程图:

Ⅲ.配置管理(Nacos)

1.共享配置(更好的管理配置文件)

在nacos里面配置 配置文件,这个配置文件是提供给每个微服务的。

在springcloud环境下配置文件加载流程:

使用步骤:

①引入依赖
java 复制代码
  <!--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>
② 新建bootstrap.yaml

然后写nacos的配置,根据上面的流程图,我们必须写nacos的配置不写就无法找到nacos。

2.配置热更新

微服务无需重启即可使配置生效。

3.动态路由

四.Sentinel(微服务保护)

1.微服务整合Sentinel

①引入依赖
bash 复制代码
<!--sentinel-->
<dependency>
    <groupId>com.alibaba.cloud</groupId> 
    <artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
</dependency>
②配置控制台
bash 复制代码
spring:
  cloud:
    sentinel:
      transport:
        dashboard: localhost:8090
      http-method-specify: true # 是否设置请求方式作为资源名称

2.请求限流

3.线程隔离

4.FallBack

触发限流或熔断后的请求不一定要直接报错,也可以返回一些默认数据或者友好提示,用户体验会更好。

①将FeignClient作为Sentinel的簇点资源
bash 复制代码
feign:
  okhttp:
    enabled: true # 开启OKHttp功能
  sentinel:
    enabled: true # 开启sentinel对OpenFeign的监控
②自定义类,实现FallbackFactory,编写对某个FeignClient的fallback逻辑
java 复制代码
// 对不同的方法写失败后的逻辑
@Slf4j
public class ItemClientFallbackFactory implements FallbackFactory<ItemClient> {
    @Override
    public ItemClient create(Throwable cause) {
        return new ItemClient() {
            @Override
            public List<ItemDTO> queryItemByIds(Collection<Long> ids) {
                log.error("远程调用ItemClient#queryItemByIds方法出现异常,参数:{}", ids, cause);
                // 查询购物车允许失败,查询失败,返回空集合
                return CollUtils.emptyList();
            }

            @Override
            public void deductStock(List<OrderDetailDTO> items) {
                // 库存扣减业务需要触发事务回滚,查询失败,抛出异常
                throw new RuntimeException(cause);
            }
        };
    }
}
③将刚刚定义的UserClientFallbackFactory注册为一个Bean
java 复制代码
public class DefaultFeignConfig {
    @Bean
    public ItemClientFallbackFactory itemClientFallbackFactory() {
        return new ItemClientFallbackFactory();
    }
}
④在UserClient接口中使用UserClientFallbackFactory
java 复制代码
// 添加到FeignClient 注解上
@FeignClient(value = "item-service",fallbackFactory = ItemClientFallbackFactory.class)
public interface ItemClient {
    @GetMapping("/items")
    List<ItemDTO> queryItemByIds(@RequestParam("ids") Collection<Long> ids);

    @PutMapping("/items/stock/deduct")
    void deductStock(@RequestBody List<OrderDetailDTO> items);
}

value参数:

  • 作用:指定要调用的远程服务的名称。这个名称通常对应于服务注册中心(如 Eureka、Consul、Nacos 等)中注册的服务名。Feign 会根据这个服务名,从服务注册中心获取服务的实例信息,并通过负载均衡选择一个实例进行调用。

fallbackFactory参数:

  • 作用:指定一个降级工厂类,用于在远程服务调用失败时提供降级逻辑。降级工厂类需要实现FallbackFactory<T>接口,其中T@FeignClient注解标注的接口类型。通过降级工厂,可以为不同的异常情况提供不同的降级处理逻辑。

5.服务熔断

三个状态

  • closed:关闭状态,断路器放行所有请求,并开始统计异常比例、慢请求比例。超过阈值则切换到open状态

  • open :打开状态,服务调用被熔断,访问被熔断服务的请求会被拒绝,快速失败,直接走降级逻辑。Open状态持续一段时间后会进入half-open状态

  • half-open:半开状态,放行一次请求,根据执行结果来判断接下来的操作。

    • 请求成功:则切换到closed状态

    • 请求失败:则切换到open状态

五.Seata(分布式事务)

在Seata的事务管理中有三个重要的角色:

  • TC ( Transaction Coordinator ) - **事务协调者:**维护全局和分支事务的状态,协调全局事务提交或回滚。

  • TM (Transaction Manager) - **事务管理器:**定义全局事务的范围、开始全局事务、提交或回滚全局事务。

  • RM (Resource Manager) - **资源管理器:**管理分支事务,与TC交谈以注册分支事务和报告分支事务的状态,并驱动分支事务提交或回滚。

1.微服务整合Seata

引入依赖

bash 复制代码
<!--统一配置管理-->
  <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>
  <!--seata-->
  <dependency>
      <groupId>com.alibaba.cloud</groupId>
      <artifactId>spring-cloud-starter-alibaba-seata</artifactId>
  </dependency>

添加配置文件

bash 复制代码
seata:
  registry: # TC服务注册中心的配置,微服务根据这些信息去注册中心获取tc服务地址
    type: nacos # 注册中心类型 nacos
    nacos:
      server-addr: 192.168.150.101:8848 # nacos地址
      namespace: "" # namespace,默认为空
      group: DEFAULT_GROUP # 分组,默认是DEFAULT_GROUP
      application: seata-server # seata服务名称
      username: nacos
      password: nacos
  tx-service-group: hmall # 事务组名称
  service:
    vgroup-mapping: # 事务组与tc集群的映射关系
      hmall: "default"

2.XA模式

①工作原理图:

RM一阶段的工作:

  1. 注册分支事务到TC

  2. 执行分支业务sql但不提交

  3. 报告执行状态到TC

TC二阶段的工作:

  1. TC检测各分支事务执行状态

    1. 如果都成功,通知所有RM提交事务

    2. 如果有失败,通知所有RM回滚事务

RM二阶段的工作:

  • 接收TC指令,提交或回滚事务

②优缺点:

XA模式的优点是什么?

  • 事务的强一致性,满足ACID原则

  • 常用数据库都支持,实现简单,并且没有代码侵入

XA模式的缺点是什么?

  • 因为一阶段需要锁定数据库资源,等待二阶段结束才释放,性能较差

  • 依赖关系型数据库实现事务

③使用

修改 application.yml 文件(每个参与事务的微服务),开启 XA 模式:

bash 复制代码
seata:
  data-source-proxy-mode: XA

给发起全局事务的入口方法添加 @GlobalTransactional 注解:

java 复制代码
@GlobalTransactional
public Long createOrder(OrderFormDTO order) {
    // 创建订单 ... 略
    // 清理购物车 ...略
    // 扣减库存 ...略
    return order.getId();
}

3.AT模式

①工作原理图:

阶段一RM的工作:

  • 注册分支事务

  • 记录undo-log(数据快照)

  • 执行业务sql并提交

  • 报告事务状态

阶段二提交时RM的工作:

  • 删除undo-log即可

阶段二回滚时RM的工作:

  • 根据undo-log恢复数据到更新前

②优缺点:

优:性能比XA模式高。

缺:不是强一致性,中间会有短暂的不一致,称为最终一致。

③使用

1.给每个微服务创建一个 undo_log 表sql语句如下

sql 复制代码
CREATE TABLE IF NOT EXISTS `undo_log`
(
    `branch_id`     BIGINT         NOT NULL COMMENT '分支事务id',
    `xid`           VARCHAR(128)   NOT NULL COMMENT '全局事务id',
    `context`       VARCHAR(128)   NOT NULL COMMENT 'undo_log context,such as serialization',
    `rollback_info` LONGBLOB       NOT NULL COMMENT 'rollback info',
    `log_status`    INT(11)        NOT NULL COMMENT '0:normal status,1:defense status',
    `log_created`   DATETIME(6)    NOT NULL COMMENT 'create datetime',
    `log_modified`  DATETIME(6)    NOT NULL COMMENT 'modify datetime',
    UNIQUE KEY `ux_undo_log` (`xid`, `branch_id`)
) ENGINE = InnoDB
  AUTO_INCREMENT = 1
  DEFAULT CHARSET = utf8mb4 COMMENT ='AT transaction mode undo table';

2.改配置文件

bash 复制代码
seata:
  data-source-proxy-mode: AT

3.使用和上面一样的分布式注解 @GlobalTransactional

相关推荐
2501_933923256 分钟前
Spring Bean作用域揭秘:单例、原型、请求与会话的区别
java·后端·spring·java-ee
学编程就要猛23 分钟前
基于Spring AI 的智能聊天机器人
java·spring ai·chat robot
修炼室26 分钟前
Java开发工程师笔试经验贴【高频知识】
java
木井巳38 分钟前
【记忆化搜索】最长递增子序列
java·算法·leetcode·深度优先·剪枝·推荐算法
杨杨杨大侠1 小时前
知识库已经有了,Java 程序员还要做什么?Spring AI RAG 实战
java·openai·ai编程
伊信1 小时前
Spring AI 基础学习与应用
java·后端
夜郎king1 小时前
基于 Java + Playwright 实现网站自动访问与数据采集(以腾讯云开发者社区为例)
java·playwright·网页数据获取
Anastasiozzzz1 小时前
重新定义 Agent 基建:Redis 在现代 AI 与智能体系统中的工程实践
java·人工智能·redis·ai
careathers1 小时前
【数据结构】队列
java·数据结构
步行cgn1 小时前
Spring 通过 factory-method 实例化 Bean 详解与常见错误排查
java·后端