springcloud alibaba -- seata原理和使用

news2024/12/23 16:24:19

文章目录

  • 一、认识Seata
    • 1.1 Seata 是什么?
    • 1.2 了解AT、TCC、SAGA事务模式?
      • AT 模式
        • 前提
        • 整体机制
        • 如何实现写隔离
        • 如何实现读隔离
      • TCC 模式
      • Saga 模式
        • Saga 模式适用场景
        • Saga 模式优势
        • Saga 模式缺点
  • 二、Seata安装
    • 2.1 下载
    • 2.2 创建所需数据表
        • 2.2.1 创建 分支表、全局表、锁表
        • 2.2.2 创建 UNDO_LOG 表
    • 2.3 修改配置文件
        • 2.3.1 修改 registry.conf 文件
        • 2.3.2 修改 file.conf 文件
    • 2.4 启动seata
  • 三、Seata的应用
    • 3.1 springcloud项目整合seata
        • 3.1.1 服务架构
        • 3.1.2 创建仓储服务
        • 3.1.3 创建仓储和订单数据库及数据表
        • 3.1.4 仓储服务相关实现
        • 3.1.5 订单服务相关实现
    • 3.2 模拟异常测试分布式事务
        • 3.2.1 测试正常流程
        • 3.2.2 模拟失败流程
        • 3.2.2 添加分布式事务注解

一、认识Seata

1.1 Seata 是什么?

Seata 是一款开源的分布式事务解决方案,致力于提供高性能和简单易用的分布式事务服务。Seata 将为用户提供了 AT、TCC、SAGA 和 XA 事务模式,为用户打造一站式的分布式解决方案。

1.2 了解AT、TCC、SAGA事务模式?

AT 模式

前提

  • 基于支持本地 ACID 事务的关系型数据库。
  • Java 应用,通过 JDBC 访问数据库。

整体机制

两阶段提交协议的演变:

  • 一阶段:业务数据和回滚日志记录在同一个本地事务中提交,释放本地锁和连接资源。
  • 二阶段:
    提交异步化,非常快速地完成。
    回滚通过一阶段的回滚日志进行反向补偿。

如何实现写隔离

过程:

  • 一阶段本地事务提交前,需要确保先拿到 全局锁
  • 拿不到 全局锁 ,不能提交本地事务。
  • 全局锁 的尝试被限制在一定范围内,超出范围将放弃,并回滚本地事务,释放本地锁。

举个栗子:
两个全局事务 tx1 和 tx2,分别对 a 表的 m 字段进行更新操作,m 的初始值 1000。

tx1 先开始,开启本地事务,拿到本地锁,更新操作 m = 1000 - 100 = 900。本地事务提交前,先拿到该记录的 全局锁 ,本地提交释放本地锁。 tx2 后开始,开启本地事务,拿到本地锁,更新操作 m = 900 - 100 = 800。本地事务提交前,尝试拿该记录的 全局锁 ,tx1 全局提交前,该记录的全局锁被 tx1 持有,tx2 需要重试等待 全局锁 。

下面来看下官方的两张图来加深下理解:
在这里插入图片描述
tx1 二阶段全局提交,释放 全局锁 。tx2 拿到 全局锁 提交本地事务。
在这里插入图片描述
如果 tx1 的二阶段全局回滚,则 tx1 需要重新获取该数据的本地锁,进行反向补偿的更新操作,实现分支的回滚。

此时,如果 tx2 仍在等待该数据的 全局锁,同时持有本地锁,则 tx1 的分支回滚会失败。分支的回滚会一直重试,直到 tx2 的 全局锁 等锁超时,放弃 全局锁 并回滚本地事务释放本地锁,tx1 的分支回滚最终成功。

因为整个过程 全局锁 在 tx1 结束前一直是被 tx1 持有的,所以不会发生 脏写 的问题。

如何实现读隔离

在数据库本地事务隔离级别 读已提交(Read Committed) 或以上的基础上,Seata(AT 模式)的默认全局隔离级别是 读未提交(Read Uncommitted) 。

