【SpringCloud-Seata源码分析2】

文章目录

  • 分支事务注册-客户端
  • 分支事务服务端的执行

分支事务注册-客户端

第一篇我们将全局事务启动,以及开启源码分析完成了,现在我们需要看一下分支事务注册。
在这里插入图片描述
我们分支事务的开始需要从PreparedStatementProxy#executeUpdate中去看。

public class PreparedStatementProxy extends AbstractPreparedStatementProxy implements PreparedStatement, ParametersHolder {
    public Map<Integer, ArrayList<Object>> getParameters() {
        return this.parameters;
    }

    public PreparedStatementProxy(AbstractConnectionProxy connectionProxy, PreparedStatement targetStatement, String targetSQL) throws SQLException {
        super(connectionProxy, targetStatement, targetSQL);
    }

    public boolean execute() throws SQLException {
        return (Boolean)ExecuteTemplate.execute(this, (statement, args) -> {
            return statement.execute();
        }, new Object[0]);
    }

    public ResultSet executeQuery() throws SQLException {
        return (ResultSet)ExecuteTemplate.execute(this, (statement, args) -> {
            return statement.executeQuery();
        }, new Object[0]);
    }
//这个是分支事务的核心入口
    public int executeUpdate() throws SQLException {
        return (Integer)ExecuteTemplate.execute(this, (statement, args) -> {
            return statement.executeUpdate();
        }, new Object[0]);
    }
}

判断出当前的业务Sql是什么类型,我们需要选择不同的执行器。

public static <T, S extends Statement> T execute(List<SQLRecognizer> sqlRecognizers, StatementProxy<S> statementProxy, StatementCallback<T, S> statementCallback, Object... args) throws SQLException {
		//判断是否是全局锁,并且是否是AT模式
        if (!RootContext.requireGlobalLock() && BranchType.AT != RootContext.getBranchType()) {
        	//否 执行普通的SQL
            return statementCallback.execute(statementProxy.getTargetStatement(), args);
        } else {
        	//获取数据库类型
            String dbType = statementProxy.getConnectionProxy().getDbType();
            if (CollectionUtils.isEmpty(sqlRecognizers)) {
                sqlRecognizers = SQLVisitorFactory.get(statementProxy.getTargetSQL(), dbType);
            }

            Object executor;
            if (CollectionUtils.isEmpty(sqlRecognizers)) {
                executor = new PlainExecutor(statementProxy, statementCallback);
            } else if (sqlRecognizers.size() == 1) {
                SQLRecognizer sqlRecognizer = (SQLRecognizer)sqlRecognizers.get(0);
                label44:
                //根据不同的SQL类型选择不同的执行器
                switch(sqlRecognizer.getSQLType()) {
                case INSERT:
                    executor = (Executor)EnhancedServiceLoader.load(InsertExecutor.class, dbType, new Class[]{StatementProxy.class, StatementCallback.class, SQLRecognizer.class}, new Object[]{statementProxy, statementCallback, sqlRecognizer});
                    break;
                case UPDATE:
                    executor = new UpdateExecutor(statementProxy, statementCallback, sqlRecognizer);
                    break;
                case DELETE:
                    executor = new DeleteExecutor(statementProxy, statementCallback, sqlRecognizer);
                    break;
                case SELECT_FOR_UPDATE:
                    executor = new SelectForUpdateExecutor(statementProxy, statementCallback, sqlRecognizer);
                    break;
                case INSERT_ON_DUPLICATE_UPDATE:
                    byte var8 = -1;
                    switch(dbType.hashCode()) {
                    case 104382626:
                        if (dbType.equals("mysql")) {
                            var8 = 0;
                        }
                        break;
                    case 839186932:
                        if (dbType.equals("mariadb")) {
                            var8 = 1;
                        }
                    }

                    switch(var8) {
                    case 0:
                    case 1:
                        executor = new MySQLInsertOrUpdateExecutor(statementProxy, statementCallback, sqlRecognizer);
                        break label44;
                    default:
                        throw new NotSupportYetException(dbType + " not support to INSERT_ON_DUPLICATE_UPDATE");
                    }
                default:
                    executor = new PlainExecutor(statementProxy, statementCallback);
                }
            } else {
                executor = new MultiExecutor(statementProxy, statementCallback, sqlRecognizers);
            }

            try {
            //核心入口执行
                T rs = ((Executor)executor).execute(args);
                return rs;
            } catch (Throwable var9) {
                Throwable ex = var9;
                if (!(var9 instanceof SQLException)) {
                    ex = new SQLException(var9);
                }

                throw (SQLException)ex;
            }
        }
    }

