Spring Cloud Alibaba学习文档

133 阅读3分钟

Spring Cloud Alibaba

Nacos

一个更易于构建云原生应用的动态服务发现、配置管理和服务管理平台。

  • 替代Eureka做服务注册中心
  • 替代Config做服务配置中心

下载:nacos.io/zh-cn/index…

文档:spring-cloud-alibaba-group.github.io/github-page…

-- nacos安装并运行

nacos单机启动

startup.cmd -m standalone

访问地址:https://localhost:8848

nacos服务注册中心

基于nacos的服务提供者9001

核心依赖

<!--SpringCloud ailibaba nacos -->
<dependency>
    <groupId>com.alibaba.cloud</groupId>
    <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>

配置文件

server:
  port: 9001

spring:
  application:
    name: alibaba-nacos-provider
  cloud:
    nacos:
      discovery:
        server-addr: localhost:8848 #配置Nacos地址

#暴露所有监控端点
management:
  endpoints:
    web:
      exposure:
        include: '*'

主启动类 @EnableDiscoveryClient

/**
 * Created by KingsLanding on 2022/11/3 15:20
 */
@EnableDiscoveryClient
@SpringBootApplication
public class AlibabaNacosOrderTest9001 {

    public static void main(String[] args) {
        SpringApplication.run(AlibabaNacosOrderTest9001.class,args);
    }
}

业务类

/**
 * Created by KingsLanding on 2022/11/3 15:23
 */
@RestController
public class NacosOrderController {

    @Value("${server.port}")
    private String serverPort;

    @GetMapping(value = "/alibaba/nacos/{id}")
    public String getPayment(@PathVariable("id") Integer id)
    {
        return "nacos registry, serverPort: "+ serverPort+"\t id"+id;
    }
}

基于nacos的服务提供者9002

同9001


基于nacos的服务调用方90

核心依赖

<!--SpringCloud ailibaba nacos -->
<dependency>
    <groupId>com.alibaba.cloud</groupId>
    <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>

配置文件

server:
  port: 90

spring:
  application:
    name: alibaba-nacos-consumer
  cloud:
    nacos:
      discovery:
        server-addr: localhost:8848


#消费者将要去访问的微服务名称(注册成功进nacos的微服务提供者)
service-url:
  nacos-user-service: http://alibaba-nacos-provider

主启动类

/**
 * Created by KingsLanding on 2022/11/3 15:45
 */
@SpringBootApplication
@EnableDiscoveryClient
public class AlibabaNacosConsumerTest90 {
    public static void main(String[] args) {
        SpringApplication.run(AlibabaNacosConsumerTest90.class,args);
    }
}

业务类

  • alibaba nacos自带Ribbon负载均衡
/**
 * Created by KingsLanding on 2022/11/3 15:47
 *
 * alibaba nacos自带Ribbon负载均衡
 */
@RestController
public class NacosConsumerController {
    @Resource
    private RestTemplate restTemplate;

    @Value("${service-url.nacos-user-service}")//获取application.yml的service-url.nacos-user-service的value数据
    private String serverURL;

    @GetMapping("/consumer/alibaba/nacos/{id}")
    public String paymentInfo(@PathVariable("id") Long id)
    {
        return restTemplate.getForObject(serverURL+"/alibaba/nacos/"+id,String.class);
    }
}

注册中心对比

服务注册中心对比.jpeg

Nacos 支持AP和CP模式的切换

C是所有节点在同一时间看到的数据是一致的;而A的定义是所有的请求都会收到响应。

何时选择使用何种模式? 一般来说,如果不需要存储服务级别的信息且服务实例是通过nacos-client注册,并能够保持心跳上报,那么就可以选择AP模式。当前主流的服务如 Spring cloud 和 Dubbo 服务,都适用于AP模式,AP模式为了服务的可能性而减弱了一致性,因此AP模式下只支持注册临时实例。

