spring-cloud学习

Spring-cloud 以微服务为核心的解决方案的一套标准,Spring-cloud-alibaba是它的实现之一

微服务介绍

架构的演变

单体应用架构:

所有功能代码全部部署到一台服务器,模块紧密耦合,单点容错率低;

当访问量大的时候,这能基于整个应用做负载均衡,可能我们只是某一个点的访问量大,整个应用做负载均衡效率就没那么高

垂直应用架构:

把一个应用拆分成多个独立应用,系统实现了流量的分担,解决了高并发的问题,而且某个应用出现问题,也不会影响其它应用,

但是拆分出来的应用之间是相互独立的,无法进行相互调用,因为系统独立,所以有的功能需要重复开发

分布式架构:把公共的服务模块抽取出来,提高代码的复用性,

但是调用关系的错综复杂,系统间的耦合度变高。

SOA架构:

当分布式架构下的服务越来越多,调用关系错综复杂,包括不同的服务访问量不一样,造成资源不合理应用,所以就出现了SOA来用于资源调度和管理中心。比如某个服务访问量大,部署当前服务的集群,中间再添加一个nginx来做负载均衡。

SOA的核心就是使用治理中心就解决了服务之间相互调用的自动调节。强行绑定服务与治理中心

常见治理中心ESB:

当某个服务挂掉会自动把当前节点排除,避免异常,当服务正常运行的时候又把服务添加进去,而且在分布式架构下,可能我们不同的服务用的语言不通,用的通信协议不同,治理中心也可以帮我们实现协议的协调,达到协议的统一(个人理解应该是有一个中间协议,通过中间协议进行相互的转换)

用dubbo作为治理中心的时候就能解耦合,去中心化

但是服务之间会有依赖关系,某一个环节出问题,就会影响比较大,造成服务雪崩服务关系也复杂

服务雪崩

服务之间相互调用,调用链路中某一个节点卡死,整个调用链路就不能走下去,从而造成服务宕机

微服务架构:

更细粒度的服务拆分,比如把商品服务拆分成,普通服务,秒杀服务,等。服务独立打包,部署,升级。采用Restful等轻量级http协议进行相互调用

但是开发维护成本更高(分布式事务,分布式缓存,分布式锁),

分布式架构和微服务架构区别:

微服务架构也是分布式架构,但是它相比于分布式架构,它对服务的拆分更细腻化,而且有各种组件去管理和服务与各个服务

SOA与微服务架构的区别:

微服务服务的拆分也是更加精细,而且微服务架构中,每个服务独立部署,而且SOA架构中可能会数据库共享,微服务则强调每个服务单独的数据库降低了服务之间的耦合度,还有就是微服务的去中心化,使得服务之间的调用不完全依赖于治理中心,更加灵活;

SOA采用SOAP协议来进行传输,它是封装的webservice,基于xml来进行传输的,更加重量级,微服务采用Restful等轻量级http协议进行相互调用

微服务架构常见问题:
这么多的小服务,如何管理?

服务治理,注册中心用来注册服务,发现,剔除 nacos

这么多的小服务,服务之间如何通信?

Java提供了一个httpclient来进行http调用,而微服务使用Restful风格,也就是rest接口来进行http调用,因为restful风格是把数据转换成json的形式来进行传递的,而spring-boot中的restTemplate就能替我们完成json数据的序列化和反序列化(java对象和json之间的相互转换)

微服务提供了feign,像调用本地方法一样来进行远程调用

这么多的小服务,客户端如何进行访问?

网关进行路由,解决跨域的问题 gateway

这么多的小服务,一旦出现问题(服务雪崩),应该如何自动处理?

Sentinel,当调用链路中某个节点出现问题,我们就可以进行服务降级,也可以对调用节点进行记录到数据库,当服务节点正常的时候,我们再进行补偿,重新发送

这么多的小服务,一旦出现问题,如何快速定位?

Skywalking 来实现链路追踪,