excute()执行

    public T execute(Object... args) throws Throwable {
    //获取事务的xid
        String xid = RootContext.getXID();
        if (xid != null) {
        //绑定xid
            this.statementProxy.getConnectionProxy().bind(xid);
        }
        //将事务绑定上全局锁
        this.statementProxy.getConnectionProxy().setGlobalLockRequire(RootContext.requireGlobalLock());
        return this.doExecute(args);
    }
    protected T executeAutoCommitTrue(Object[] args) throws Throwable {
        ConnectionProxy connectionProxy = this.statementProxy.getConnectionProxy();

        Object var3;
        try {
        	//将事务改为手动提交
            connectionProxy.changeAutoCommit();
            var3 = (new AbstractDMLBaseExecutor.LockRetryPolicy(connectionProxy)).execute(() -> {
            	//执行非自动提交
                T result = this.executeAutoCommitFalse(args);
                connectionProxy.commit();
                return result;
            });
        } catch (Exception var7) {
            LOGGER.error("execute executeAutoCommitTrue error:{}", var7.getMessage(), var7);
            if (!AbstractDMLBaseExecutor.LockRetryPolicy.isLockRetryPolicyBranchRollbackOnConflict()) {			
            	//报错将事务进行回滚
                connectionProxy.getTargetConnection().rollback();
            }

            throw var7;
        } finally {
            connectionProxy.getContext().reset();
            //将自动提交置为true
            connectionProxy.setAutoCommit(true);
        }

        return var3;
    }

    protected T executeAutoCommitFalse(Object[] args) throws Exception {
        if (!"mysql".equalsIgnoreCase(this.getDbType()) && this.isMultiPk()) {
            throw new NotSupportYetException("multi pk only support mysql!");
        } else {
        	//设置前置镜像
            TableRecords beforeImage = this.beforeImage();
            //执行业务Sql
            T result = this.statementCallback.execute(this.statementProxy.getTargetStatement(), args);
            int updateCount = this.statementProxy.getUpdateCount();
            if (updateCount > 0) {
            	//执行后置镜像
                TableRecords afterImage = this.afterImage(beforeImage);
                this.prepareUndoLog(beforeImage, afterImage);
            }

            return result;
        }
    }

执行提交

    public void commit() throws SQLException {
        try {
            this.lockRetryPolicy.execute(() -> {
                this.doCommit();
                return null;
            });
        } catch (SQLException var2) {
            if (this.targetConnection != null && !this.getAutoCommit() && !this.getContext().isAutoCommitChanged()) {
                this.rollback();
            }

            throw var2;
        } catch (Exception var3) {
            throw new SQLException(var3);
        }
    }

执行事务的提交

   private void doCommit() throws SQLException {
        if (this.context.inGlobalTransaction()) {
        	//如果是全局事务就执行此方法
            this.processGlobalTransactionCommit();
        } else if (this.context.isGlobalLockRequire()) {
			//执行全局锁的事务提交
            this.processLocalCommitWithGlobalLocks();
        } else {
        	//其他
            this.targetConnection.commit();
        }

    }

组装请求数据,发送后端

  public Long branchRegister(BranchType branchType, String resourceId, String clientId, String xid, String applicationData, String lockKeys) throws TransactionException {
        try {
            BranchRegisterRequest request = new BranchRegisterRequest();
            request.setXid(xid);
            request.setLockKey(lockKeys);
            request.setResourceId(resourceId);
            request.setBranchType(branchType);
            request.setApplicationData(applicationData);
            BranchRegisterResponse response = (BranchRegisterResponse)RmNettyRemotingClient.getInstance().sendSyncRequest(request);
            if (response.getResultCode() == ResultCode.Failed) {
                throw new RmTransactionException(response.getTransactionExceptionCode(), String.format("Response[ %s ]", response.getMsg()));
            } else {
                return response.getBranchId();
            }
        } catch (TimeoutException var9) {
            throw new RmTransactionException(TransactionExceptionCode.IO, "RPC Timeout", var9);
        } catch (RuntimeException var10) {
            throw new RmTransactionException(TransactionExceptionCode.BranchRegisterFailed, "Runtime", var10);
        }
    }

分支事务服务端的执行

