news 2026/10/22 3:41:02

Seata AT模式原理与实战:从全局锁到undo_log的分布式事务方案

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Seata AT模式原理与实战:从全局锁到undo_log的分布式事务方案

1. 分布式事务的痛点,以及Seata AT为何成了“标配选择”

1.1 跨服务数据一致性:那个让无数人深夜失眠的问题

标题里这个“夭”字,我第一眼看到时愣了一下,结合上下文猜了很久,大概率是“探究”的意思,也可能就是口语里“呀”的误打。不管哪种,这篇我打算认真把Seata的AT模式拆开揉碎聊一遍,因为它确实是我这几年微服务架构里用得最多、也踩坑最多的分布式事务方案,没有之一。

先说说痛点。单体应用时代,一个订单从创建、扣库存、扣余额到发消息,都在同一个数据库事务里完成,要么全成功,要么全回滚,ACID由数据库保证。但服务化拆开以后,订单服务在A库,库存服务在B库,余额服务在C库,跨库跨服务的数据一致性立刻变成硬骨头。消息队列能做最终一致性,但“最终”这两个字在业务高峰期往往意味着脏数据可见、对账复杂、投诉不断。两阶段提交(XA)能做强一致,但XA对数据库资源的锁定时间长、协调器单点、性能损耗大,很多团队在压测阶段就放弃了。

Seata的AT模式之所以成为很多团队的默认选择,核心原因是它在“尽量不侵入业务代码”的前提下,把分布式事务包装成了“像本地事务一样使用”。业务方只需要在入口方法上打一个@GlobalTransactional注解,剩下的全局事务协调、分支事务注册、回滚日志生成、反向SQL补偿,框架全部代劳。对于已经跑了好几年的老项目,只要数据源能代理,改造量可以压缩到很小。

1.2 AT模式的设计出发点:把分布式事务做得像本地事务

要理解AT模式,必须先接受它“乐观加补偿”的思想。所谓AT,是Automatic Transaction的缩写,自动事务。它和TCC最大的区别在于:TCC要求业务方手工写Try、Confirm、Cancel三组代码,事务的每一个分支都要自行控制资源预备、确认和释放;AT模式则把这一套机制收敛到数据源代理和SQL解析层里,业务代码不需要感知“我正在参与一个分布式事务”。

这里的关键就在于Seata对数据源做了代理。当你在Spring配置里把DataSource替换成DataSourceProxy之后,所有经过MyBatis、JPA或者其他ORM框架执行的SQL,Seata都能在连接层面拦截到。它会在业务SQL真正执行之前,先查询当前数据行的镜像(Before Image),执行业务SQL之后,再查出新的数据行镜像(After Image),把这两份镜像连同SQL信息一起写入到undo_log表里,作为回滚的依据。

听起来简单,但这个机制里藏了好几个关键设计,后面会详细展开。总之,从使用角度讲,AT模式让人觉得“爽”的地方在于:你不需要自己维护事务状态表,不需要写补偿代码,调用方看起来就是一个普普通通的事务方法。从架构角度讲,它解决的不仅是“跨库事务怎么做”,还有“旧系统怎么低成本接入分布式事务”的问题。

2. AT模式的核心执行链路:从一条SQL到一次全局事务

2.1 一阶段:本地事务 + 全局锁 + undo_log的三角配合

AT模式的一次完整全局事务,被拆成两个阶段。一阶段的主要目标,是把所有分支事务的本地事务全部提交,同时保证必要的隔离性。这个阶段里,全局事务管理器会生成一个全局唯一的事务ID(XID),并通过调用链传递给所有参与服务。

参与者拿到XID之后,在各自的服务里执行本地业务SQL。先注意一个容易忽略的下面的操作:执行SQL之前,Seata会先向TC(Transaction Coordinator)注册分支事务,TC会返回一个分支ID。有了全局事务ID和分支ID,后续才能做统一的提交回滚决策。

具体到一条UPDATE语句的执行过程,Seata做了四步事情:

  1. 解析SQL,获取表名、主键、条件、更新字段等元数据。
  2. 根据SQL条件执行SELECT查询,获得Before Image,也就是数据修改前的完整行快照。
  3. 执行业务方自己写的UPDATE语句,真正修改数据。
  4. 再次查询该行数据,得到After Image,也就是修改后的行快照。