如果应用在特定场景下,必需要求全局的 读已提交 ,目前 Seata 的方式是通过 SELECT FOR UPDATE 语句的代理。

见官方图:
在这里插入图片描述
SELECT FOR UPDATE 语句的执行会申请 全局锁 ,如果 全局锁 被其他事务持有,则释放本地锁(回滚 SELECT FOR UPDATE 语句的本地执行)并重试。这个过程中,查询是被 block 住的,直到 全局锁 拿到,即读取的相关数据是 已提交 的,才返回。

出于总体性能上的考虑,Seata 目前的方案并没有对所有 SELECT 语句都进行代理,仅针对 FOR UPDATE 的 SELECT 语句。

TCC 模式

一个分布式的全局事务,整体是 两阶段提交 的模型。全局事务是由若干分支事务组成的,分支事务要满足 两阶段提交 的模型要求,即需要每个分支事务都具备自己的:

  • 一阶段 prepare 行为
  • 二阶段 commit 或 rollback 行为

TCC 模式,不依赖于底层数据资源的事务支持:

  • 一阶段 prepare 行为:调用 自定义 的 prepare 逻辑。
  • 二阶段 commit 行为:调用 自定义 的 commit 逻辑。
  • 二阶段 rollback 行为:调用 自定义 的 rollback 逻辑。

所谓 TCC 模式,是指支持把 自定义 的分支事务纳入到全局事务的管理中。

Saga 模式

Saga模式是SEATA提供的长事务解决方案,在Saga模式中,业务流程中每个参与者都提交本地事务,当出现某一个参与者失败则补偿前面已经成功的参与者,一阶段正向服务和二阶段补偿服务都由业务开发实现。
官方图:
在这里插入图片描述

Saga 模式适用场景

  • 业务流程长、业务流程多
  • 参与者包含其它公司或遗留系统服务,无法提供 TCC 模式要求的三个接口

Saga 模式优势

  • 一阶段提交本地事务,无锁,高性能
  • 事件驱动架构,参与者可异步执行,高吞吐
  • 补偿服务易于实现

Saga 模式缺点

  • 不保证隔离性

二、Seata安装

2.1 下载

下载地址:

  • https://github.com/seata/seata/releases
  • https://github.com/seata/seata/releases/download/v1.4.2/seata-server-1.4.2.zip

我这边下载的是v1.4.2版本,大家下载时需要注意下seata版本和springcloud alibaba的版本,根据自己的alibaba的版本选择对应的seata

给大家贴出组件版本关系对应:
在这里插入图片描述

2.2 创建所需数据表

2.2.1 创建 分支表、全局表、锁表

首先需要创建 分支表、全局表、锁表
创建sql如下

-- -------------------------------- The script used when storeMode is 'db' --------------------------------
-- the table to store GlobalSession data
CREATE TABLE IF NOT EXISTS `global_table`
(
    `xid`                       VARCHAR(128) NOT NULL,
    `transaction_id`            BIGINT,
    `status`                    TINYINT      NOT NULL,
    `application_id`            VARCHAR(32),
    `transaction_service_group` VARCHAR(32),
    `transaction_name`          VARCHAR(128),
    `timeout`                   INT,
    `begin_time`                BIGINT,
    `application_data`          VARCHAR(2000),
    `gmt_create`                DATETIME,
    `gmt_modified`              DATETIME,
    PRIMARY KEY (`xid`),
    KEY `idx_gmt_modified_status` (`gmt_modified`, `status`),
    KEY `idx_transaction_id` (`transaction_id`)
) ENGINE = InnoDB
  DEFAULT CHARSET = utf8;

-- the table to store BranchSession data
CREATE TABLE IF NOT EXISTS `branch_table`
(
    `branch_id`         BIGINT       NOT NULL,
    `xid`               VARCHAR(128) NOT NULL,
    `transaction_id`    BIGINT,
    `resource_group_id` VARCHAR(32),
    `resource_id`       VARCHAR(256),
    `branch_type`       VARCHAR(8),
    `status`            TINYINT,
    `client_id`         VARCHAR(64),
    `application_data`  VARCHAR(2000),
    `gmt_create`        DATETIME(6),
    `gmt_modified`      DATETIME(6),
    PRIMARY KEY (`branch_id`),
    KEY `idx_xid` (`xid`)
) ENGINE = InnoDB
  DEFAULT CHARSET = utf8;