常见微服务架构图

常见微服务架构
  1. dubbo:zookeeper+dubbo+springMVC/springBoot

配套 通信方式:rpc

注册中心:dubbo/redis

配置中心:diamond

  1. springCloud:全家桶+轻松嵌入的第三方组件(Netflix最早的微服务的实现,现在不再进行更新)

配套 通信方式:http restful

注册中心:eruka/consul

配置中心:config

断路器:hystrix

网关;zuul

分布式追踪系统:sleuth+zipkin

  1. spring-cloud-alibaba
Spring-cloud-alibaba和其他微服务实现的对比

Spring-boot 搭建分布式项目

Spring-cloud-alibaba微服务项目搭建-nacos作为注册中心

  1. 下载nacos,根据部署环境来下载,我准备搭建windos单机环境,所以是zip

Release 1.4.1 (Jan 15, 2021) · alibaba/nacos · GitHub

  1. 下载完成后解压,但是先不着急启动,因为nacos默认是集群模式,而集群是必须要进行持久化的,所以要配置数据库,为了方便,先给它改成单机模式

修改starup.cmd

if not exist "%JAVA_HOME%\bin\java.exe" echo Please set the JAVA_HOME variable in your environment, We need java(x64)! jdk8 or later is better! & EXIT /B 1
set "JAVA=%JAVA_HOME%\bin\java.exe"

setlocal enabledelayedexpansion

set BASE_DIR=%~dp0
rem added double quotation marks to avoid the issue caused by the folder names containing spaces.
rem removed the last 5 chars(which means \bin\) to get the base DIR.
set BASE_DIR="%BASE_DIR:~0,-5%"

set CUSTOM_SEARCH_LOCATIONS=file:%BASE_DIR%/conf/

set MODE="standalone"
  1. 如果启动时报错说JDK最少需要8,检查jdk环境是否配置为JAVA_HOME,而且不到/bin,这是脚本中定义好的
if not exist "%JAVA_HOME%\bin\java.exe" echo Please set the JAVA_HOME variable in your environment, We need java(x64)! jdk8 or later is better! & EXIT /B 1
  1. 分布式项目中引入依赖,因为我们父项目中引入了cloud-alibaba,所以不要版本
<dependency>
    <groupId>com.alibaba.cloud</groupId>
    <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>

  1. 修改配置文件
server:
  port: 8100
spring:
  cloud:
    nacos:
      discovery:
        server-addr: 127.0.0.1:8848
  application:
#服务名 必须配置 它是根据服务名来进行调用
    name: orderCloud

  1. 因为nacos是在客户端进行负载均衡的,所以它的消费者对提供者进行远程调用时,要使用负载均衡器,很简单,我们添加一个注解就可以了,不添加是进行不了远程调用的,找不到服务
@SpringBootApplication
public class OrderApplication {
    public static void main(String[] args) {
        SpringApplication.run(OrderApplication.class,args);
    }
    @Bean
    @LoadBalanced
    public RestTemplate restTemplate(RestTemplateBuilder builder){
        return builder.build();
    }
}

不添加报错

注:如果是nacos集群的话,需要再加一个nginx做反向代理,

Alibaba 微服务组件Nacos

集注册中心+配置中心+服务管理的一个平台

Nacos关键特性:

  1. 服务发现和服务的健康监测
  2. 动态配置服务
  3. 动态DNS服务
  4. 服务及服务元数据管理(包括url,名称等服务的信息)
NACOS注册中心

每个app或者服务都是nacos-client,nacos-server是一个独立的app

早期的nacos
  1. 我们的服务启动的时候,向nacos-server去注册服务,nacos去维护了一个服务列表,默认是放在缓存中的,也可以进行永久存储到数据库
  2. 每次进行远程调用的时候,就会去注册中心去拉取服务列表
  3. 根据拉取到的服务列表,选择一个服务地址去调用

nacos的升级