然后把Before Image、After Image以及SQL类型、表名、条件等元数据序列化,写入到当前本地事务里的undo_log表中。注意这里的关键点:undo_log的写入操作必须和业务SQL在同一个本地事务里,只有这样才能保证“业务改了数据,就一定有回滚日志”的原子性。如果undo_log写入失败,本地事务直接回滚,不让业务SQL生效。

同时,Seata还会在修改数据行时获得一把全局锁。全局锁是Seata在数据库层面模拟出来的分布式锁,锁的粒度是“行”,锁的持有者是当前全局事务的XID。任何其他全局事务要修改同一行数据,都必须先检查这行的全局锁是否被别人持有,如果持有,就需要等待。

一阶段结束的时候,Seata会把分支事务的状态上报给TC,本地事务直接提交。是的,你没看错,AT模式的一阶段就完成了本地事务提交,数据已经真实落库了,不用像XA那样在提交前一直锁着资源。这是AT模式性能优于XA的关键原因之一,但也正是这个设计,带来了后续隔离性上的一系列麻烦。

2.2 二阶段提交:删中间态,动作轻得像无事发生

当整个全局事务的所有分支事务都成功完成一阶段提交后,TC会通知所有参与者执行二阶段提交。这个阶段的处理方式非常简单,几乎没有什么业务成本:Seata会异步删除当前分支事务之前写入的undo_log记录。

为什么是删除日志而不是再做别的?因为一阶段已经把数据真正提交了,全局事务成功时不需要再做任何数据恢复操作。undo_log只在回滚时才有价值,既然不需要回滚,留着只会占用存储空间、增加后续查询负担,所以直接清理掉。

这里有个值得注意的细节:删除undo_log是异步批量的。如果全局事务的并发量很大,每个事务一阶段都要写日志,二阶段如果同步一条一条删,会对TC和数据库造成额外压力。异步删除的思路是把“提交”这个动作的成本压到最低,二阶段永远比一阶段轻,这就是所谓的“提交轻量”。

我自己在生产中遇到过一种特殊情况:全局事务已经成功提交,但异步清理undo_log还没来得及完成,此时如果业务上有定时任务去扫这个表做分析,可能会看到残留的回滚日志。这不会影响正确性,因为回滚日志不会参与业务查询,但会让对数据敏感的同学产生困惑。所以我通常会建议团队在数据库侧对undo_log表做定期归档或者和业务表物理隔离,别让它和核心业务表放在同一个高优存储里。

2.3 二阶段回滚:利用undo_log做逆向补偿

如果全局事务中任何一个分支在一阶段失败,或者全局事务超时,TC就会给所有已经提交本地事务的参与者发送回滚指令。这里就有意思了:分支事务的本地事务已经提交了,数据已经改了,怎么回滚?

答案就在那份undo_log里。Seata拿到回滚指令后,会执行以下几步:

  1. 根据XID和分支ID找到对应的undo_log记录。
  2. 校验当前数据是否还是After Image。这一步至关重要,稍后详细解释为什么。
  3. 用Before Image里的数据,生成一条反向的UPDATE语句,把数据恢复成修改前的样子。
  4. 在恢复完成后,删除undo_log记录,并向TC上报回滚成功。

第二步的校验是整个回滚流程的防线。举例说明:如果事务A修改了某行数据,把金额从100改成50,生成了After Image为50的记录。在事务A回滚之前,事务B(不在同一个全局事务里,或者是一个本地事务)又把这行金额从50改成了80,那么当前数据库的值是80,既不是After Image的50,也不是Before Image的100。这种情况下,Seata如果强行把数据改成100,就把事务B的修改覆盖掉了,造成数据丢失。所以Seata必须先读当前值,和After Image对比,一致才允许回滚;不一致说明数据被别人改过,无法安全回滚,需要人工介入或者走其他降级方案。

这一层校验,是AT模式“默认信任数据库大多数情况下不会同时多人改同一行”的乐观假设的体现。如果冲突频繁,AT模式就会频繁校验失败,回滚不了,业务就会体验很差。所以,AT模式并不适合高冲突热点的场景,比如“超高并发抢同一个库存扣减”,这类业务更适合TCC或者最终一致性方案。