如果需要在服务级别编辑或者存储配置信息,那么 CP 是必须,K8S服务和DNS服务则适用于CP模式。CP模式下则支持注册持久化实例,此时则是以 Raft 协议为集群运行模式,该模式下注册实例之前必须先注册服务,如果服务不存在,则会返回错误。

Nacos服务配置中心3377

核心依赖

<!--nacos-config-->
<dependency>
    <groupId>com.alibaba.cloud</groupId>
    <artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>
</dependency>
<!--nacos-discovery-->
<dependency>
    <groupId>com.alibaba.cloud</groupId>
    <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>

配置文件

bootstrop.yml

# nacos配置
server:
  port: 3377

spring:
  application:
    name: nacos-config-client
  cloud:
    nacos:
      discovery:
        server-addr: localhost:8848 #Nacos服务注册中心地址
      config:
        server-addr: localhost:8848 #Nacos作为配置中心地址
        file-extension: yaml #指定yaml格式的配置
        group: DEV_GROUP #nacos配置文件分组读取,环境隔离
        namespace: 6ee63955-8237-4919-8d83-572d35de6691 #命名空间ID ,隔离效果;namespaceID-->groupID-->dataID


# ${spring.application.name}-${spring.profile.active}.${spring.cloud.nacos.config.file-extension}

application.yml

spring:
  profiles:
    active: dev # 表示开发环境;根据不同的开发环境,可以实现拉取指定的配置文件
#    active: test

主启动类

/**
 * Created by KingsLanding on 2022/11/3 19:26
 */
@SpringBootApplication
@EnableDiscoveryClient
public class AlibabaNacosConfigClient3377 {
    public static void main(String[] args) {
        SpringApplication.run(AlibabaNacosConfigClient3377.class,args);
    }
}

业务类 @RefreshScope

/**
 * Created by KingsLanding on 2022/11/3 19:40
 */
@RestController
@RefreshScope  //在控制器类加入@RefreshScope注解使当前类下的配置支持Nacos的动态刷新功能。
public class NacosConfigController {

    @Value("${config.info}")
    private String configInfo;

    @GetMapping("/config/info")
    public String getConfigInfo() {
        return configInfo;
    }

}

nacos中配置信息添加与匹配规则

nacos配置中心.png

匹配规则对应配置文件

data ID:

  • spring.application.name:nacos-config-client
  • spring.profile.active:dev
  • spring.cloud.nacos.config.file-extension:yaml**(注意此处必须写yaml)**

spring.application.name{spring.application.name}-{spring.profile.active}.${spring.cloud.nacos.config.file-extension}

nacos命名空间与Group和Data ID

Namespace+Group+Data ID

默认情况: Namespace=public,Group=DEFAULT_GROUP, 默认Cluster是DEFAULT

Namespace主要用来实现隔离 比方说我们现在有三个环境:开发、测试、生产环境,我们就可以创建三个Namespace,不同的Namespace之间是隔离的。

Group可以把不同的微服务划分到同一个分组里面去

Service就是微服务;一个Service可以包含多个Cluster(集群),Nacos默认Cluster是DEFAULT,Cluster是对指定微服务的一个虚拟划分。 比方说为了容灾,将Service微服务分别部署在了杭州机房和广州机房, 这时就可以给杭州机房的Service微服务起一个集群名称(HZ), 给广州机房的Service微服务起一个集群名称(GZ),还可以尽量让同一个机房的微服务互相调用,以提升性能。

最后是Instance,就是微服务的实例。


隔离效果;namespaceID-->groupID-->dataID

spring:
  application:
    name: nacos-config-client
  cloud:
    nacos:
      discovery:
        server-addr: localhost:8848 #Nacos服务注册中心地址
      config:
        server-addr: localhost:8848 #Nacos作为配置中心地址
        file-extension: yaml #指定yaml格式的配置
        group: DEV_GROUP #nacos配置文件分组读取,环境隔离
        namespace: 6ee63955-8237-4919-8d83-572d35de6691 #命名空间ID ,隔离效果;namespaceID-->groupID-->dataID

