SpringCloud保姆级教程(黑马程序员)建议收藏!!!(可作对比,课程小细节)

目录

一、导入黑马商城

1、后端项目

2、前端项目

二、压力测试

1、jmeter软件模拟并发

三、搭建微服务架构

1、相关科普介绍

2、熟悉项目功能

3、购物车订单支付等

4、商品服务拆分

1、maven聚合:

2、相关配置修改

3、拷贝hmall-service的相关文件倒item-service

启动

4、访问swagger测试

5、购物车服务拆分

1、前置工程

2、运行

3、访问swagger测试

四、远程调用

1、restTemplate(了解)

restTemplate:小需求可以用到,后面以Feign为主。但是我还是完整地编写了下来,可以当底层原理学习使用。

问题分析:restTemplate存在的问题主要是到时候负载均衡(并发处理)的时候无法同时请求多个ip地址,后面需要服务治理来解决。

2、注册中心

配置nacos

访问nacos

3、服务治理

服务注册

服务发现

问题集合

4、OpenFeign

快速入门

引入注解、注解启动类

编写Client客户端,先创建一个接口

改造用restTemplate编写的代码

测试

底层原理

连接池

引入依赖

配置文件中开启连接池

最佳实践

第一种方案

第二种方案(课程采用)

日志

说明

声明配置

测试

拆分作业

网关(Spring Cloud Gateway)

问题引入

快速入门

1、创建新模块

2、引入网关依赖

3、编写启动类

4、配置路由规则(重点)

测试

过滤器

登录校验

1、自定义过滤器

GlobalFilter(重点)

GatewatFilter

有参数的GatewayFilter

2、实现登录校验

3、网关传递用户

思路:

过滤器中保存用户到请求头

调用SpringMVC的拦截器获取用户保存到ThreadLocal(给其他服务拿取)

4、OpenFeign传递用户

网关中补充新的路径

改造DefaultFeignConfig类

建议重新启动所有服务

配置管理

问题引入

配置共享

拉取共享配置

 引入依赖

配置热更新

动态路由

微服务保护和分布式事务

雪崩问题

解决方案

请求限流:

线程隔离(舱壁隔离):

服务熔断

组件使用选择

Sentinel快速入门

初始Sentinel

安装和启用jar包

连接微服务

簇点链路

请求限流

线程隔离

Fallback

服务熔断

分布式事务

Seata

初识Seata

部署TC服务

微服务集成Seata

XA模式(强一致)

AT模式 (性能)


一、导入黑马商城

1、后端项目

1、复制代码到自己的代码资料区。alt+8打开Services点击下面的蓝色内容选择springboot

      记得把虚拟机地址改成自己的。

选择后先不要运行,

修改完成后直接启动服务即可。遇到端口号占用问题,可以参考端口占用

然后浏览器输入地址测试即可

2、前端项目

nginx不能在中文或者特殊符号路径运行,要拷贝到另一个地方(例如lesson目录)。

另外,不要双击nginx.exe,因为后面不方便关闭。

我们可以在hmall-nginx文件里打开控制台

start nginx

启动失败可以看nginx目录下的logs文件中的内容

我遇到的事80端口被占用的问题,uu们有问题的话可以去网上找解决方案,一般是修改端口范围啥的,我这里为了贪图方便就直接改变了nginx.conf里的80端口为90,然后再浏览器输入地址即可访问黑马商城。

然后还是跟之前一样登录测试,账号jack,密码123。登录报错的可以改jdk11,maven改成自己的。

因为我们之前已经配置springboot项目启动的是local文件,所以我们还要启动虚拟机里面的mysql,

如果报错的话可以重启一下docker

systemctl restart docker

再进行登录测试就可以进来啦~

二、压力测试

1、jmeter软件模拟并发

可以再官网下载,也可以由我分享的网盘下载。

通过网盘分享的文件:apache-jmeter-5.4.1.zip
链接: https://pan.baidu.com/s/1xyApIc8IxCt0a2EuVo4F2Q 提取码: 4567

发现打不开一直是白屏的话直接点击/bin目录下的jmeter.bat,会弹出命令窗和软件(窗口不要关掉,否则软件也会关掉)。

先进入该网址(记得端口号问题,docker和mysql记得开)

localhost:8080/search/list?pageNo=1&pageSize=2

把准备好的黑马商城.jmx拖进jmeter工作区,点击开始,可以进行对比压测前后的延时

可以设置进程数模拟更高的并发

可以看出单体架构有服务崩溃的风险,因此我们要采用微服务架构,保证整体服务不会蚁穴溃堤!

三、搭建微服务架构

1、相关科普介绍

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

拆分成独立项目,不是一个小项目一个controller,而是一个小项目单独负责一个功能,例如增删改查,登录注册。

各个微服务再各自打包成jar包在tomcat服务器上运行,各自搭配一个mysql数据库。

spring的springboot框架优势:自动装配和依赖管理

spring cloud是封装了各个组件。

2、熟悉项目功能

还是跟之前一样的方式打开nginx,访问localhost:18080端口然后进行登录,查看req和storage。

3、购物车订单支付等

4、商品服务拆分

第一种方式是Project(大工程),第二种方式是maven聚合(用的较多)

1、maven聚合:

父工程子工程,一般在父工程下面创建子工程。

hmall目录下创建新module(item-service),记得选用maven(模版可以选用quickstart)

接下来就是拷贝依赖(可以复制hmall-service的pom文件的全部depedencies和buidl)

然后选择性干掉不用到的服务(这里只要商品服务)

单元测试也不用(因为父工程有)

redis也可以不要,到时用再引回来

maven打包依赖就保留(很重要)

然后就引入点击maven图标。

2、相关配置修改

引用hm-service的启动类并改名字为ItemApplication,文件名修改(current directory)(建议难改的话直接把文件删掉重建)

启动类修改下,然后记得创建不同文件夹

记得在main目录下创建resources文件夹,点击maven source directories下面提示即可。

在hmall-service的resources目录下拷一下这三个,下面的hmall.jks是加密用到的,不用拷

