分布式锁
千万级流量以上的项目-基本上都会用redis。
RedLock-redis创始人 比较提出的方案。
我们真的需要锁么?
需要锁的条件:
操作周期:
JVM 锁 解决不了分布式环境中的加锁问题。
分布式锁应用场景:服务集群-比如N个订单服务-接受到大量司机的发送的对一个订单的抢单请求。如果是单个服务-可以用jvm锁控制-但是服务集群-jvm 就不行了。因为不在一个jvm中。
分布式锁解决方案
api-driver, eureka 7900 service-order 8004,8005
无锁情况
``sh
@Qualifier("grabNoLockService")tb_order表中 status设置0执行jmeter。司机抢单。结果:司机:1 执行抢单逻辑司机:2 执行抢单逻辑司机:1 抢单成功司机:3 执行抢单逻辑司机:2 抢单成功司机:4 执行抢单逻辑司机:3 抢单失败司机:5 执行抢单逻辑司机:4 抢单失败司机:6 执行抢单逻辑司机:5 抢单失败司机:7 执行抢单逻辑司机:6 抢单失败司机:8 执行抢单逻辑司机:7 抢单失败司机:8 抢单失败司机:9 执行抢单逻辑司机:10 执行抢单逻辑司机:9 抢单失败司机:10 抢单失败1和2 都抢单成功。
`
JVM 锁
`sh
@Qualifier("grabJvmLockService")司机:1 执行抢单逻辑2020-03-07 12:20:46.931 INFO 20484 --- [nio-8004-exec-1] com.alibaba.druid.pool.DruidDataSource : {dataSource-9} inited司机:1 抢单成功司机:10 执行抢单逻辑司机:10 抢单失败司机:9 执行抢单逻辑司机:9 抢单失败司机:8 执行抢单逻辑司机:8 抢单失败司机:7 执行抢单逻辑司机:7 抢单失败司机:6 执行抢单逻辑司机:6 抢单失败司机:5 执行抢单逻辑司机:5 抢单失败司机:4 执行抢单逻辑司机:4 抢单失败司机:3 执行抢单逻辑司机:3 抢单失败司机:2 执行抢单逻辑司机:2 抢单失败只有一个抢单成功
`
但是:启动两个service-order8004,8005-则有下面情况
`sh
8005:司机:1 执行抢单逻辑2020-03-07 12:43:49.821 INFO 9292 --- [nio-8005-exec-1] com.alibaba.druid.pool.DruidDataSource : {dataSource-1} inited司机:1 抢单成功司机:9 执行抢单逻辑司机:9 抢单失败司机:7 执行抢单逻辑司机:7 抢单失败司机:5 执行抢单逻辑司机:5 抢单失败司机:3 执行抢单逻辑司机:3 抢单失败8004:司机:2 执行抢单逻辑2020-03-07 12:43:49.977 INFO 8880 --- [nio-8004-exec-1] com.alibaba.druid.pool.DruidDataSource : {dataSource-1} inited司机:2 抢单成功司机:10 执行抢单逻辑司机:10 抢单失败司机:8 执行抢单逻辑司机:8 抢单失败司机:6 执行抢单逻辑司机:6 抢单失败司机:4 执行抢单逻辑司机:4 抢单失败
`
问题:无法解决分布式-集群环境的问题。所以要用分布锁
基于mysql
测试时要恢复数据。tbl_order 中status 为0-tbl_order_lock清空
@Qualifier(“grabMysqlLockService”) 实际用 事件实现。
`sh
8005:司机6加锁成功司机:6 执行抢单逻辑司机:6 抢单成功司机4加锁成功司机:4 执行抢单逻辑司机:4 抢单失败司机8加锁成功司机:8 执行抢单逻辑司机:8 抢单失败司机10加锁成功司机:10 执行抢单逻辑司机:10 抢单失败司机2加锁成功司机:2 执行抢单逻辑司机:2 抢单失败8004:2020-03-07 12:50:04.938 INFO 7356 --- [nio-8004-exec-1] com.alibaba.druid.pool.DruidDataSource : {dataSource-1} inited司机7加锁成功司机:7 执行抢单逻辑司机:7 抢单失败司机1加锁成功司机:1 执行抢单逻辑司机:1 抢单失败司机5加锁成功司机:5 执行抢单逻辑司机:5 抢单失败司机9加锁成功司机:9 执行抢单逻辑司机:9 抢单失败司机3加锁成功司机:3 执行抢单逻辑司机:3 抢单失败
`
问题:
1、如果中间出异常了-如何释放锁-用存储过程-还是可以解决。
2、mysql 并发是由限制的。不适合高并发场景。
压测结果:https://help.aliyun.com/document_detail/150351.html?spm=a2c4g.11186623.6.1463.1e732d02nCMBBa
基于Redis
`sh
stringRedisTemplate 用法https://blog.csdn.net/zzz127333092/article/details/88742088
`
redis:内存存储的数据结构服务器-内存数据库。可用于:数据库-高速缓存-消息队列。采用单线程模型-并发能力强大。10万并发没问题。
分布锁知识:
redis的单进程单线程。
缓存有效期。有效期到-删除数据。
setnx。当key存在-不做任何操作-key不存在-才设置。
> 《Redis 分布锁》
#### 单节点
_加锁_
SET orderId driverId NX PX 30000
上面的命令如果执行成功-则客户端成功获取到了锁-接下来就可以访问共享资源了;而如果上面的命令执行失败-则说明获取锁失败。
_释放锁_
关键-判断是不是自己加的锁。
_关注点_:
`sh
SETNX orderId driverIdEXPIRE orderId 30虽然这两个命令和前面算法描述中的一个SET命令执行效果相同-但却不是原子的。如果客户端在执行完SETNX后崩溃了-那么就没有机会执行EXPIRE了-导致它一直持有这个锁。造成死锁。
`
`sh
1.客户端1获取锁成功。2.客户端1在某个操作上阻塞了很长时间。3.过期时间到了-锁自动释放了。4.客户端2获取到了对应同一个资源的锁。5.客户端1从阻塞中恢复过来-释放掉了客户端2持有的锁。之后-客户端2在访问共享资源的时候-就没有锁为它提供保护了。
`
`sh
1.客户端1获取锁成功。2.客户端1访问共享资源。3.客户端1为了释放锁-先执行'GET'操作获取随机字符串的值。4.客户端1判断随机字符串的值-与预期的值相等。5.客户端1由于某个原因阻塞住了很长时间。6.过期时间到了-锁自动释放了。7.客户端2获取到了对应同一个资源的锁。8.客户端1从阻塞中恢复过来-执行DEL操纵-释放掉了客户端2持有的锁。
`
如果redis故障了-所有客户端无法获取锁-服务变得不可用。为了提高可用性。我们给redis 配置主从。当master不可用时-系统切换到slave-由于Redis的主从复制(replication)是异步的-这可能导致丧失锁的安全性。
`sh
1.客户端1从Master获取了锁。2.Master宕机了-存储锁的key还没有来得及同步到Slave上。3.Slave升级为Master。4.客户端2从新的Master获取到了对应同一个资源的锁。
`<