Nacos持久化配置

Nacos默认自带的是嵌入式数据库derby

github.com/alibaba/nac…

#### 将nacos数据库从derby切换到MySQL

  • \nacos\conf目录下找到application.properties
spring.datasource.platform=mysql

db.num=1
db.url.0=jdbc:mysql://localhost:3306/nacos_config?serverTimezone=UTC&useUnicode=true&characterEncoding=utf-8&useSSL=false
db.user=root
db.password=zmj.666.999

Sentinel实现熔断与限流

官网:github.com/alibaba/Sen…

下载:github.com/alibaba/Sen…

sentinel功能结构.jpeg

启动:java -jar sentinel-dashboard-1.7.0.jar

访问:http://localhost:8080

登录账号密码均为sentinel

Sentinel采用的懒加载


构建Sentinel服务

核心依赖

<!--SpringCloud ailibaba nacos -->
<dependency>
    <groupId>com.alibaba.cloud</groupId>
    <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>
<!--SpringCloud ailibaba sentinel-datasource-nacos 后续做持久化用到-->
<dependency>
    <groupId>com.alibaba.csp</groupId>
    <artifactId>sentinel-datasource-nacos</artifactId>
</dependency>
<!--SpringCloud ailibaba sentinel -->
<dependency>
    <groupId>com.alibaba.cloud</groupId>
    <artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
</dependency>

配置文件

server:
  port: 8401

spring:
  application:
    name: cloud-alibaba-sentinel-service
  cloud:
    nacos:
      discovery:
        #Nacos服务注册中心地址
        server-addr: localhost:8848
    sentinel:
      transport:
        #配置Sentinel dashboard地址
        dashboard: localhost:8080
        #默认8719端口,假如被占用会自动从8719开始依次+1扫描,直至找到未被占用的端口
        port: 8719

management:
  endpoints:
    web:
      exposure:
        include: '*'

主启动类

/**
 * Created by KingsLanding on 2022/11/4 19:07
 */
@SpringBootApplication
@EnableDiscoveryClient
public class AlibabaSentinelService8401 {
    public static void main(String[] args) {
        SpringApplication.run(AlibabaSentinelService8401.class,args);
    }
}

效果

sentinel控制台.png

Sentinel流控

直接模式

/testA这个请求每秒可成功一次,超过一秒请求的阈值就会降级异常

关联模式

  • 当关联的资源达到阈值时,就限流自己
  • 当与A关联的资源B达到阀值后,就限流A自己

sentinel流控关联模式.png

链路模式

多个请求调用同一个微服务

Sentinel流控效果

快速失败

出错或者超出阈值直接返回错误异常,恢复正常后继续使用

Warm Up

sentinelWarmUp.png

默认 coldFactor 为 3,即请求QPS从(threshold / 3) 开始,经多少预热时长才逐渐升至设定的 QPS 阈值。 案例,阀值为10+预热时长设置5秒。 系统初始化的阀值为10 / 3 约等于3,即阀值刚开始为3;然后过了5秒后阀值才慢慢升高恢复到10

应用场景:

如:秒杀系统在开启的瞬间,会有很多流量上来,很有可能把系统打死,预热方式就是把为了保护系统,可慢慢的把流量放进来,慢慢的把阀值增长到设置的阀值。

排队等待

匀速排队,阈值必须设置为QPS

匀速排队,让请求以均匀的速度通过

设置含义:/testA每秒1次请求,超过的话就排队等待,等待的超时时间为20000毫秒。

当多次点击,虽然请求没有被立即执行,但是进入了排队等待,排队中的请求会一步步被执行(似乎是有排队数量限制设置)

sentinel超时等待.png

Sentinel降级

sentinel降级.png