在这里插入图片描述
我们首先看一下分支事务服务端注册的入口DefaultCoordinator#

    @Override
    public AbstractResultMessage onRequest(AbstractMessage request, RpcContext context) {
    //判断请求是否来自于事务的客户端
        if (!(request instanceof AbstractTransactionRequestToTC)) {
            throw new IllegalArgumentException();
        }
        AbstractTransactionRequestToTC transactionRequest = (AbstractTransactionRequestToTC) request;
        transactionRequest.setTCInboundHandler(this);
		
        return transactionRequest.handle(context);
    }

我们的核心分支事务注册代码

    @Override
    public BranchRegisterResponse handle(BranchRegisterRequest request, final RpcContext rpcContext) {
    	//创建返回的实例
        BranchRegisterResponse response = new BranchRegisterResponse();
        exceptionHandleTemplate(new AbstractCallback<BranchRegisterRequest, BranchRegisterResponse>() {
            @Override
            public void execute(BranchRegisterRequest request, BranchRegisterResponse response)
                throws TransactionException {
                try {
                	//核心分支注册实例
                    doBranchRegister(request, response, rpcContext);
                } catch (StoreException e) {
                    throw new TransactionException(TransactionExceptionCode.FailedStore, String
                        .format("branch register request failed. xid=%s, msg=%s", request.getXid(), e.getMessage()), e);
                }
            }
        }, request, response);
        return response;
    }
 @Override
    public Long branchRegister(BranchType branchType, String resourceId, String clientId, String xid,
                               String applicationData, String lockKeys) throws TransactionException {
        // key1:获取GlobalSession
        GlobalSession globalSession = assertGlobalSessionNotNull(xid, false);
        return SessionHolder.lockAndExecute(globalSession, () -> {
            // 检查事务状态
            globalSessionStatusCheck(globalSession);
            globalSession.addSessionLifecycleListener(SessionHolder.getRootSessionManager());
            //key2: 创建分支会话
            BranchSession branchSession = SessionHelper.newBranchByGlobal(globalSession, branchType, resourceId,
                    applicationData, lockKeys, clientId);
            MDC.put(RootContext.MDC_KEY_BRANCH_ID, String.valueOf(branchSession.getBranchId()));
            //key3:增加分支事务锁
            branchSessionLock(globalSession, branchSession);
            try {
                //key4: 全局会话添加分支会话
                globalSession.addBranch(branchSession);
            } catch (RuntimeException ex) {
                // key5: 出现异常释放锁
                branchSessionUnlock(branchSession);
                throw new BranchTransactionException(FailedToAddBranch, String
                        .format("Failed to store branch xid = %s branchId = %s", globalSession.getXid(),
                                branchSession.getBranchId()), ex);
            }
            if (LOGGER.isInfoEnabled()) {
                LOGGER.info("Register branch successfully, xid = {}, branchId = {}, resourceId = {} ,lockKeys = {}",
                        globalSession.getXid(), branchSession.getBranchId(), resourceId, lockKeys);
            }
            // key6: 返回分支会话的分支ID
            return branchSession.getBranchId();
        });
    }

创建封装分支事务的信息

    public static BranchSession newBranchByGlobal(GlobalSession globalSession, BranchType branchType, String resourceId,
            String applicationData, String lockKeys, String clientId) {
        BranchSession branchSession = new BranchSession();
        branchSession.setXid(globalSession.getXid());
        branchSession.setTransactionId(globalSession.getTransactionId());
        branchSession.setBranchId(UUIDGenerator.generateUUID());
        branchSession.setBranchType(branchType);
        branchSession.setResourceId(resourceId);
        branchSession.setLockKey(lockKeys);
        branchSession.setClientId(clientId);
        branchSession.setApplicationData(applicationData);

        return branchSession;
    }

分支事务加锁

