Redis 事务
Redis 事务:一次执行多个命令
Redis事务工作特点:
- 批量操作放入队列缓存。(执行有顺序)
- 事务中任意命令执行失败,其余的命令依然被执行。(不保证原子性)
- 在事务执行过程,其他命令请求不会插入到事务执行命令序列中。(排他性)
三个阶段:
- 开始事务。(multi)
- 命令入队。
- 执行事务。(exec)
实例
127.0.0.1:6379> multi #开启事务 OK ########## 命令入队 开始################## 127.0.0.1:6379(TX)> set k1 v1 QUEUED 127.0.0.1:6379(TX)> set k2 v2 QUEUED 127.0.0.1:6379(TX)> get k1 QUEUED 127.0.0.1:6379(TX)> set k3 v3 QUEUED ########### 命令入队 结束 ################# 127.0.0.1:6379(TX)> exec # 执行事务 1) OK 2) OK 3) "v1" 4) OK
放弃事务
127.0.0.1:6379> multi OK 127.0.0.1:6379(TX)> set k4 v4 QUEUED 127.0.0.1:6379(TX)> set k5 v5 QUEUED 127.0.0.1:6379(TX)> discard #取消事务 OK 127.0.0.1:6379> get k4 ####取消执行事务中所有命令,k4就会没值 (nil)
命令有误,事务中所有命令都不执行
127.0.0.1:6379> multi OK 127.0.0.1:6379(TX)> set k1 v1 QUEUED 127.0.0.1:6379(TX)> getset k2 #命令错误 (error) ERR wrong number of arguments for 'getset' command 127.0.0.1:6379(TX)> set k3 v3 QUEUED 127.0.0.1:6379(TX)> exec (error) EXECABORT Transaction discarded because of previous errors.
运行时异常
127.0.0.1:6379> set k1 v1 OK 127.0.0.1:6379> multi OK 127.0.0.1:6379(TX)> INCR k1 #k1自加一,必须时数值,有错误,不影响其他命令执行 QUEUED 127.0.0.1:6379(TX)> set k2 v2 QUEUED 127.0.0.1:6379(TX)> exec 1) (error) ERR value is not an integer or out of range 2) OK 127.0.0.1:6379> get k2 "v2"
Redis 事务--锁
Watch:相当于乐观锁
通过监视key的值是否改变来决定事务能否正常提交
实例
正常执行
127.0.0.1:6379> set money 100 OK 127.0.0.1:6379> set out 0 OK 127.0.0.1:6379> watch money #监视money OK 127.0.0.1:6379> multi OK 127.0.0.1:6379(TX)> decrby money 20 QUEUED 127.0.0.1:6379(TX)> incrby out 20 QUEUED 127.0.0.1:6379(TX)> exec #正常执行 1) (integer) 80 2) (integer) 20
事务正常执行完成后,watch监视结束
多线程操作
127.0.0.1:6379> watch money OK 127.0.0.1:6379> multi OK 127.0.0.1:6379(TX)> DECRBY money 50 QUEUED 127.0.0.1:6379(TX)> INCRBY out 50 QUEUED 127.0.0.1:6379(TX)>
事务没有执行,另一个线程修改了监视的key
执行事务
127.0.0.1:6379(TX)> exec #执行失败 (nil)
重新执行事务,使用UNWATCH命令(用于取消 WATCH 命令对所有 key 的监视)来保证下一个事务的执行不会受到影响
127.0.0.1:6379> watch money #开启监视 OK 127.0.0.1:6379> multi OK 127.0.0.1:6379(TX)> DECRBY money 50 QUEUED 127.0.0.1:6379(TX)> INCRBY out 50 QUEUED 127.0.0.1:6379(TX)> exec # 执行之前,另一个线程执行了 set money 200 (nil) 127.0.0.1:6379> UNWATCH #取消 WATCH 命令对所有 key 的监视 OK 127.0.0.1:6379> WATCH money #开启监视 OK 127.0.0.1:6379> multi OK 127.0.0.1:6379(TX)> DECRBY money 50 QUEUED 127.0.0.1:6379(TX)> INCRBY out 50 QUEUED 127.0.0.1:6379(TX)> exec 1) (integer) 150 2) (integer) 50
分布式锁(setnx)
利用setnx命令的特征(存在Key则返回设置失败,不存在Key则返回设置成功),并通过del命令释放锁。
127.0.0.1:6379> set num 10 OK 127.0.0.1:6379> setnx lock-num 2 #设置key (integer) 1 127.0.0.1:6379> incrby num -1 (integer) 9 127.0.0.1:6379> del lock-num #释放,如果不释放的话,就无法再次通过setnx设置key (integer) 1 127.0.0.1:6379> setnx lock-num 1 (integer) 1 127.0.0.1:6379> setnx lock-num 1 (integer) 0
使用setnx可能会出现忘记释放的情况,需要针对key设置有效时间
- expire lock-key second
- pexpire lock-key milliseconds
127.0.0.1:6379> setnx lock-num 1 (integer) 0 127.0.0.1:6379> expire lock-num 1 (integer) 1 127.0.0.1:6379> setnx lock-num 1 (integer) 1
由于操作通常都是微秒或毫秒级,因此该锁定时间不宜设置过大。具体时间需要业务测试后确认。
例如:持有锁的操作最长执行时间127ms,最短执行时间7ms。
测试百万次最长执行时间对应命令的最大耗时,测试百万次网络延迟平均耗时
锁时间设定推荐:最大耗时*120%+平均网络延迟*110%
如果业务最大耗时<<网络平均延迟,通常为2个数量级,取其中单个耗时较长即可