如何保证不同微服务MySql+MQ调用直接的事务一致性

问题复现:

服务A 保存mysql多个表,多个表要在一个事务内操作,同时还要调用服务B的MQ数据处理,要求服务A的mysql操作和服务B的MQ数据操作要在一个事务内进行,要么同时成功,要么同时失败

解决方案1:

将服务B的MQ发送放在服务A的mysql事务内,问题:如果服务A的mysql失败可以回滚,服务B的MQ无法回滚,这里加个try catch 进行拟补操作也可以

解决方案2:

引入rocketMQ的事务发送消息,先对服务B的MQ发送成功状态进行尝试,如果能确定发送成功,执行服务A的mysql操作,只有服务A的mysql操作成功了,才会发送服务B的MQ发送,失败了就一起回滚。

 OrderRefundApplyMessage message = refundApplyService.buildEvent(OrderRefundApplyMessage.EventType.NEW_RECEIVE_ADDRESS_CHANGE, refundApply, operateId);

        ReceiveAddressChange finalChange = change;
        TransactionSendResult sendResult = producer.sendMessageInTransaction(RocketMQConst.TopicChdOrder.TOPIC,
                RocketMQConst.TopicChdOrder.Tag.REFUND_APPLY,
                message,
                () -> {
                    refundApplyService.save(refundApply);

                    addressChangeApplyService.save(addressChangeApply);

                    addressChangeApplyRecordService.save(addressChangeApplyRecords);

                    if (null != finalChange) {
                        addressChangeService.save(finalChange);
                    }
                });

        if (sendResult.getLocalTransactionState() != LocalTransactionState.COMMIT_MESSAGE) {
            throw new ChdIgnorableException("申请失败");
        }