RT(平均响应时间,秒级) 平均响应时间 超出阈值 且 在时间窗口内通过的请求>=5,两个条件同时满足后触发降级 窗口期过后关闭断路器 RT最大4900(更大的需要通过-Dcsp.sentinel.statistic.max.rt=XXXX才能生效)

异常比列(秒级) QPS >= 5 且异常比例(秒级统计)超过阈值时,触发降级;时间窗口结束后,关闭降级

异常数(分钟级) 异常数(分钟统计)超过阈值时,触发降级;时间窗口结束后,关闭降级


Sentinel 熔断降级会在调用链路中某个资源出现不稳定状态时(例如调用超时或异常比例升高),对这个资源的调用进行限制, 让请求快速失败,避免影响到其它的资源而导致级联错误。

当资源被降级后,在接下来的降级时间窗口之内,对该资源的调用都自动熔断(默认行为是抛出 DegradeException)。


@SentinelResource(初谈)

sentinel系统默认的提示:Blocked by Sentinel (flow limiting)

使用@SentinelResource自定义blockHandler或fallback

    @GetMapping("/testHotKey")
    @SentinelResource(value = "testHotKey",blockHandler = "dealHandler_testHotKey")
    public String testHotKey(@RequestParam(value = "p1",required = false) String p1,
                             @RequestParam(value = "p2",required = false) String p2){

         /*
        @SentinelResource
        处理的是Sentinel控制台配置的违规情况,有blockHandler方法配置的兜底处理;

        RuntimeException
        int age = 10/0,这个是java运行时报出的运行时异常RunTimeException,@SentinelResource不管
         */
//        System.out.println(10/0);
        return "------testHotKey";
    }
    public String dealHandler_testHotKey(String p1, String p2, BlockException exception)
    {
        return "-----dealHandler_testHotKey";
    }

注意:

RuntimeException int age = 10/0,这个是**java运行时报出的运行时异常RunTimeException,@SentinelResource不管,**后面熔断篇再解释

Sentinel热点key限流

何为热点 热点即经常访问的数据,很多时候我们希望统计或者限制某个热点数据中访问频次最高的TopN数据,并对其访问进行限流或者其它操作

官网:github.com/alibaba/Sen…

特殊数据有不同的限流规则

![sentinel热点数据流控规则](D:\Desktop\java\sentinel热点数据流控规则.jpeg)    @GetMapping("/testHotKey")
    @SentinelResource(value = "testHotKey",blockHandler = "dealHandler_testHotKey")
    public String testHotKey(@RequestParam(value = "p1",required = false) String p1,
                             @RequestParam(value = "p2",required = false) String p2){

         /*
        @SentinelResource
        处理的是Sentinel控制台配置的违规情况,有blockHandler方法配置的兜底处理;

        RuntimeException
        int age = 10/0,这个是java运行时报出的运行时异常RunTimeException,@SentinelResource不管
         */
//        System.out.println(10/0);
        return "------testHotKey";
    }
    public String dealHandler_testHotKey(String p1, String p2, BlockException exception)
    {
        return "-----dealHandler_testHotKey";
    }

设置当p1的值等于5时,它的阈值可以达到200

sentinel热点数据流控规则.jpeg

当访问:http://localhost:8401/testHotKey?p1=5时,限流阈值为200次/秒,如果p1不是5的话,那么限流任然为默认1次/秒(没设置的情况下)

Sentinel按资源名称限流及异常返回

sentinel根据资源名进行流控.jpeg

@GetMapping("/byResource")
@SentinelResource(value = "byResource",blockHandler = "handleException")
public CommonResult byResource()
{
    return new CommonResult(200,"按资源名称限流测试OK",new User(null,"zmj","123123"));
}
//降级处理方法,与主业务在同一个类中,产生耦合
public CommonResult handleException(BlockException exception)
{
    return new CommonResult(444,exception.getClass().getCanonicalName()+"\t 服务不可用");
}

全局Handler处理

