业务系统如何保证一致性

分布式

业务系统如何保证一致性

回答思路:分场景分析

强一致性

案例:

1、酒店管理系统中创建订单需要先修改房态,才能创建订单成功执行

2、订单积分抵扣、优惠券核销

强一致性往往存在于只有前置行为执行成功后才能继续执行的场景,如果乱序则会使数据不一致和业务流程异常,造成超卖或使商家造成损失等。

解决方法:

1、加分布式锁,短时间内只允许一个订单进行

2、分布式事务管理,二阶段提交或补偿事务模式

2PC

二阶段提交

Atomikos

Atomikos 支持XA分布式事务处理,可以帮助开发者解决跨多数据库或资源的事务管理问题,从而实现如2PC这样的分布式事务机制。

1.Maven引入jar

<dependency>
    <groupId>com.atomikos</groupId>
    <artifactId>atomikosTransactionsEssentials</artifactId>
    <version>5.0.6</version>
</dependency>

2.配置事务管理器,在Spring框架中,可以定义一个Manger bean来初始化Atomikos事务管理器

@Bean
public AtomikosTransactionManager atomikosTransactionManager() {
    AtomikosTransactionManager atomikosTransactionManager = new AtomikosTransactionManager();
    // 设置相关属性,例如最大事务超时时间等
    return atomikosTransactionManager;
}

3.配置数据源:确保数据源是Atomikos提供XADataSource,这样才可以参与到分布式事务中

4.在服务类或DAO层启动事务传播行为,通过@Transactional注解开启事务管理,并根据业务需求设置事务的隔离级别、传播行为等属性。

@Service
public class MyService {

    @Autowired
    private JdbcTemplate jdbcTemplate1;

    @Autowired
    private JdbcTemplate jdbcTemplate2;

    @Transactional
    public void doTransaction() {
        // 在这里对两个不同的数据库进行操作
        jdbcTemplate1.update(...);
        jdbcTemplate2.update(...);
    }
}

Atomikos只适用于单服务管理多数据源,需要与其他服务治理策略相结合,处理微服务间事务问题。

Bitronix Transaction Manager
<dependency>
    <groupId>org.bitronix</groupId>
    <artifactId>bitronix-transaction-manager</artifactId>
    <version>3.0.1</version>
</dependency>

BTM 与 Atomikos 差异不大,其中Atomikos 社区活跃更高,但在不同的场景需要具体分析决定。

附:XA分布式事务处理指的是XA规范,分布式环境下实现跨多个数据库或资源的原子性事务操作。XA协议的关键组件包括,全局事务管理器,资源管理器。

Seata-AT模式
  • 分支事务注册
  • SQL解析与拦截
  • 一阶段提交
  • 全局提交/回滚
  • 二阶段提交/回滚

Seata降低了传统2PC协议在准备阶段长时间锁定资源的问题,提高了系统并发性能,对业务代码侵入性较低,只需要简单的配置即可。

Seata-AT 模式,在事务资源器执行数据操作(SQL)后,其它线程或服务可以对这条数据进行再次修改,但资源管理器任然可以发起Undo 操作,这就造成了“幻读”风险。

TCC

Try-Confirm-Cancel 是分布式事务的一种补偿性模式。

TCC模式的基本原理

  • Try 阶段
    • 尝试执行业务操作,这个阶段不仅实际业务改动,仅保证后续确认操作可以成功执行
  • Confirm阶段
    • 如果全局事务提交,则确认Try阶段的业务操作,正在执行业务逻辑并修改业务状态。
  • Cancel阶段
    • 如果全局事务需要会馆,那么取消Try阶段的操作,释放预留的资源,确保嵟恢复到初始状态
Seata

Seata针对不同的框架有对应的jar包,比如Fegin和DUBBO都有对应的实现jar包,使用相对简单。

  • 集成

引入Maven配置,在根据项目情况配置nacos或者Zookeeper

<dependency>
	<groupId>io.seata</groupId>
	<artifactId>seata-tcc</artifactId>
	<version>1.7.0</version>
</dependency>
  • 接口定义与实现

在业务模块中定义Try/Confirm/Cancel三个方法的接口和实现类。

如:订单核销优惠券

@LocalTCC
public interface CouponTccService {

    // Try阶段:尝试核销优惠券,预留资源(如检查优惠券有效性、锁定优惠券等)
    boolean tryConsumeCoupon(String orderId, String couponId);

    // Confirm阶段:确认核销优惠券,执行实际业务操作(如更新优惠券状态为已使用)
    boolean confirmConsumeCoupon(String orderId, String couponId);

    // Cancel阶段:取消核销优惠券,回滚预留资源(如恢复优惠券到未使用状态)
    boolean cancelConsumeCoupon(String orderId, String couponId);
}
  • 事务协调