早期的nacos中,因为我们每次调用的时候要去拉取服务列表,而且当某个服务节点挂掉的时候,当我们远程调用的时候还是可能会调用到,所以后来引入心跳机制,来定时更新服务列表,而且客户端也会有定时拉取服务列表,而不是每次调用的时候拉取

  1. 核心就是心跳判断服务状态,然后事实接口进行注册与注销
  2. 服务列表也不是在调用的节点去获取,而是单独的定时任务拉取,对于拉取到的服务列表,通过负载nacos-client中的负载均衡器来选择一台进行访问;

Spring Cloud Alibaba Nacos Discovery

Spring-cloud-alibaba的服务注册于发现组件Spring Cloud Alibaba Nacos Discovery

Nacos discovery · alibaba/spring-cloud-alibaba Wiki · GitHub

 Nacos Discovery的核心功能总结:

服务注册:nacos-client会通过发送REST请求的方式想nacos-server注册自己的服务,并提供自己的元数据,ip,端口等,nacos-server接收到后,会进行存储

服务心跳:服务注册完成后,会维护一个心跳,来确保服务处于可用状态

服务同步:nacos-server集群会互相同步服务实例,保证服务信息的一致

服务发现:服务消费者在第一次进行远程调用时,会发送一个REST请求给nacos-server,获取服务列表,并在之后启动一个定时任务来拉取服务列表

服务健康检查:nacos-server会开启一定定时任务,来监测注册的服务健康情况,其实就是看是否正常收到心跳,如果一段时间没收到,健康状态发生改变,如果后续还是没有收到,则会注销服务

常见注册中心

微服务负载均衡器Ribbon

Nacos的客户端的负载均衡就是通过netflix的ribbon来做的

负载均衡:在集群的环境下,根据负载均衡策略,为某一个请求,合理的选择一台应用去调用;

目前负载均衡分两种方式:

集中式负载均衡:使用中间代理,单独在应用外做均衡,比如nginx,它就是要在nginx中配置各个服务,以及均衡策略。所以需要手动配置

Ribbon:客户端做负载均衡,也就是客户端也保存了服务提供者的数据,在当前应用做远程调用时,再根据客户端负载均衡策略进行选择;这种就会有自动注册的机制,不需要手动注册

负载均衡策略:

自定义负载均衡策略iruler

Spring-cloud官方负载均衡器 spring-cloud-LoadBanlenser

Feign

之前我们搭建的分布式架构,服务之间的远程调用使用的是resttemplate,这种我们就需要在代码中维护一个resttemplate,

Feign是netflix开发的声明式模板化的http客户端,它是声明在服务的消费端的,也带负载均衡

Spring-cloud官方提供了openfeign,对feign进行了增强,还整合了ribbon和nacos。

它可以像调用本地方法一样,来进行http请求的远程调用

feign的使用

1.引入依赖

<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>

  1. 创建接口,这里有点像mybatis,都是根据接口创建了代理对象
//name=服务名 path=controller路径
@FeignClient(name="stock-service",path="/stock")
public interface StockFeignService {
    @RequestMapping("/reduce")
    String reduce();
}
/*@RequestMapping("/stock")
public class StockController {
    @RequestMapping("/reduce")
    public String reduce(){
        return "库存减一";
    }
}
*/

  1. 开启feignClient
@SpringBootApplication
@EnableFeignClients
public class OrderApplication {
    public static void main(String[] args) {
        SpringApplication.run(OrderApplication.class,args);
    }
    @Bean
//    @LoadBalanced
    public RestTemplate restTemplate(RestTemplateBuilder builder){
        return builder.build();
    }
}

  1. 注入并调用、
@RestController
@RequestMapping("/order")
public class OrderController {
    @Autowired
    private RestTemplate restTemplate;
    @Autowired
    private StockFeignService stockFeignService;

