Fescar 全局锁介绍

简介: 开篇 这篇文章的目的主要是讲解TC的在处理分支事务注册过程中对全局锁的处理流程,理解了全局锁以后才能明白对DB同一个记录进行多次变更是如何解决的。 如上图所示,问最终全局事务A对资源R1应该回滚到哪种状态?很明显,如果再根据UndoLog去做回滚,就会发生严重问题:覆盖了全局事务B对资源R1的变更。

开篇

 这篇文章的目的主要是讲解TC的在处理分支事务注册过程中对全局锁的处理流程,理解了全局锁以后才能明白对DB同一个记录进行多次变更是如何解决的。

image

 如上图所示,问最终全局事务A对资源R1应该回滚到哪种状态?很明显,如果再根据UndoLog去做回滚,就会发生严重问题:覆盖了全局事务B对资源R1的变更。
 那Fescar是如何解决这个问题呢?答案就是 Fescar的全局写排它锁解决方案,在全局事务A执行过程中全局事务B会因为获取不到全局锁而处于等待状态。


Fescar 全局锁处理流程

RM 尝试获取全局锁

public class ConnectionProxy extends AbstractConnectionProxy {
    public void commit() throws SQLException {
        if (context.inGlobalTransaction()) {
            try {
                // 1、向TC发起注册操作并检查是否能够获取全局锁
                register();
            } catch (TransactionException e) {
                recognizeLockKeyConflictException(e);
            }

            try {
                if (context.hasUndoLog()) {
                    UndoLogManager.flushUndoLogs(this);
                }
                // 2、执行本地的事务的commit操作
                targetConnection.commit();
            } catch (Throwable ex) {
                report(false);
                if (ex instanceof SQLException) {
                    throw (SQLException) ex;
                } else {
                    throw new SQLException(ex);
                }
            }
            report(true);
            context.reset();

        } else {
            targetConnection.commit();
        }
    }

    private void register() throws TransactionException {
        Long branchId = DataSourceManager.get().branchRegister(
                BranchType.AT, getDataSourceProxy().getResourceId(),
                null, context.getXid(), context.buildLockKeys());
        context.setBranchId(branchId);
    }
}

说明:

  • RM 执行本地事务提交操作在ConnectionProxy的commit()完成。
  • commit()的过程当中按照register->commit的流程执行。
  • register()过程RM会向TC发起注册请求判断是否能够获取全局锁。
  • register()通过DataSourceManager的branchRegister()操作完成。


TC处理全局锁申请流程

public class DefaultCore implements Core {

    protected void doBranchRegister(BranchRegisterRequest request, BranchRegisterResponse response,
                                    RpcContext rpcContext) throws TransactionException {
        response.setTransactionId(request.getTransactionId());
        response.setBranchId(
            core.branchRegister(request.getBranchType(), request.getResourceId(), rpcContext.getClientId(),
                XID.generateXID(request.getTransactionId()), request.getLockKey()));

    }

    public Long branchRegister(BranchType branchType, String resourceId, 
                 String clientId, String xid, String lockKeys) throws TransactionException {
        GlobalSession globalSession = assertGlobalSession(XID.getTransactionId(xid), GlobalStatus.Begin);

        BranchSession branchSession = new BranchSession();
        branchSession.setTransactionId(XID.getTransactionId(xid));
        branchSession.setBranchId(UUIDGenerator.generateUUID());
        branchSession.setApplicationId(globalSession.getApplicationId());
        branchSession.setTxServiceGroup(globalSession.getTransactionServiceGroup());
        branchSession.setBranchType(branchType);
        branchSession.setResourceId(resourceId);
        branchSession.setLockKey(lockKeys);
        branchSession.setClientId(clientId);

        // 判断branchSession是否能够获取锁
        if (!branchSession.lock()) {
            throw new TransactionException(LockKeyConflict);
        }
        try {
            globalSession.addBranch(branchSession);
        } catch (RuntimeException ex) {
            throw new TransactionException(FailedToAddBranch);

        }
        return branchSession.getBranchId();
    }

    public boolean lock() throws TransactionException {
        return LockManagerFactory.get().acquireLock(this);
    }

}

说明:

  • TC 在处理branchRegister()的过程中会判断branchResiter请求携带的session信息能否获取全局锁。
  • branchSession.lock()判断能否获取锁,如果获取失败则抛出TransactionException(LockKeyConflict)异常。
  • branchSession.lock()判断能否获取锁,如果获取成功则将branchSession添加到全局锁当中。


TC 判断全局锁分配流程

public class DefaultLockManagerImpl implements LockManager {

