使用 为了保证原子性, redis提供了简单的事务. 但不支持事务回滚
1 2 3 multi 开始事务, 之后输入命令, 会放入队列而不是真正的执行 discard 停止事务 exec 执行事务, 真正执行命令
细节
在事务中, 如果有一条命令执行错误, 其他的命令也会正常执行 即redis的事务不满足原子性, 但满足隔离性(redis为单线程, 一个事务内的指定可以得到严格的串行化执行)
使用discard停止事务后, 之前提交的命令不会执行流水线vs事务 流水线: 将多个命令打包, 然后一并发送到服务器 事务: 将多个命令打包, 然后让服务器一并执行客户端使用 在客户端中使用事务, 很多库的实现方式是用pipline将命令打包发送到服务器, 并在服务端用事务执行. 这样就能实现多个命令的打包发送, 以及打包执行.
watch命令 watch命令可以对数据库的key进行监视, 如果在exec之前, 这些键的值发生了变化, 那么该事务执行失败.
取消watch
unwatch命令
exec命令执行事务后, 监视自动取消
discard终止事务, 监视自动取消
使用watch实现乐观锁 用被监视的key当做锁, 如果键值发生改变, 事务执行失败;
分布式锁是一种悲观锁
案例 假设我们对redis中的数据进行倍数操作; 如果是加减操作, 可以直接用incrby, 不会产生并发问题. 但是redis并没有提供倍数操作的指令, 我们只能先取值, 运算, 再存入. 这就会产生并发问题. 解决方法可以用watch
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 package redis; import redis.clients.jedis.Jedis; import redis.clients.jedis.Transaction; import java.util.List; /** * @Author wenxuan.hao * @create 2020-02-16 00:31 */ public class Watch { public static void main(String[] args) { Jedis jedis = new Jedis(); String userId = "abc"; String key = keyFor(userId); jedis.setnx(key, String.valueOf(5)); System.out.println(doubleAccount(jedis, userId)); jedis.close(); } public static int doubleAccount(Jedis jedis, String userId) { String key = keyFor(userId); // 若加锁失败, 自旋 while (true) { jedis.watch(key); int value = Integer.parseInt(jedis.get(key)); value *= 2; // 加倍 // 事务 Transaction tx = jedis.multi(); tx.set(key, String.valueOf(value)); List<Object> res = tx.exec(); // 因为对key进行了watch, 如果key没有改变, 更新成功, 跳出循环 if (res != null) { break; // 成功了 } } return Integer.parseInt(jedis.get(key)); // 重新获取余额 } public static String keyFor(String userId) { return String.format("account_%s", userId); } }