protected void branchSessionLock(GlobalSession globalSession, BranchSession branchSession)
        throws TransactionException {
        //分支事务的参数校验
        String applicationData = branchSession.getApplicationData();
        boolean autoCommit = true;
        boolean skipCheckLock = false;
        if (StringUtils.isNotBlank(applicationData)) {
            if (objectMapper == null) {
                objectMapper = new ObjectMapper();
            }
            try {
                Map<String, Object> data = objectMapper.readValue(applicationData, HashMap.class);
                Object clientAutoCommit = data.get(AUTO_COMMIT);
                if (clientAutoCommit != null && !(boolean)clientAutoCommit) {
                    autoCommit = (boolean)clientAutoCommit;
                }
                Object clientSkipCheckLock = data.get(SKIP_CHECK_LOCK);
                if (clientSkipCheckLock instanceof Boolean) {
                    skipCheckLock = (boolean)clientSkipCheckLock;
                }
            } catch (IOException e) {
                LOGGER.error("failed to get application data: {}", e.getMessage(), e);
            }
        }
        try {
            // 增加分支锁,如果返回false加锁失败我们直接抛出异常
            if (!branchSession.lock(autoCommit, skipCheckLock)) {
                throw new BranchTransactionException(LockKeyConflict,
                    String.format("Global lock acquire failed xid = %s branchId = %s", globalSession.getXid(),
                        branchSession.getBranchId()));
            }
        } catch (StoreException e) {
            if (e.getCause() instanceof BranchTransactionException) {
                throw new BranchTransactionException(((BranchTransactionException)e.getCause()).getCode(),
                    String.format("Global lock acquire failed xid = %s branchId = %s", globalSession.getXid(),
                        branchSession.getBranchId()));
            }
            throw e;
        }
    }
    public boolean lock(boolean autoCommit, boolean skipCheckLock) throws TransactionException {
        // 必须还AT事务
        if (this.getBranchType().equals(BranchType.AT)) {
            return LockerManagerFactory.getLockManager().acquireLock(this, autoCommit, skipCheckLock);
        }
        return true;
    }
    public boolean acquireLock(BranchSession branchSession, boolean autoCommit, boolean skipCheckLock) throws TransactionException {
        if (branchSession == null) {
            throw new IllegalArgumentException("branchSession can't be null for memory/file locker.");
        }
        // 获取分支锁的key
        String lockKey = branchSession.getLockKey();
        if (StringUtils.isNullOrEmpty(lockKey)) {
            // no lock
            return true;
        }
        // get locks of branch
        // 行锁收集
        List<RowLock> locks = collectRowLocks(branchSession);
        if (CollectionUtils.isEmpty(locks)) {
            // no lock
            return true;
        }
        // 进行存储
        return getLocker(branchSession).acquireLock(locks, autoCommit, skipCheckLock);
    }

添加分支事务

 private void writeSession(LogOperation logOperation, SessionStorable sessionStorable) throws TransactionException {
        // 将事务信息写入数据库
        if (!transactionStoreManager.writeSession(logOperation, sessionStorable)) {
            if (LogOperation.GLOBAL_ADD.equals(logOperation)) {
                throw new GlobalTransactionException(TransactionExceptionCode.FailedWriteSession,
                    "Fail to store global session");
            } else if (LogOperation.GLOBAL_UPDATE.equals(logOperation)) {
                throw new GlobalTransactionException(TransactionExceptionCode.FailedWriteSession,
                    "Fail to update global session");
            } else if (LogOperation.GLOBAL_REMOVE.equals(logOperation)) {
                throw new GlobalTransactionException(TransactionExceptionCode.FailedWriteSession,
                    "Fail to remove global session");
            } else if (LogOperation.BRANCH_ADD.equals(logOperation)) {
                throw new BranchTransactionException(TransactionExceptionCode.FailedWriteSession,
                    "Fail to store branch session");
            } else if (LogOperation.BRANCH_UPDATE.equals(logOperation)) {
                throw new BranchTransactionException(TransactionExceptionCode.FailedWriteSession,
                    "Fail to update branch session");
            } else if (LogOperation.BRANCH_REMOVE.equals(logOperation)) {
                throw new BranchTransactionException(TransactionExceptionCode.FailedWriteSession,
                    "Fail to remove branch session");
            } else {
                throw new BranchTransactionException(TransactionExceptionCode.FailedWriteSession,
                    "Unknown LogOperation:" + logOperation.name());
            }
        }
    }

如果发生异常

    @Override
    public boolean unlock() throws TransactionException {
        if (this.getBranchType() == BranchType.AT) {
            // 释放锁
            return LockerManagerFactory.getLockManager().releaseLock(this);
        }
        return true;
    }
    public boolean releaseLock(BranchSession branchSession) throws TransactionException {
        if (branchSession == null) {
            throw new IllegalArgumentException("branchSession can't be null for memory/file locker.");
        }
        List<RowLock> locks = collectRowLocks(branchSession);
        try {
            // 释放锁
            return getLocker(branchSession).releaseLock(locks);
        } catch (Exception t) {
            LOGGER.error("unLock error, branchSession:{}", branchSession, t);
            return false;
        }
    }
  public boolean releaseLock(List<RowLock> locks) {
        if (CollectionUtils.isEmpty(locks)) {
            // no lock
            return true;
        }
        try {
            return lockStore.unLock(convertToLockDO(locks));
        } catch (StoreException e) {
            throw e;
        } catch (Exception t) {
            LOGGER.error("unLock error, locks:{}", CollectionUtils.toString(locks), t);
            return false;
        }
    }

