分布式锁

千万级流量以上的项目-基本上都会用redis。

RedLock-redis创始人 比较提出的方案。

我们真的需要锁么?

需要锁的条件:

  • 多任务环境下。(进程-线程)
  • 任务都对同一共享资源进行写操作。
  • 对资源的访问是互斥的。
  • 操作周期:

  • 竞争锁。获取锁后才能对资源进行操作。
  • 占有锁。操作中。
  • 其他竞争者-任务阻塞。
  • 占有锁者-释放锁。继续从1开始。
  • 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

    牛逼点的:https://help.aliyun.com/document_detail/101100.html?spm=5176.11065259.1996646101.searchclickresult.5a6316bcjenDJn

    基于Redis

    `sh stringRedisTemplate 用法https://blog.csdn.net/zzz127333092/article/details/88742088 `

    redis:内存存储的数据结构服务器-内存数据库。可用于:数据库-高速缓存-消息队列。采用单线程模型-并发能力强大。10万并发没问题。

    分布锁知识:

    redis的单进程单线程。

    缓存有效期。有效期到-删除数据。

    setnx。当key存在-不做任何操作-key不存在-才设置。

    > 《Redis 分布锁》

    #### 单节点

    _加锁_

    SET orderId driverId NX PX 30000

    上面的命令如果执行成功-则客户端成功获取到了锁-接下来就可以访问共享资源了;而如果上面的命令执行失败-则说明获取锁失败。

    _释放锁_

    关键-判断是不是自己加的锁。

    _关注点_

  • orderId-是我们的key-要锁的目标。
  • driverId是由我们的司机ID-它要保证在足够长的一段时间内在所有客户端的所有获取锁的请求中都是唯一的。即一个订单被一个司机抢。
  • NX表示只有当orderId不存在的时候才能SET成功。这保证了只有第一个请求的客户端才能获得锁-而其它客户端在锁被释放之前都无法获得锁。
  • PX 30000表示这个锁有一个30秒的自动过期时间。当然-这里30秒只是一个例子-客户端可以选择合适的过期时间。
  • 这个锁必须要设置一个过期时间。否则的话-当一个客户端获取锁成功之后-假如它崩溃了-或者由于发生了网络分区-导致它再也无法和Redis节点通信了-那么它就会一直持有这个锁-而其它客户端永远无法获得锁了。antirez在后面的分析中也特别强调了这一点-而且把这个过期时间称为锁的有效时间(lock validity time)。获得锁的客户端必须在这个时间之内完成对共享资源的访问。
  • 此操作不能分割。
  • `sh SETNX orderId driverIdEXPIRE orderId 30虽然这两个命令和前面算法描述中的一个SET命令执行效果相同-但却不是原子的。如果客户端在执行完SETNX后崩溃了-那么就没有机会执行EXPIRE了-导致它一直持有这个锁。造成死锁。 `

  • 必须给key设置一个value。value保证每个线程不一样。如果value在每个线程间一样。会发生 误解锁的问题。
  • `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故障了-所有客户端无法获取锁-服务变得不可用。为了提高可用性。我们给redis 配置主从。当master不可用时-系统切换到slave-由于Redis的主从复制(replication)是异步的-这可能导致丧失锁的安全性。

    `sh 1.客户端1从Master获取了锁。2.Master宕机了-存储锁的key还没有来得及同步到Slave上。3.Slave升级为Master。4.客户端2从新的Master获取到了对应同一个资源的锁。 `<