-- the table to store lock data
CREATE TABLE IF NOT EXISTS `lock_table`
(
    `row_key`        VARCHAR(128) NOT NULL,
    `xid`            VARCHAR(128),
    `transaction_id` BIGINT,
    `branch_id`      BIGINT       NOT NULL,
    `resource_id`    VARCHAR(256),
    `table_name`     VARCHAR(32),
    `pk`             VARCHAR(36),
    `gmt_create`     DATETIME,
    `gmt_modified`   DATETIME,
    PRIMARY KEY (`row_key`),
    KEY `idx_branch_id` (`branch_id`)
) ENGINE = InnoDB
  DEFAULT CHARSET = utf8;

2.2.2 创建 UNDO_LOG 表

SEATA AT 模式需要 UNDO_LOG 表
创建sql如下:

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,
  `ext` varchar(100) DEFAULT NULL,
  PRIMARY KEY (`id`),
  UNIQUE KEY `ux_undo_log` (`xid`,`branch_id`)
) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8;

在这里插入图片描述

2.3 修改配置文件

2.3.1 修改 registry.conf 文件

conf目录下找到registry.conf文件
在这里插入图片描述
首先将type类型改为 nacos,type默认file (这种方式需要把源码的file.conf文件复制到项目中,比较麻烦不推荐) ,然后修改seata的注册中心的相关配置
在这里插入图片描述

其次修改seata的配置中心的相关配置,同样type类型改为nacos

在这里插入图片描述

2.3.2 修改 file.conf 文件

下面还需要修改seata的DB类型

我们在conf目录下找到file.conf文件
mode类型改为db
然后修改自己的数据库配置
在这里插入图片描述

2.4 启动seata

在bin目录下找到 seata-server.bat 双击启动
在这里插入图片描述

看到日志输出 Server started 应该就启动成功了
在这里插入图片描述
查看nacos注册中心
观察服务列表,发现seata服务已经成功注册
在这里插入图片描述

对nacos还不了解的可以看这里

三、Seata的应用

3.1 springcloud项目整合seata

我们简单模拟下用户从下单到扣减库存的流程,来看看seata在项目中是如何应用的

3.1.1 服务架构

先看下我的项目的整体模块架构
在这里插入图片描述
springcloud版本

	<properties>
        <maven.compiler.source>8</maven.compiler.source>
        <maven.compiler.target>8</maven.compiler.target>
        <spring-cloud.version>2021.0.1</spring-cloud.version>
        <spring-cloud-alibaba.version>2021.0.1.0</spring-cloud-alibaba.version>
    </properties>

因为我的项目中已经有order的相关服务了, 为了故事的延续性我在建一个仓储的服务用来扣减库存

想参考我的项目架构的同学可以点击下面的地址
mdx-shop gitee地址

3.1.2 创建仓储服务

创建一个maven模块
在这里插入图片描述

为服务添加启动类配置文件和seata依赖等

seata依赖

		<dependency>
            <groupId>com.alibaba.cloud</groupId>
            <artifactId>spring-cloud-starter-alibaba-seata</artifactId>
            <version>${spring-cloud-alibaba.version}</version>
        </dependency>

仓储服务application.yml文件

server:
  port: 9092

spring:
  application:
    name: mdx-shop-storage
  cloud:
    nacos:
      discovery:
        server-addr: localhost:8848
        namespace: mdx
        group: mdx

  datasource:
    type: com.alibaba.druid.pool.DruidDataSource
    url: jdbc:mysql://localhost:3306/mdx_storage?autoRec&useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8
    driverClassName: com.mysql.cj.jdbc.Driver
    username: root
    password: Bendi+Ceshi+

  jpa:
    show-sql: true #打印执行的sql语句,false则不打印sql
    properties:
      hibernate:
        ddl-auto: none
        dialect: org.hibernate.dialect.MySQL5InnoDBDialect
    open-in-view: true

