Seata 与 Redisson从底层到实战
在分布式系统的江湖中,数据一致性与分布式锁是两座绕不开的大山。当业务规模突破单机界限,分布式事务的一致性保障和分布式锁的并发控制就成了系统稳定的关键。今天我们要深入剖析两款明星级中间件 ——Seata 和 Redisson,它们看似都在解决分布式问题,却有着截然不同的技术路径和应用场景。本文将从底层原理到实战代码,带你彻底搞懂这两大工具的区别与联系,让你在面对分布式难题时不再迷茫。
一、初识 Seata 与 Redisson:不是一个赛道的选手
在开始深入技术细节之前,我们首先要明确一个核心认知:Seata 和 Redisson 虽然都服务于分布式系统,但它们的定位和解决的核心问题有着本质区别。
1.1 Seata:分布式事务的守护者
Seata 是阿里巴巴开源的分布式事务解决方案,专注于在分布式环境下保证事务的 ACID 特性。它的诞生源于电商等复杂业务场景中对分布式事务一致性的迫切需求,比如用户下单流程中,订单服务、库存服务、支付服务的操作必须同时成功或同时失败,否则就会出现超卖、漏单等严重问题。
Seata 的核心目标是让分布式事务的使用像本地事务一样简单,通过提供标准化的事务模式和易用的 API,降低分布式事务的实现门槛。
1.2 Redisson:分布式锁与 Java 对象容器
Redisson 是基于 Redis 的 Java 驻内存数据网格(In-Memory Data Grid),它不仅提供了强大的分布式锁实现,还封装了一系列分布式 Java 对象(如 Map、List、Queue 等)和服务(如分布式计数器、布隆过滤器等)。
Redisson 的核心价值在于利用 Redis 的高性能和分布式特性,为 Java 开发者提供了一套贴近原生 Java 集合的分布式工具,解决分布式环境下的并发控制、数据共享等问题。
1.3 本质区别:事务一致性 vs 并发控制
用一句话概括两者的区别:Seata 解决的是 "多操作要么都成功要么都失败" 的问题,Redisson 解决的是 "同一时刻只有一个操作能执行" 的问题。
为了更直观地理解,我们可以用一个生活场景类比:
- 用 Seata 就像组织一场多人协作的手术,所有医生必须协同完成各自步骤,一旦某个步骤失败,所有人都要回退到手术前的状态。
- 用 Redisson 就像在公共卫生间门口放一个牌子,有人使用时就挂上 "有人",其他人必须等待,确保同一时间只有一个人使用。
二、底层原理大揭秘:不同的技术路径
2.1 Seata 的分布式事务实现原理
Seata 定义了三个核心组件来实现分布式事务:
- Transaction Coordinator (TC):事务协调器,维护全局事务的运行状态,负责协调并驱动全局事务的提交或回滚。
- Transaction Manager (TM):事务管理器,控制全局事务的边界,负责开启一个全局事务,并最终发起全局提交或全局回滚的决议。
- Resource Manager (RM):资源管理器,管理分支事务处理的资源,与 TC 交谈以注册分支事务和报告分支事务的状态,并驱动分支事务的提交或回滚。
Seata 提供了四种事务模式,每种模式的底层实现各有不同:
2.1.1 AT 模式(Automatic Transaction)
AT 模式是 Seata 的默认模式,也是使用最广泛的模式,它基于两阶段提交和本地事务 + undo 日志实现:
第一阶段:
- 解析 SQL,生成 undo 日志(记录数据修改前后的状态)
- 执行 SQL 并提交本地事务
- 向 TC 注册分支事务
第二阶段:
- 若全局提交:异步删除 undo 日志
- 若全局回滚:根据 undo 日志执行反向操作,恢复数据
AT 模式的优势在于对业务代码侵入性极低,几乎可以做到零改造,但需要数据库支持事务和全局锁。
2.1.2 TCC 模式(Try-Confirm-Cancel)
TCC 模式是一种编程式事务模式,需要业务代码实现三个接口:
- Try:资源检查和预留
- Confirm:确认执行业务操作,必须保证幂等
- Cancel:取消执行业务操作,必须保证幂等
TCC 模式的优势是可以在无数据库支持的场景下使用,灵活性高,但对业务代码侵入性强,开发成本高。
2.1.3 SAGA 模式
SAGA 模式适用于长事务场景,它将分布式事务拆分为多个本地事务,每个本地事务都有对应的补偿操作,通过正向流程和补偿流程的顺序执行来保证最终一致性。
SAGA 模式适合业务流程长、业务逻辑复杂的场景,但同样需要手动编写补偿逻辑。
2.1.4 XA 模式
XA 模式基于数据库的 XA 协议实现,利用数据库本身的事务协调能力,Seata 在此模式下主要扮演资源管理器的角色。
XA 模式的优势是强一致性,但性能较差,适用于对一致性要求极高而对性能要求不高的场景。
2.2 Redisson 的分布式锁实现原理
Redisson 的分布式锁基于 Redis 实现,但并非简单地使用SETNX
命令,而是提供了一系列高级特性,使其成为生产环境中可靠的分布式锁解决方案。
2.2.1 可重入锁(Reentrant Lock)
Redisson 的分布式锁支持重入性,底层通过在 Redis 中存储哈希结构实现,键为锁名称,值为{持有锁的线程ID: 重入次数}
。
获取锁的核心逻辑:
- 尝试通过 Lua 脚本获取锁,若成功则设置过期时间
- 若失败且锁未被当前线程持有,则订阅锁释放事件并阻塞等待
- 若失败但锁已被当前线程持有,则增加重入次数
释放锁的核心逻辑:
- 减少重入次数,若重入次数大于 0 则更新 Redis 中的值
- 若重入次数等于 0 则删除锁键,并发布锁释放事件
2.2.2 看门狗机制(Watch Dog)
为了解决锁持有者因异常崩溃而无法释放锁的问题,Redisson 引入了看门狗机制:
- 当获取锁时未指定过期时间,Redisson 会自动设置 30 秒过期时间
- 启动一个后台线程,每隔 10 秒(过期时间的 1/3)检查锁是否仍被持有
- 若仍被持有,则延长锁的过期时间至 30 秒
这种机制确保了锁不会在持有者正常工作时过期,同时避免了死锁。
2.2.3 其他高级特性
Redisson 还提供了多种锁类型以适应不同场景:
- 公平锁:保证锁的获取顺序与请求顺序一致
- 读写锁:允许多个读操作同时进行,读写、写写操作互斥
- 红锁(RedLock):在多个 Redis 节点上获取锁,提高可用性
- 信号量(Semaphore):控制并发访问的数量
- 闭锁(CountDownLatch):等待多个线程完成后再执行
三、技术细节对比:从特性到性能
3.1 核心功能对比
特性 | Seata | Redisson |
---|---|---|
核心目标 | 保证分布式事务一致性 | 提供分布式锁和分布式对象 |
核心组件 | TC、TM、RM | Redis 服务器 + 客户端 SDK |
数据存储 | 事务日志存储在数据库 | 分布式对象存储在 Redis |
一致性保障 | 强一致性(AT/XA)或最终一致性(TCC/SAGA) | 基于 Redis 的一致性,最终一致 |
隔离级别 | 支持不同隔离级别 | 主要通过锁实现隔离 |
侵入性 | AT 模式低,TCC/SAGA 模式高 | 中等,需要使用特定 API |
3.2 性能对比
性能表现是选择中间件时的重要考量因素,我们从以下几个维度对比两者的性能特性:
3.2.1 响应时间
- Seata:由于需要与 TC 通信并处理事务日志,响应时间相对较长,尤其是 AT 模式下的两阶段提交会增加额外开销。
- Redisson:基于 Redis 的内存操作,响应时间极短,通常在毫秒级甚至微秒级。
3.2.2 吞吐量
- Seata:受限于事务协调和日志写入,吞吐量相对较低,适合写操作不频繁的场景。
- Redisson:得益于 Redis 的高性能,吞吐量极高,适合高并发场景。
3.2.3 资源消耗
- Seata:需要部署 TC 服务器,RM 会产生额外的数据库操作(undo 日志),资源消耗较高。
- Redisson:主要消耗 Redis 服务器资源,客户端资源消耗较低。
3.2.4 扩展性
- Seata:TC 可以集群部署,但事务协调的复杂性限制了水平扩展能力。
- Redisson:Redis 可以通过主从、哨兵、集群等方式扩展,扩展性较好。
3.3 可靠性对比
在分布式系统中,可靠性至关重要,我们从故障处理能力角度对比两者:
3.3.1 单点故障处理
- Seata:TC 支持集群部署,通过 DB 或 Redis 存储全局事务状态,避免单点故障。
- Redisson:依赖 Redis 的高可用方案(如哨兵、集群),自身不提供故障转移能力。
3.3.2 网络分区处理
- Seata:在网络分区发生时,可能出现事务悬而未决的情况,需要通过超时机制处理。
- Redisson:提供了多种锁超时策略,能在网络分区时避免死锁。
3.3.3 数据恢复能力
- Seata:基于 undo 日志可以恢复数据,具备较强的故障恢复能力。
- Redisson:Redis 的数据持久化(RDB/AOF)决定了数据恢复能力,锁本身不提供数据恢复。
四、实战代码:从配置到使用
4.1 Seata 实战:分布式事务管理
我们以电商下单场景为例,演示如何使用 Seata 的 AT 模式实现订单服务、库存服务和支付服务的分布式事务。
4.1.1 环境准备
首先需要搭建 Seata 服务器,参考官方文档完成配置后,在各个微服务中添加 Seata 依赖:
<dependency><groupId>io.seata</groupId><artifactId>seata-spring-boot-starter</artifactId><version>2.0.0</version>
</dependency>
<dependency><groupId>com.alibaba.cloud</groupId><artifactId>spring-cloud-starter-alibaba-seata</artifactId><version>2022.0.0.0-RC2</version><exclusions><exclusion><groupId>io.seata</groupId><artifactId>seata-spring-boot-starter</artifactId></exclusion></exclusions>
</dependency>
配置 application.yml:
seata:enabled: trueapplication-id: order-servicetx-service-group: my_test_tx_groupregistry:type: nacosnacos:server-addr: 127.0.0.1:8848group: SEATA_GROUPnamespace:config:type: nacosnacos:server-addr: 127.0.0.1:8848group: SEATA_GROUPnamespace:service:vgroup-mapping:my_test_tx_group: defaultgrouplist:default: 127.0.0.1:8091
4.1.2 数据库准备
需要在每个参与事务的数据库中创建 undo_log 表:
CREATE TABLE `undo_log` (`id` bigint NOT NULL AUTO_INCREMENT,`branch_id` bigint NOT NULL,`xid` varchar(100) NOT NULL,`context` varchar(128) NOT NULL,`rollback_info` longblob NOT NULL,`log_status` int NOT NULL,`log_created` datetime NOT NULL,`log_modified` datetime NOT NULL,PRIMARY KEY (`id`),UNIQUE KEY `ux_undo_log` (`xid`,`branch_id`)
) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8mb4;
创建订单表、库存表和支付记录表:
CREATE TABLE `t_order` (`id` bigint NOT NULL AUTO_INCREMENT,`user_id` bigint NOT NULL,`product_id` bigint NOT NULL,`count` int NOT NULL,`money` decimal(10,2) NOT NULL,`status` int NOT NULL COMMENT '订单状态:0-创建中,1-已完成,2-已取消',PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8mb4;CREATE TABLE `t_storage` (`id` bigint NOT NULL AUTO_INCREMENT,`product_id` bigint NOT NULL,`total` int NOT NULL COMMENT '总库存',`used` int NOT NULL COMMENT '已用库存',`residue` int NOT NULL COMMENT '剩余库存',PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8mb4;CREATE TABLE `t_payment` (`id` bigint NOT NULL AUTO_INCREMENT,`user_id` bigint NOT NULL,`order_id` bigint NOT NULL,`amount` decimal(10,2) NOT NULL,`status` int NOT NULL COMMENT '支付状态:0-未支付,1-已支付',PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8mb4;
4.1.3 实体类定义
使用 MyBatis-Plus 定义实体类:
@Data
@TableName("t_order")
@ApiModel(value = "Order对象", description = "订单表")
public class Order {@TableId(type = IdType.AUTO)private Long id;@ApiModelProperty("用户ID")private Long userId;@ApiModelProperty("商品ID")private Long productId;@ApiModelProperty("购买数量")private Integer count;@ApiModelProperty("总金额")private BigDecimal money;@ApiModelProperty("订单状态:0-创建中,1-已完成,2-已取消")private Integer status;
}@Data
@TableName("t_storage")
@ApiModel(value = "Storage对象", description = "库存表")
public class Storage {@TableId(type = IdType.AUTO)private Long id;@ApiModelProperty("商品ID")private Long productId;@ApiModelProperty("总库存")private Integer total;@ApiModelProperty("已用库存")private Integer used;@ApiModelProperty("剩余库存")private Integer residue;
}@Data
@TableName("t_payment")
@ApiModel(value = "Payment对象", description = "支付表")
public class Payment {@TableId(type = IdType.AUTO)private Long id;@ApiModelProperty("用户ID")private Long userId;@ApiModelProperty("订单ID")private Long orderId;@ApiModelProperty("支付金额")private BigDecimal amount;@ApiModelProperty("支付状态:0-未支付,1-已支付")private Integer status;
}
4.1.4 Mapper 层定义
public interface OrderMapper extends BaseMapper<Order> {
}public interface StorageMapper extends BaseMapper<Storage> {/*** 扣减库存*/@Update("UPDATE t_storage SET used = used + #{count}, residue = residue - #{count} WHERE product_id = #{productId} AND residue >= #{count}")int decrease(@Param("productId") Long productId, @Param("count") Integer count);
}public interface PaymentMapper extends BaseMapper<Payment> {
}
4.1.5 Service 层实现
订单服务:
@Service
@Slf4j
public class OrderServiceImpl implements OrderService {@Resourceprivate OrderMapper orderMapper;@Resourceprivate StorageFeignClient storageFeignClient;@Resourceprivate PaymentFeignClient paymentFeignClient;/*** 创建订单,包含扣减库存和创建支付记录* * @param order 订单信息* @return 创建的订单*/@Override@GlobalTransactional(name = "create-order", rollbackFor = Exception.class)public Order createOrder(Order order) {log.info("开始创建订单: {}", order);// 1. 创建订单order.setStatus(0);orderMapper.insert(order);log.info("订单创建成功: {}", order.getId());try {// 2. 扣减库存storageFeignClient.decrease(order.getProductId(), order.getCount());log.info("库存扣减成功: 商品ID={}, 数量={}", order.getProductId(), order.getCount());// 3. 创建支付记录Payment payment = new Payment();payment.setUserId(order.getUserId());payment.setOrderId(order.getId());payment.setAmount(order.getMoney());payment.setStatus(0);paymentFeignClient.create(payment);log.info("支付记录创建成功: {}", payment.getId());// 4. 更新订单状态为已完成order.setStatus(1);orderMapper.updateById(order);log.info("订单状态更新为已完成: {}", order.getId());return order;} catch (Exception e) {log.error("创建订单失败,将回滚事务", e);// 手动触发回滚TransactionContextHolder.setRollbackOnly();throw new RuntimeException("创建订单失败", e);}}
}
库存服务:
@Service
@Slf4j
public class StorageServiceImpl implements StorageService {@Resourceprivate StorageMapper storageMapper;/*** 扣减库存* * @param productId 商品ID* @param count 扣减数量*/@Overridepublic void decrease(Long productId, Integer count) {log.info("开始扣减库存: 商品ID={}, 数量={}", productId, count);int rows = storageMapper.decrease(productId, count);if (rows == 0) {log.error("库存不足: 商品ID={}, 需求数量={}", productId, count);throw new RuntimeException("库存不足");}log.info("库存扣减成功: 商品ID={}, 数量={}", productId, count);}
}
支付服务:
@Service
@Slf4j
public class PaymentServiceImpl implements PaymentService {@Resourceprivate PaymentMapper paymentMapper;/*** 创建支付记录* * @param payment 支付信息* @return 创建的支付记录*/@Overridepublic Payment create(Payment payment) {log.info("开始创建支付记录: {}", payment);paymentMapper.insert(payment);log.info("支付记录创建成功: {}", payment.getId());return payment;}
}
4.1.6 Controller 层实现
@RestController
@RequestMapping("/order")
@Slf4j
@ApiModel(value = "OrderController", description = "订单管理")
public class OrderController {@Resourceprivate OrderService orderService;/*** 创建订单* * @param order 订单信息* @return 创建的订单*/@PostMapping("/create")@ApiOperation("创建订单")public Result<Order> createOrder(@RequestBody @Valid Order order) {Order result = orderService.createOrder(order);return Result.success(result);}
}
以上代码实现了一个完整的分布式事务场景,当任何一个步骤失败(如库存不足、支付失败),Seata 会自动回滚所有操作,保证数据一致性。
4.2 Redisson 实战:分布式锁应用
我们以商品秒杀场景为例,演示如何使用 Redisson 实现分布式锁,防止超卖问题。
4.2.1 环境准备
添加 Redisson 依赖:
<dependency><groupId>org.redisson</groupId><artifactId>redisson-spring-boot-starter</artifactId><version>3.24.0</version>
</dependency>
配置 application.yml:
spring:redis:host: 127.0.0.1port: 6379password:database: 0redisson:config: |singleServerConfig:address: "redis://127.0.0.1:6379"password: nulldatabase: 0connectionPoolSize: 64connectionMinimumIdleSize: 24idleConnectionTimeout: 10000pingTimeout: 1000connectTimeout: 10000timeout: 3000retryAttempts: 3retryInterval: 1500
4.2.2 秒杀服务实现
@Service
@Slf4j
public class SeckillServiceImpl implements SeckillService {@Resourceprivate RedissonClient redissonClient;@Resourceprivate ProductMapper productMapper;@Resourceprivate OrderMapper orderMapper;private static final String SECKILL_LOCK_KEY = "seckill:lock:";/*** 执行秒杀* * @param productId 商品ID* @param userId 用户ID* @return 秒杀结果*/@Overridepublic Result<String> doSeckill(Long productId, Long userId) {// 1. 参数校验if (ObjectUtils.isEmpty(productId) || ObjectUtils.isEmpty(userId)) {return Result.fail("商品ID和用户ID不能为空");}// 2. 获取分布式锁,针对每个商品单独加锁,提高并发度RLock lock = redissonClient.getLock(SECKILL_LOCK_KEY + productId);try {// 3. 尝试获取锁,最多等待100毫秒,10秒后自动释放boolean locked = lock.tryLock(100, 10, TimeUnit.MILLISECONDS);if (!locked) {log.warn("秒杀失败,获取锁超时: 商品ID={}, 用户ID={}", productId, userId);return Result.fail("手慢了,商品已被抢完");}// 4. 再次检查库存,防止重复秒杀Product product = productMapper.selectById(productId);if (ObjectUtils.isEmpty(product)) {log.warn("秒杀失败,商品不存在: 商品ID={}, 用户ID={}", productId, userId);return Result.fail("商品不存在");}if (product.getStock() <= 0) {log.warn("秒杀失败,商品已售罄: 商品ID={}, 用户ID={}", productId, userId);return Result.fail("手慢了,商品已被抢完");}// 5. 检查用户是否已经秒杀过该商品QueryWrapper<Order> queryWrapper = new QueryWrapper<>();queryWrapper.eq("product_id", productId).eq("user_id", userId);Order existingOrder = orderMapper.selectOne(queryWrapper);if (!ObjectUtils.isEmpty(existingOrder)) {log.warn("秒杀失败,用户已秒杀过该商品: 商品ID={}, 用户ID={}", productId, userId);return Result.fail("您已秒杀过该商品");}// 6. 扣减库存product.setStock(product.getStock() - 1);productMapper.updateById(product);log.info("库存扣减成功: 商品ID={}, 剩余库存={}", productId, product.getStock());// 7. 创建订单Order order = new Order();order.setUserId(userId);order.setProductId(productId);order.setCount(1);order.setMoney(product.getPrice());order.setStatus(1); // 已完成orderMapper.insert(order);log.info("秒杀成功,订单创建: 订单ID={}, 商品ID={}, 用户ID={}", order.getId(), productId, userId);return Result.success("秒杀成功,订单ID: " + order.getId());} catch (InterruptedException e) {log.error("秒杀过程中发生中断异常", e);Thread.currentThread().interrupt();return Result.fail("秒杀失败,请重试");} catch (Exception e) {log.error("秒杀过程中发生异常", e);return Result.fail("秒杀失败,请重试");} finally {// 8. 释放锁,只有持有锁的线程才能释放if (lock.isHeldByCurrentThread()) {lock.unlock();log.info("释放锁成功: 商品ID={}, 用户ID={}", productId, userId);}}}
}
4.2.3 控制器实现
@RestController
@RequestMapping("/seckill")
@Slf4j
@ApiModel(value = "SeckillController", description = "商品秒杀")
public class SeckillController {@Resourceprivate SeckillService seckillService;/*** 执行秒杀* * @param productId 商品ID* @param userId 用户ID* @return 秒杀结果*/@PostMapping("/do")@ApiOperation("执行秒杀")public Result<String> doSeckill(@ApiParam(value = "商品ID", required = true) @RequestParam Long productId,@ApiParam(value = "用户ID", required = true) @RequestParam Long userId) {return seckillService.doSeckill(productId, userId);}
}
以上代码实现了一个分布式环境下的商品秒杀功能,通过 Redisson 的分布式锁确保了同一商品在高并发下不会出现超卖问题。代码中使用了 tryLock 方法并指定了等待时间和过期时间,避免了死锁风险,同时针对每个商品单独加锁,提高了系统的并发处理能力。
五、应用场景深度解析
5.1 Seata 的典型应用场景
Seata 适用于需要保证多个分布式操作原子性的场景,主要包括:
5.1.1 电商交易系统
在电商系统中,下单流程涉及订单创建、库存扣减、支付处理、积分增加等多个跨服务操作,这些操作必须同时成功或同时失败,否则会出现订单创建成功但库存未扣减(超卖)、支付成功但订单未确认(用户投诉)等严重问题。
使用 Seata 的 AT 模式可以在几乎不修改业务代码的情况下实现这些操作的事务一致性,是电商系统的理想选择。
5.1.2 金融支付系统
金融系统对数据一致性要求极高,转账、汇款等操作涉及多个账户的资金变动,必须严格保证一致性。Seata 的 XA 模式可以利用数据库本身的事务能力,提供强一致性保障,适合金融级场景。
5.1.3 物流配送系统
物流系统中的订单分配、库存调度、运输安排等操作需要跨多个服务协同,任何一个环节失败都需要回滚整个流程。Seata 的 TCC 模式可以在这些非数据库操作场景下提供事务保障。
5.1.4 长事务场景
某些业务流程可能持续较长时间,如审批流程、订单超时处理等,这些场景适合使用 Seata 的 SAGA 模式,通过补偿机制保证最终一致性。
5.2 Redisson 的典型应用场景
Redisson 适用于需要分布式并发控制和分布式数据结构的场景,主要包括:
5.2.1 商品秒杀系统
秒杀系统的核心挑战是高并发下的库存控制,Redisson 的分布式锁可以确保库存操作的原子性,防止超卖和库存不一致问题。同时,Redisson 的高性能特性也能支撑秒杀场景的高并发需求。
5.2.2 分布式任务调度
在分布式系统中,可能需要确保某个任务在同一时间只被一个节点执行(如定时任务),Redisson 的分布式锁可以实现这一需求。此外,Redisson 的 RDelayedQueue 还可以实现分布式延迟任务。
5.2.3 分布式限流
基于 Redisson 的 RRateLimiter 可以实现分布式限流功能,控制某个接口或资源的访问频率,保护系统不被流量峰值击垮。
5.2.4 分布式缓存
Redisson 提供了丰富的分布式数据结构(如 RMap、RList、RSet 等),可以作为分布式缓存使用,比直接使用 Redis 客户端更方便,且提供了更多高级特性(如过期策略、淘汰机制等)。
5.2.5 分布式计数器
在需要跨服务统计数量的场景(如网站访问量、下载次数等),Redisson 的 RAtomicLong 可以提供高效的分布式计数功能。
5.3 两者结合使用的场景
虽然 Seata 和 Redisson 解决的问题不同,但在某些复杂场景下,它们可以结合使用:
5.3.1 高并发事务场景
在高并发的事务场景中(如促销活动),可以使用 Redisson 的分布式锁控制并发量,防止大量请求同时进入 Seata 事务,从而提高系统的稳定性和性能。
@Service
@Slf4j
public class PromotionServiceImpl implements PromotionService {@Resourceprivate RedissonClient redissonClient;@Resourceprivate OrderService orderService;private static final String PROMOTION_LOCK_KEY = "promotion:lock:";/*** 促销活动下单,结合分布式锁和分布式事务* * @param order 订单信息* @return 创建的订单*/@Overridepublic Order createPromotionOrder(Order order) {// 获取促销活动锁,限制并发量RLock lock = redissonClient.getLock(PROMOTION_LOCK_KEY + order.getProductId());try {// 尝试获取锁,控制并发数量boolean locked = lock.tryLock(500, 5, TimeUnit.MILLISECONDS);if (!locked) {throw new RuntimeException("活动太火爆,请稍后再试");}// 调用订单服务,内部使用Seata分布式事务return orderService.createOrder(order);} catch (InterruptedException e) {log.error("创建促销订单时发生中断", e);Thread.currentThread().interrupt();throw new RuntimeException("创建订单失败,请重试");} finally {// 释放锁if (lock.isHeldByCurrentThread()) {lock.unlock();}}}
}
5.3.2 事务中的资源竞争
在分布式事务中,如果多个事务同时操作同一资源(如同一商品的库存),可能会出现死锁或数据不一致问题。此时可以在 Seata 事务内部使用 Redisson 的分布式锁来控制对这些资源的访问。
六、选型指南:如何选择合适的工具
在实际项目中,选择 Seata 还是 Redisson(或两者结合)需要根据具体需求来决定,以下是一些选型建议:
6.1 何时选择 Seata
需要保证多个跨服务操作的原子性:当业务流程涉及多个服务的写操作,且这些操作必须同时成功或同时失败时,选择 Seata。
对数据一致性要求高:在金融、电商等核心业务场景,数据一致性至关重要,Seata 的事务机制可以提供可靠保障。
希望减少业务代码侵入:Seata 的 AT 模式对业务代码侵入性极低,适合快速集成。
需要处理长事务:对于持续时间较长的事务,Seata 的 SAGA 模式是较好的选择。
6.2 何时选择 Redisson
需要控制分布式并发:当多个服务或节点需要竞争同一资源时,Redisson 的分布式锁是理想选择。
需要分布式数据结构:当需要在分布式环境中使用 Map、List、Queue 等数据结构时,Redisson 提供了便捷的实现。
对性能要求高:Redisson 基于 Redis,性能优异,适合高并发场景。
需要实现分布式服务:如分布式限流、分布式计数器、分布式延迟队列等,Redisson 都能提供现成的实现。
6.3 混合使用的场景
高并发下的事务处理:用 Redisson 控制并发量,用 Seata 保证事务一致性。
事务中的资源竞争:在 Seata 事务内部使用 Redisson 锁控制对特定资源的访问。
复杂业务流程:既有跨服务的事务需求,又有分布式并发控制需求的复杂场景。
七、常见问题与最佳实践
7.1 Seata 常见问题与解决方案
7.1.1 事务回滚失败
问题:分布式事务执行过程中发生异常,但部分分支事务未回滚。
解决方案:
- 检查 undo_log 表是否正确创建,权限是否足够
- 确保所有分支事务都正确集成了 Seata
- 检查 TC 服务器是否正常运行
- 查看 Seata 日志,分析具体失败原因
7.1.2 性能问题
问题:使用 Seata 后系统性能明显下降。
解决方案:
- 合理设置事务超时时间,避免长事务
- 尽量缩小事务范围,只包含必要的操作
- 考虑使用 TCC 模式替代 AT 模式,减少 undo 日志开销
- 对 TC 服务器进行性能优化,如使用 Redis 作为会话存储
7.1.3 分布式事务悬挂
问题:由于网络延迟等原因,全局事务已回滚,但分支事务仍在执行。
解决方案:
- 合理设置各阶段的超时时间
- 在业务代码中增加状态判断,避免无效操作
- 使用 TCC 模式时,在 Cancel 方法中增加幂等性处理
7.2 Redisson 常见问题与解决方案
7.2.1 锁超时问题
问题:业务逻辑执行时间超过锁的过期时间,导致锁被提前释放。
解决方案:
- 合理评估业务执行时间,设置足够长的过期时间
- 使用 Redisson 的看门狗机制自动续期
- 避免在锁保护范围内执行耗时操作
7.2.2 死锁问题
问题:由于异常等原因,锁未被正确释放,导致死锁。
解决方案:
- 总是在 finally 块中释放锁
- 使用 tryLock 方法并设置超时时间
- 确保锁的获取和释放顺序一致
7.2.3 Redis 性能瓶颈
问题:高并发场景下,Redis 成为性能瓶颈。
解决方案:
- 对 Redis 进行集群部署
- 合理设计锁的粒度,避免过大的锁范围
- 使用 Redisson 的本地缓存减少 Redis 访问
- 考虑使用红锁(RedLock)提高可用性
7.3 最佳实践总结
Seata 最佳实践:
- 优先使用 AT 模式,减少开发成本
- 合理设计事务边界,避免大事务
- 确保所有分支事务都有明确的回滚逻辑
- 对 TC 服务器进行集群部署,提高可用性
- 监控事务执行情况,及时发现和解决问题
Redisson 最佳实践:
- 总是使用 try-finally 块确保锁的释放
- 合理设置锁的过期时间,避免死锁
- 锁的粒度要尽可能小,提高并发度
- 利用 Redisson 的高级特性(如看门狗、读写锁)优化性能
- 对 Redis 进行高可用配置,避免单点故障
混合使用最佳实践:
- 明确划分两者的职责,避免功能重叠
- 在高并发场景下,先用 Redisson 控制并发,再用 Seata 保证事务
- 注意两者的性能平衡点,避免过度设计
八、总结与展望
通过本文的深入分析,我们可以清晰地看到 Seata 和 Redisson 在分布式系统中的不同定位:
Seata是分布式事务的专家,专注于保证多个跨服务操作的原子性,确保数据一致性。它通过多种事务模式,满足不同场景下的一致性需求,是构建可靠分布式系统的重要工具。
Redisson是分布式并发控制的利器,基于 Redis 提供了丰富的分布式锁和分布式数据结构,解决了分布式环境下的并发竞争问题,同时提供了高性能的分布式服务。
两者并非对立关系,而是可以相互配合,共同构建稳定、高效的分布式系统。在实际项目中,我们需要根据具体业务场景,选择合适的工具或将它们结合使用,以达到最佳的系统性能和可靠性。