修改applicatoin.yaml,主要修改端口号,微服务名字和mysql端口号(这里改数据库名就好)

一个微服务对应一台mysql成本有点高,我们可以在一个mysql里创建多个database来模拟

引入资料中的hmall-item.sql,直接运行

改一下swagger的接口文档名字和controller信息

Swagger扫描到Controller,会把Controller接口信息作为接口文档信息

下面的hm登录加密可以全部去掉

3、拷贝hmall-service的相关文件倒item-service

拷贝顺序:domain->mapper->service->controller

找与item有关的拷贝下来

ItemServiceImpl的一个地方需要修改:

依赖错误问题:把爆红的引入删掉就行,idea会自动导入

启动

Alt+8打开services:

第一种方法:刷新maven

第二种方法:右击HmallApplication点击Copy Configuration

输入一个名字(例:ItemApplicatoin)

然后找到ItemApplication启动类即可

先别急着启动!!关键一步!!

右击新创建的Application,点击Edit Configuration,设置为local

apply应用后直接启动即可,就可以完成多端口启动微服务。

上面的8080端口可以先关掉。

4、访问swagger测试

5、购物车服务拆分

1、前置工程

(1)创建工程等服务跟上一步相同,照搬即可,命名为cart-service。

(2)直接引入已经经过筛选的依赖(从item-service中全部引进来)

(3)设置启动类,跟之前的操作一样

(4)增加resources文件夹和增加三个配置文件,跟之前的操作一样,无非就是修改端口号、微服务名称、数据库database名、swagger文档说明、controller包扫描位置...

在Impl类中有一些地方需要修改:

先注释掉商品服务(得到差价)和登录用到的拦截器

拷贝完大致如图:

2、运行

alt+8然后记得修改配置为local

3、访问swagger测试

端口号是8082哦

业务成功但是不完整(没有显示商品信息),我们这就来实现完整功能

四、远程调用

1、restTemplate(了解)

需求:请求另一个商品管理的微服务

原理:模拟前端向后端请求,java应用之间也能调用请求

restTemplate:小需求可以用到,后面以Feign为主。但是我还是完整地编写了下来,可以当底层原理学习使用。

打开impl的方法注释,引入ItemDto

在启动类注入RestTemplate的bean注入到spring的bean工厂中。

在实现类引入RestTemplate:

不推荐用@autowired而是用构造函数(但要是要引入的东西很多的话就很繁琐了),所以用lombok的@requiredargsconstructor更方便。

为了防止一些常变量也被当做构造初始化的成员变量,我们在resttemplate前加上final就行了,保证一定要被初始化(requiredargsconstructor)

修改如下:

代码就贴在这里了,exchange那个选有url,method之类的api

        // 2.查询商品
        // List<ItemDTO> items = itemService.queryItemByIds(itemIds);
        // 2.1.利用restTemplate发起http请求,得到http的响应
        ResponseEntity<List<ItemDTO>> response = restTemplate.exchange(
                "http://localhost:8081/items?ids={ids}",
                HttpMethod.GET,
                null,
                new ParameterizedTypeReference<List<ItemDTO>>() {
                },
                Map.of("ids", CollectionUtil.join(itemIds, ","))
        );
        // 2.2.解析相应
        if(!response.getStatusCode().is2xxSuccessful()){
            //查询失败,直接结束
            return;
        }
        List<ItemDTO> items = response.getBody();

把id拼接成字符串:CollectionUtils.join(ids,",");记得是hutool包下的工具类,以逗号分隔。

重启cart和item两个微服务,然后再接口文档测试,可以看到,已经能查询到商品信息了!!

测试newPrice,修改原来的price,记得保存修改。

可以看到,newPrice已经不一样了,说明接口调用成功。

问题分析:restTemplate存在的问题主要是到时候负载均衡(并发处理)的时候无法同时请求多个ip地址,后面需要服务治理来解决。

2、注册中心

原理:类似生活中的家政公司。

为什么使用nacos?因为不管是nacos还是eruka,都遵循springcloud标准,用起来差不多,而且nacos是阿里巴巴产品,中文文档更容易看懂,这里我们可以参考语雀里的文档:Docs

配置nacos

把资料中的nacos.sql拖进mysql可视化软件(如Navicat)中

接下来就是在docker中部署nacos,等下要引用资料nacos中的custom.env(主要是配置mysql),记得修改里面的虚拟机ip地址。

安装nacos镜像文件(官方下载比较慢,资料中有nacos.tar)

把nacos文件夹(里面有custom.env)和nacos.tar引入虚拟机/root目录

然后加载镜像

docker load -i nacos.tar

输入dis命令查看镜像状态

运行指令即可

docker run -d \
--name nacos \
--env-file ./nacos/custom.env \
-p 8848:8848 \
-p 9848:9848 \
-p 9849:9849 \
--restart=always \
nacos/nacos-server:v2.1.0-slim

dps查看容器状态

不放心的话可以查看日志

docker logs -f nacos

访问nacos

浏览器输入虚拟机ip地址+8848(nacos地址)/nacos

进入后会是一个登录界面,账号和密码都是nacos,输入即可。

然后我们就成功进来啦~

3、服务治理
服务注册

在item-service中引入nacos discovery依赖

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

配置地址(快速:输入nacos找到有addr的那一栏)

spring:
  cloud:
    nacos:
      server-addr: 192.168.150.101:8848

两个步骤都做完之后就已经完成了服务注册,接下来我们模拟一下多实例部署

alt+8,右击item-service,点击copy configuration,命名为ItemApplicatoin2即可,然后不要立即点OK,线上部署不同台机器,端口号一定不一样,但是我们是在idea部署,要修改端口号,怎么修改呢?中间那里有个Modify options,点击Add VM options,中间输入-Dserver.port=8083

-Dserver.port=8083

apply应用即可。然后启动该服务:

查看日志可以发现,服务一旦启动就已经开始注册了。

我们再到nacos控制台可以看到服务已经注册成功了。

实例数:同一个服务有两个实例。健康实例数:可以用的实例。