/**
 * 自定义通用的限流处理逻辑,--->全局处理
 blockHandlerClass = CustomerBlockHandler.class
 blockHandler = handleException2
 上述配置:找CustomerBlockHandler类里的handleException2方法进行兜底处理
 */
@GetMapping("/rateLimit/customerBlockHandler")
@SentinelResource(value = "customerBlockHandler",
        blockHandlerClass = CustomerBlockHandler.class, blockHandler = "handleException1")
public CommonResult customerBlockHandler()
{
    return new CommonResult(200,"符合客户自定义限流处理逻辑");
}

Handler

/**
 * Created by KingsLanding on 2022/11/5 16:49
 */
@RestController
public class CustomerBlockHandler {

    public static CommonResult handleException1(BlockException exception){
        return new CommonResult(2022,"自定义的限流处理信息......CustomerBlockHandler...handleException1");
    }

    public static CommonResult handleException2(BlockException exception){
        return new CommonResult(2022,"自定义的限流处理信息......CustomerBlockHandler...handleException2");
    }
}

Sentinel按照Url地址限流及异常返回

通过访问的URL来限流,会返回Sentinel自带默认的限流处理信息

@GetMapping("/rateLimit/byUrl")
@SentinelResource(value = "byUrl")
public CommonResult byUrl()
{
    return new CommonResult(200,"按url限流测试OK",new Payment(2020L,"serial002"));
}

sentinel根据URL进行流控.jpeg

@SentinelResource(初谈)

sentinel系统默认的提示:Blocked by Sentinel (flow limiting)

使用@SentinelResource自定义blockHandler或fallback

    @GetMapping("/testHotKey")
    @SentinelResource(value = "testHotKey",blockHandler = "dealHandler_testHotKey")
    public String testHotKey(@RequestParam(value = "p1",required = false) String p1,
                             @RequestParam(value = "p2",required = false) String p2){

         /*
        @SentinelResource
        处理的是Sentinel控制台配置的违规情况,有blockHandler方法配置的兜底处理;

        RuntimeException
        int age = 10/0,这个是java运行时报出的运行时异常RunTimeException,@SentinelResource不管
         */
//        System.out.println(10/0);
        return "------testHotKey";
    }
    public String dealHandler_testHotKey(String p1, String p2, BlockException exception)
    {
        return "-----dealHandler_testHotKey";
    }

@SentinelResource处理的是Sentinel控制台配置的违规情况,有blockHandler方法配置的兜底处理;

RuntimeException int age = 10/0,这个是java运行时报出的运行时异常RunTimeException,@SentinelResource不管

Sentinel熔断

@SentinelResource(详解)

sentinel系统默认的提示:Blocked by Sentinel (flow limiting)

使用@SentinelResource自定义blockHandler或fallback

  • blockHandler:管Sentinel配置违规
  • fallback:管运行异常
  • 注意:如果fallback和blockHandler都配置;被限流降级而抛出 BlockException 时只会进入 blockHandler 处理逻辑
/**
 * Created by KingsLanding on 2022/11/5 19:02
 */
@RestController
public class ConsumerController {
    public static final String SERVICE_URL = "http://nacos-order-provider90";

    @Resource
    private RestTemplate restTemplate;

    @RequestMapping("/consumer/fallback/{id}")
//    @SentinelResource(value = "fallback",blockHandler = "blockHandler") //blockHandler负责在sentinel里面配置的降级限流
//    @SentinelResource(value = "fallback",fallback = "handlerFallback") //fallback负责业务异常
    //fallback和blockHandler都配置;被限流降级而抛出 BlockException 时只会进入 blockHandler 处理逻辑。
    @SentinelResource(value = "fallback",fallback = "handlerFallback",blockHandler = "blockHandler")
    public CommonResult<User> fallback(@PathVariable Integer id)
    {
        CommonResult<User> result = restTemplate.getForObject(SERVICE_URL + "/paymentSQL/"+id, CommonResult.class,id);

        if (id == 4) {
            throw new IllegalArgumentException ("IllegalArgumentException,非法参数异常....");
        }else if (result.getData() == null) {
            throw new NullPointerException ("NullPointerException,该ID没有对应记录,空指针异常");
        }
        return result;
    }