    public boolean acquireLock(BranchSession branchSession) throws TransactionException {
        String resourceId = branchSession.getResourceId();
        long transactionId = branchSession.getTransactionId();
        //1、根据resourceId去LOCK_MAP获取,获取失败则新增一个空的对象。
        ConcurrentHashMap<String, ConcurrentHashMap<Integer, Map<String, Long>>> dbLockMap = LOCK_MAP.get(resourceId);
        if (dbLockMap == null) {
            LOCK_MAP.putIfAbsent(resourceId, new ConcurrentHashMap<String, ConcurrentHashMap<Integer, Map<String, Long>>>());
            dbLockMap = LOCK_MAP.get(resourceId);
        }


        ConcurrentHashMap<Map<String, Long>, Set<String>> bucketHolder = branchSession.getLockHolder();
        
        // 2、获取branchSession的全局锁的key对象
        String lockKey = branchSession.getLockKey();
        if(StringUtils.isEmpty(lockKey)) {
            return true;
        }
        
        // 3、按照分号“;”切割多个LockKey,每个LockKey按照table:pk1;pk2;pk3格式组装。
        String[] tableGroupedLockKeys = lockKey.split(";");
        for (String tableGroupedLockKey : tableGroupedLockKeys) {
            int idx = tableGroupedLockKey.indexOf(":");
            if (idx < 0) {
                branchSession.unlock();
                throw new ShouldNeverHappenException("Wrong format of LOCK KEYS: " + branchSession.getLockKey());
            }
            // 4、分割获取branchRegister请求的表名和pks。
            String tableName = tableGroupedLockKey.substring(0, idx);
            String mergedPKs = tableGroupedLockKey.substring(idx + 1);
            // 5、获取表下的已经加锁的记录tableLockMap 
            ConcurrentHashMap<Integer, Map<String, Long>> tableLockMap = dbLockMap.get(tableName);
            if (tableLockMap == null) {
                dbLockMap.putIfAbsent(tableName, new ConcurrentHashMap<Integer, Map<String, Long>>());
                tableLockMap = dbLockMap.get(tableName);
            }
            // 6、遍历该表所有pks判断是否已加锁。
            String[] pks = mergedPKs.split(",");
            for (String pk : pks) {
                // 7、同一个表的pk按照hash值进行hash分配到tableLockMap当中。
                int bucketId = pk.hashCode() % BUCKET_PER_TABLE;
                Map<String, Long> bucketLockMap = tableLockMap.get(bucketId);
                if (bucketLockMap == null) {
                    tableLockMap.putIfAbsent(bucketId, new HashMap<String, Long>());
                    bucketLockMap = tableLockMap.get(bucketId);
                }
                // 8、根据pk去获取bucketLockMap当中获取锁对象。
                synchronized (bucketLockMap) {
                    Long lockingTransactionId = bucketLockMap.get(pk);
                    if (lockingTransactionId == null) {
                        // No existing lock
                        // 9、将锁添加到branchSession当中
                        bucketLockMap.put(pk, transactionId);
                        Set<String> keysInHolder = bucketHolder.get(bucketLockMap);
                        if (keysInHolder == null) {
                            bucketHolder.putIfAbsent(bucketLockMap, new ConcurrentSet<String>());
                            keysInHolder = bucketHolder.get(bucketLockMap);
                        }
                        keysInHolder.add(pk);

                    } else if (lockingTransactionId.longValue() == transactionId) {
                        // Locked by me
                        continue;
                    } else {
                        // 直接返回异常
                        LOGGER.info("Global lock on [" + tableName + ":" + pk + "] is holding by " + lockingTransactionId);
                        branchSession.unlock(); // Release all acquired locks.
                        return false;
                    }
                }
            }
        }
        return true;
    }
}

说明:

  • TC 判断branchRegister操作能否获取锁按照维度层层递进判断。
  • 1、先从ResourceId的维度进行判断, LOCK_MAP.get(resourceId)。
  • 2、再从tableName的维度进行判断, dbLockMap.get(tableName)。
  • 3、再从pk的hashcode的维度进行判断,tableLockMap.get(bucketId)。
  • 4、在从pk的维度进行判断,bucketLockMap.get(pk)。
  • 5、如果按照上述维度判断后未存在加锁的branchSession就返回能够加锁,否则返回不能加锁。
  • 6、判断加锁过程中处理了幂等,如果自己已经加锁了可以再次获取锁。


TM 全局锁维护数据结构

private static final ConcurrentHashMap<String, 
                     ConcurrentHashMap<String, 
                     ConcurrentHashMap<Integer, 
                     Map<String, Long>>>> LOCK_MAP = 
                     new ConcurrentHashMap<String, 
                         ConcurrentHashMap<String, 
                        ConcurrentHashMap<Integer, 
                        Map<String, Long>>>>();

说明:

  • LOCK_MAP 作为TC全局锁的保存结构。
  • 第一层ConcurrentHashMap的key为resourceId。
  • 第二层ConcurrentHashMap的key为tableName。
  • 第三层ConcurrentHashMap的key为pk的hashCode。
  • 第四层的Map的key为pk,value为transactionId(RM携带过来)。


参考文章


Fescar源码分析连载