详情点进去可以发现有两个端口号:

服务发现

在cart-service中操作:

 在cart-service中引入nacos discovery依赖

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

配置地址(快速:输入nacos找到有addr的那一栏)

spring:
  cloud:
    nacos:
      server-addr: 192.168.150.101:8848

改造之前的restTemplate方法

在impl实现类中引入discoveryClient来完成服务的拉取

方法修改前(上)与修改后(下),可以观察到地址已经不是写死的了,这是划时代的改变!

        // 2.查询商品
        // List<ItemDTO> items = itemService.queryItemByIds(itemIds);
        // 2.1.根据服务名称获取服务的实例列表
        List<ServiceInstance> instances = discoveryClient.getInstances("item-service");
        if(CollectionUtil.isEmpty(instances)){
            return;
        }
        // 2.2.手写负载均衡,从实例列表中挑选一个实例
        ServiceInstance instance = instances.get(RandomUtil.randomInt(instances.size()));
        // 2.3.利用restTemplate发起http请求,得到http的响应
        ResponseEntity<List<ItemDTO>> response = restTemplate.exchange(
                instance.getUri() + "/items?ids={ids}",
                HttpMethod.GET,
                null,
                new ParameterizedTypeReference<List<ItemDTO>>() {
                },
                Map.of("ids", CollectionUtil.join(itemIds, ","))
        );

知识点:手写负载均衡算法instances.get(RandomUtil.RandomInt(instances.size()))

然后我们重启CartApplication,再次请求购物车数据,目的:两个服务都有请求到。

先清空两个服务的日志,然后调用接口(第一次调用有点慢,需要多调用几次),看有无日志刷新即可。

可以看到两个服务都有日志输出,说明负载均衡已经实现了!!

模拟服务宕机:暂停8081服务,到nacos查看item-service详情(可以看到只有一个端口8083了),再次请求购物车数据,看是否还能查询得到数据

可以看到还是可以请求到数据的,我们再反向操作启动服务到nacos中查看......

可以看到不管是服务宕机还是服务启动,随时都能被我们感应到,随时都能拿到最新的服务列表,这就是服务发现的强大之处。不用写死地址,而是动态感知服务的地址。

问题集合

如果发现一直连不上,就两条指令重复输入

docker restart mysql
docker restart nacos
4、OpenFeign

前面写的请求调用步骤太繁琐了,我们需要进一步优化代码,因此改用OpenFeign(声明式)。

快速入门
引入注解、注解启动类

等下需要引入两个依赖和在启动类声明一下注解

之前的负载均衡是Ribbon,现在用的大多是loadbalancer(要知道)

在服务调用者(cart-service)中操作

  <!--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

编写Client客户端,先创建一个接口

然后编写代码(记得在注解里面声明要拉取的服务内容)

@FeignClient("item-service")
public interface ItemClient {
    @GetMapping("/items")
    List<ItemDTO> queryItemByIds(@RequestParam("ids") Collection<Long> ids);
}
改造用restTemplate编写的代码

在实现类中,引入的实例全都不要,只要ItemClient

编写的方法也全都注释掉

注释的代码换成

List<ItemDTO> items = itemClient.queryItemByIds(itemIds);

一行代码全部搞定

测试

alt+8重启服务,到swagger文档进行购物车接口多调试几次,清空两个item-service的控制台,看能否正常进行负载均衡

可以看到请求是正常的两边都有日志输出。

底层原理

动态代理对象invocationhandler

源码其实就是对我们之前注释的代码进行了封装。

连接池

Feign底层发起http请求,依赖于其它的框架。我们通常会使用带有连接池的客户端来代替默认的HttpURLConnection。比如,我们使用OK Http.

引入依赖
<!--OK http 的依赖 -->
<dependency>
  <groupId>io.github.openfeign</groupId>
  <artifactId>feign-okhttp</artifactId>
</dependency>
配置文件中开启连接池
feign:
  okhttp:
    enabled: true # 开启OKHttp功能

debug请求可以看到delegate对象已经由原来的默认HttpURLConnection变为OkHttpClient,性能会优化

最佳实践
第一种方案

如果很多业务都要请求商品信息,就会造成代码重复,维护起来也很困难,因此我们不如把请求的服务都放在item-service里,需要用到的就导入pom文件即可。维护人员只在item-service里面修改。代码结构合理容易维护,缺点是项目模块变多会复杂。

第二种方案(课程采用)

耦合度会高一些

hmall下创建新的module(hm-api)直接创建

在cart-service中将下面的两个依赖剪切掉并移到hm-api中

hm-api创建这两个文件夹并引入ItemDTO,引入swagger依赖(alt + enter),然后ItemClient也拿过来

把cart-service中的client包和ItemDTO删除掉

然后在pom文件中引入自己编写的hm-api,刷新一下maven

实现类爆红的话在顶部导包中删除爆红的引入即可,idea会自动导入新的包

当定义的FeignClient不在SpringBootApplication的扫描包范围时,这些FeignClient无法使用。扫描包默认是当前包及其子包。当前包不可能有FeignClient,因此我们要指定包的范围。

在cart-service的启动类中修改如下

重新启动,清除两个item-service的日志,在swagger文档中测试接口即可。

日志
说明

日志级别为debug时才有日志,我们可以再配置文件中查看

但是我们还是看不到日志,这是因为Feign也有日志级别。

声明bean需要在配置类下(有configuration将其引入)

声明配置

在hm-api模块下创建包和类,声明如下,直接生命@Bean,不要@Configuration

public class DefaultFeignConfig {
    @Bean
    public Logger.Level feignLoggerLevel(){
        return Logger.Level.FULL;
    }
}

cart-service启动类的@EnableFeignClients后面添加

测试

debug模式启动cart-service的启动类,清空控制台,swagger调试

可以看到输出很多东西,这些就是日志

拆分作业

参考‌⁠​‬‬​​‌⁠‍​‍⁠​​‬​​​​​​‌​​​‬‬​⁠‬⁠‍⁠​​⁠‍‍‍⁠微服务拆分作业参考 - 飞书云文档,有问题的话在评论区评论,博主会一一解答。