<think>嗯,用户现在想了解Spring Boot如何保证数据一致性相关的面试问题。首先,我需要确定用户的需求是什么。他们可能正在准备面试,需要整理这方面的知识点,或者在实际项目中遇到了数据一致性的问题。 接下来,我得回想一下数据一致性在Spring Boot中的常见处理方式。比如事务管理、分布式事务、乐观锁、幂等性设计、消息队列等等。这些都是面试中常被问到的点。 然后,用户可能想知道具体的实现方法常见问题。比如,本地事务如何使用@Transactional注解,分布式事务框架如Seata的应用,还有如何通过乐观锁机制避免并发问题。此外,幂等性设计也是保证数据一致性的重要部分,特别是在接口重复提交的情况下。 还需要考虑用户是否有分布式系统的经验,或者只是在单体应用中的问题。比如,如果系统是微服务架构,可能需要用到分布式事务解决方案,而单体应用可能更关注数据库事务锁机制。 另外,用户提到的面试问题,应该涵盖理论知识实际案例。比如,如何处理跨服务的数据一致性问题,或者如何在并发场景下保证数据正确性。这些都需要结合Spring Boot的特性相关技术栈来回答。 还要注意用户可能对某些术语不太熟悉,比如CAP定理、BASE理论,或者具体的框架如Seata、RocketMQ事务消息。需要用简洁明了的语言解释这些概念,并给出实际应用的例子。 最后,应该整理出一个结构化的回答,分点说明不同保证数据一致性的方法,每个方法的原理、实现步骤以及适用场景。同时,给出一些常见面试问题及其回答示例,帮助用户更好地准备面试。</think>以下是关于 Spring Boot 数据一致性保障的常见面试问题及核心知识点整理,涵盖单机事务、分布式事务、锁机制等高频考点: --- ### **一、单机事务** #### **1. `@Transactional` 注解原理** ```text - 底层依赖数据库事务(如MySQL的InnoDB引擎) - 基于AOP实现,通过TransactionInterceptor拦截方法 - 事务传播行为(默认PROPAGATION_REQUIRED) - 常见坑点:非public方法失效、异常类型不匹配(默认只回滚RuntimeException) ``` **示例代码**: ```java @Transactional(rollbackFor = Exception.class) public void transferMoney(Long from, Long to, BigDecimal amount) { accountDao.deduct(from, amount); // 模拟业务异常 if (amount.compareTo(BigDecimal.ZERO) < 0) { throw new RuntimeException("金额非法"); } accountDao.add(to, amount); } ``` --- ### **二、分布式事务** #### **1. CAP与BASE理论** ```text - CAP只能满足其中两个:一致性(C)、可用性(A)、分区容错性(P) - BASE理论:基本可用(Basically Available)、软状态(Soft State)、最终一致(Eventually Consistent) ``` #### **2. 常见解决方案** | **方案** | **实现方式** | **适用场景** | |-------------------|----------------------------------------|---------------------------| | **2PC** | 协调者+两阶段提交(prepare/commit) | 强一致、低并发场景 | | **TCC** | Try-Confirm-Cancel三阶段补偿 | 高一致性要求的金融交易 | | **本地消息表** | 数据库+消息队列(事务提交后异步通知) | 最终一致、高吞吐量场景 | | **Seata AT模式** | 全局锁+undo_log(自动反向补偿SQL) | 简单业务逻辑的分布式事务 | **Seata配置示例**: ```properties # seata-server配置 seata.tx-service-group=my_tx_group seata.service.vgroup-mapping.my_tx_group=default ``` --- ### **三、高并发场景下的数据一致性** #### **1. 乐观锁 vs 悲观锁** | **对比项** | 乐观锁 | 悲观锁 | |------------------|---------------------------------------|---------------------------| | 实现方式 | 版本号机制(如`@Version`) | `SELECT ... FOR UPDATE` | | 冲突检测 | 提交时校验版本 | 操作前加锁 | | 适用场景 | 读多写少 | 写多读少 | **JPA乐观锁实现**: ```java @Entity public class Product { @Id private Long id; @Version private Integer version; // 版本号字段 private Integer stock; } ``` #### **2. Redis分布式锁** ```java public boolean tryLock(String key, String value, long expireTime) { return redisTemplate.opsForValue() .setIfAbsent(key, value, expireTime, TimeUnit.SECONDS); } // 使用Lua脚本保证原子性解锁 String script = "if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1]) else return 0 end"; redisTemplate.execute(new DefaultRedisScript<>(script, Long.class), Collections.singletonList(key), value); ``` --- ### **四、最终一致性保障** #### **1. 消息队列事务消息** ```text 实现步骤: 1. 发送半消息(RocketMQ事务消息) 2. 执行本地事务 3. 根据本地事务结果提交/回滚消息 4. 消费者幂等消费 ``` **RocketMQ事务消息示例**: ```java TransactionMQProducer producer = new TransactionMQProducer("group"); producer.setTransactionListener(new TransactionListener() { @Override public LocalTransactionState executeLocalTransaction(Message msg, Object arg) { try { // 执行本地数据库操作 return LocalTransactionState.COMMIT_MESSAGE; } catch (Exception e) { return LocalTransactionState.ROLLBACK_MESSAGE; } } }); ``` #### **2. 幂等性设计** ```text 实现方法: - 唯一索引:防止重复数据插入 - 令牌机制:请求携带唯一token,服务端校验后删除 - 状态机:业务状态流转校验(如订单状态不可逆) ``` --- ### **五、高频面试问题** #### **Q1:`@Transactional`注解在哪些情况下会失效?** ```text 1. 方法非public修饰 2. 异常类型非RuntimeException且未指定rollbackFor 3. 同一类内非事务方法调用事务方法(未通过代理对象) 4. 多数据源未指定事务管理器 ``` #### **Q2:如何避免超卖问题?** ```text 解决方案: 1. 数据库层面:UPDATE库存时校验stock > 0 UPDATE product SET stock = stock - 1 WHERE id = ? AND stock > 0 2. Redis原子操作:DECR命令或Lua脚本 3. 队列削峰:下单请求进入MQ顺序处理 ``` #### **Q3:跨服务数据一致性如何保障?** ```text 典型回答: - 强一致:Seata AT模式(适合简单业务) - 最终一致:本地消息表+RocketMQ事务消息(适合高并发场景) - 补偿机制:TCC手工回滚(适合资金类敏感业务) ``` --- ### **六、实践案例** **案例**:电商系统扣减库存与生成订单的一致性 ```text 技术选型: 1. 下单服务本地事务: - 预扣库存(Redis扣减) - 写入订单表(状态为"待支付") 2. 支付回调: - 实际扣减数据库库存 - 更新订单状态为"已支付" 3. 超时未支付: - 定时任务回滚Redis预扣库存 - 订单状态改为"已取消" ``` --- 掌握以上内容后,建议结合项目经验准备**具体数字**(如:"通过Seata将分布式事务成功率从92%提升至99.8%")**异常处理细节**(如:如何处理网络抖动导致的事务悬挂问题)。面试时重点展现**技术深度**与**系统设计思维**。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值