将数据库的锁删除

private static final String BATCH_DELETE_LOCK_SQL = "delete from " + LOCK_TABLE_PLACE_HOLD
        + " where " + ServerTableColumnsName.LOCK_TABLE_XID + " = ? and (" + LOCK_TABLE_PK_WHERE_CONDITION_PLACE_HOLD + ") ";

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

GPT-4o一夜被赶超,Claude 3.5一夜封王|快手可灵大模型推出图生视频功能|“纯血”鸿蒙大战苹果AI|智谱AI“钱途”黯淡|月之暗面被曝进军美国

快手可灵大模型推出图生视频功能“纯血”鸿蒙大战苹果AI&#xff0c;华为成败在此一举大模型低价火拼间&#xff0c;智谱AI“钱途”黯淡手握新“王者”&#xff0c;腾讯又跟渠道干上了“美食荒漠”杭州&#xff0c;走出一个餐饮IPOGPT-4o一夜被赶超&#xff0c;Anthropic推出Cl…

Rocky Linux archive下载地址

Index of /vault/rocky/https://dl.rockylinux.org/vault/rocky/

利口 202. 快乐数

力扣 202. 快乐数 编写一个算法来判断一个数 n 是不是快乐数。 「快乐数」 定义为&#xff1a; 对于一个正整数&#xff0c;每一次将该数替换为它每个位置上的数字的平方和。然后重复这个过程直到这个数变为 1&#xff0c;也可能是 无限循环 但始终变不到 1。如果这个过程 结…

猫头虎分享已解决Bug || Null Pointer Exception: `java.lang.NullPointerException`

猫头虎分享已解决Bug || Null Pointer Exception: java.lang.NullPointerException &#x1f63a;&#x1f42f; 关于猫头虎 大家好&#xff0c;我是猫头虎&#xff0c;别名猫头虎博主&#xff0c;擅长的技术领域包括云原生、前端、后端、运维和AI。我的博客主要分享技术教程…

营业性演出许可证:直播行业规范与繁荣的关键

随着互联网和直播行业的迅猛发展&#xff0c;营业性演出许可证的重要性愈加凸显。《网络表演经纪机构管理办法》明确指出&#xff0c;任何形式的盈利性演出活动&#xff0c;无论是线下还是线上&#xff0c;都必须取得营业性演出许可证。这一规定为行业规范提供了法律基础&#…

钓鱼隐藏--文件后缀压缩文件捆绑文件

免责声明:本文仅做技术交流与学习... 目录 文件后缀-钓鱼伪装-RLO 压缩文件-自解压-释放执行 捆绑文件-打包加载-释放执行 文件后缀-钓鱼伪装-RLO 改后缀--伪装 w.exe wgpj.exe (要改的后缀反写)(jpg--->gpj) | (光标移到要改的后缀的前边)(w和g中间) …

idea导入文件里面的子模块maven未识别处理解决办法

1、File → Project Structure → 点击“Modules” → 点击“” → “Import Model” 2、可以看到很多子模块&#xff0c;选择子模块下的 pom.xml 文件导入一个一个点累死了&#xff0c;父目录下也没有pom文件 解决办法&#xff1a;找到子模块中有一个pom.xml文件&#xff0c;…

CentOS9镜像下载地址加速下载

CentOS 9 是 CentOS 项目的最新版本之一&#xff0c;它基于 RHEL&#xff08;Red Hat Enterprise Linux&#xff09;9 的源代码构建。CentOS&#xff08;Community ENTerprise Operating System&#xff09;是一个免费的企业级 Linux 发行版&#xff0c;旨在提供一个与 RHEL 兼…

基于YOLOv5的PCB板缺陷检测系统的设计与实现

简介 随着电子设备的广泛应用,PCB(印刷电路板)作为其核心部件,其质量和可靠性至关重要。然而,PCB生产过程中常常会出现各种缺陷,如鼠咬伤、开路、短路、杂散、伪铜等。这些缺陷可能导致设备故障,甚至引发严重的安全问题。为了提高PCB检测的效率和准确性,我们基于YOLOv…

【C语言】操作符(上)

