最近看了一些redis方面的书籍,对分布式锁重点看了一下,觉得想写出一把优秀的分布式锁真的非常难,下面我做一个秒杀商品的分布式锁。
使用flask简单模拟一个秒杀场景。
首先最简单的分布式锁一般是使用一个 setnx
即可,但是如果在秒杀业务的过程中出现了代码报错怎么办?ok,我们想到了用try…finally…,没问题,但是如果是出现了断电呢?那么我们的分布式锁就永远得不到释放了,所以在写分布式锁时,要设置一个超时时间。还有就是为什么要用一个uuid作为分布式锁的value呢?是不是用一个随便什么数都能作为锁的value?
其实不是的,例如A进程,当执行到try 中的代码时,因为一些因素,比如进程调度等等因素,他设置的分布式锁已经超时被redis删除掉了,那么此时进程B就得到了这把锁,然后B去执行业务,但此时是不是可能出现A进程执行完毕,A执行了释放分布式锁的代码?那么如果不加以控制,就会出现A释放了B的分布式锁。此时如果并发极高,会立刻有新的进程得到了这把锁,从而进入了业务代码,造成两个进程同时进入业务代码的情况,这种情况是不能容忍的。
所以呢,我们要生成一个唯一的uuid来标示这把分布式锁是我设置的,只能我去解除,别人不可以。
当然,这把分布式锁还不完善,在大并发下还有很多的bug,
import redis,uuid
from flask import Flask, request
pool = redis.ConnectionPool(host='127.0.0.1',port=6379,password='',decode_responses=True,db=5)
r = redis.Redis(connection_pool=pool)
app = Flask(__name__)
@app.route('/')
def hello():
clientid = str(uuid.uuid4())
if r.setnx('mutex',clientid):
# 其实这一步和if语句应该合并成一个原子操作执行,
但是现在还没有在pyredis中找到支持的方法,java语言的jedis貌似是支持的
r.expire('mutex',10)
else:
return '在线人数较多,请稍后再试。'
try:
g_num = int(r.get('goods'))
if g_num > 0:
r.decr('goods')
status = True
else:
status = False
finally:
if r.get('mutex') == clientid: # 释放锁
r.delete('mutex')
return '抢货成功~!' if status else '货已售完~0.0'
if __name__ == '__main__':
app.run()