seata:
  tx-service-group: my_test_tx_group
  enabled: true
  registry:
    type: nacos
    nacos:
      application: mdx-seata-server  #注册在nacos服务名
      server-addr: localhost:8848
      group : mdx
      namespace: mdx #注册在nacos命名空间



3.1.3 创建仓储和订单数据库及数据表

我们为仓储服务和订单服务分别创建数据库
数据库自己提前建好

// 仓储
DROP TABLE IF EXISTS `storage_tbl`;
CREATE TABLE `storage_tbl`  (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `commodity_code` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
  `count` int(11) NULL DEFAULT 0,
  PRIMARY KEY (`id`) USING BTREE,
  UNIQUE INDEX `commodity_code`(`commodity_code`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 2 CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Dynamic;

-- ----------------------------
-- Records of storage_tbl
-- ----------------------------
INSERT INTO `storage_tbl` VALUES (1, 'S123434455666777', 10);

// 订单
DROP TABLE IF EXISTS `order_tbl`;
CREATE TABLE `order_tbl` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `user_id` varchar(255) DEFAULT NULL,
  `commodity_code` varchar(255) DEFAULT NULL,
  `count` int(11) DEFAULT 0,
  `money` int(11) DEFAULT 0,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

3.1.4 仓储服务相关实现

这里只贴出仓储服务的主要几个方法,具体的项目结构可以参考 https://gitee.com/Ji_Agang/mdx-shop

对于数据库的操作我们使用Jpa来实现

依赖参考

		<dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-jpa</artifactId>
        </dependency>

启动类
注意启动类一定要加 @EnableAutoDataSourceProxy 注解,来开启数据源代理

/**
 * @author : jiagang
 * @date : Created in 2022/7/1 11:25
 */
@SpringBootApplication
@EnableFeignClients
@EnableAutoDataSourceProxy
public class MdxShopStorageApplication {
    public static void main(String[] args) {
        SpringApplication.run(MdxShopStorageApplication.class, args);
    }
}

controller

/**
 * @author : jiagang
 * @date : Created in 2022/7/1 18:42
 */
@RestController
@RequestMapping("/storage")
public class StorageController {

    @Autowired
    private StorageService service;

    @GetMapping("/deduct")
    public CommonResponse deduct(String commodityCode, int count){
        service.deduct(commodityCode, count);
        return CommonResponse.success();
    }
}

接口

/**
 * @author : jiagang
 * @date : Created in 2022/7/1 18:40
 */
public interface StorageService {

    /**
     * 扣除存储数量
     */
    void deduct(String commodityCode, int count);
}

实现类

/**
 * @author : jiagang
 * @date : Created in 2022/7/1 18:42
 */
@Service
public class StorageServiceImpl implements StorageService {

    @Autowired
    private StorageRepository storageRepository;

    /**
     * 扣减库存
     * @param commodityCode
     * @param count
     */
    @Override
    public void deduct(String commodityCode, int count) {
        StorageTbl storageTbl = storageRepository.findByCommodityCode(commodityCode);
        if (storageTbl == null){
            throw new BizException("storageTbl is null");
        }

        // 这里先不考虑超卖的情况
        storageTbl.setCount(storageTbl.getCount() - count);
        // 使用jpa 存在就更新
        storageRepository.save(storageTbl);
    }
}

数据层

/**
 * @author : jiagang
 * @date : Created in 2023/1/16 15:44
 */
@Repository
public interface StorageRepository extends JpaRepository<StorageTbl,Integer> {

    /**
     * 通过商品code查询库存
     * @param commodityCode
     * @return
     */
    @Query
    StorageTbl findByCommodityCode(String commodityCode);
}

3.1.5 订单服务相关实现

这里只贴出订单服务的主要几个方法,具体的项目结构可以参考 https://gitee.com/Ji_Agang/mdx-shop

对于数据库的操作我们同样使用Jpa来实现

application.yml 配置文件

server:
  port: 9091

spring:
  application:
    name: mdx-shop-order
  cloud:
    nacos:
      discovery:
        server-addr: localhost:8848
        namespace: mdx
        group: mdx

  datasource:
    type: com.alibaba.druid.pool.DruidDataSource
    url: jdbc:mysql://localhost:3306/mdx_order?autoRec&useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8
    driverClassName: com.mysql.cj.jdbc.Driver
    username: root
    password: Bendi+Ceshi+

  jpa:
    show-sql: true #打印执行的sql语句,false则不打印sql
    properties:
      hibernate:
        ddl-auto: none
        dialect: org.hibernate.dialect.MySQL5InnoDBDialect
    open-in-view: true

seata:
  tx-service-group: my_test_tx_group
  enabled: true
  registry:
    type: nacos
    nacos:
      application: mdx-seata-server  #注册在nacos服务名
      server-addr: localhost:8848
      group : mdx
      namespace: mdx #注册在nacos命名空间

feign:
  sentinel:
    enabled: true



启动类
注意启动类一定要加 @EnableAutoDataSourceProxy 注解,来开启数据源代理

/**
 * @author : jiagang
 * @date : Created in 2022/7/1 11:25
 */
@SpringBootApplication
@EnableFeignClients
@EnableAutoDataSourceProxy
public class MdxShopOrderApplication {
    public static void main(String[] args) {
        SpringApplication.run(MdxShopOrderApplication.class, args);
    }
}

controller

/**
 * @author : jiagang
 * @date : Created in 2022/7/1 18:42
 */
@RestController
@RequestMapping("/order")
public class OrderController {

    @Autowired
    private OrderService orderService;

    /**
     * 用户下单接口
     * @param userId
     * @param commodityCode
     * @return
     */
    @PostMapping("createOrder")
    public CommonResponse<String> createOrder(String userId, String commodityCode){
        return CommonResponse.success(orderService.createOrder(userId,commodityCode));
    }
}

接口

/**
 * @author : jiagang
 * @date : Created in 2022/7/1 18:40
 */
public interface OrderService {

    /**
     * 下单接口
     * @param userId 用户id
     * @param commodityCode 商品代码
     * @return
     */
    String createOrder(String userId, String commodityCode);
    
}

实现类

/**
 * @author : jiagang
 * @date : Created in 2022/7/1 18:42
 */
@Service
public class OrderServiceImpl implements OrderService {

    @Resource
    private OrderRepository orderRepository;

    @Resource
    private StorageFeign storageFeign;

    /**
     * 下单接口
     * @param userId 用户id
     * @param commodityCode 商品代码
     * @return
     */
    @Override
    public String createOrder(String userId, String commodityCode) {
        try {
            System.out.println("事务id---------------------->" + RootContext.getXID());
            // 创建订单
            OrderTbl orderTbl = new OrderTbl();
            orderTbl.setUserId(userId);
            orderTbl.setCommodityCode(commodityCode);
            orderTbl.setCount(1); // 假设为1件
            orderTbl.setMoney(10); // 假设为十元

            // 保存订单
            orderRepository.save(orderTbl);

            // 保存订单成功后扣减库存
            storageFeign.deduct(commodityCode,orderTbl.getCount());

            return "success";
        }catch (Exception e){
            throw new BizException("创建订单失败");
        }

    }
    
}

数据层

/**
 * @author : jiagang
 * @date : Created in 2023/1/16 15:44
 */
@Repository
public interface OrderRepository extends JpaRepository<OrderTbl,Integer> {
}

feign接口
对微服务之前使用feign来调用还熟悉的同学可以点下面的链接
springcloud alibaba微服务 – openfeign的使用(保姆级)

/**
 * @author : jiagang
 * @date : Created in 2022/7/4 10:26
 */
@FeignClient(value = "mdx-shop-storage")
@Component
public interface StorageFeign {

    /**
     * 扣减库存
     * @param commodityCode
     * @param count
     * @return
     */
    @GetMapping("storage/deduct")
    String deduct(@RequestParam String commodityCode,@RequestParam Integer count);

}

3.2 模拟异常测试分布式事务

在进行测试之前,我们先来看下业务逻辑,我们使用postman来调用下单接口进行下单,接口地址为 http://localhost:9091/order/createOrder?userId=admin&commodityCode=S123434455666777 (POST请求) ,然后下单接口保存订单,并通过feign接口调用仓储服务扣减库存。

3.2.1 测试正常流程

正常流程下用户下单,订单数据库增加订单,仓储数据库为下单的商品扣减库存。

首先看一下订单数据库order_tbl表和仓储数据库storage_tbl表
订单表没有数据
在这里插入图片描述
仓储表有一条商品,库存为10
在这里插入图片描述
正常情况下,下单之后(我们只买一件商品)订单增加一条数据,仓储的S123434455666777商品库存减1 为 9

POST 请求调用 下单接口 http://localhost:9091/order/createOrder?userId=admin&commodityCode=S123434455666777

postman提示成功
在这里插入图片描述

看一下数据库
订单新增成功
库存减为9

在这里插入图片描述
在这里插入图片描述

3.2.2 模拟失败流程

先将数据库订单表清空,仓储表库存继续设置为10(这里不操作也行,大家记住之前的状态就可以了)

我们在仓储服务的扣减库存的方法中手动写一个异常,异常如下
System.out.println(1 / 0);

/**
     * 扣减库存
     * @param commodityCode
     * @param count
     */
    @Override
    public void deduct(String commodityCode, int count) {
        System.out.println("事务id---------------------->" + RootContext.getXID());
        StorageTbl storageTbl = storageRepository.findByCommodityCode(commodityCode);
        if (storageTbl == null){
            throw new BizException("storageTbl is null");
        }

        // 模拟异常
        System.out.println(1 / 0);

        // 这里先不考虑超卖的情况
        storageTbl.setCount(storageTbl.getCount() - count);
        // 使用jpa 存在就更新
        storageRepository.save(storageTbl);
    }

此时,再来调用下单接口
http://localhost:9091/order/createOrder?userId=admin&commodityCode=S123434455666777

可以看到服务报错,提示创建订单失败
在这里插入图片描述

再来观察一下数据库

发现订单依然创建成功,但是库存缺没有减少,还是10,这就导致了用户下单成功了,但是没给人减库存,造成数据不一致,可能会发生超卖。

在这里插入图片描述

在这里插入图片描述

3.2.2 添加分布式事务注解

先将数据库订单表清空(这里不操作也行,大家记住之前的状态就可以了)

为了解决上面的问题,我们为创建订单方法增加seata的分布式注解
@GlobalTransactional

/**
     * 下单接口
     * @param userId 用户id
     * @param commodityCode 商品代码
     * @return
     */
    @Override
    @GlobalTransactional
    public String createOrder(String userId, String commodityCode) {
        try {
            System.out.println("事务id---------------------->" + RootContext.getXID());
            // 创建订单
            OrderTbl orderTbl = new OrderTbl();
            orderTbl.setUserId(userId);
            orderTbl.setCommodityCode(commodityCode);
            orderTbl.setCount(1); // 假设为1件
            orderTbl.setMoney(10); // 假设为十元

            // 保存订单
            orderRepository.save(orderTbl);

            // 保存订单成功后扣减库存
            storageFeign.deduct(commodityCode,orderTbl.getCount());

            return "success";
        }catch (Exception e){
            throw new BizException("创建订单失败");
        }

    }

加上注解之后重启服务继续调用下单接口

可以看到创建订单失败
在这里插入图片描述
然后再来观察下数据库

发现订单表没有此商品的订单,库存也没变,那就表示事务已经成功回滚了,不会再出现订单创建成功了单库存没减的情况。
在这里插入图片描述
在这里插入图片描述

这篇文章连查资料、测试、发现问题、解决问题花了两天时间,创作不易,点个赞吧👍

最后的最后送大家一句话
白驹过隙,沧海桑田,与君共勉

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/169061.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

内存一致性模型概念

phrase-20230117184107 内存一致性模型(Memory Consistency Models)提供内存一致性保证&#xff0c;一致性结果体现在程序内存操作是可预测的。例如在多核或多处理器硬件上&#xff0c;在编写并行的程序时&#xff0c;如果理解当前系统所使用的一致性模型&#xff0c;有助于使…

OpenStack GPU直通服务器

layout: post title: OpenStack GPU直通服务器 catalog: true tag: [OpenStack, GPU] 1. 概述2. 直通GPU特性3. 功能说明 3.1. 操作系统支持3.2. 设备支持 4. 实现方案5. 部署方案 5.1. 示例环境说明5.2. 上线步骤 5.2.1. 硬件安装5.2.2. GPU计算节点主机配置 5.2.2.1. IOMMU设…

【数据结构与算法学习8】二叉查找树的基本介绍与添加数据的过程

程序员语录&#xff1a; 把时髦的技术挂在嘴边&#xff0c;还不如把过时的技术记在心里。 1 二叉查找树是什么&#xff1f; 二叉查找树是一种数据结构&#xff0c;又叫作二叉搜索树或二叉排序树,采用了图的树形结构&#xff0c;数据存储于二叉查找树的各个结点中&#xff0c;每…

GEE 9:Earth Engine Reducers 的基本操作

目录1.Image 、ImageCollection and Regions Reducers&#xff08;图层和区域的相关操作&#xff09;1.1 Image Reductions&#xff08;处理单个图层&#xff09;1.2 ImageCollection Reductions&#xff08;处理图层集&#xff09;1.3 Greenest pixel (maximum NDVI) composit…

01背包——二维动态规划【c++】代码实现

今天学了01背包&#xff0c;就想来讲一讲&#xff0c;正好回顾一下&#xff08;BZOJ上的题目&#xff09;。 01背包 所谓01背包&#xff0c;也就是背包的一种&#xff0c;01背包和完全背包的区别就在于&#xff0c;01背包一件物品只能选择一次&#xff0c;而完全背包可以重复…

架构运维篇(七):Centos7/Linux中安装Zookeeper

版本说明 JDK &#xff1a;1.8&#xff08;已安装&#xff09;ZK : 3.8.0 安装部署Zookeeper 第一步&#xff1a;下载最新版本 官网地址&#xff1a;Apache DownloadsHome page of The Apache Software Foundationhttps://www.apache.org/dyn/closer.lua/zookeeper/zookeep…

libvirt零知识学习1 —— libvirt简介

本文内容部分取自《KVM实战 —— 原理、进阶与性能调优》&#xff0c;非常好的一本书。 1. 概述 提到KVM的管理工具&#xff0c;首先必须要介绍的无疑是大名鼎鼎的libvirt。libvirt是目前使用最为广泛的对KVM虚拟机进行管理的工具和应用程序接口。并且&#xff0c;一些常用的虚…

Oracle VM VirtualBox 安装 CentOS Linux

Virtual Box VirtualBox是一个强大的、面向个人用户或者企业用户的虚拟机产品&#xff0c;其支持x86以及AMD64/Intel64的计算架构&#xff0c;功能特性丰富、性能强劲&#xff0c;支持GPL开源协议&#xff0c;其官方网址是www.virtualbox.org&#xff0c;由Oracle开源&#xf…

Spring自动装配

自动装配的三种方式一、xml方式自动装配二、 按类型自动装配三、使用注解一、xml方式自动装配 byName: 会自动在容器上下文查找&#xff0c;和自己对象set方法后面的值对应的bean id&#xff08;通过id匹配&#xff09; //实体类 Data public class Pepole {private String n…

GameFrameWork框架(Unity3D)使用笔记(八) 实现场景加载进度条

前言&#xff1a; 游戏在转换场景的时候&#xff0c;需要花费时间来加载相关的资源。而这个过程往往因为游戏场景的规模和复杂度以及玩家电脑配置的原因花费一小段时间&#xff08;虽然这个项目里用不到&#xff09;。 所以&#xff0c;如果这一小段时间&#xff0c;画面就卡在…

计算机网络 —— TCP篇 三次握手与四次挥手

计算机网络系列文章目录 TCP篇 三次握手四次挥手 socket 文章目录计算机网络系列文章目录前言4.1 tcp三次握手与四次挥手面试题4.1.1 TCP的基本认识TCP头部为什么需要tcp&#xff0c;tcp工作在哪一层什么是tcp什么是tcp连接如何唯一确定一个tcp连接(tcp&udp比较) UDP 和 T…

机器学习【西瓜书/南瓜书】--- 第1章绪论+第二章模型选择和评估(学习笔记+公式推导)

【西瓜书南瓜书】task01: 第1、2章&#xff08;2天&#xff09; 第一章 绪论 主要符号表 下述这些符号在本书中将频繁的使用&#xff0c;务必牢记于心各个特殊符号所具有的的含义 &#x1f31f;对上述部分定义做一下解释&#xff1a; 向量有维度&#xff0c;其中的元素是有…

路由器NAT典型配置

1.组网需求内部网络中IP地址为10.110.10.0/24的用户可以访问Internet&#xff0c;其它网段的用户则不能访问Internet。外部的PC可以访问内部的服务器。公司具有202.38.160.100/24至202.38.160.105/24六个合法的IP地址。选用202.38.160.100作为公司对外的IP地址&#xff0c;WWW服…

stm32平衡小车(2)-----编码器电机驱动

前言&#xff1a;之前做arduino小车的时候使用的是L298N电机&#xff0c;没有用过编码器&#xff0c;这次第一次用编码器&#xff0c;还是比较懵圈&#xff0c;记录一下学的整个过程。 1.编码器的简介 霍尔编码器是一种通过磁电转换将输出轴上的机械几何位移量转换成脉冲或数字…

下载整个网站

使用Cyotek WebCopy下载整个网站一、下载网站复制所需软件&#xff1a;二、打开Cyotek WebCopy&#xff0c;然后下载网站系统环境&#xff1a;win10 所需工具&#xff1a;Cyotek WebCopy 一、下载网站复制所需软件&#xff1a; https://www.cyotek.com/cyotek-webcopy/downloa…

3-3存储系统-高速缓冲存储器Cache

文章目录一.概述二.Cache与主存的映射方式1.直接映射2.全相联映射3.组相联映射三.Cache中主存块的替换算法1.随机算法RAND2.先进先出算法FIFO3.最近最少使用算法LRU4.最不经常使用算法LFU四.Cache写策略1.写命中2.写不命中一.概述 高速缓冲存储器&#xff08;Cache&#xff09…

labelme使用labelme2voc.py忽略部分标签生成VOC/coco类型数据集(Ignore a label in json file)

一&#xff1a;背景和问题描述这是一个比较小众&#xff0c;且查找全网未有答案的问题。在使用labelme标注数据之后&#xff0c;生成了json文件。在利用json文件生成可用于训练分割模型的voc或者coco数据集时&#xff0c;可以使用labelme中自带的示例文件labelme2voc.py&#x…

第二章.线性回归以及非线性回归—弹性网

第二章.线性回归以及非线性回归 2.14 弹性网&#xff08;Elastic Net&#xff09; Elastic Net是一种使用L1和L2作为正则化矩阵的线性回归模型&#xff0c;这种组合用于只有很少的权重非零的稀疏模型。 当多个特征和另一个特征相关时&#xff0c;弹性网络非常有用&#xff0c;L…

C++ STL简介

文章目录前言一、STL概述二、STL历史三、STL六大组件功能与运用前言 对C/C学习感兴趣的可以看看这篇文章哦&#xff1a;C/C教程 一、STL概述 在软件行业&#xff0c;一直都希望建立一种可以重复运用的东西和一种可以制造出“可重复使用的东西”的方法&#xff0c;来让程序员…

什么是基于模型设计(MBD)?

MBD(Model based Design)是什么&#xff1f; 随着嵌入式行业的快速发展&#xff0c;MBD作为解决专家紧缺、国内解决方案匮乏的新软件开发方式备受关注。 MBD是一种通过建模自动生成代码的&#xff08;Auto-generation Code&#xff09;开发方式。 可以说它与如何用传统的C/C等计…