Seata TCC 模式下解决幂等、悬挂、空回滚问题 | Spring Cloud56

news2025/1/16 23:58:25

一、前言

通过以下系列章节:

docker-compose 实现Seata Server高可用部署 | Spring Cloud 51

Seata AT 模式理论学习、事务隔离及部分源码解析 | Spring Cloud 52

Spring Boot集成Seata利用AT模式分布式事务示例 | Spring Cloud 53

Seata XA 模式理论学习、使用及注意事项 | Spring Cloud54

Seata TCC 模式理论学习、生产级使用示例搭建及注意事项 | Spring Cloud55

我们对Seata及其ATXATCC事务模式的理论、使用有了深入的了解,今天继续学习SeataTCC事务模式下是如何解决的空回滚、防悬挂、幂等控制问题。

理论部分来自Seata官网:http://seata.io/zh-cn/docs/dev/mode/tcc-mode.html

二、TCC 回顾

TCC 模式是最经典的分布式事务解决方案,它将分布式事务分为两个阶段来执行,try 阶段对每个分支事务进行预留资源,如果所有分支事务都预留资源成功,则进入 commit 阶段提交全局事务,如果有一个节点预留资源失败则进入 cancel 阶段回滚全局事务。

2.1 TCC 优势

TCC 模式最大的优势是效率高。TCC 模式在 try 阶段的锁定资源并不是真正意义上的锁定,而是真实提交了本地事务,将资源预留到中间态,并不需要阻塞等待,因此效率比其他模式要高。

同时 TCC 模式还可以进行如下优化:

2.1.1 异步提交

try 阶段成功后,不立即进入 confirm/cancel 阶段,而是认为全局事务已经结束了,启动定时任务来异步执行 confirm/cancel,扣减或释放资源,这样会有很大的性能提升。

2.1.2 同库模式

TCC 模式中有三个角色:

  • TM:管理全局事务,包括开启全局事务,提交/回滚全局事务;
  • RM:管理分支事务;
  • TC: 管理全局事务和分支事务的状态。
    在这里插入图片描述
    TM 开启全局事务时,RM 需要向 TC 发送注册消息,TC 保存分支事务的状态。TM 请求提交或回滚时,TC 需要向 RM 发送提交或回滚消息。这样包含两个个分支事务的分布式事务中,TCRM 之间有四次 RPC

优化后的流程如下图:
在这里插入图片描述
TC 保存全局事务的状态。TM 开启全局事务时,RM 不再需要向 TC 发送注册消息,而是把分支事务状态保存在了本地。TMTC 发送提交或回滚消息后,RM 异步线程首先查出本地保存的未提交分支事务,然后向 TC 发送消息获取(本地分支事务所在的)全局事务状态,以决定是提交还是回滚本地事务。

这样优化后,RPC 次数减少了 50%,性能大幅提升。

2.2 RM 代码示例

以库存服务为例,RM 库存服务接口代码如下:

import io.seata.rm.tcc.api.BusinessActionContext;
import io.seata.rm.tcc.api.BusinessActionContextParameter;
import io.seata.rm.tcc.api.LocalTCC;
import io.seata.rm.tcc.api.TwoPhaseBusinessAction;

@LocalTCC
public interface StorageService {

    /**
     * 执行资源检查及预业务操作
     */
    // @BusinessActionContextParameter 注解就是将对应的参数放入到 BusinessActionContext 中,将来可以从 BusinessActionContext 中取出对应的参数。
    @TwoPhaseBusinessAction(name = "storageService", commitMethod = "commit", rollbackMethod = "rollback")
    boolean prepare(BusinessActionContext actionContext, @BusinessActionContextParameter(paramName = "commodityCode") String commodityCode, @BusinessActionContextParameter(paramName = "count") Integer  count);

    /**
     * 全局事物进行提交
     */
    boolean commit(BusinessActionContext actionContext);