    @RequestMapping("/add")
    public String add() {
//        String msg = restTemplate.getForObject("http://localhost:8001/stock/reduce", String.class);
        String msg = restTemplate.getForObject("http://stockCloud/stock/reduce", String.class);
        return "下单成功" + msg;
    }

  1. 测试

注册成功

调用成功

Feign自定义拦截器

它的拦截器是在发起服务调用的时候,而spring-mvc是在接收到服务调用的时候,性质不一样;一般是在请求头中添加某些参数;

Nacos-配置中心
Sentinel-主要用来解决服务雪崩的问题

Sentinel:alibaba开源的面向分布式服务架构的高可用防护组件

服务雪崩效应:因服务提供者不可用,导致服务消费者的不可用并把不可用逐渐放大的过程;

所以对于微服务,对流量的控制和容错的处理就很重要

容错机制

超时机制:最大请求时间限制,超过降级

服务限流:对qps进行限制,超过降级

隔离:用线程池的空闲线程来访问服务,当线程池满了,则进行降级。可以为每一个服务限制可以访问的数量,

服务熔断:与超时相似,都是当流量过大,对服务进行降级,但是超时是在服务提供方实现的,而熔断是直接停用服务提供方,它的降级处理是在消费方实现的;

强依赖:整个业务流程中,不可或缺的一个点

弱依赖:一些附加服务,比如下单中的积分服务,它挂掉,还是能进行下单,这个时候进行降级处理,把当前请求,存起来,后期用定时任务等做补偿

服务的降级一般是在弱依赖中去做,

流控组件Sentinel和hystrix比较

sentinel的使用

Sentinel是一个分布式的流控组件,不一定要是微服务架构,可以单独使用,所以基于spring-boot开发的项目就可以使用

java代码来实现,大致分为三步

  1. 定义资源名
  2. 为资源配置流控规则,并把流控规则添加进流控管理器中
  3. 在流控点进行使用

依赖

<dependency>
    <groupId>com.alibaba.csp</groupId>
    <artifactId>sentinel-core</artifactId>
    <version>${sentinel-core.version}</version>
</dependency>

@RestController
@RequestMapping("/stock")
@Slf4j
public class StockController {
    //1.定义资源名称 根据资源来进行流控的
    private static final String RESOURCE_NAME="stock";
    @RequestMapping("/reduce")
    public String reduce(){
        Entry entry = null;
        try {
            //3.设置流控点 这里进行流控 下面则是被保护的业务
            entry = SphU.entry(RESOURCE_NAME);
            String s="库存减一";
            System.out.println("----------------"+s+"----------------");
            return s;
        } catch (BlockException e) {
            System.out.println("被流控了");
            return "被流控了";
        }finally {
            if(entry!=null){
                entry.exit();
            }
        }
    }


