使用redis分布式锁解决并发线程资源共享问题
众所周知, 在多线程中,因为共享全局变量,会导致资源修改结果不一致,所以需要加锁来解决这个问题,保证同一时间只有一个线程对资源进行操作
但是在分布式架构中,我们的服务可能会有n个实例,但线程锁只对同一个实例有效,就需要用到分布式锁—-redis setnx
原理:
修改某个资源时, 在redis中设置一个key,value根据实际情况自行决定如何表示
我们既然要通过检查key是否存在(存在表示有线程在修改资源,资源上锁,其他线程不可同时操作,若key不存在,表示资源未被线程占用,允许线程抢占,然后将通过setnx设置vlaue,表示资源上锁,其他线程不可同时操作)
图示:
分析:
我们的服务处于一个集群中,如果只是简单的的使用线程锁来解决以上问题,是存在问题的,因为线程是基于进程的,两个web server处于不同的进程空间,也就是说,user1的请求发往web server1,那只能与web server1的其他请求进行锁的操作,而不能对web server2的请求产生影响,这就会导致最终数据不一致,所以就要用到分布式锁
为什么用redis?
因为redis是单线程的,不存在多线程资源竞争,并且它真的很快
为什么用setnx 而不是set?
setnx表示只有在key不存在时才能设置成功,但是set会在key存在的情况下修改value
利用setnx的特性,我们可以这样这样设计:
伪代码:
# 设置redis锁的 redis key = 'residue_ticket_lock' # get_ticket是处理购票的逻辑 def get_ticket(): time_out = 5 # 为了防止线程过多,当前线程获取不到锁,长时间处于循环中而导致的性能影响,我们设置一个超时时间,如果当前线程在超时时间内还没有抢占到分布式锁,就返回失败的结果 while True: if redis.setnx('residue_ticket_lock'): # 如果setnx返回True, 表示此刻没有其他线程在操作数据库,当前线程可以上锁成功 residue_ticket = mysql.get('residue_ticket') # 从mysql中获取当前剩余票数 mysql.update('residue_ticket',residue_ticket-1) # 订购成功,将票数-1,更新数据到mysql return True else: # 如果setnx返回False,表示有其他线程对在操作,当前线程等待0.01s,并继续循环 time.sleep(0.01) time_out -= 0.01 continue return False