    /**
     * 全局事务进行回滚
     */
    boolean rollback(BusinessActionContext actionContext);
}

通过 @LocalTCC 这个注解,RM 初始化的时候会向 TC 注册一个分支事务。在 try 阶段的方法(prepare方法)上有一个 @TwoPhaseBusinessAction 注解,这里定义了分支事务的 resourceIdcommit 方法和 cancel 方法。

其中@TwoPhaseBusinessAction 注解有useTCCFence 属性也就是本章的重点内容,敲黑板请注意听讲哈

三、TCC 存在问题

TCC 模式中存在的三大问题是:幂等悬挂空回滚。在 Seata1.5.1 版本中,增加了一张事务控制表,表名是 tcc_fence_log 来解决这个问题。

而在上一节 @TwoPhaseBusinessAction 注解中提到的属性 useTCCFence 就是来指定是否开启这个机制,这个属性值默认是 false

脚本所在GitHub地址:https://github.com/seata/seata/tree/develop/script/client/tcc/db

tcc_fence_log 建表语句如下(MySQL 语法):

CREATE TABLE IF NOT EXISTS `tcc_fence_log`
(
    `xid`           VARCHAR(128)  NOT NULL COMMENT 'global id',
    `branch_id`     BIGINT        NOT NULL COMMENT 'branch id',
    `action_name`   VARCHAR(64)   NOT NULL COMMENT 'action name',
    `status`        TINYINT       NOT NULL COMMENT 'status(tried:1;committed:2;rollbacked:3;suspended:4)',
    `gmt_create`    DATETIME(3)   NOT NULL COMMENT 'create time',
    `gmt_modified`  DATETIME(3)   NOT NULL COMMENT 'update time',
    PRIMARY KEY (`xid`, `branch_id`),
    KEY `idx_gmt_modified` (`gmt_modified`),
    KEY `idx_status` (`status`)
) ENGINE = InnoDB
DEFAULT CHARSET = utf8mb4;

3.1 幂等

commit/cancel 阶段,因为 TC 没有收到分支事务的响应,需要进行重试,这就要分支事务支持幂等。

我们看一下新版本是怎么解决的。下面的代码在 TCCResourceManager 类:

/**
 * TCC branch commit
 *
 * @param branchType
 * @param xid             Transaction id.
 * @param branchId        Branch id.
 * @param resourceId      Resource id.
 * @param applicationData Application data bind with this branch.
 * @return BranchStatus
 * @throws TransactionException TransactionException
 */
@Override
public BranchStatus branchCommit(BranchType branchType, String xid, long branchId, String resourceId,
                                 String applicationData) throws TransactionException {
    TCCResource tccResource = (TCCResource)tccResourceCache.get(resourceId);
    if (tccResource == null) {
        throw new ShouldNeverHappenException(String.format("TCC resource is not exist, resourceId: %s", resourceId));
    }
    Object targetTCCBean = tccResource.getTargetBean();
    Method commitMethod = tccResource.getCommitMethod();
    if (targetTCCBean == null || commitMethod == null) {
        throw new ShouldNeverHappenException(String.format("TCC resource is not available, resourceId: %s", resourceId));
    }
    try {
        //BusinessActionContext
        BusinessActionContext businessActionContext = getBusinessActionContext(xid, branchId, resourceId,
            applicationData);
        Object[] args = this.getTwoPhaseCommitArgs(tccResource, businessActionContext);
        Object ret;
        boolean result;
        // 注解 useTCCFence 属性是否设置为 true
        if (Boolean.TRUE.equals(businessActionContext.getActionContext(Constants.USE_TCC_FENCE))) {
            try {
                result = TCCFenceHandler.commitFence(commitMethod, targetTCCBean, xid, branchId, args);
            } catch (SkipCallbackWrapperException | UndeclaredThrowableException e) {
                throw e.getCause();
            }
        } else {
            ret = commitMethod.invoke(targetTCCBean, args);
            if (ret != null) {
                if (ret instanceof TwoPhaseResult) {
                    result = ((TwoPhaseResult)ret).isSuccess();
                } else {
                    result = (boolean)ret;
                }
            } else {
                result = true;
            }
        }
        LOGGER.info("TCC resource commit result : {}, xid: {}, branchId: {}, resourceId: {}", result, xid, branchId, resourceId);
        return result ? BranchStatus.PhaseTwo_Committed : BranchStatus.PhaseTwo_CommitFailed_Retryable;
    } catch (Throwable t) {
        String msg = String.format("commit TCC resource error, resourceId: %s, xid: %s.", resourceId, xid);
        LOGGER.error(msg, t);
        return BranchStatus.PhaseTwo_CommitFailed_Retryable;
    }
}