扫描TCC接口,如果使用的是Spring框架,自动扫描识别并注册TCC接口到Seata容器中。Seata TCC 是通过接口方法的命名来区分Try/Confirm/Cancel,如:try/prepare 都是指try

开启全局事务

@GlobalTransactional(timeoutMills = 30000, name = "order-create-transaction")
public void createOrderAndConsumeCoupon(Order order) throws Exception {
        // 创建订单...
        // ...

        // 尝试核销优惠券
        boolean tryResult = couponTccService.tryConsumeCoupon(order.getId(), order.getCouponId());

        if (tryResult) {
            // 其他业务逻辑...

            // 确认创建订单和核销优惠券的其他相关操作

            // 如果没有异常,全局事务将在方法结束时自动提交
        } else {
            // 如果尝试阶段失败,由于全局事务的存在,整个操作将会被回滚
            throw new RuntimeException("尝试核销优惠券失败");
        }
}

@GlobalTransactional 注解来开启全局事务,这个注解会让Seata管理方法内的数据库操作,并根据业务流程来决定最终是提交还是回滚。

  • 异常处理与重试
  1. 捕获异常
    Seata通过全局事务管理和资源管理器之间的通信,以及对本地数据库的操作的代理,可以捕获到分布式事务执行过程中发生的各种异常。
  2. 分支事务状态管理
    当一个分支事务在Try/Confirm/Cancel阶段发生异常时,Seata会将该分支事务的状态更新为需要重试的状态,并存储到TC中,等待后续重试。
  3. 重试策略
    Seata允许用户配置重试次数和重试时间。当分支事务因网络问题、短暂的服务不可用等原因失败后,Seata会根据这些配置进行自动重试。
  4. 回滚策略
    在Confirm或Cancel阶段如果遇到异常导致无法提交或回滚,Seata同样会重试。
  5. 超时处理
    对于整个全局事务或分支事务,Seata支持设置超时时间,一旦超时,Seata会主动触发事务回滚流程,避免长时间锁定资源影响性能。
  6. 事务上下文管理
    Seata会再每个服务调用链路中传播全局事务ID,XID。确保即使在一部调用或跨进程调用的情况下,也能确保正确识别和管理事务。
  7. 异常通知
    Seata通常会提供异常日志和报警功能,以便开发者及时发现和处理异常。
2PC和TCC的差异
  • 设计思路
    • 2PC
      • 将多数据库事务分为两个阶段,准备和提交。准备阶段会在各个数据库开启事务,并向协调者反馈是否准备好提交。提交阶段,协调者会根据参与者反馈决定提交或回滚事务。
    • TCC
      • 一种业务补偿型的分布式事务方案,强调应用层控制事务边界。每个服务都需要提供三个接口Try/Confirm/Cancel,事务管理器会根据需要调用这些方法。
  • 优点
    • 2PC
      • 简单易理解
      • 保证了数据强一致性
    • TCC
      • 可以灵活地根据业务场景定制资源管理策略,有利于提高系统性能和可扩展性。
      • 资源锁定时间少,适合与高并发环境,通过业务逻辑的设计避免长时间锁表。
      • 提供了一种更细粒度的事务控制机制。
弱一致性

案例:向第三方异步发放优惠券

第三方系统无法做到尽可能通知。

解决方法:重试。超时中心,

生成失败日志,记录失败内容、原因、时间等要素,在失败后通过重试策略进行重新发放,双方要确认好幂等。

最终一致性

无法理解更新所有副本的数据以达到一致状态,但在一段时间后,系统会通过数据复制、补偿等方式使得所有副本数据达成一致。

案例:支付后,因为网络问题未同步支付结果回订单系统。

Seata Saga模式

核心思想:

  • 将一个长事务拆分成一系列短的本地事务,每个子事务都是一个原子操作
  • 这些原子操作按照一定的顺序执行,并且每个操作都对应一个补偿操作。补偿操作的作用是撤销或者修正前面已经成功执行的操作,保证数据一致性。
  • Saga模式通常不支持事务的ACID特性中的隔离性,但可以提供最终一致性。

事件驱动
状态机

使用方式

1.Maven引入Seata

 <!-- 引入 Spring Cloud Alibaba Seata 相关依赖,使用 Seata 实现分布式事务,并实现对其的自动配置 -->
        <dependency>
            <groupId>com.alibaba.cloud</groupId>
            <artifactId>spring-cloud-starter-alibaba-seata</artifactId>
            <version>${spring.cloud.alibaba.version}</version>
            <exclusions>
                <exclusion>
                    <groupId>io.seata</groupId>
                    <artifactId>seata-spring-boot-starter</artifactId>
                </exclusion>
            </exclusions>
        </dependency>
        <dependency>
            <groupId>io.seata</groupId>
            <artifactId>seata-spring-boot-starter</artifactId>
            <version>${seata.version}</version>
        </dependency>

2.各微服务实现业务代码
简单的只写一个订单案例