记得一个地方不要漏了,不然swagger扫描包不到controller

网关(Spring Cloud Gateway)

问题引入

1、后端服务器有很多的端口号,前端不知道要请求哪个ip地址

2、每次操作都要做登录校验,很繁琐,而且还有泄漏密钥的风险

身份校验:你是谁?

路由:住处(几楼几号单元)

转发:带他过去

快速入门
1、创建新模块

2、引入网关依赖
<dependencies>
        <!--common-->
        <dependency>
            <groupId>com.heima</groupId>
            <artifactId>hm-common</artifactId>
            <version>1.0.0</version>
        </dependency>
        <!--网关-->
        <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>
    </dependencies>
    <build>
        <finalName>${project.artifactId}</finalName>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>
3、编写启动类

4、配置路由规则(重点)

这里我们先把SearchController从hmall-service拷到item-service中

创建一个resources包下的application.yaml

server:
  port: 8080
spring:
  application:
    name: gateway
  cloud:
    nacos:
      server-addr: 192.168.89.129:8848
    gateway:
      routes:
        - id: item-service
          uri: lb://item-service
          predicates:
            - Path=/items/**,/search/**
        - id: user-service
          uri: lb://user-service
          predicates:
            - Path=/users/**,/addresses/**

lb:loadbalance的缩写 

测试

启动该网关服务和有两个端口号的商品服务,然后到浏览器输入ip地址测试

localhost:8081/search/list
localhost:8080/search/list
localhost:8083/search/list

可以看到网关正常工作,而且实现了负载均衡

启动UserApplication,然后在黑马商城实现登录测试

过滤器

官网有33中过滤器,值一般就是key-value的形式

重启一下网关和两个商品服务application,然后清空日志,在浏览器输入ip地址,然后看控制台有无指定输出

localhost:8080/items/page

 可以看到有输出,过滤器的增加请求头有效果

全局配置过滤器可以放在和route同级下面

登录校验
1、自定义过滤器
GlobalFilter(重点)

tips:对着类按ctrl + H可以看到所有子类(其中有重要的负责路由转发的NettyRoutingFilter)

在网关模块创建一下该类

@Component
public class GlobalFilter implements org.springframework.cloud.gateway.filter.GlobalFilter, Ordered {
    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        // TODO 模拟登录校验逻辑
        ServerHttpRequest request = exchange.getRequest();
        HttpHeaders headers = request.getHeaders();
        System.out.println(headers);
        // 放行
        return chain.filter(exchange);
    }


    @Override
    public int getOrder() {
        return 0;
    }
}

打断点调试

debug重启,然后在黑马商城点击搜索按钮,然后回来查看是否有headers

可以看到用户的token

回顾:继承一个GlobalFilter,从exchange获得信息,再把上下文对象exchange传给过滤器链chain放行。还要保证过滤器在NettyRoutingFilter之前执行,这里我们用Ordered接口来控制执行顺序。(过滤器执行顺序,值越小,优先级越高)

GatewatFilter

创建新的过滤器

@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);
    }
}

记得配置

重新启动服务,在黑马商城点击搜索。看IDEA控制台打印

可以看到有顺序地打印了请求头、然后我们另外自定义的GatewayFilter

有参数的GatewayFilter

改造过滤器

@Component
public class PrintAnyGatewayFilterFactory extends AbstractGatewayFilterFactory<PrintAnyGatewayFilterFactory.Config> {
    @Override
    public GatewayFilter apply(Config config) {
        return new OrderedGatewayFilter(new GatewayFilter() {
            @Override
            public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
                String a = config.getA();
                String b = config.getB();
                String c = config.getC();
                System.out.println("a="+a+",b="+b+",c="+c);
                System.out.println("print any filter running");
                return chain.filter(exchange);
            }
        },1);
    }

    @Data
    public static class Config{

        private String a;
        private String b;
        private String c;
    }

    public PrintAnyGatewayFilterFactory() {
        super(Config.class);
    }

    @Override
    public List<String> shortcutFieldOrder() {
        return List.of("a","b","c");
    }
}

改造配置文件

        - PrintAny=1,2,3

重启然后刷新黑马商城

可以拿到参数

2、实现登录校验

从hm-service中引入三个配置类倒hm-gateway,其中一个要加上@Component

引入工具类和jwt密钥文件hmall.jks,拷贝一些jwt内容到application.yaml

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

编写AuthGlobalFilter

package com.hmall.gateway.filters;

import com.hmall.common.exception.UnauthorizedException;
import com.hmall.common.utils.CollUtils;
import com.hmall.gateway.config.AuthProperties;
import com.hmall.gateway.util.JwtTool;
import lombok.RequiredArgsConstructor;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.core.Ordered;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.http.server.reactive.ServerHttpResponse;
import org.springframework.stereotype.Component;
import org.springframework.util.AntPathMatcher;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;

import java.util.List;

@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;
    }
}

网关增加一个carts

重启服务后到黑马商城测试,先是/items/page(直接放行的,直接进入),然后是/carts(有登录拦截,报401)

技术点:AntPathMatch(spring提供的匹配)

3、网关传递用户
思路:

过滤器中保存用户到请求头

编写业务逻辑

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

在cart-service中的controller方法中调试(传统方法解析请求头)

记得开启cartApplication服务,然后在控制台查看是否有信息打印出来

调用SpringMVC的拦截器获取用户保存到ThreadLocal(给其他服务拿取)

在common模块中操作,就不用在每个微服务都编写一个springmvc拦截器

创建新类(继承HandlerInterceptor)

package com.hmall.common.interceptors;

import cn.hutool.core.util.StrUtil;
import com.hmall.common.utils.UserContext;
import org.springframework.web.servlet.HandlerInterceptor;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

/**
 * @Author:Nanami
 * @Date:2024/11/9 21:20
 * @Version: v1.0.0
 * @Description: TODO
 **/