上面的代码可以看到,执行分支事务提交方法时,首先判断 useTCCFence 属性是否为 true,如果为 true,则走 TCCFenceHandler 类中的 commitFence 逻辑,否则走普通提交逻辑。

TCCFenceHandler 类中的 commitFence 方法调用了 TCCFenceHandler 类的 commitFence 方法,代码如下:

/**
 * tcc commit method enhanced
 *
 * @param commitMethod          commit method
 * @param targetTCCBean         target tcc bean
 * @param xid                   the global transaction id
 * @param branchId              the branch transaction id
 * @param args                  commit method's parameters
 * @return the boolean
 */
public static boolean commitFence(Method commitMethod, Object targetTCCBean,
                                  String xid, Long branchId, Object[] args) {
    return transactionTemplate.execute(status -> {
        try {
            Connection conn = DataSourceUtils.getConnection(dataSource);
            TCCFenceDO tccFenceDO = TCC_FENCE_DAO.queryTCCFenceDO(conn, xid, branchId);
            if (tccFenceDO == null) {
                throw new TCCFenceException(String.format("TCC fence record not exists, commit fence method failed. xid= %s, branchId= %s", xid, branchId),
                        FrameworkErrorCode.RecordNotExists);
            }
            if (TCCFenceConstant.STATUS_COMMITTED == tccFenceDO.getStatus()) {
                LOGGER.info("Branch transaction has already committed before. idempotency rejected. xid: {}, branchId: {}, status: {}", xid, branchId, tccFenceDO.getStatus());
                return true;
            }
            if (TCCFenceConstant.STATUS_ROLLBACKED == tccFenceDO.getStatus() || TCCFenceConstant.STATUS_SUSPENDED == tccFenceDO.getStatus()) {
                if (LOGGER.isWarnEnabled()) {
                    LOGGER.warn("Branch transaction status is unexpected. xid: {}, branchId: {}, status: {}", xid, branchId, tccFenceDO.getStatus());
                }
                return false;
            }
            return updateStatusAndInvokeTargetMethod(conn, commitMethod, targetTCCBean, xid, branchId, TCCFenceConstant.STATUS_COMMITTED, status, args);
        } catch (Throwable t) {
            status.setRollbackOnly();
            throw new SkipCallbackWrapperException(t);
        }
    });
}

从代码中可以看到,提交事务时首先会判断 tcc_fence_log 表中是否已经有记录,

  • 如果有记录,则判断事务执行状态并返回。这样如果判断到事务的状态已经是 STATUS_COMMITTED,就不会再次提交,保证了幂等。
  • 如果 tcc_fence_log 表中没有记录,则抛出自定义异常TCCFenceException

tcc_fence_log 表中的记录是通过ActionInterceptorHandler类的 proceed 方法调用TCCFenceHandler.prepareFence进行插入,在下节会详细介绍

Rollback 的逻辑跟 commit 类似,逻辑在类 TCCFenceHandlerrollbackFence 方法。

3.2 空回滚