    /**
     * 2.为我们的资源进行流控规则的配置
     */
    //在spring 初始化完成后的后置处理器做的
    @PostConstruct
    private static void initFlowRules(){
        //放流控规则
        ArrayList<FlowRule> rules = new ArrayList<>();
        FlowRule flowRule = new FlowRule();
        //为哪个资源进行流控
        flowRule.setResource(RESOURCE_NAME);
        //流控规则 这里是qps
        flowRule.setGrade(RuleConstant.FLOW_GRADE_QPS);
        //qps设置为1 阈值为1
        flowRule.setCount(1);
        rules.add(flowRule);

        //把定义好的流控规则加载到流控管理器
        FlowRuleManager.loadRules(rules);
    }
}

进行测试,

当qps小于1

当qps大于1

通过@SentinelResource注解来实现

额外添加依赖

<dependency>
    <groupId>com.alibaba.csp</groupId>
    <artifactId>sentinel-annotation-aspectj</artifactId>
    <version>${sentinel-annotation-aspectj.version}</version>
</dependency>

代码

/**
 * 2.为我们的资源进行流控规则的配置
 */
//在spring 初始化完成后的后置处理器做的
@PostConstruct
private static void initFlowRules(){
    //放流控规则
    ArrayList<FlowRule> rules = new ArrayList<>();
    FlowRule flowRule = new FlowRule();
    //为哪个资源进行流控
    flowRule.setResource(RESOURCE_NAME);
    //流控规则 这里是qps
    flowRule.setGrade(RuleConstant.FLOW_GRADE_QPS);
    //qps设置为1 阈值为1
    flowRule.setCount(1);
    rules.add(flowRule);
    FlowRule flowRule2 = new FlowRule();
    //为哪个资源进行流控
    flowRule2.setResource(USER_RESOURCE_NAME);
    //流控规则 这里是qps
    flowRule2.setGrade(RuleConstant.FLOW_GRADE_QPS);
    //qps设置为1 阈值为1
    flowRule2.setCount(1);
    rules.add(flowRule2);
    //把定义好的流控规则加载到流控管理器
    FlowRuleManager.loadRules(rules);
}

@RequestMapping("/reduce2")
/**
 * fallback 当出现异常的时候进行处理 就是catch时不是 BlockException
 * blockHandler 流控时的异常处理 就是我们之前针对catch到的BlockException 进行的处理
 */
@SentinelResource(value = USER_RESOURCE_NAME,  fallback = "fallBackMethod",blockHandler = "blockMethod")
public String reduce2(){
    String s="库存减一";
    //int i=1/0;
    System.out.println("----------------"+s+"----------------");
    return s;
}

public  String  fallBackMethod(){
    return "异常处理";
}

public  String  blockMethod(){
    return "流控处理";
}

当然也可以通过sentinel的http客户端去设置,具体不做记录,对于分布式架构的程序,http客户端也可以引入,不过就是在启动的时候,需要添加一些jvm参数,如果是spring-cloud微服务,则可以直接在配置文件中配置;

Seata-分布式事务

Seata,一款开源的分布式事务的解决方案,致力于提供高性能和简单易用的分布式事务服务;

事务的四大特性-ACID

原子性:事务中的操作要么都成功,要么都回滚

一致性:事务必须是从一个一致性状态,变成另一个一致性状态,事物的中间状态不能出现

隔离性:事务之间,事务内部的操作以及数据是隔离的,

持久性:事务提交后,就会改变数据库中的值

本地事务

在我们的单体应用中,比如对订单和库存的操作,我们可以用同一个连接来做,当全部操作完成之后我们再进行提交,spring的@Transaction就是如此,它提前关闭了默认的自动提交的配置,在开始之前就获取了连接,结束后要么集体提交,要么集体回滚;

分布式事务

分布式事务的两种场景

  1. 同一个服务在同一个事务中操作了不同的数据库,因为事务的本质是基于数据库连接connection,不同的数据库的连接不同,自然用本地事务就不能解决;
  2. 在微服务中,多个服务进行操作时,比如订单接口和库存接口从业务上来讲是同一个事务,但是因为不同的服务,虽然可能是同一个数据库,但是连接也是不同,用本地事务还是不能解决
分布式事务的事务模式

AT(auto transaction)

无侵入的分布式事务解决方案,会拦截sql的执行,将操作的数据先拿出来,保存到快照中,然后执行sql,更新数据,然后加上行锁,其他事务不能对其读写操作,事务在DB层完成。

二阶段提交

二阶段回滚

TCC

事务在业务层中完成,没有锁的概念,侵入性强,并且要实现相关事务逻辑,

MQ可靠消息最终一致性,利用消息中间件,最开始发送预备消息,然后后续进行前置数据库处理的状态,来更新中间件中预备消息的状态,只有当前置数据库处理成功时,预备消息状态改变为前置成功,则可以让中间件去通知后续处理,来保证最终消息的一致性;这种和tcc相似也可以结合使用

SAGA

XA

常见的分布式事务解决方案
  1. seata阿里分布式框架
  2. 消息队列
  3. Saga
  4. XA

他们都有一个共同点,都是两阶段(2PC),两阶段是指完成整个分布式事务,分成两个步骤进行完成。

这四种分布式解决方案就对应着分布式事务的四种模式,AT,TCC,SAGA,XA

两阶段提交过程