@RestController
@RequestMapping("order")
public class OrderController {

    @Resource
    private OrderService orderService;

    @RequestMapping("/createOrder")
    public Boolean createOrder(@RequestParam("orderId") Long orderId,
                               @RequestParam("userId") Long userId,
                               @RequestParam("productId") Long productId,
                               @RequestParam("amount") Integer amount,
                               @RequestParam("count") Integer count) throws Exception {
        return orderService.createOrder(orderId, userId, productId, amount, count);
    }

    @RequestMapping("/revokeOrder")
    public Boolean revokeOrder(@RequestParam("orderId") Long orderId) throws Exception {
        return orderService.revokeOrder(orderId);
    }
}
@Service
public class OrderServiceImpl implements OrderService {

    private Logger logger = LoggerFactory.getLogger(getClass());

    @Autowired
    OrderDao orderDao;

    @Transactional(rollbackFor = Exception.class)
    @Override
    public Boolean createOrder(Long orderId, Long userId, Long productId, Integer amount, Integer count) throws Exception {
        OrderDTO orderDTO = new OrderDTO(orderId, userId, productId, count, amount);
        logger.info("[createOrder] 开始创建订单: {}", orderDTO.toString());
        logger.info("[createOrder] XID: {}", RootContext.getXID());
        int result = orderDao.createOrder(orderDTO);
        if(result == 0){
            logger.warn("[createOrder] 创建订单 {} 失败", orderDTO.toString());
            return false;
        }
        logger.info("[createOrder] 保存订单成功: {}", orderDTO.getId());
        return true;
    }

    @Transactional(rollbackFor = Exception.class)
    @Override
    public Boolean revokeOrder(Long orderId) throws Exception {
        logger.info("[revokeOrder] 开始撤销订单, orderId: {}", orderId);
        logger.info("[revokeOrder] XID: {}", RootContext.getXID());
        int result = orderDao.revokeOrder(orderId);
        if(result == 0){
            logger.warn("[revokeOrder] 撤销订单 {} 失败",orderId);
            return false;
        }
        logger.info("[revokeOrder] 撤销订单成功: {}", orderId);
        return true;
    }
}

3.配置Config

  • ThreadPoolExecutor
    • 线程池,如果是串行化执行,则可以不需要
    • 通过线程池可以有效的管理并发执行任务,并根据需要进行调度
  • DbStateMachineConfig
    • 配置数据库资源、Seata 应用编号、Seata 事务组编号
    • 配置存放 Saga事务状态图(Json文件)的目录
  • StateMachineEngine
    • 分布式事务状态机的核心引擎,负责解析和执行事务状态机的状态机模型。
  • StateMachineEngineHolder
    • 单例持有类,确保整个应用上下文中只存在一个全局唯一的StateMachineEngine实例

4.初始化

  • io.seata.saga.engine.impl.DefaultStateMachineConfig#init
    • 初始化方法
  • io.seata.saga.engine.repo.impl.StateMachineRepositoryImpl#registryByResources
    • 读取在Config配置的json文件目录
    • 解析JSON文件
  • io.seata.saga.engine.repo.impl.StateMachineRepositoryImpl#registryStateMachine
    • 比对新旧数据
    • 重新插入

5.服务调度

开启订单创建事件

StateMachineInstance instance = stateMachineEngine.start("BusinessOrder", null, businessParam);

(1)初始化并验证Saga事务状态

(2)加载和准备执行的业务流程模型

通过Feign 从nacos中获取对应的服务信息,需要实现实际服务API代理对象

@FeignClient(value = "saga-order-service", configuration = {FeignErrorDecoder.class})
@RequestMapping("/order")
public interface OrderService {

    @RequestMapping("/createOrder")
    Boolean createOrder(@RequestParam("orderId") Long orderId,
                        @RequestParam("userId") Long userId,
                        @RequestParam("productId") Long productId,
                        @RequestParam("amount") Integer amount,
                        @RequestParam("count") Integer count) throws Exception;

    @RequestMapping("/revokeOrder")
    Boolean revokeOrder(@RequestParam("orderId") Long orderId) throws Exception;
}

(3)异步地执行这些任务,并根据执行结果更新Saga事务状态。

AsyncEventBus

@Override
public boolean offer(ProcessContext context) throws FrameworkException {
    List<EventConsumer> eventConsumers = getEventConsumers(context.getClass());
    if (CollectionUtils.isEmpty(eventConsumers)) {
        if (LOGGER.isWarnEnabled()) {
            LOGGER.warn("cannot find event handler by class: " + context.getClass());
        }
        return false;
    }
    for (EventConsumer eventConsumer : eventConsumers) {
        threadPoolExecutor.execute(() -> eventConsumer.process(context));
    }
    return true;
}

(4)等待结果返回

5.补偿

DirectEventBus

按序将已经执行的任务执行补偿方法。

  • 16
    点赞
  • 16
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值