try 阶段服务发生了某些故障,造成对应的方法未执行,try 阶段在不考虑重试的情况下,全局事务要走向结束状态,这样就需要在服务上执行一次 cancel(rollback) 操作,这样就空跑了一次回滚操作。

Seata 的解决方案是在 try 阶段往tcc_fence_log 表插入一条记录,status 字段值是 STATUS_TRIED。代码如下:

/**
 * tcc prepare method enhanced
 *
 * @param xid            the global transaction id
 * @param branchId       the branch transaction id
 * @param actionName     the action name
 * @param targetCallback the target callback
 * @return the boolean
 */
public static Object prepareFence(String xid, Long branchId, String actionName, Callback<Object> targetCallback) {
    return transactionTemplate.execute(status -> {
        try {
            Connection conn = DataSourceUtils.getConnection(dataSource);
            boolean result = insertTCCFenceLog(conn, xid, branchId, actionName, TCCFenceConstant.STATUS_TRIED);
            LOGGER.info("TCC fence prepare result: {}. xid: {}, branchId: {}", result, xid, branchId);
            if (result) {
                return targetCallback.execute();
            } else {
                throw new TCCFenceException(String.format("Insert tcc fence record error, prepare fence failed. xid= %s, branchId= %s", xid, branchId),
                        FrameworkErrorCode.InsertRecordError);
            }
        } catch (TCCFenceException e) {
            if (e.getErrcode() == FrameworkErrorCode.DuplicateKeyException) {
                LOGGER.error("Branch transaction has already rollbacked before,prepare fence failed. xid= {},branchId = {}", xid, branchId);
                addToLogCleanQueue(xid, branchId);
            }
            status.setRollbackOnly();
            throw new SkipCallbackWrapperException(e);
        } catch (Throwable t) {
            status.setRollbackOnly();
            throw new SkipCallbackWrapperException(t);
        }
    });
}

Rollback 阶段判断记录是否存在,如果不存在,则插入一条记录,status 字段值是 STATUS_SUSPENDED,且不执行回滚操作(在下节会详细介绍),在 Rollback 阶段的处理逻辑如下:

// TCCFenceHandler 类

/**
 * tcc rollback method enhanced
 *
 * @param rollbackMethod        rollback method
 * @param targetTCCBean         target tcc bean
 * @param xid                   the global transaction id
 * @param branchId              the branch transaction id
 * @param args                  rollback method's parameters
 * @param actionName            the action name
 * @return the boolean
 */
public static boolean rollbackFence(Method rollbackMethod, Object targetTCCBean,
                                    String xid, Long branchId, Object[] args, String actionName) {
    return transactionTemplate.execute(status -> {
        try {
            Connection conn = DataSourceUtils.getConnection(dataSource);
            TCCFenceDO tccFenceDO = TCC_FENCE_DAO.queryTCCFenceDO(conn, xid, branchId);
            // non_rollback
            if (tccFenceDO == null) {
                boolean result = insertTCCFenceLog(conn, xid, branchId, actionName, TCCFenceConstant.STATUS_SUSPENDED);
                LOGGER.info("Insert tcc fence record result: {}. xid: {}, branchId: {}", result, xid, branchId);
                if (!result) {
                    throw new TCCFenceException(String.format("Insert tcc fence record error, rollback fence method failed. xid= %s, branchId= %s", xid, branchId),
                            FrameworkErrorCode.InsertRecordError);
                }
                return true;
            } else {
                if (TCCFenceConstant.STATUS_ROLLBACKED == tccFenceDO.getStatus() || TCCFenceConstant.STATUS_SUSPENDED == tccFenceDO.getStatus()) {
                    LOGGER.info("Branch transaction had already rollbacked before, idempotency rejected. xid: {}, branchId: {}, status: {}", xid, branchId, tccFenceDO.getStatus());
                    return true;
                }
                if (TCCFenceConstant.STATUS_COMMITTED == tccFenceDO.getStatus()) {
                    if (LOGGER.isWarnEnabled()) {
                        LOGGER.warn("Branch transaction status is unexpected. xid: {}, branchId: {}, status: {}", xid, branchId, tccFenceDO.getStatus());
                    }
                    return false;
                }
            }
            return updateStatusAndInvokeTargetMethod(conn, rollbackMethod, targetTCCBean, xid, branchId, TCCFenceConstant.STATUS_ROLLBACKED, status, args);
        } catch (Throwable t) {
            status.setRollbackOnly();
            throw new SkipCallbackWrapperException(t);
        }
    });
}

