redis分布式锁解决超卖问题
redis事务
redis事务介绍:
1. redis事务可以一次执行多个命令,本质是一组命令的集合。
2.一个事务中的所有命令都会序列化,按顺序串行化的执行而不会被其他命令插入
作用:一个队列中,一次性、顺序性、排他性的执行一系列命令
multi指令的使用
1. 下面指令演示了一个完整的事物过程,所有指令在exec前不执行,而是缓存在服务器的一个事物队列中
2. 服务器一旦收到exec指令才开始执行事物队列,执行完毕后一次性返回所有结果
3. 因为redis是单线程的,所以不必担心自己在执行队列是被打断,可以保证这样的“原子性”
注:redis事物在遇到指令失败后,后面的指令会继续执行
# Multi 命令用于标记一个事务块的开始事务块内的多条命令会按照先后顺序被放进一个队列当中,最后由 EXEC 命令原子性( atomic )地执行 > multi(开始一个redis事物) incr books incr books > exec (执行事物) > discard (丢弃事物)
[ ~]# redis-cli 127.0.0.1:6379> multi OK 127.0.0.1:6379> set test 123 QUEUED 127.0.0.1:6379> exec 1) OK 127.0.0.1:6379> get test "123" 127.0.0.1:6379> multi OK 127.0.0.1:6379> set test 456 QUEUED 127.0.0.1:6379> discard OK 127.0.0.1:6379> get test "123" 127.0.0.1:6379>
redis客户端测试事务指令
def multi_test(): r = redis.Redis(host=‘127.0.0.1‘) pipe = r.pipeline() pipe.multi() #开启事务 pipe.set(‘key2‘, 400) #存储子命令 pipe.execute() #执行事务 print("第一次事务提交后的结果"+r.get(‘key2‘).decode("utf-8")) pipe.multi() # 开启事务 pipe.set(‘key2‘, 100) # 存储子命令 print("第二次未提交事务的结果"+r.get("key2").decode("utf-8")) #第一次事务提交后的结果400 #第二次未提交事务的结果400
python测试事务指令
注:mysql的rollback与redis的discard的区别
1. mysql回滚为sql全部成功才执行,一条sql失败则全部失败,执行rollback后所有语句造成的影响消失
2. redis的discard只是结束本次事务,正确命令造成的影响仍然还在.
1)redis如果在一个事务中的命令出现错误,那么所有的命令都不会执行;
2)redis如果在一个事务中出现运行错误,那么正确的命令会被执行。
watch 指令作用
实质:WATCH 只会在数据被其他客户端抢先修改了的情况下通知执行命令的这个客户端(通过 WatchError 异常)但不会阻止其他客户端对数据的修改
1. watch其实就是redis提供的一种乐观锁,可以解决并发修改问题
2. watch会在事物开始前盯住一个或多个关键变量,当服务器收到exec指令要顺序执行缓存中的事物队列时,redis会检查关键变量自watch后是否被修改
3. WATCH 只会在数据被其他客户端抢先修改了的情况下通知执行命令的这个客户端(通过 WatchError 异常)但不会阻止其他客户端对数据的修改
watch+multi实现乐观锁
setnx指令(redis的分布式锁)
1、分布式锁
1. 分布式锁本质是占一个坑,当别的进程也要来占坑时发现已经被占,就会放弃或者稍后重试
2. 占坑一般使用 setnx(set if not exists)指令,只允许一个客户端占坑
3. 先来先占,用完了在调用del指令释放坑
> setnx lock:codehole true .... do something critical .... > del lock:codehole
4. 但是这样有一个问题,如果逻辑执行到中间出现异常,可能导致del指令没有被调用,这样就会陷入死锁,锁永远无法释放
5. 为了解决死锁问题,我们拿到锁时可以加上一个expire过期时间,这样即使出现异常,当到达过期时间也会自动释放锁
> setnx lock:codehole true > expire lock:codehole 5 .... do something critical .... > del lock:codehole
6. 这样又有一个问题,setnx和expire是两条指令而不是原子指令,如果两条指令之间进程挂掉依然会出现死锁
7. 为了治理上面乱象,在redis 2.8中加入了set指令的扩展参数,使setnx和expire指令可以一起执行
> set lock:codehole true ex 5 nx ‘‘‘ do something ‘‘‘ > del lock:codehole