1.1 事务的本质是什么?
在餐厅点餐时,我们常常把多个菜品一次性交给服务员——这就是事务的朴素理解。Redis事务的本质就是将多个命令打包成一个原子操作序列。想象你正在开发一个秒杀系统,扣库存、记录订单、发优惠券这三个动作必须同时成功或失败,这就是事务存在的意义。
1.2 与传统数据库的对比
相较于传统关系型数据库的事务(ACID特性):
- Redis事务不支持回滚(ROLLBACK),但可通过
DISCARD
放弃执行 - 执行期间不会中断,适合高并发场景
- 命令先入队后执行(类似批处理)
2. Java操作Redis事务的完整代码示例
(Jedis技术栈)
2.1 基础事务演示
// 创建Jedis连接
try (Jedis jedis = new Jedis("localhost", 6379)) {
// 开启事务
Transaction tx = jedis.multi();
// 连续发送命令到队列
tx.set("user:101:status", "locked");
tx.incrBy("inventory:item1001", -1);
tx.sadd("orders:202308", "order10001");
// 执行事务(返回所有命令的响应列表)
List<Object> results = tx.exec();
// 处理执行结果
for(Object result : results) {
System.out.println("命令执行结果:" + result);
}
} catch (Exception e) {
// 异常时自动调用DISCARD
System.out.println("事务执行失败:" + e.getMessage());
}
2.2 带有Watch的乐观锁实现
jedis.watch("account:2001:balance"); // 监控关键键
try {
int currentBalance = Integer.parseInt(jedis.get("account:2001:balance"));
if(currentBalance >= 500) {
Transaction tx = jedis.multi();
tx.decrBy("account:2001:balance", 500);
tx.incrBy("account:2002:balance", 500);
List<Object> results = tx.exec();
if(results == null) {
System.out.println("转账失败:余额被其他操作修改");
}
} else {
jedis.unwatch();
System.out.println("余额不足");
}
} catch (Exception e) {
jedis.unwatch();
System.out.println("系统异常:" + e.getMessage());
}
2.3 事务中的错误处理模式
Transaction tx = jedis.multi();
try {
// 错误命令示例:对字符串进行集合操作
tx.sadd("user:101:profile", "vip"); // 正确用法
tx.get("invalidKey").trim(); // 故意制造语法错误
tx.exec(); // 此处不会执行,在入队时就检测到错误
} catch (JedisDataException e) {
System.out.println("命令入队错误:" + e.getMessage());
tx.discard();
}
3. Redis事务的四大核心命令解析
3.1 MULTI:事务启动开关
- 语法:
MULTI
- 作用:标志事务开始,后续命令进入队列
3.2 EXEC:事务执行器
- 语法:
EXEC
- 效果:
- 执行队列中的所有命令
- 返回命令执行结果的数组
3.3 DISCARD:事务终止符
- 语法:
DISCARD
- 注意点:
- 会清空命令队列
- 自动取消WATCH监控
3.4 WATCH:数据变更监听者
- 使用场景:转账前的余额监控
- 实现原理:基于CAS(Compare and Swap)机制
- 重要特性:
// 监控多个key的示例 jedis.watch("key1", "key2", "key3"); // 当任一被监控键被修改时事务将失败
4. 事务应用场景深度解析
4.1 库存精确扣减系统
在电商秒杀场景中:
Transaction tx = jedis.multi();
tx.decr("product:1001:stock");
tx.hincrBy("user:5001:activity", "seckill_count", 1);
tx.exec();
4.2 分布式锁的原子续期
// 续期操作的原子性保障
if(jedis.setnx("lock:order", "client1") == 1) {
Transaction tx = jedis.multi();
tx.expire("lock:order", 30);
tx.pexpire("order:status", 30000);
tx.exec();
}
4.3 用户行为批处理
收集用户行为日志时:
jedis.multi()
.rpush("user:log:1001", "click:buttonA")
.rpush("user:log:1001", "view:product100")
.expire("user:log:1001", 3600)
.exec();
5. Redis事务的优缺点全景剖析
5.1 优势集中体现
- 性能优异:10万次事务操作仅需约1.2秒(基准测试数据)
- 原子性保障:命令队列的全执行或全放弃
- 监控机制灵活:WATCH实现乐观锁控制
5.2 局限性认知
- 无回滚机制:需自行实现补偿逻辑
- 不支持条件判断:需依赖WATCH实现类似功能
- 长事务阻塞风险:单线程架构下的性能陷阱
6. 开发者必须掌握的注意事项
6.1 事务中的Lua脚本优化
String luaScript =
"local current = redis.call('GET', KEYS[1])\n" +
"if tonumber(current) >= tonumber(ARGV[1]) then\n" +
" redis.call('DECRBY', KEYS[1], ARGV[1])\n" +
" return 1\n" +
"else\n" +
" return 0\n" +
"end";
// 执行原子操作
Object result = jedis.eval(luaScript, 1, "inventory:item2001", "5");
6.2 Spring整合Redis事务的要点
@Configuration
public class RedisConfig {
@Bean
public PlatformTransactionManager transactionManager() {
return new DataSourceTransactionManager(dataSource());
}
@Bean
public RedisTemplate<String, Object> redisTemplate() {
RedisTemplate<String, Object> template = new RedisTemplate<>();
template.setEnableTransactionSupport(true);
// 其他配置项...
return template;
}
}
7. 从架构角度看待Redis事务
7.1 集群环境下的限制
- 所有操作必须落在一个节点(Hash Tag的使用技巧)
- 事务与Pipeline的结合应用
7.2 事务监控的黄金指标
- 事务执行成功/失败率
- 平均事务耗时(通常应<5ms)
- Watch命令调用频率
8. 文章总结与实践建议
Redis事务在分布式系统中扮演着轻量级原子操作执行者的角色。当遇到需要批量操作且需要原子性的场景时,应该优先考虑Redis事务方案。但需要特别注意:
- 提前规划键值结构:良好的数据结构设计可以减少事务复杂度
- 事务隔离级别验证:根据业务需求测试竞态条件
- 压力测试不可少:建议在预发布环境进行全链路压测
实践中的两个黄金法则:
- 简单事务直接用原生命令
- 复杂业务优先采用Lua脚本