updateStatusAndInvokeTargetMethod 方法执行的 sql 如下:

update tcc_fence_log set status = ?, gmt_modified = ?
    where xid = ? and  branch_id = ? and status = ? ;

可见就是把 tcc_fence_log 表记录的 status 字段值从 STATUS_TRIED 改为 STATUS_ROLLBACKED,如果更新成功,就执行回滚逻辑。

3.3 悬挂

悬挂是指因为网络问题,RM 开始没有收到 try 指令,但是执行了 RollbackRM 又收到了 try 指令并且预留资源成功,这时全局事务已经结束,最终导致预留的资源不能释放。如下图:
在这里插入图片描述

Seata 解决这个问题的方法是执行 Rollback 方法时先判断 tcc_fence_log 是否存在当前 xid 的记录,如果没有则向 tcc_fence_log 表插入一条记录,状态是 STATUS_SUSPENDED,并且不再执行回滚操作。代码如下:

// TCCFenceHandler 类

/**
 * tcc rollback method enhanced
 *
 * @param rollbackMethod        rollback method
 * @param targetTCCBean         target tcc bean
 * @param xid                   the global transaction id
 * @param branchId              the branch transaction id
 * @param args                  rollback method's parameters
 * @param actionName            the action name
 * @return the boolean
 */
public static boolean rollbackFence(Method rollbackMethod, Object targetTCCBean,
                                    String xid, Long branchId, Object[] args, String actionName) {
    return transactionTemplate.execute(status -> {
        try {
            Connection conn = DataSourceUtils.getConnection(dataSource);
            TCCFenceDO tccFenceDO = TCC_FENCE_DAO.queryTCCFenceDO(conn, xid, branchId);
            // non_rollback
            if (tccFenceDO == null) {
            	// 插入防悬挂记录
                boolean result = insertTCCFenceLog(conn, xid, branchId, actionName, TCCFenceConstant.STATUS_SUSPENDED);
                LOGGER.info("Insert tcc fence record result: {}. xid: {}, branchId: {}", result, xid, branchId);
                if (!result) {
                    throw new TCCFenceException(String.format("Insert tcc fence record error, rollback fence method failed. xid= %s, branchId= %s", xid, branchId),
                            FrameworkErrorCode.InsertRecordError);
                }
                return true;
            } else {
                if (TCCFenceConstant.STATUS_ROLLBACKED == tccFenceDO.getStatus() || TCCFenceConstant.STATUS_SUSPENDED == tccFenceDO.getStatus()) {
                    LOGGER.info("Branch transaction had already rollbacked before, idempotency rejected. xid: {}, branchId: {}, status: {}", xid, branchId, tccFenceDO.getStatus());
                    return true;
                }
                if (TCCFenceConstant.STATUS_COMMITTED == tccFenceDO.getStatus()) {
                    if (LOGGER.isWarnEnabled()) {
                        LOGGER.warn("Branch transaction status is unexpected. xid: {}, branchId: {}, status: {}", xid, branchId, tccFenceDO.getStatus());
                    }
                    return false;
                }
            }
            return updateStatusAndInvokeTargetMethod(conn, rollbackMethod, targetTCCBean, xid, branchId, TCCFenceConstant.STATUS_ROLLBACKED, status, args);
        } catch (Throwable t) {
            status.setRollbackOnly();
            throw new SkipCallbackWrapperException(t);
        }
    });
}