public class UserInfoInterceptor implements HandlerInterceptor {
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        // 1.获取登录用户信息
        String userInfo = request.getHeader("user-info");
        // 2.判断是否获取了用户,如果有,存入ThreadLocal
        if(StrUtil.isNotBlank(userInfo)){
            UserContext.setUser(Long.valueOf(userInfo));
        }
        // 3.放行
        return true;
    }
    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
        // 清理用户
        UserContext.removeUser();
    }
}

要想SpringMVC拦截器实现还需要编写配置类config(继承WebMvcConfigurer),注解@Configuration

package com.hmall.common.config;

import com.hmall.common.interceptors.UserInfoInterceptor;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;

/**
 * @Author:Nanami
 * @Date:2024/11/9 21:29
 * @Version: v1.0.0
 * @Description: TODO
 **/
@Configuration
public class MvcConfig implements WebMvcConfigurer {
    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(new UserInfoInterceptor());
    }
}

但是这样拦截器还没有生效,因为其他微服务扫描不到common模块的包,我们还得在META-INF下的spring.factories中声明

org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
  com.hmall.common.config.MyBatisConfig,\
  com.hmall.common.config.MvcConfig,\
  com.hmall.common.config.JsonConfig

但是重新启动后还是报错了,这是因为网关也引用了common中的springMVC拦截器,网关的底层拦截器和springMVC的拦截器冲突了。

网关的底层拦截器依靠的是非阻塞式的响应式编程WebFlux,因此我们让springMVC的拦截器只在微服务生效而不要在网关生效。

这里还是考察springboot的自动装配原理,让其只在某些特定条件下生效,这里我们引用springmvc的重要api:DispatcherServlet,在配置类加入注解

@Configuration
@ConditionalOnClass(DispatcherServlet.class)
public class MvcConfig implements WebMvcConfigurer {
    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(new UserInfoInterceptor());
    }
}

下一步:记得换掉之前在cart-service的实现类中写死的用户ID

把1L换成UserContext.getUser() 

重新启动CartApplication,重新进入黑马商城的/cart.html,控制台打印中看CartMapper的方法能不能从UserContext中取得userId。 

可以看到拦截器存入了userId,拦截器生效了。 

重新登录,账号为rose,密码为123再来测试

可以看到userId变成了“2”。

快捷键学习:ctrl+o:快速重写