需要特别提醒的是:undo_log表的结构每个社区版本可能略有差异,但核心字段大致是这样:branch_id(分支事务ID)、xid(全局事务ID)、rollback_info(JSON序列化后的镜像数据)、log_status(日志状态,0正常,1全局已完成)、log_created、log_modified。这个表没有硬性的主键约束,但强烈建议在branch_id上建唯一索引,因为回滚时查找日志是以分支ID为关键条件的,没有索引在大量回滚时会拖垮数据库。

3. 隔离性与一致性:AT模式下那些容易踩的坑

3.1 读隔离问题:为什么脏读会绕过全局锁

聊完主流程,进入大部分团队真正头疼的部分,隔离性。AT模式在“写隔离”上做了比较完善的设计,但在“读隔离”上,它默认提供的是读已提交(RC)级别,而很多业务团队在迁移前用的是可重复读(RR),这就很容易踩坑。

先理解读隔离的问题从哪来。在AT模式中,一阶段本地事务提交后,数据是真实可见的。虽然写数据时Seata加了全局锁,能挡住其他全局事务的写操作,但挡不住普通本地事务的读操作,也挡不住其他没有经过Seata代理的查询语句。比如事务A扣减了库存但整个全局事务还没结束,另一个服务直接查数据库看到的已经是扣减后的库存。从全局事务的角度看,这个数据其实只有一个“未提交的中间态”,但被外部读到就是脏读。

如果业务上能接受这种短期的读已提交,那么AT模式是够用的。如果业务有强一致性的读需求,比如一个人发起订单后,前端要立刻看到“扣款成功、库存锁定”的最终结果,那AT模式就扛不住这个场景。解决办法通常有两种:一种是在查询端也走Seata代理并开启@GlobalTransactional,在全局事务内通过SELECT FOR UPDATE来读取最新数据,让读操作也被全局锁约束;另一种是业务上接受“最终一致性”,等全局事务完成后通过消息通知、刷新等机制更新页面。

坦白讲,在实际项目中,大多数读场景并不会把几个服务的数据强一致地摆在一个查询里,所以AT模式的读隔离短板大多数时候是可以容忍的。但你必须知道这个边界在哪里,否则等线上出了诡异数据才发现,就晚了。

3.2 写隔离问题:全局锁与本地锁的竞争

写隔离这块,AT模式用的是“全局锁 + 本地锁”两层设计。本地锁就是数据库自己的行锁,保证同一行数据不能被两个本地事务同时修改;全局锁则是Seata在TC层面维护的一把锁,保证同一行数据在同一时间只能被一个全局事务修改。

把这两个锁放到一起看,很有意思。假设全局事务A先修改了行X,持有了行X的全局锁,随后它的本地事务提交了。此时另一个全局事务B也要修改行X,B的写操作会先尝试获取全局锁,发现X被A持有,于是B本地事务的写入会被阻塞等待。注意,这里数据库的行锁在A提交后已经释放了,B在本地执行UPDATE其实是能立刻执行成功的,真正让B等的是Seata的全局锁检查。

全局锁的等待是有超时时间的,默认配置里lock.retry.internal是10毫秒,lock.retry.times是30次。也就是说,B最多等上300毫秒左右,等不到就抛异常,让B所在的全局事务回滚。这个超时配置在业务高峰时要特别关注,默认值对高并发场景是不够的。

另一个容易被忽视的问题是:如果B是一个没有经过Seata代理的普通本地事务,它就感知不到全局锁的存在,照样能修改行X。这就会造成一种情况:AT模式里出现了一个“漏网”的写操作,把全局事务的中间态数据给改了,后续二阶段回滚时After Image校验就大概率失败,导致无法回滚。所以,凡是参与分布式事务链路的数据源,必须确保所有入口都走Seata代理,包括定时任务、消息消费、管理后台的批量修改。团队里经常出现“核心交易没问题,后台改单数据乱了”的情况,根因就在这里。

3.3 幂等与悬挂:两个必须提前想清楚的问题

处理分布式事务的同学,对“幂等”这个词肯定不陌生。在AT模式里,二阶段提交的回滚指令、重试机制,都可能让同一个分支事务被重复处理。虽然Seata在内部用分支状态做了去重,但如果业务系统自己对“同一笔业务操作”没有幂等保护,在极端情况下(比如网络重发、TC重启后重放)还是会出现重复扣款、重复发货等问题。