而后面执行 try 阶段方法时首先会向 tcc_fence_log 表插入一条当前 xid 的记录,这样就造成了主键冲突。代码如下:

/**
 * tcc prepare method enhanced
 *
 * @param xid            the global transaction id
 * @param branchId       the branch transaction id
 * @param actionName     the action name
 * @param targetCallback the target callback
 * @return the boolean
 */
public static Object prepareFence(String xid, Long branchId, String actionName, Callback<Object> targetCallback) {
    return transactionTemplate.execute(status -> {
        try {
            Connection conn = DataSourceUtils.getConnection(dataSource);
            boolean result = insertTCCFenceLog(conn, xid, branchId, actionName, TCCFenceConstant.STATUS_TRIED);
            LOGGER.info("TCC fence prepare result: {}. xid: {}, branchId: {}", result, xid, branchId);
            if (result) {
                return targetCallback.execute();
            } else {
                throw new TCCFenceException(String.format("Insert tcc fence record error, prepare fence failed. xid= %s, branchId= %s", xid, branchId),
                        FrameworkErrorCode.InsertRecordError);
            }
        } catch (TCCFenceException e) {
            if (e.getErrcode() == FrameworkErrorCode.DuplicateKeyException) {
                LOGGER.error("Branch transaction has already rollbacked before,prepare fence failed. xid= {},branchId = {}", xid, branchId);
                addToLogCleanQueue(xid, branchId);
            }
            status.setRollbackOnly();
            throw new SkipCallbackWrapperException(e);
        } catch (Throwable t) {
            status.setRollbackOnly();
            throw new SkipCallbackWrapperException(t);
        }
    });
}

/**
 * Insert TCC fence log
 *
 * @param conn     the db connection
 * @param xid      the xid
 * @param branchId the branchId
 * @param status   the status
 * @return the boolean
 */
private static boolean insertTCCFenceLog(Connection conn, String xid, Long branchId, String actionName, Integer status) {
    TCCFenceDO tccFenceDO = new TCCFenceDO();
    tccFenceDO.setXid(xid);
    tccFenceDO.setBranchId(branchId);
    tccFenceDO.setActionName(actionName);
    tccFenceDO.setStatus(status);
    return TCC_FENCE_DAO.insertTCCFenceDO(conn, tccFenceDO);
}

注意:queryTCCFenceDO 方法 sql 中使用了 for update,这样就不用担心 Rollback 方法中获取不到 tcc_fence_log 表记录而无法判断 try 阶段本地事务的执行结果了。

四、总结

TCC 模式是分布式事务使用最多的模式,但是幂等、悬挂和空回滚一直是 TCC 模式需要考虑的问题,Seata 框架在 1.5.1 版本完美解决了这些问题。

tcc_fence_log 表的操作也需要考虑事务的控制。

Seata 需使用代理数据源,使 tcc_fence_log 表操作和 RM 业务操作在同一个本地事务中执行,这样就能保证本地操作和对 tcc_fence_log 的操作同时成功或失败

seata:
  # 是否开启spring-boot自动装配,seata-spring-boot-starter 专有配置,默认true
  enabled: true
  # 是否开启数据源自动代理,seata-spring-boot-starter专有配置,默认会开启数据源自动代理,可通过该配置项关闭
  enable-auto-data-source-proxy: true

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

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

相关文章

antd-vue - - - - - date-picker组件在非本地不好使?无法切换日期?

date-picker组件在非本地不好使&#xff1f;无法切换日期&#xff1f; 1.问题描述2.问题原因 & 解决办法 离离原上谱&#xff0c;真的是离了大谱 记录一个深深的踩坑记录&#xff01;&#xff01;&#xff01; 选择日期&#xff0c;肯定要选择ui组件的date-picker&#xf…

tensorflow基础知识