    public CommonResult handlerFallback(@PathVariable  Integer id,Throwable e) {
        User payment = new User(id,"null",null);
        return new CommonResult<>(444,"fallback,无此流水,exception  "+e.getMessage(),payment);
    }
    public CommonResult blockHandler(@PathVariable  Integer id, BlockException blockException) {
        User payment = new User(id,"null",null);
        return new CommonResult<>(445,"blockHandler-sentinel限流,无此流水: blockException  "+blockException.getMessage(),payment);
    }

}

Sentinel结合OpenFeign

注意启动类加@EnableFeignClients

配置文件

#激活feign功能
feign:
  sentinel:
    enabled: true

feign接口

contextId的作用,作为一个标识,新起一个controller层的时候需要指明,否则产生冲突

/**
 * Created by KingsLanding on 2022/11/6 0:32
 * 记住做笔记记录contextId的作用,作为一个标识,新起一个controller层的时候需要指明,否则产生冲突
 */
@FeignClient(value = "nacos-order-provider90",fallback = FeignServiceFallback.class,contextId = "feign")
public interface FeignService {

    @GetMapping(value = "/paymentSQL/{id}")
    public CommonResult<User> paymentSQL(@PathVariable("id") Integer id);

}

fallback方法

/**
 * Created by KingsLanding on 2022/11/6 0:33
 * fallback方法
 */
@Component
public class FeignServiceFallback implements FeignService{

    @Override
    public CommonResult<User> paymentSQL(Integer id) {
        return new CommonResult<>(444,"服务降级返回,没有该流水信息",new User(id,"zmj", "errorSerial......"));
    }
}

业务类

/**
 * Created by KingsLanding on 2022/11/6 0:40
 */
@RestController
public class ConsumerFeignController {

    @Resource
    private FeignService feignService;

    //==================OpenFeign

    @GetMapping(value = "/consumer/openfeign/{id}")
    public CommonResult<User> paymentSQL(@PathVariable("id") Integer id)
    {
        if(id == 4)
        {
            throw new RuntimeException("没有该id");
        }
        return feignService.paymentSQL(id);
    }
}

Sentinel规则持久化

一旦我们重启应用,sentinel规则将消失,生产环境需要将配置规则进行持久化

将限流配置规则持久化进Nacos保存,只要刷新8401某个rest地址,sentinel控制台的流控规则就能看到,只要Nacos里面的配置不删除,针对8401上sentinel上的流控规则持续有效

配置文件

spring:
  application:
    name: nacos-sentinel-consumer80
  cloud:
    nacos:
      discovery:
        server-addr: localhost:8848
    sentinel:
      transport:
        #配置Sentinel dashboard地址
        dashboard: localhost:8080
        #默认8719端口,假如被占用会自动从8719开始依次+1扫描,直至找到未被占用的端口
        port: 8719
        
      #nacos规则持久化
      datasource:
        ds1:
          nacos:
            server-addr: localhost:8848
            dataId: nacos-sentinel-consumer80
            groupId: DEFAULT_GROUP
            data-type: json
            rule-type: flow

添加Nacos业务规则配置

sentinel规则持久化nacos配置.jpeg

重启服务后Sentinel中自动添加规则

Seata处理分布式事务

下载:github.com/seata/seata…

Seata是一款开源的分布式事务解决方案,致力于在微服务架构下提供高性能和简单易用的分布式事务服务。

单体应用被拆分成微服务应用,原来的多个模块被拆分成多个独立的应用,分别使用独立的数据源,业务操作需要调用多个服务来完成。