我的建议是:不要在@GlobalTransactional方法内部依赖Seata的幂等,业务侧必须自己为关键操作设计幂等机制。最常用的做法是维护一张业务幂等表,用全局事务ID或者业务唯一键做唯一约束,同一个XID下的操作只能成功一次。Seata的全局事务ID本身就适合承担这个“业务唯一键”的角色,前提是你愿意把一个技术框架的ID和业务语义绑定,这需要评估业务上的可接受性。

“悬挂”这个问题很多人到了生产事故复盘时才知道。所谓悬挂,简单说就是分支事务在一阶段没执行成功,但TC因为网络原因已经超时,给其他分支发了回滚指令。这种情况下,如果之前卡住的请求后来又悄无声息地把数据提交了,就会产生一个“已经回滚了但数据又变了”的状态。Seata的AT模式虽然会记录分支状态,但在网络分区、进程假死等复杂场景下,还是可能出现悬挂。这类问题没有银弹,能做的就是把超时时间、重试参数调到一个相对合适的值,并在关键数据表上做针对性的对账扫描,通过脚本去发现“应该一致但实际不一致”的数据。

写到这里,我必须说一句:凡是分布式事务方案,都不可能做到像单机数据库事务那样完美。AT模式用可接受的隔离级别和一定的中间态容忍度,换来了业务代码的低侵入和相对高的性能。你要做的是清楚它的假设,再在业务关键性最高的路径上做额外保护。

4. 与TCC、XA、Saga的对比:AT模式的取舍

4.1 各种方案的核心区别与适用面

很多同学问过我,Seata里既然有AT、TCC、Saga、XA好几种模式,到底怎么选?我的理解是这样的:XA是数据库层的两阶段提交,强一致,但锁时间太长,性能成本高;TCC是业务层的两阶段,需要手写Try、Confirm、Cancel,在一阶段做资源预留,能比较好的控制隔离性,但业务侵入最大,开发成本最高;Saga是长事务模式,通过一串子事务和一个可选的逆向补偿步骤链来处理复杂流程,适合流程长、独立性强、中间状态多的业务;AT则是把“自动生成回滚日志 + 代理数据源 + 全局锁”组合在一起,是最接近本地事务体验的分布式事务模式。

从性能上讲,AT模式一阶段就释放了数据库本地锁,比起XA“两阶段全程持锁”的性能优势非常明显。从开发成本上讲,AT模式几乎不需要业务方写额外代码,而TCC每一组操作都要设计三套方法,还要处理Confirm和Cancel的幂等、空回滚等细节。从一致性强度上讲,TCC通过资源预留能做到更强的隔离,AT则在读隔离上是RC级别,写隔离受全局锁保护。从长事务的适配度上讲,Saga更适合,AT对长期持有全局锁比较敏感,长事务会放大全局锁的竞争问题。

领域里的经验是:如果业务链路短(3个以内分支)、数据冲突概率低、团队想快速改造遗留系统,AT是最稳的选择。如果链路长、分支多、业务流程复杂,比如一个采购流程要从创建订单、锁定预算、调用外部接口到异步通知一路走完,那么TCC或者Saga可能更合适。如果对数据一致性容忍度极低、且冲突率本身很低,才值得去考虑XA,但现实里很少团队会为此付出性能代价。

4.2 一个具体的选型参考:什么场景我肯定不用AT

分享一个我比较保守的选型原则:只要分支事务里存在“跨公司、跨系统、无法控制数据源”的调用,我就不用AT。因为AT的回滚依赖undo_log,undo_log必须和业务数据在同一个数据库里,如果某个下游是别人维护的老系统,你没法给它部署Seata代理和undo_log表,那AT的自动补偿机制就断了。

这里典型的例子是“调用外部支付网关”。支付接口这类远程调用天然不可控,超时、悬挂、重复回调都是常态。用AT模式把支付动作包进全局事务里,等于强行要求一个你不掌控的系统参与你的事务,一旦对方状态不确定,全局事务就会被卡到超时。更合理的做法是先更新本地订单状态为“支付中”,调用支付后通过回调或者主动轮询来推进状态,这就是一个典型的Saga或本地消息表场景。

除此之外,还有一个容易误用的场景:全局事务里的本地事务如果跨了多个数据源,叫作“多数据源本地事务”。AT模式代理的DataSource本身能处理多数据源吗?答案是不能直接处理。每个数据源需要单独代理成DataSourceProxy,每个数据源都需要独立的undo_log表,每一个分支事务在Seata里都是独立注册的。如果业务代码在一个方法里同时操作两个数据源并期望一个事务搞定所有,靠AT的“一个分支”是做不到的,必须拆成多条分支链路来走。

