针对一人一单问题,采用锁的方式可以解决在单机部署下此问题。
但是在面对集群部署情况,单独的内部监视锁会出现并发问题。
在多线程访问时,可能会发生同时获取到以用户ID作为锁的情况,
因为在不同机器内部,锁并非同一把,所以针对分布式情况下,不同集群部署的机器,
无法共用同一把锁的情况,使用需要使用分布式锁来解决此问题。
本次采用REDIS实现分布式锁
1-初级实现
利用redis的互斥特性实现获取和释放锁
向redis中存入以userId为标志的互斥锁,同时设置过期时间(以防止系统奔溃引起的永远无法获取到互斥锁)
如果设置锁失败,则返回错误
如果设置锁成功则执行业务
执行成功后释放锁,删除掉redis中的互斥锁,完成分布式锁的步骤
初步完成的分布式锁可以解决分布式系统中存在的并发问题,
但是仍旧不完美存在系统在并发情况下的误删问题,
在访问集群的情况下,当某线程获取到互斥锁发生线程堵塞,该线程在redis中的互斥锁会在达到过期时间后失效,释放掉锁,此时其他先线程可以获取到互斥锁执行任务,但是已经堵塞的线程在死灰复燃后,依旧向下执行会执行到删除redis中互斥锁的操作,于是其余线程发生了可以再次获取到锁执行的情况,总结发生了非一人一单的情况,针对于此,对初步实现的redis互斥锁进行优化。
解决方案
在存入redis互斥锁的时候,对redis互斥锁的value做标识,一般使用uuid或者其他随机数字,生成当前线程的唯一标识作为自己的互斥锁,在删除释放锁的时候,对value值进行判断,如果锁值不一致,则证明不是同一个线程的互斥锁,不允许进行删除释放。
执行逻辑
判断锁是否存在
如果存在证明已经有线程获取到了锁,此时直接返回错误
如果不存在,则执行在redis中插入锁,
执行业务逻辑后,在删除互斥锁之前进行判断,判断互斥锁的value值是否和时本系统的生成的值相等,如果不相等不允许删除互斥锁,如果相对则删除redis中的互斥锁。
(即通过uuid随机数生成的随机值+线程ID)
至此解决大部分问题
然而依旧存在原子性问题,
当线程1在执行过程中,判断互斥锁的value值相同后,做出是当前线程的判断后,线程发生阻塞(一般发生在垃圾回收),,此时线程2同样可以获取到锁,执行任务,在此时线程1,死灰复燃后继续执行,会去删除不属于自己线程的锁,会造成其他线程依旧可以获取到锁的情况,使一人一单的互斥锁失效。
解决方法
提高互斥锁的原子性,利用Lua脚本解决多条命令原子性的问题。
redis分布式锁的基本实现思路
利用set nx ex 获取互斥锁,并设置过期时间,
同时保存线程标识
释放互斥锁时先判断线程标识与自己是否一致,一致则删除锁
特性:
利用set nx满足互斥性
利用set ex保证故障时锁依旧能释放,避免死锁发生
,提高安全性
利用redis集群部署保证高可用和高并发特性