计算图的理解 ref: https://zhuanlan.zhihu.com/p/344846077 计算图模型由节点&#xff08;nodes&#xff09;和线&#xff08;edges&#xff09;组成&#xff0c;节点表示操作符/算子Operator&#xff0c;线表示计算间的依赖。实线表示有数据传递的依赖&#xff0c;传递的数…

【LeetCode】HOT 100(17)

题单介绍&#xff1a; 精选 100 道力扣&#xff08;LeetCode&#xff09;上最热门的题目&#xff0c;适合初识算法与数据结构的新手和想要在短时间内高效提升的人&#xff0c;熟练掌握这 100 道题&#xff0c;你就已经具备了在代码世界通行的基本能力。 目录 题单介绍&#…

three.js中通过gsap动画库实现物体的动画

一、什么是gsap GSAP&#xff08;GreenSock Animation Platform&#xff09;是一个JavaScript动画库&#xff0c;由GreenSock公司开发&#xff0c;用于在Web应用程序中创建高性能动画。 使用GSAP可以通过一些简单的动画操作来实现复杂的动画效果&#xff0c;例如TweenLite、T…

临期商品app小程序软件开发

临期商品APP小程序是一种专门销售或推广临近保质期商品的应用程序&#xff0c;以下是可能有助于临期商品APP小程序软件开发的功能&#xff1a; 商品展示&#xff1a;允许用户查看和浏览不同种类的临期商品&#xff0c;包括产品图片、描述、价格等信息。 用户登录和注册…

笔记:WebRTC 网络技术理论与实战(二)

WebRTC技术笔记 笔记&#xff1a;WebRTC 网络技术理论与实战&#xff08;一&#xff09; 作者&#xff1a;李俊才 &#xff08;jcLee95&#xff09;&#xff1a;https://blog.csdn.net/qq_28550263 邮箱 &#xff1a;291148484163.com 本文地址&#xff1a;https://blog.csdn.n…

【Flutter——复选框实例代码超详细讲解!】

Flutter——复选框实例代码超详细讲解&#xff01; 文章目录 Flutter——复选框实例代码超详细讲解&#xff01;代码实现效果代码实例代码结构总结 代码实现效果 代码实例 import package:flutter/material.dart; // 导入 Flutter 的核心库void main() {runApp(const MyApp())…

linux-2.6.22.6内核poll总结

1.为什么需要poll&#xff0c;其原因是当进行某些操作时&#xff0c;该操作会一直消耗cpu&#xff0c;浪费资源&#xff0c;所以引入poll可以让该进程在指定时间内如果没有结果产生&#xff08;可以是某个操作或者获取数据&#xff09;则进行休眠&#xff0c;这样就会释放cpu。…

【MySQL】数据库基础 ②

✍LIKE 子句 说明&#xff1a; 使用 SELECT 来查询数据&#xff0c; 同时我们可以在 SELECT 语句中使用 WHERE 子句来获取指定的记录。 WHERE 子句中可以使用等号 来设定获取数据的条件&#xff0c;如 "字段(text_title) 值()"。 但是有时候我们需要获取 text_…

Android 神奇的 SpannableStringBuilder

文章目录 前言一、SpannableStringBuilder 是什么&#xff1f;二、使用步骤1.示例代码2.参数对应start&#xff1a;样式生效的开始位置&#xff0c;包括该位置end&#xff1a; 样式结束的位置&#xff0c;不包括该位置flags&#xff1a;取值有如下四个Spannable.SPAN_EXCLUSIVE…

wandb快速上手、使用心得(超好用的Tensorboard高替品)

这里写目录标题 1 wandb介绍2 快速上手3 使用心得3.1 一张图展示两条线3.2 想要科学上网和wandb一起使用&#xff08;离线使用&#xff09;3.3 未完待续 1 wandb介绍 wandb地址&#xff1a;wandb Wandb&#xff08;Weights & Biases&#xff09;是一个用于机器学习实验跟踪…