我见过不少团队把AT模式当万能胶水,什么业务都往上贴,最后在跨数据源、外部系统、高频冲突这几张网上撞得头破血流。真诚建议,选型这一步多花一周时间做压测和场景分析,比上线后擦一个月的屁股划算得多。

5. 生产落地:从Demo到线上的最后一段路

5.1 Seata Server部署与TC集群搭建

跑通Demo只是开始,把Seata稳定地跑在生产环境里才是考验。整个Seata架构里有三个角色:TC(事务协调者)、TM(事务管理器)和RM(资源管理器)。TC是独立部署的Server端,TM和RM是嵌入在业务服务里的Client端。生产环境必须把TC做成集群,而不是单点,因为一旦TC挂了,所有正在执行的全局事务都会因为无法上报分支状态而超时回滚,这比业务接口本身挂了还要严重。

TC集群的部署方式根据版本有所不同,但大致思路是一致的:多台Seata Server实例注册到注册中心(比如Nacos、Consul、Etcd),客户端通过注册中心发现TC的地址列表。TC之间需要共享事务会话存储,旧版本默认是文件存储,只能支持单机;生产环境建议用数据库存储(比如MySQL),把全局事务会话持久化到独立的库里,多台TC实例才能共享会话数据。

这里要提醒一个很多人忽略的点:TC集群的数据库必须单独准备,不要和业务库混在一起。Seata的全局事务会话在高并发下会频繁读写,如果和业务库共用同一个数据库实例,性能互相影响,排查问题时也会相互干扰。我通常会单独准备一个小规格的数据库实例,专门给TC使用,性能要求不高,但稳定性要求高。

5.2 客户端配置与参数调优的关键项

客户端的配置主要有三块:注册中心配置、事务分组配置、和全局锁参数配置。业务里经常遇到的“Seata无报错但不生效”问题,大概率出在事务分组配置上。

Seata的事务分组机制是这样的:客户端配置一个tx-service-group(例如my_tx_group),这个分组名对应TC上配置的一个“虚拟服务名”,TC通过服务名找到真实的TC集群地址。如果分组名对不上,客户端会以为事务已经初始化成功,实际上一旦发起全局事务,TM找不到对应TC地址就会抛异常或者静默失败。所以上线前一定要在两端把分组配置核对清楚。

锁等待参数也是重点。全局锁在冲突时会影响所有并发请求。当你发现某个接口在高峰期经常超时或者报“Global lock wait timeout”,先检查是不是事务链路太长导致锁持有时间过长,再考虑调整lock.retry.internal和lock.retry.times。但调大这两个值是有代价的:每个等待锁的请求都会占用线程池资源,调太大容易把线程池拖死,调太小又容易误伤低冲突场景。

事务超时时间timeout同样不能忽略。默认的全局事务超时时间是60秒,如果某个链路里有一个慢SQL或者外部调用,本地事务执行时间逼近60秒,就会很容易触发全局超时回滚。建议先排查业务真实耗时再设置,而不是盲调大。

undo_log的清理策略也值得做。二阶段提交后Seata虽然会删日志,但如果事务量巨大,删除本身也会产生大量Binlog。有些团队选择关闭自动删除,改用定时任务在低峰期清理log_status=1的历史日志。这个方案对数据库压力更平稳,但需要保证TC参数client.report.retry.count和业务侧清理任务配合,避免误删未完成事务的日志。

5.3 监控、日志与踩坑后的应急思路

Seata在日志里会输出XID、分支状态、事务执行过程等关键信息,建议在初期把日志级别调到DEBUG,至少跑一个月再降到INFO。线上排查分布式事务问题时,最直观的动作就是拿全局事务ID去各个服务捞日志,看每个分支的一阶段是否成功注册、二阶段是提交还是回滚、undo_log是否被正常删除。

监控指标上,重点关注三块:全局事务的提交成功率、回滚率、全局锁等待超时次数。回滚率突然升高通常意味着业务代码出了异常或者数据冲突变多;锁等待超时次数升高意味着热点数据冲突在变多,需要业务侧优化并发控制方式。