  1. 事务的请求首先到事务的协调者,协调者同意发送请求到事务的参与者,去做预处理,并把undo回滚和redo提交操作或者数据进行记录
  2. 等待所有的参与者返回一个应答,到底是可以执行还是不可以执行,等收到所有的应答者的ack后,如果都为yes,那么则通知参与者进行提交,只要有一个为no则进行回滚,参与者收到回滚请求的时候,使用之前记录的undo日志进行回滚
  3. 不管是提交还是回滚,所有参与者完成后都会向协调者发送响应,来反馈提交或者回滚完成,当收到全部参与者的ack后,事务结束;
两阶段存在的问题

Seta三大角色

TC-事务协调者:维护全局和分支事务的状态,以驱动事务管理者提交全局事务或者回滚

TM-事务管理者:开启,提交或者回滚全局事务

RM-资源管理者:管理分支事务资源,与TC交互来注册分支事务,并反馈分支事务状态,驱动TM分支事务的提交或回滚

Seta的TC需要单独部署,TM和RM是由业务端集成的;

Seta的使用

Seata Server(TC)环境搭建

新人文档 | Apache Seata

步骤一:下载安装包

Releases · apache/incubator-seata · GitHub

打开config/file.conf   

修改mode="db"

修改数据库连接信息(URL\USERNAME\PASSWORD)

创建数据库seata_server

新建表:   可以去seata提供的资源信息中下载:

incubator-seata/script at 1.4.0 · apache/incubator-seata · GitHub

\script\server\db\mysql.sql

branch 表  存储事务参与者的信息

store { mode = "db" db { ## the implement of javax.sql.DataSource, such as DruidDataSource(druid)/BasicDataSource(dbcp)/HikariDataSource(hikari) etc. datasource = "druid" ## mysql/oracle/postgresql/h2/oceanbase etc. dbType = "mysql" driverClassName = "com.mysql.jdbc.Driver" url = "jdbc:mysql://192.168.65.220:3306/seata_server" user = "root" password = "123456" minConn = 5 maxConn = 30 globalTable = "global_table" branchTable = "branch_table" lockTable = "lock_table" queryLimit = 100 maxWait = 5000 } } 

redis:Seata-Server 1.3及以上版本支持,性能较高,存在事务信息丢失风险,请提前配置适合当前场景的redis持久化配置

资源目录:https://github.com/seata/seata/tree/1.3.0/script

Client

存放client端sql脚本,参数配置

config-center

各个配置中心参数导入脚本,config.txt(包含server和client,原名nacos-config.txt)为通用参数文件

Server

server端数据库脚本及各个容器配置

db存储模式+Nacos(注册&配置中心)部署

步骤五:配置Nacos注册中心 负责事务参与者(微服务) 和TC通信

将Seata Server注册到Nacos,修改conf目录下的registry.conf配置

然后启动注册中心Nacos Server

#进入Nacos安装目录,linux单机启动

 bin/startup.sh -m standalone

# windows单机启动

bin/startup.bat

步骤六:配置Nacos配置中心

注意:如果配置了seata server使用nacos作为配置中心,则配置信息会从nacos读取,file.conf可以不用配置。 客户端配置registry.conf使用nacos时也要注意group要和seata server中的group一致,默认group是"DEFAULT_GROUP"

获取/seata/script/config-center/config.txt,修改配置信息

配置事务分组, 要与客户端配置的事务分组一致

#my_test_tx_group需要与客户端保持一致 default需要跟客户端和registry.conf中registry中的cluster保持一致

(客户端properties配置:spring.cloud.alibaba.seata.tx‐service‐group=my_test_tx_group)

事务分组: 异地机房停电容错机制

my_test_tx_group 可以自定义 比如:(guangzhou、shanghai...) , 对应的client也要去设置

seata.service.vgroup-mapping.projectA=guangzhou

default 必须要等于 registry.confi cluster = "default"

配置事务分组, 要与客户端配置的事务分组一致

#my_test_tx_group需要与客户端保持一致 default需要跟客户端和registry.conf中registry中的cluster保持一致

(客户端properties配置:spring.cloud.alibaba.seata.tx‐service‐group=my_test_tx_group)

事务分组: 异地机房停电容错机制

my_test_tx_group 可以自定义 比如:(guangzhou、shanghai...) , 对应的client也要去设置

seata.service.vgroup-mapping.projectA=guangzhou

default 必须要等于 registry.confi cluster = "default"

配置参数同步到Nacos

shell:

sh ${SEATAPATH}/script/config-center/nacos/nacos-config.sh -h localhost -p 8848 -g SEATA_GROUP -t 5a3c7d6c-f497-4d68-a71a-2e5e3340b3ca

参数说明:

-h: host,默认值 localhost

-p: port,默认值 8848

-g: 配置分组,默认值为 'SEATA_GROUP'

-t: 租户信息,对应 Nacos 的命名空间ID字段, 默认值为空 ''

精简配置

service.vgroupMapping.my_test_tx_group=default service.default.grouplist=127.0.0.1:8091 service.enableDegrade=false service.disableGlobalTransaction=false store.mode=db store.db.datasource=druid store.db.dbType=mysql store.db.driverClassName=com.mysql.jdbc.Driver store.db.url=jdbc:mysql://127.0.0.1:3306/seata?useUnicode=true store.db.user=root store.db.password=root store.db.minConn=5 store.db.maxConn=30 store.db.globalTable=global_table store.db.branchTable=branch_table store.db.queryLimit=100 store.db.lockTable=lock_table store.db.maxWait=5000

步骤七:启动Seata Server

  • 源码启动: 执行server模块下io.seata.server.Server.java的main方法
  • 命令启动: bin/seata-server.sh -h 127.0.0.1 -p 8091 -m db -n 1 -e test

bin/seata-server.sh -p 8091 -n 1

bin/seata-server.sh -p 8092 -n 2 

bin/seata-server.sh -p 8093 -n 3 

启动成功,默认端口8091

在注册中心中可以查看到seata-server注册成功

Seata Client快速开始

声明式事务实现(@GlobalTransactional)

接入微服务应用

业务场景:

用户下单,整个业务逻辑由三个微服务构成:

  • 订单服务:根据采购需求创建订单。
  • 库存服务:对给定的商品扣除库存数量。

1)启动Seata server端,Seata server使用nacos作为配置中心和注册中心(上一步已完成)

2)配置微服务整合seata

第一步:添加pom依赖

<!-- seata--> <dependency>

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

第二步: 各微服务对应数据库中添加undo_log表

CREATE TABLE `undo_log` ( `id` bigint(20) NOT NULL AUTO_INCREMENT, `branch_id` bigint(20) NOT NULL, `xid` varchar(100) NOT NULL, `context` varchar(128) NOT NULL, `rollback_info` longblob NOT NULL, `log_status` int(11) NOT NULL, `log_created` datetime NOT NULL, `log_modified` datetime NOT NULL, PRIMARY KEY (`id`), UNIQUE KEY `ux_undo_log` (`xid`,`branch_id`) ) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8;

第五步:修改register.conf,配置nacos作为registry.type&config.type,对应seata server也使用nacos

注意:需要指定group = "SEATA_GROUP",因为Seata Server端指定了group = "SEATA_GROUP" ,必须保证一致

registry { # file 、nacos 、eureka、redis、zk、consul、etcd3、sofa type = "nacos" nacos { serverAddr = "localhost" namespace = "" cluster = "default" group = "SEATA_GROUP" } } config { # file、nacos 、apollo、zk、consul、etcd3、springCloudConfig type = "nacos" nacos { serverAddr = "localhost" namespace = "" group = "SEATA_GROUP" } }

如果出现这种问题:

一般大多数情况下都是因为配置不匹配导致的:

1.检查现在使用的seata服务和项目maven中seata的版本是否一致

2.检查tx-service-group,nacos.cluster,nacos.group参数是否和Seata Server中的配置一致

跟踪源码:seata/discover包下实现了RegistryService#lookup,用来获取服务列

NacosRegistryServiceImpl#lookup 》String clusterName = getServiceGroup(key); #获取seata server集群名称 》List<Instance> firstAllInstances = getNamingInstance().getAllInstances(getServiceName(), getServiceGroup(), clusters)

第六步:修改application.yml配置

配置seata 服务事务分组,要与服务端nacos配置中心中service.vgroup_mapping的后缀对应

server: port: 8020 spring: application: name: order-service cloud: nacos: discovery: server-addr: 127.0.0.1:8848 alibaba: seata: tx-service-group: my_test_tx_group # seata 服务事务分组 datasource: type: com.alibaba.druid.pool.DruidDataSource druid: driver-class-name: com.mysql.cj.jdbc.Driver url: jdbc:mysql://localhost:3306/seata_order?useUnicode=true&characterEncoding=UTF-8&serverTimezone=Asia/Shanghai username: root password: root initial-size: 10 max-active: 100 min-idle: 10 max-wait: 60000 pool-prepared-statements: true max-pool-prepared-statement-per-connection-size: 20 time-between-eviction-runs-millis: 60000 min-evictable-idle-time-millis: 300000 test-while-idle: true test-on-borrow: false test-on-return: false stat-view-servlet: enabled: true url-pattern: /druid/* filter: stat: log-slow-sql: true slow-sql-millis: 1000 merge-sql: false wall: config: multi-statement-allow: true

第七步:微服务发起者(TM 方)需要添加@GlobalTransactional注解

 @Override
//@Transactional
    @GlobalTransactional(name="createOrder")
    public Order saveOrder(OrderVo orderVo){
        log.info("=============用户下单=================");
        log.info("当前 XID: {}", RootContext.getXID());

        // 保存订单
        Order order = new Order();
        order.setUserId(orderVo.getUserId());
        order.setCommodityCode(orderVo.getCommodityCode());
        order.setCount(orderVo.getCount());
        order.setMoney(orderVo.getMoney());
        order.setStatus(OrderStatus.INIT.getValue());

        Integer saveOrderRecord = orderMapper.insert(order);
        log.info("保存订单{}", saveOrderRecord > 0 ? "成功" : "失败");

        //扣减库存
        storageFeignService.deduct(orderVo.getCommodityCode(),orderVo.getCount());

        //扣减余额
        accountFeignService.debit(orderVo.getUserId(),orderVo.getMoney());

        //更新订单
        Integer updateOrderRecord = orderMapper.updateOrderStatus(order.getId(),OrderStatus.SUCCESS.getValue());
        log.info("更新订单id:{} {}", order.getId(), updateOrderRecord > 0 ? "成功" : "失败");

        return order;

    }

}

测试

分布式事务成功,模拟正常下单、扣库存,扣余额

分布式事务失败,模拟下单扣库存成功、扣余额失败,事务是否回滚

  1. TM请求TC服务端开启一个全局事务,TC会生成一个XID作为该全局事务的编号,XID会在微服务的调用链路中传播,保证将微服务中的多个子事务关联在一起;

当已进入事务方法,就会生成XID,global_table救赎存储全局事务信息

  1. RM请求TC会将本地事务注册为全局事务的分支事务,通过XID关联,

当运行数据库操作方法,branch_table存储事务参与者

  1. TM请求TC,高数XID对应的全局事务是进行提交还是回滚
  2. TC驱动RM们,将XID对应对事务进行提交还是回滚
  • 11
    点赞
  • 23
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值