4、OpenFeign传递用户
网关中补充新的路径
        - id: pay-service
          uri: lb://pay-service
          predicates:
            - Path=/pay-orders/**
        - id: trade-service
          uri: lb://trade-service
          predicates:
            - Path=/orders/**

这里清除购物车的话会清除失败。因为这只是微服务之间互相调用,没有走网关,因此请求头中并没有存入用户的Id

接下来的操作都是在hm-api中操作

先在pom文件中引入hm-common模块

改造DefaultFeignConfig类
    @Bean
    public RequestInterceptor userInfoRequestInterceptor(){
        return new RequestInterceptor() {
            @Override
            public void apply(RequestTemplate template) {
                Long userId = UserContext.getUser();
                if(userId != null){
                  template.header("user-info",userId.toString());
                }
            }
        };
    }

另外,trade启动类上面也要加上注解Feign配置类才能生效。 

建议重新启动所有服务

可以看到购物车的清除操作有了userId

配置管理

问题引入

1、微服务重复配置过多,维护成本高

2、业务配置经常变动,每次修改都要重启服务

3、网关路由配置写死,如果变更要重启网关

很多微服务可能有相同的配置比如jdbc,swagger,日志等,要修改的话就很麻烦,重启的时候服务不可用,用户的体验就会下降。nacos不仅能担任注册中心的角色,还可以管理微服务的共享配置

配置共享

拷贝cart-service的部分配置文件,这里我们先拷贝jdbc部分,然后到nacos的配置列表里点击最右边的“+”号

相关设置如下:

记得标上配置格式!!勾选yaml

配置内容就贴上下面的代码

spring:
  datasource:
    url: jdbc:mysql://${hm.db.host:192.168.89.129}:${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

点击发布即可。

接下来我们再来配置log和swagger,命名和描述仿照上面的步骤

logging:
  level:
    com.hmall: debug
  pattern:
    dateformat: HH:mm:ss:SSS
  file:
    path: "logs/${spring.application.name}"
knife4j:
  enable: true
  openapi:
    title: ${hm.swagger.title:黑马商城购物车接口文档}
    description: ${hm.swagger.desc:黑马商城购物车接口文档}
    email: zhanghuyi@itcast.cn
    concat: 虎哥
    url: https://www.itcast.cn
    version: v1.0.0
    group:
      default:
        group-name: default
        api-rule: package
        api-rule-resources:
          - ${hm.swagger.package}

 接下来就要到idea编写hm.db之类的配置给nacos读取了

拉取共享配置

共享配置已经添加到nacos了,但是微服务还没有拉取这些配置到本地,所以我们接下来就要做拉取共享配置的操作

我们现在是普通的springboot项目,内部加载顺序如下:

springcloud的加载顺序,但是我们也不知道nacos的地址

有了引导文件bootstrap.yaml后,我们就可以知道nacos的地址了

 引入依赖

放入cart-service的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>

创建一个bootstrap.yaml文件

spring:
  application:
    name: cart-service #微服务名称
  profiles:
    active: dev
  cloud:
    nacos:
      server-addr: 192.168.89.129:8848
      config:
        file-extension: yaml
        shared-configs:
          - data-id: share-jdbc.yaml
          - data-id: share-log.yaml
          - data-id: share-swagger.yaml

把原来的application.yaml公共配置删去,只剩

server:
  port: 8082
feign:
  okhttp:
    enabled: true
hm:
  db:
    database: hm-cart
  swaager:
    title: "黑马商城购物车服务接口文档"
    package: com.hmall.cart.controller

重新启动服务,随后到黑马商城添加物品到购物车,看是否添加成功,报错的看看文件名啥的有没有书写正确。 

配置热更新

定义:当修改配置文件中的配置时,微服务无需重启即可使配置生效。

案例:动态修改购物车限制数量

对count >= 10进行操作

新建一个类

@Data
@Component
@ConfigurationProperties(prefix = "hm.cart")
public class CartProperties {
    // 最大商品数量
    private Integer maxItems;
}

在实现类注入该类

改写之前的cart >= 10

nacos中添加新的配置管理

发布后重新启动微服务,然后到黑马商城刷新,看一下添加物品到购物车成不成功

这样就成功生效了,接下来就要测试热更新,到nacos中修改maxItems的值,但是不需要重启微服务 (这里是我已经改过的了)

可以看到成功添加进来了,成功实现了热更新。

动态路由

当路由发生改变时,网关中的配置也相应需要更新,然后重启网关,但是网关不能随便重启,因此我们需要动态路由。

引入依赖到网关

        <!--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>

复制cart-service的bootstrap.yaml文件

spring:
  application:
    name: gateway #微服务名称
  profiles:
    active: dev
  cloud:
    nacos:
      server-addr: 192.168.89.129:8848
      config:
        file-extension: yaml
        shared-configs:
          - data-id: share-log.yaml

修改application.yaml

server:
  port: 8080 # 端口
hm:
  jwt:
    location: classpath:hmall.jks # 秘钥地址
    alias: hmall # 秘钥别名
    password: hmall123 # 秘钥文件密码
    tokenTTL: 30m # 登录有效期
  auth:
    excludePaths: # 无需登录校验的路径
      - /search/**
      - /users/login
      - /items/**

创建一个新类

更新路由配置需要解析yaml文件,但是很麻烦,所以我们改作解析json文件。

Mono是springboot提供的一个响应式编程容器

编写配置类

@Slf4j
@Component
@RequiredArgsConstructor
public class DynamicRouteLoader {

    private final NacosConfigManager nacosConfigManager;

    private final RouteDefinitionWriter writer;

    private final String dataId = "gateway-routes.json";

    private final String group = "DEFAULT_GROUP";

    private final Set<String> routeIds = new HashSet<>();


    @PostConstruct
    public void initRouteConfigListener() throws NacosException{
        // 1.项目启动时,先拉取一次配置,并且添加配置监听器
        String configInfo = nacosConfigManager.getConfigService()
                .getConfigAndSignListener(dataId, group, 5000, new Listener() {
                    @Override
                    public Executor getExecutor() {
                        return null;
                    }

                    @Override
                    public void receiveConfigInfo(String configInfo) {
                        // 2.监听到配置变更,需要去更新路由表
                        updateConfigInfo(configInfo);
                    }
                });
        // 3.第一次读取到配置,也需要更新到路由表
        updateConfigInfo(configInfo);
    }

    public void updateConfigInfo(String configInfo){
        log.info("收到最新的网关路由配置信息:{}", configInfo);
        // 1.解析配置信息,转为RouteDefinition
        List<RouteDefinition> routeDefinitions = JSONUtil.toList(configInfo, RouteDefinition.class);
        // 2.删除旧的路由表
        for (String routeId : routeIds) {
            writer.delete(Mono.just(routeId)).subscribe();
        }
        routeIds.clear();
        // 3.更新路由表
        for (RouteDefinition routeDefinition : routeDefinitions) {
            // 3.1.更新路由表
            writer.save(Mono.just(routeDefinition)).subscribe();
            // 3.2.记录路由id,便于下一次更新时删除
            routeIds.add(routeDefinition.getId());
        }
    }

重启服务 

接下来不用重启微服务,在nacos中编写路由配置(路由内容),这里虎哥给的id没有加上service,这里可以加上。

[
    {
        "id": "item-service",
        "predicates": [{
            "name": "Path",
            "args": {"_genkey_0":"/items/**", "_genkey_1":"/search/**"}
        }],
        "filters": [],
        "uri": "lb://item-service"
    },
    {
        "id": "cart-service",
        "predicates": [{
            "name": "Path",
            "args": {"_genkey_0":"/carts/**"}
        }],
        "filters": [],
        "uri": "lb://cart-service"
    },
    {
        "id": "user-service",
        "predicates": [{
            "name": "Path",
            "args": {"_genkey_0":"/users/**", "_genkey_1":"/addresses/**"}
        }],
        "filters": [],
        "uri": "lb://user-service"
    },
    {
        "id": "trade-service",
        "predicates": [{
            "name": "Path",
            "args": {"_genkey_0":"/orders/**"}
        }],
        "filters": [],
        "uri": "lb://trade-service"
    },
    {
        "id": "pay-service",
        "predicates": [{
            "name": "Path",
            "args": {"_genkey_0":"/pay-orders/**"}
        }],
        "filters": [],
        "uri": "lb://pay-service"
    }
]

发布的时候可以看看控制台有无打印路由信息(没有的话可以重启服务)

浏览器测试

微服务保护和分布式事务

雪崩问题

解决思路:一个是服务提供者想办法避免服务出现故障,另一个是服务调用者去调用服务器的时候发现出现了故障要想办法去隔离这种故障,避免这个故障传递到自己出现级联失败。

解决方案
请求限流:

使QPS线由突起转化为平稳的曲线

线程隔离(舱壁隔离):

服务器只给用户一定的线程数保证tomcat资源不会被消耗完,但是如果不断占用线程去请求故障的服务会浪费资源,故有后面的服务熔断

服务熔断

断路器统计请求的异常比例或慢调用比例,如果超出阈值则会熔断该业务,则拦截该接口的请求。熔断期间,所有请求快速失败,全都走fallback逻辑。

组件使用选择

Sentinel快速入门
初始Sentinel

Sentinel是阿里巴巴开源的一款微服务流量控制组件。 官方地址: home | Sentinel 

我们要做的就是去搭建控制台和安装依赖。

安装和启用jar包

安装步骤可以参考‬​​​​⁠​​‍​​​​​‌​‬​‬​⁠‬​​⁠​‍​​​​​‍​⁠​​‍​‌​​​day05-服务保护和分布式事务 - 飞书云文档

这里资料文件夹里面有1.8.6版本的Sentinel,复制到非中文目录(记得去掉版本号)然后在文件终端执行命令即可(操作跟启动nginx一样)。

java -Dserver.port=8090 -Dcsp.sentinel.dashboard.server=localhost:8090 -Dproject.name=sentinel-dashboard -jar sentinel-dashboard.jar

访问localhost:8090端口,会进入到Sentinel登录界面,账号和密码都是Sentinel

然后就进来啦~

连接微服务

在cart-service中引入依赖

<!--sentinel-->
<dependency>
    <groupId>com.alibaba.cloud</groupId> 
    <artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
</dependency>

配置控制台

spring:
  cloud:
    sentinel:
      transport:
        dashboard: localhost:8090  #Sentinel的控制台地址

重启一下cart-service,此时Sentinel客户端还监测不到购物车服务,这是因为我们还没有进行接口调用,我们到黑马商城调用几次购物车(多刷新几次)请求然后再回来刷新一下就可以看到了。

簇点链路

Restful风格的API请求路径一般都相同,这会导致簇点资源名称重复。因此我们要修改配置,把请求方式+请求路径作为簇点资源名称

spring:
  cloud:
    sentinel:
      transport:
        dashboard: localhost:8090  # Sentinel的控制台地址
      http-method-specify: true # 是否设置请求方式作为资源名称

重启服务然后到黑马商城的购物车做一下增删改查操作看一下Sentinel簇点链路变多有没有

可以看到是有变多的。 

请求限流

给购物车接口请求限流(点击GET:/carts右边的流控,设置单击阈值为6)

 

打开jmeter测试,拖进资料中的雪崩测试.jms 

点击开始限流测试

查看结果

10个请求只允许6个,这是预期情况,可以看见限流成功了。 

线程隔离

还是跟请求限流一样点击流控,但是我们选择并发线程数

我们先模拟业务延迟(相当于1秒只执行两次)qps为2

修改cat-service的application.yaml

server:
  port: 8082
  tomcat:
    threads:
      max: 25
    accept-count: 25
    max-connections: 100

重启购物车服务,然后到黑马商城测试购物车,查看网络请求。 

可以看到请求要500多毫秒,模拟成功

我们再试试除了查询之外的操作。可以看到就不用500多毫秒

接下来我们测试查询业务高并发会不会影响别的业务

开始执行jmeter的线程隔离测试,可以看到我这里是直接查询不到了,实际上因为tomcat资源被耗尽,其他的服务也会受到影响。

接下来到sentinel去做线程隔离,控制并发线程数的单机阈值为5,可以看到虽然请求不了,但是查询速度变快了。 

Fallback

hm-api模块下创建fallback类

@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("查询商品失败!",cause);
                return CollUtils.emptyList();
            }

            @Override
            public void deductStock(List<OrderDetailDTO> items) {
                log.error("扣减商品失败!",cause);
                throw new RuntimeException(cause);
            }
        };
    }

}

在config包下的配置类添加bean

    //注册bean
    @Bean
    public ItemClientFallbackFactory itemClientFallbackFactory(){
        return new ItemClientFallbackFactory();
    }

 ItemClient类上注解后面添加fallbackFactory

 cart-service模块的yaml配置修改

feign:
  okhttp:
    enabled: true
  sentinel:
    enabled: true

 重启购物车服务,可见feign的远程调用也变成了一个簇点

 我们之间在下面控制并发线程数就好,然后访问购物车不断刷新

可以看到请求都是成功的,只是fallback的item信息是null 

服务熔断

 

还是配置商品服务的熔断

 然后再去jmeter测试

发现购物车根本不查询了,因为执行了熔断规则

分布式事务

Seata
初识Seata

TM:标志:controller开始。实现类执行完就结束

TC:都正常就提交,有一个不正常就都回滚。

部署TC服务

引入seata数据库表seata-tc.sql

把seata.tar包和seata配置文件都移到虚拟机的/root目录下

加载镜像

docker load -i seata -i seata-1.5.2.tar

dis查看是否加进来

接下来我们要保证nacos.mysql,seata都在hm-net网络下

docker inspect mysql
docker inspect nacos

查看网段有无hm-net

docker network ls

 没有的话就添加

docker network create hm-net

添加容器到网段的方法(把mysql、nacos都加进来)

docker network connect hm-net nacos

connect后面就跟上网段名和容器名

添加seata容器,记得把虚拟机IP改成自己的

docker run --name seata \
-p 8099:8099 \
-p 7099:7099 \
-e SEATA_IP=192.168.89.129 \
-v ./seata:/seata-server/resources \
--privileged=true \
--network hm-net \
-d \
seataio/seata-server:1.5.2

查看seata容器情况

docker logs -f seata

打开nacos的服务列表可以看见seata-server

访问seata网站控制台

192.168.89.129:7099

 账号和密码都是admin

微服务集成Seata

在nacos编写共享配置文件(记得修改虚拟机IP为自己的)

seata:
  registry: # TC服务注册中心的配置,微服务根据这些信息去注册中心获取tc服务地址
    type: nacos # 注册中心类型 nacos
    nacos:
      server-addr: 192.168.150.89.129 # 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"

 在购物车、商品、交易服务都引入依赖(有的服务引入seata依赖即可)

<!--统一配置管理-->
  <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>

不要把依赖放入common模块的原因是不是每个微服务都需要这些依赖 

在cart-service的bootstrap.yaml配置项引入seata共享配置

          - data-id: share-seata.yaml

item-service的bootstrap.yaml配置(没有则直接引入)

spring:
  application:
    name: item-service #微服务名称
  profiles:
    active: dev
  cloud:
    nacos:
      server-addr: 192.168.89.129:8848
      config:
        file-extension: yaml
        shared-configs:
          - data-id: share-jdbc.yaml
          - data-id: share-log.yaml
          - data-id: share-swagger.yaml
          - data-id: share-seata.yaml

 item-service的application.yaml改造

server:
  port: 8081
hm:
  db:
    database: hm-item
  swaager:
    title: "黑马商城商品服务接口文档"
    package: com.hmall.item.controller

trade-service服务模块的改造也是如此

bootstrap.yaml

spring:
  application:
    name: trade-service #微服务名称
  profiles:
    active: dev
  cloud:
    nacos:
      server-addr: 192.168.89.129:8848
      config:
        file-extension: yaml
        shared-configs:
          - data-id: share-jdbc.yaml
          - data-id: share-log.yaml
          - data-id: share-swagger.yaml
          - data-id: share-seata.yaml

 application.yaml

server:
  port: 8085
hm:
  db:
    database: hm-trade
  swaager:
    title: "黑马商城交易服务接口文档"
    package: com.hmall.trade.controller

三个模块都重启一下 

回去docker查看日志查找关键词“TM”"RM”可见服务已经和seata建立起了联系

XA模式(强一致)

1、修改配置文件

在nacos中修改share-seata.yaml并点击发布

seata:
  data-source-proxy-mode: XA

2、添加注解 

在trade-service模块的OrderServiceImpl的createOrder方法上添加@GlobalTransactional注解

在分支事务的item-service的扣减库存操作上添加@Transactional注解回滚 

在分支事务的cart-service的移除操作上添加@Transactional注解回滚  

 可以测试,数据库设某个商品库存为0,然后支付,看一下购物车的商品有没有移除,没有的话就是回滚了。

在TradeApplication启动类控制台有如下注解:

AT模式 (性能)

 实现:

1、引入seata-at.sql表

在trade、item、cart的database都引入该undolog表

2、修改XA模式为AT模式

可以在nacos中注释原来的配置(因为seata默认是AT模式),也可以修改值

重启三个微服务,照常下单操作看有无回滚即可。(提交成功后undolog快照表会删除)

转载请标明出处: http://blog.csdn.net/forezp/article/details/70148833 本文出自方志朋的博客 错过了这一篇,你可能再也学不会 Spring Cloud 了!Spring Boot做为下一代 web 框架,Spring Cloud 作为最新最火的微服务的翘楚,你还有什么理由拒绝。赶快上船吧,老船长带你飞。终章不是最后一篇,它是一个汇总,未来还会写很多篇。 案例全部采用Spring Boot 1.5.x ,Spring Cloud版本为Dalston.RELEASE 我为什么这些文章?一是巩固自己的知识,二是希望有更加开放和与人分享的心态,三是接受各位大神的批评指教,有任何问题可以联系我: miles02@163.com . 码农下载:https://git.oschina.net/forezp/SpringCloudLearning github下载:https://github.com/forezp/SpringCloudLearning,记得star哦! 欢迎购买我的书《深入理解Spring Cloud与微服务构建》 1.jpg 京东购买 当当购买 亚马逊购买 CSDN专栏汇总:史上最简单的 SpringCloud 教程 《史上最简单的 SpringCloud 教程》系列: 史上最简单的 SpringCloud 教程 | 第一篇: 服务的注册与发现(Eureka) 史上最简单的SpringCloud教程 | 第二篇: 服务消费者(rest+ribbon) 史上最简单的SpringCloud教程 | 第三篇: 服务消费者(Feign) 史上最简单的SpringCloud教程 | 第四篇:断路器(Hystrix) 史上最简单的SpringCloud教程 | 第五篇: 路由网关(zuul) 史上最简单的SpringCloud教程 | 第六篇: 分布式配置中心(Spring Cloud Config) 史上最简单的SpringCloud教程 | 第七篇: 高可用的分布式配置中心(Spring Cloud Config) 史上最简单的SpringCloud教程 | 第八篇: 消息总线(Spring Cloud Bus) 史上最简单的SpringCloud教程 | 第九篇: 服务链路追踪(Spring Cloud Sleuth) 史上最简单的SpringCloud教程 | 第十篇: 高可用的服务注册中心 史上最简单的SpringCloud教程 | 第十一篇:docker部署spring cloud项目 史上最简单的SpringCloud教程 | 第十二篇: 断路器监控(Hystrix Dashboard) 史上最简单的SpringCloud教程 | 第十三篇: 断路器聚合监控(Hystrix Turbine) 史上最简单的 SpringCloud 教程 | 第十四篇: 服务注册(consul) 未完。。。 还有很多篇。。。 进阶篇 Spring Cloud Sleuth超详细实战 源码篇: 深入理解Feign之源码解析 深入理解Eureka之源码解析 深入理解Ribbon之源码解析 深入理解Hystrix之文档翻译 深入理解Zuul之源码解析 番外篇: 如何使用MongoDB+Springboot实现分布式ID? 如何在springcloud分布式系统中实现分布式锁? 如何用Redlock实现分布式锁 如何在IDEA启动多个Spring Boot工程实例 JWT如何在Spring Cloud微服务系统中在服务相互调时传递
黑马程序员是一家知名的IT培训机构,提供了一系列关于SpringCloud的学习资源。根据引用\[1\]中的描述,他们的学习路线建议先刷黑马程序员的实用篇,以最少的时间快速掌握SpringCloud的相关知识。而引用\[3\]中提到的尚硅谷和黑马教程也是学习SpringCloud的参考资料。SpringCloud是目前国内使用最广泛的微服务框架,它是微服务架构的一站式解决方案,集成了各种优秀微服务功能组件,并基于SpringBoot实现了这些组件的自动装配,从而提供了良好的开箱即用体验。你可以通过访问SpringCloud的官网地址https://spring.io/projects/spring-cloud来获取更多关于SpringCloud的详细信息。 #### 引用[.reference_title] - *1* *3* [黑马2021最新版 SpringCloud基础篇全技术栈导学(RabbitMQ+Docker+Redis+搜索+分布式)](https://blog.csdn.net/weixin_44757863/article/details/120959505)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v91^koosearch_v1,239^v3^insert_chatgpt"}} ] [.reference_item] - *2* [【黑马-SpringCloud技术栈】【02】服务拆分及远程调用_服务提供者与消费者](https://blog.csdn.net/weixin_44018671/article/details/125653829)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v91^koosearch_v1,239^v3^insert_chatgpt"}} ] [.reference_item] [ .reference_list ]
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值