万一出现数据不一致,第一时间不要盲目手动改数据。先根据XID从TC的会话存储里查全局事务状态,再根据分支ID去对应服务的undo_log表里找镜像数据,分析是哪个分支回滚失败。如果是因为After Image校验失败导致无法自动回滚,需要结合业务日志人工判断是“该回滚没回”还是“不该回滚被回滚了”。整个过程讲究先梳理、后动手,避免越修越乱。

6. 最后说几句实在话

我接触Seata AT模式这几年,最大的感受是:它把一个原本让团队望而却步的分布式事务痛点,降级成了一个“加注解 + 配好数据源”的工程问题,这已经是很大的贡献。但任何技术方案都有它的适用边界,AT模式绝对不是银弹,它把复杂度从业务代码转移到了全局锁、undo_log、调用链和参数配置这些“隐形成本”里,你以为没写业务逻辑就万事大吉,其实真正的工作在后面。

如果团队准备引入AT模式,我的三条建议很简单:第一,先做压测,专门打“多事务并发更新同一行”的场景,看全局锁的等待和超时是否符合预期;第二,把Seata相关的监控图表接到值班群里,让每个开发都能看到全局事务的回滚率和锁等待情况,而不是出了问题才去查日志;第三,在任何外部系统、跨公司调用、长链路场景里,默认不要用AT,宁可多写点业务代码做最终一致性。

最后分享一个小技巧:在开发阶段,可以在数据库里写一个定时脚本,每隔几分钟扫描一下undo_log表里长时间未被清理的记录,一旦发现create_time确实超过了全局事务超时时间但log_status=0的记录,多半说明二阶段回滚卡住了。这个脚本帮我抓过好几次线上问题,比翻日志快得多。分布式事务这条路没有终点,但把AT模式弄明白,至少能让你的微服务体系在一个相对平稳的轨道上往前跑。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/22 3:40:58

H3C级联式堆叠实战:物理设计、IRF调优与故障诊断

1. 为什么“级联式堆叠”不是H3C官方术语,但工程师天天在用?刚接触H3C设备的新人常被一个词绕晕:文档里写的是“IRF(Intelligent Resilient Framework)”,培训PPT上讲的是“堆叠”,而老同事随口…

作者头像 李华
网站建设 2026/10/22 3:40:30

全栈重构的典范:DeepSeek-V4架构与基础设施深度解析

1. 全栈重构的整体思路与设计哲学1.1 为什么需要一次全栈重构做深度学习这么多年,我越来越觉得“模型发布”这件事的本质,其实是一场系统工程的集中验收。架构设计、数据管线、训练框架、推理优化,任何一个环节掉链子,最终都会在评…

作者头像 李华
网站建设 2026/10/22 3:40:30

Docker 搭建 Redis 主从哨兵集群:高可用架构实战

Redis 这玩意儿,单机跑起来确实爽,一条docker run就完事。但真到了线上环境,单点故障就是悬在头顶的刀——主节点一挂,整个缓存层直接雪崩,数据库瞬间被流量打穿。我见过太多团队在这个问题上栽跟头,平时不…

作者头像 李华
网站建设 2026/10/22 3:39:59

Delphi上位机通过libnodave与S7 PLC通信实战指南

简介:libnodave-0.8.4 是一套面向西门子 S7 系列 PLC 的开源通信库,目标用户是需要在 Delphi、Visual Basic、Visual C 等语言环境中实现上位机与 S7-200/300/400 之间数据交换的工业自动化开发者。资源包内含 334 个文件、约 3.02MB,主体为 …

作者头像 李华
网站建设 2026/10/22 3:39:57

用AI智能体做项目排期:告别手动加班,实现分钟级调度

上周我为了把一份新项目的排期表定下来,连着熬了三个夜。手动排期这种事,干过的人都知道,表面上看是画甘特图、拉时间轴,实际上是在解一道满是约束条件的方程组。后来我们西南这边的项目组把金加德的AI调度官接进了内部协作平台&a…

作者头像 李华
网站建设 2026/10/22 3:39:42

基于YOLO与SpringBoot的疲劳驾驶检测系统:从模型训练到前后端部署全解析

做一个能真正落地用的疲劳驾驶识别系统,比想象中要折腾得多。这个项目是我自己从模型选型到前后端部署完整跑了一遍的,核心就是用 YOLOv8/v10/v11/v12 这几个版本的检测模型做眼睛、嘴巴、头部的状态识别,后端用 SpringBoot 搭一套前后端分离…

作者头像 李华