智慧城市建设电气火灾智慧消防措施 安科瑞 许敏

1、引言 “经检测&#xff0c;发现管辖单位南通王子造纸企业二氧化氯装置发生二级警报&#xff0c;可能引发火灾&#xff0c;请立即出警。”日前&#xff0c;南通消防救援支队大数据平台DCS化工火源预警系统发出警报&#xff0c;大屏幕显示辖区企业南通王子造纸企业出现险情。D…

B站618“杀”疯了?UP主直播带货GMV连年破亿!

众多主流平台已经早早构建出较为完整的直播带货体系&#xff0c;而B站则是从去年双十一才宣布正式加入全平台直播带货&#xff0c;同时上线购物直播专区&#xff0c;到今年618年中购物大促&#xff0c;B站已经在大步向前摸索属于本平台的直播带货阵营。 一直以来B站带着二次元…

TC8:SOMEIPSRV_FORMAT_15-18

SOMEIPSRV_FORMAT_15: Instance ID field of the Type 1 Entry 目的 检查Type 1 Entry(Offer Service)的“Instance ID”字段 测试步骤 DUT CONFIGURE:启动具有下列信息的服务Service ID:SERVICE-ID-1Instance数量:1Tester:客户端-1发送SOME/IP Notification消息Entry T…

后端web开发之maven

这里写目录标题 介绍创建maven项目作用作用1作用2作用3 简介 依赖管理依赖配置依赖传递简介依赖传递的可视化快捷键 排除依赖依赖范围生命周期介绍执行流程 介绍 创建maven项目 注意 maven属于项目一级&#xff0c;所以在创建项目的时候 直接选择maven项目按照步骤创建即可&a…

Java Web基础面试题整理

1、什么是Servlet&#xff1f; 可以从两个方面去看Servlet&#xff1a; a、API&#xff1a;有一个接口servlet&#xff0c;它是servlet规范中定义的用来处理客户端请求的程序需要实现的顶级接口。 b、组件&#xff1a;服务器端用来处理客户端请求的组件&#xff0c;需要在we…

【2023,学点儿新Java-12】小结:阶段性复习 | Java学习书籍推荐(小白该读哪类Java书籍?有一定基础后,再去读哪类书籍?)

前情回顾&#xff1a; 【2023&#xff0c;学点儿新Java-11】基础案例练习&#xff1a;输出个人基础信息、输出心形 | Java中 制表符\t 和 换行符\n 的简单练习【2023&#xff0c;学点儿新Java-10】Java17 API文档简介&获取 |详解Java核心机制&#xff1a;JVM |详解Java内存…

【FPGA入门】第六篇、异步串口通信

目录 第一部分、相关知识 1、UART和RS232的区别 2、UART与USART的区别 3、全双工&#xff1f; 4、RS232通信协议 5、波特率 6、如何将外部异步信号变为内部同步信号&#xff1f; 7、什么时间点让FPGA去采集rx线上的数据&#xff1f; 第二部分、串口通信时序图 1、…

OWASP之SSRF服务器伪造请求

文章目录 一、SSRF定义二、形成原因1.提供请求功能2.地址没做限制 三、漏洞危害1.可以对服务器所在内网、本地进行端口扫描&#xff0c;获取一些服务的信息等2.目标网站本地敏感数据的读取3.内外网主机应用程序漏洞的利用4.内外网Web站点漏洞的利用 四、ssrf挖掘1.从WEB功能上寻…

SpringBoot相关知识

SpringBoot知识 1 SpringBoot 介绍及其使用原因 Spring Boot是一个用于创建独立的、基于Java的生产级别的应用程序的框架。它旨在简化Spring应用程序的开发过程&#xff0c;减少开发人员的配置工作&#xff0c;从而提高开发效率。 原因: (1) 简化开发&#xff1a;Spring Boot…