1.为什么需要redis分布式锁?
世界服无状态服务器,玩家数据处理过程不一定落地到哪个节点的协程,这时候为了保证数据的一致性,就需要有redis分布式锁来锁定数据,保证同个数据只能在一个协程处理
实现分布式锁要满足3点:多进程可见,互斥,可重入。
1 多进程可见:所有操作数据的节点进程都能连接锁对应的redis集群;
2 互斥:各个进程之间持有锁是互斥的;我们可以通过redis的setnx实现,只有第一次执行的才会成功并返回1,其它情况返回0。那么问题来了,同一个进程内怎么办呢?当然是进程内也加锁了,加锁的时候锁标记+1类似引用计数器;这样分布式锁就可以锁到协程级别了;
释放锁
如果锁过期了怎么办呢?
释放锁其实只需要把锁的key删除即可,使用del xxx指令。不过,如果在我们执行del之前,服务突然宕机,那么锁就永远无法删除了。所以我们可以通过setex 命令设置过期时间即可。
以上问题解决又有新的问题如下:
一:获取锁时
setnx获取锁成功了,还没来得及setex服务就宕机了,由于这种非原子性的操作,死锁又发生了。其实redis提供了 nx 与 ex连用的命令。
二:释放锁时
1. 3个进程:A和B和C,在执行任务,并争抢锁,此时A获取了锁,并设置自动过期时间为10s
2. A开始执行业务,因为某种原因,业务阻塞,耗时超过了10秒,此时锁自动释放了
3. B恰好此时开始尝试获取锁,因为锁已经自动释放,成功获取锁
4. A此时业务执行完毕,执行释放锁逻辑(删除key),于是B的锁被释放了,而B其实还在执行业务
5. 此时进程C尝试获取锁,也成功了,因为A把B的锁删除了。
问题出现了:B和C同时获取了锁,违反了互斥性!如何解决这个问题呢?我们应该在删除锁之前,判断这个锁是否是自己设置的锁,如果不是(例如自己 的锁已经超时释放),那么就不要删除了。所以我们可以在set 锁时,存入当前线程的唯一标识!删除锁前,判断下里面的值是不是与自己标识释放一 致,如果不一致,说明不是自己的锁,就不要删除了。
3可重入锁:
也叫做递归锁,指的是在同一线程内,外层函数获得锁之后,内层递归函数仍然可以获取到该锁。换一种说法:同一个线程再次进入同步代码时,可以使用自己已获取到的锁。可重入锁可以避免因同一线程中多次获取锁而导致死锁发生。像synchronized就是一个重入锁,它是通过moniter函数记录当前线程信息来实现的。实现可重入锁需要考虑两点:
获取锁:首先尝试获取锁,如果获取失败,判断这个锁是否是自己的,如果是则允许再次获取, 而且必须记录重复获取锁的次数。
释放锁:释放锁不能直接删除了,因为锁是可重入的,如果锁进入了多次,在内层直接删除锁, 导致外部的业务在没有锁的情况下执行,会有安全问题。因此必须获取锁时累计重入的次数,释放时则减去重入次数,如果减到0,则可以删除锁。
开源库有直接可以用的详情可以参考 :
redis分布式锁与redsync库源码分析
这个库的原理就是redis集群锁定