目录 1. 操作符的分类 2. 原码、反码、补码 3. 移位操作符 3.1 左移操作符 3.2 右移操作符 4. 位操作符&#xff1a;&、|、^、~ 5. 单目操作符 6. 逗号表达式 最近准备期末考试&#xff0c;好久不见啦&#xff0c;现在回归—— 正文开始—— 1. …

基于CPWM与DPWM综合调制的光伏逆变器

1. 光伏并网逆变器矢量控制 图 1 为光伏发电系统常用的逆变器拓扑结 构,太阳能光伏电池板发电所产生的直流电能接 入光伏并网逆变器直流侧。逆变器将电能逆变, 经过滤波器与隔离升压变压器连接,最终并入电 网。其中隔离变压器低压侧漏感与LC滤波器组 成LCL滤波。为便于分析…

C语言小例程

题目&#xff1a;两个乒乓球队进行比赛&#xff0c;各出三人。甲队为a,b,c三人&#xff0c;乙队为x,y,z三人。已抽签决定比赛名单。有人向队员打听比赛的名单。a说他不和x比&#xff0c;c说他不和x,z比&#xff0c;请编程序找出三队赛手的名单。 #include <stdio.h> #in…

Windows11系统自动获取电脑IPV6地址,并且开机自动发送到指定邮箱

废话&#xff1a;最近放假回家&#xff0c;在家里突然想玩游戏了&#xff0c;Steamdeck性能终归有限。部分游戏始终玩的不爽&#xff0c;想到之前了解到的SunshnieMoonlight串流的方案&#xff0c;远程调用家里的电脑打游戏&#xff0c;简直不要太爽。 一顿折腾之后配置好了所有…

算法05 模拟算法之二维数组相关内容详解【C++实现】

大家好&#xff0c;我是bigbigli&#xff0c;前面一节我们一节讲过一维数组的模拟了&#xff0c;如果还没看的话&#xff0c;可以&#x1f449;点击此处。模拟算法还有很多内容需要讲&#xff0c;比如图像、日期相关的模拟算法&#xff0c;后续将继续更新&#xff0c;今天先来讲…

C语言| 数组的顺序查找

顺序查找 查找数组a中第一次出现数字m的下标&#xff0c;并输出该下标&#xff1b; 如果没有则输出sorry。 1 定义变量 数组a&#xff0c;n表示数组的个数&#xff0c; m要查找的数字 2 用sizeof()函数&#xff0c;求出数组元素的个数 3 从键盘中任意输出一个数字m&#xff0c;…

大疆炸机后MOV修复方法(DJI Inspire 3)

dji大疆可以说是无人机中的华为&#xff0c;产品线之广性能之高让高傲的美国人侧面&#xff0c;质量和性价比才是王道。另外产品线的细分也是制胜法宝&#xff0c;无论是手持、农用机、特殊无人机还是影视级产品DJI都有涉及&#xff0c;给人的感觉就是在无人机细分方面它已经无…

信号基本分析方法——频域分析

二、频域分析 随机信号的时域分析只能提供有限的时域故障特征信息&#xff0c;故障发生时往往会引起信号频率结构的变化&#xff0c;而故障频率可以计算和预知&#xff0c;通过检测频率的幅值变换规律&#xff0c;就可以监控故障的发展过程。 频谱分析的理论基础是傅里叶变换…

支持 MKV、MP4、AVI、MPG 等格式视频转码器

一、简介 1、一款开源的视频转码器&#xff0c;适用于 Linux、Mac 和 Windows。它是一个免费的工具&#xff0c;由志愿者们开发&#xff0c;可以将几乎所有格式的视频转换为现代、广泛支持的编码格式。你可以在官网上下载该应用或源代码。该软件支持 MKV、MP4、AVI、MPG 等格式…

Graphviz——实现动态更新协议状态机

1、描述 为了实现动态更新协议状态机&#xff0c;首先需要定义类来表示协议状态机。初始化该类后&#xff0c;保存状态机对象。在后续更新过程中&#xff0c;就可以加载保存的状态机对象&#xff0c;添加新的状态或事件。Graphviz的安装过程参考&#xff1a;Graphviz——安装、…

PCL 三次样条插值(二维点)

一、简介 在插值计算中,最简单的分段多项式近似应该是分段线性插值,它由连接一组数据点组成,仅仅只需要将这些点一一用直线进行顺序相连即可。不过线性函数插值的缺点也很明显,就是在两个子区间变化的比较突兀,也就是没有可微性(不够光滑)。因此我们需要更为符合物理情况…