此时每个服务内部的数据一致性由本地事务来保证,但是全局的数据一致性问题没法保证。

简而言之:一次业务操作需要跨多个数据源或需要跨多个系统进行远程调用,就会产生分布式事务问题

3组件概念

Transaction Coordinator (TC)

事务协调器,维护全局事务的运行状态,负责协调并驱动全局事务的提交或回滚;

Transaction Manager (TM)

控制全局事务的边界,负责开启一个全局事务,并最终发起全局提交或全局回滚的决议;

Resource Manager (RM)

控制分支事务,负责分支注册、状态汇报,并接收事务协调器的指令,驱动分支(本地)事务的提交和回滚

处理过程

  • TM 向 TC 申请开启一个全局事务,全局事务创建成功并生成一个全局唯一的 XID;
  • XID 在微服务调用链路的上下文中传播;
  • RM 向 TC 注册分支事务,将其纳入 XID 对应全局事务的管辖;
  • TM 向 TC 发起针对 XID 的全局提交或回滚决议;
  • TC 调度 XID 下管辖的全部分支事务完成提交或回滚请求。

Seata-Server安装

官网:seata.io/zh-cn/

下载:github.com/seata/seata…

这里的安装过程:seata-server-0.9.0.zip

修改conf目录下的file.conf配置文件

service模块

service {

  vgroup_mapping.zmj_tx_group = "default" #修改自定义事务组名称

  default.grouplist = "127.0.0.1:8091"
  enableDegrade = false
  disable = false
  max.commit.retry.timeout = "-1"
  max.rollback.retry.timeout = "-1"
  disableGlobalTransaction = false
}

store模块

## transaction log store
store {
  ## store mode: file、db
  mode = "db"

  ## file store
  file {
    dir = "sessionStore"

    # branch session size , if exceeded first try compress lockkey, still exceeded throws exceptions
    max-branch-session-size = 16384
    # globe session size , if exceeded throws exceptions
    max-global-session-size = 512
    # file buffer size , if exceeded allocate new buffer
    file-write-buffer-cache-size = 16384
    # when recover batch read size
    session.reload.read_size = 100
    # async, sync
    flush-disk-mode = async
  }

  ## database store
  db {
    ## the implement of javax.sql.DataSource, such as DruidDataSource(druid)/BasicDataSource(dbcp) etc.
    datasource = "dbcp"
    ## mysql/oracle/h2/oceanbase etc.
    db-type = "mysql"
    driver-class-name = "com.mysql.jdbc.Driver"
    url = "jdbc:mysql://127.0.0.1:3306/seata"
    user = "root"
    password = "zmj.666.999"
    min-conn = 1
    max-conn = 3
    global.table = "global_table"
    branch.table = "branch_table"
    lock-table = "lock_table"
    query-limit = 100
  }
}

数据库新建库seata

新版本seata步骤已改:没有sql脚本需要自己执行了(生成数据表的方式改变)

修改conf目录下的registry.conf配置文件

目的是:指明注册中心为nacos,及修改nacos连接信息