目录
相关文章
|
存储 自然语言处理 搜索推荐
ChatGPT 文本Embedding融合Qdrant向量数据库:构建智能问答系统的技术探索
向量数据库结合ChatGPT带来了什么 1. **语义搜索:** 使用向量数据库进行语义搜索,可以更准确地找到与查询相关的信息。ChatGPT可以理解用户的自然语言查询,而向量数据库可以根据语义相似性返回匹配的向量数据。 2. **智能推荐:** 结合ChatGPT的智能理解和向量数据库的相似性搜索,可以实现更智能的推荐系统。系统可以根据用户的历史行为和语境,向用户推荐相似的向量数据,如文章、产品或其他内容。 3. **自然语言处理与向量表示结合:** ChatGPT可以将自然语言转换为向量表示,这样就可以在向量数据库中进行更高效的查询。这种集成使得自然语言处理和向量数据库可以相互补充等
1371 0
ChatGPT 文本Embedding融合Qdrant向量数据库:构建智能问答系统的技术探索
|
自然语言处理 安全 Linux
一文详解远程控制安全与远程控制软件(横测ToDesk\AnyDesk\向日葵)
一文详解远程控制安全与远程控制软件(横测ToDesk\AnyDesk\向日葵)
1784 0
一文详解远程控制安全与远程控制软件(横测ToDesk\AnyDesk\向日葵)
|
3月前
|
存储 人工智能 运维
新版无影云电脑全功能解析:AI 智能体、运维管控与 API 实操教程
随着远程办公、研发安全沙箱、AI智能体运行环境的业务需求持续增长,传统物理PC和老旧云桌面方案逐渐暴露出硬件迭代慢、批量运维繁琐、数据泄露风险高、AI任务算力不足等一系列痛点。新版无影云电脑作为一套完整的云端桌面服务平台,重构底层调度架构,在原有云桌面基础之上新增AI智能体运行环境、批量运维能力、镜像在线制作、精细化安全策略、多终端体验优化以及完备开放API体系,既能够满足企业大规模办公桌面交付,也可以作为Agent程序7×24小时不间断运行的专属算力容器,兼顾普通办公、图形设计、程序开发、AI自动化任务等多元化业务场景。整套平台将计算、存储全部放置云端,本地终端仅负责画面渲染与输入输出,原始
360 0
|
5月前
|
人工智能 运维 JavaScript
阿里云无影云电脑部署OpenClaw与Hermes Agent +百炼Token Plan简易配置流程
随着AI智能体技术的普及,轻量化AI Agent框架结合大模型服务的使用场景愈发广泛。阿里云无影云电脑依托云端桌面形态,无需本地硬件支撑、环境隔离安全、即开即用,区别于传统ECS云服务器,操作逻辑更贴近个人电脑,对于不熟悉纯命令行运维的使用者十分友好,是部署各类应用程序与AI服务的优质载体。
800 2
|
JavaScript 开发者 索引
鸿蒙应用开发从入门到实战(九):ArkTS渲染控制
ArkTS拓展了TypeScript,可以结合ArkUI进行渲染控制,是的界面设计具有可编程性。本文简要描述鸿蒙应用开发中的条件渲染和循环渲染。
483 5
|
7月前
|
存储 安全 物联网
系统科学与哲学方法论
本大纲系统整合系统科学(老三论、新三论、复杂系统论)与唯物辩证法核心原理(对立统一、内外因、主次矛盾等),强调实践论指导,揭示系统演化共性规律,为系统规划与数字化转型提供哲学方法论支撑。
604 1
|
7月前
|
存储 监控 安全
Anthropic Managed Agents:把智能体从“聪明脚本”重构成“可编排系统”
Managed Agents 并非给模型“加功能”,而是对智能体进行系统级重构:将大脑(决策)、手(执行)、记忆(状态)解耦为独立可调度角色,告别单体式脚本设计。它让智能体真正具备可管理、可扩展、可编排的系统属性,成为生产环境中的一等公民。
843 2
|
人工智能 监控 安全
数字化矿山---推动矿山行业数字化转型
数字化矿山通过AI、物联网等技术推动矿业转型,实现生产可视化、管理智能化,构建三维地质模型与七大监测系统,提升安全、环保与效率,助力行业可持续发展。
|
10月前
|
JSON 监控 API
印度尼西亚(IDX)股票数据对接开发
本接口文档助力开发者快速接入印尼证券交易所(IDX)实时行情。通过`countryId=48`,可获取股票列表、指数、IPO日历、K线及公司基本面等全量金融数据,支持多周期K线与实时推送,一键打通东南亚市场投资洞察。
|
10月前
|
前端开发 CDN
微信小游戏的开发费用
微信小游戏开发费用因美术、玩法等差异大,轻量级3-8万,精品中度10-35万,重度游戏达50万以上。另需考虑软著、版号、服务器及买量等硬成本,运营投入常超开发。2026年混合变现与首发激励成新趋势。#微信游戏 #游戏外包