registry {
  # file 、nacos 、eureka、redis、zk、consul、etcd3、sofa
  type = "nacos"
 
  nacos {
    serverAddr = "localhost:8848"
    namespace = ""
    cluster = "default"
  }

注意:先启动Nacos再启动Seata

构建Seata模块

核心依赖

    <!--nacos-->
    <dependency>
        <groupId>com.alibaba.cloud</groupId>
        <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
    </dependency> 
	<!--seata-->
    <dependency>
        <groupId>com.alibaba.cloud</groupId>
        <artifactId>spring-cloud-starter-alibaba-seata</artifactId>
        <exclusions>
            <exclusion>
                <artifactId>seata-all</artifactId>
                <groupId>io.seata</groupId>
            </exclusion>
        </exclusions>
    </dependency>

配置文件

spring:
  application:
    name: seata-storage-service
  cloud:
    alibaba:
      seata:
        #自定义事务组名称需要与seata-server中的对应
        tx-service-group: zmj_tx_group
    nacos:
      discovery:
        server-addr: localhost:8848

复制修改后的file.conf和register

Seata复制文件到resource下.jpeg

MyBatisConfig

目的,因为mapper文件的全路径是com.springloud.mapper,而mapper.xml的路径是mapper,并不一致,所以必须设定,也可在启动类指定

/**
 * Created by KingsLanding on 2022/11/6 17:10
 */
@Configuration
//目的,因为mapper文件的全路径是com.springloud.mapper,而mapper.xml的路径是mapper,并不一致,所以必须设定
//也可在启动类指定
@MapperScan({"com.springcloud.mapper"})
public class MyBatisConfig {
}

StorageDataSourceConfig

使用Seata对数据源进行代理

/**
 * Created by KingsLanding on 2022/11/6 16:15
 * 使用Seata对数据源进行代理
 */
@Configuration
public class StorageDataSourceConfig {
    @Value("${mybatis.mapperLocations}")
    private String mapperLocations;

    @Bean
    @ConfigurationProperties(prefix = "spring.datasource")
    public DataSource druidDataSource(){
        return new DruidDataSource();
    }

    @Bean
    public DataSourceProxy dataSourceProxy(DataSource dataSource) {
        return new DataSourceProxy(dataSource);
    }

    @Bean
    public SqlSessionFactory sqlSessionFactoryBean(DataSourceProxy dataSourceProxy) throws Exception {
        SqlSessionFactoryBean sqlSessionFactoryBean = new SqlSessionFactoryBean();
        sqlSessionFactoryBean.setDataSource(dataSourceProxy);
        sqlSessionFactoryBean.setMapperLocations(new PathMatchingResourcePatternResolver().getResources(mapperLocations));
        sqlSessionFactoryBean.setTransactionFactory(new SpringManagedTransactionFactory());
        return sqlSessionFactoryBean.getObject();
    }
}

主启动

/**
 * Created by KingsLanding on 2022/11/6 17:30
 */
@SpringBootApplication(exclude = DataSourceAutoConfiguration.class)//取消数据源的自动创建
@EnableDiscoveryClient
@EnableFeignClients
public class AlibabaSeataStorage2002 {
    public static void main(String[] args) {
        SpringApplication.run(AlibabaSeataStorage2002.class,args);
    }
}

服务调用方

@GlobalTransactional

实现微服务之间的事务管理

/**
 * Created by KingsLanding on 2022/11/6 15:58
 */
@Service
@Slf4j
public class OrderServiceImpl implements OrderService{
    
    //本地数据库
    @Resource
    private OrderMapper orderMapper;

    //库存微服务数据库
    @Resource
    private StorageService storageService;

    //账户微服务数据库
    @Resource
    private AccountService accountService;

    /**
     * 创建订单->调用库存服务扣减库存->调用账户服务扣减账户余额->修改订单状态
     * 简单说:
     * 下订单->减库存->减余额->改状态
     * name: 一个代号罢了
     */
    @Override
    @GlobalTransactional(name = "zmj-create-order",rollbackFor = Exception.class)
    public void create(Order order) {
        log.info("------->下单开始");
        //本应用创建订单
        orderMapper.create(order);

        //远程调用库存服务扣减库存
        log.info("------->order-service中扣减库存开始");
        storageService.decrease(order.getProductId(),order.getCount());
        log.info("------->order-service中扣减库存结束");

        //远程调用账户服务扣减余额
        log.info("------->order-service中扣减余额开始");
        accountService.decrease(order.getUserId(),order.getMoney());
        log.info("------->order-service中扣减余额结束");

        //修改订单状态为已完成
        log.info("------->order-service中修改订单状态开始");
        orderMapper.update(order.getUserId(),0);
        log.info("------->order-service中修改订单状态结束");

        log.info("------->下单结束");
    }
}