分布式锁实现目前有三种:
- 数据库乐观锁;
- ZooKeeper的分布式锁;
- Redis的分布式锁;
在以前单体架构Web应用场景下,我们可以使用ReentrantLock或synchronized进行上锁,保证资源安全,现如今大部分Web应用都采用分布式架构,分布式架构可以简单理解为将一个Web应用,部署在多个应用服务器上
由于分布在不同服务器上,这将使原单体架构使用的锁(例如synchronized)失效,为了解决这个问题就需要一种跨JVM的互斥机制来控制共享资源的访问,这时就需要使用分布式锁。
分布式锁适用于分布式架构场景,例如:
- 商品秒杀
- 抢优惠卷
平时开发过程中,如果没有保证线程安全,就可能会出现商品超卖、优惠卷发超等现象,例如下图代码:
我们会判断商品库存是否大于0,如果有库存就会将库存 - 1,不过有个问题,如果同时有多个用户(多线程)同时发送请求给接口,例如三个用户同时请求接口,同时获取商品库存、扣除库存,很有可能商品库存最终只扣除1次,这时候数据安全性就得不到保障了,线程安全问题发生的本质是多线程访问同一临界区进行上下文切换,最终导致指令交错!
想要解决线程安全问题,我们首先想到的是synchronized
,通过此方法来保障某一时刻,只有一个线程拿到锁资访问接口,如果是单体架构项目通过这种方式是可以的(功能可以保证,但是性能很差)。
现如今,大多数Web应用使用的是分布式部署,需要使用多个服务器(例如Tomcat)进行分布式部署,Nginx对我们的服务器集群做反向代理负载均衡,那这种分布式架构场景使用synchronized
会不会有问题?会!
为什么分布式场景使用synchronized
依然会出现线程安全问题?
因为synchronized是JVM进程级别的锁,由于分布式部署原因,同样的synchronized代码块内容在不同的服务器(tomcat1和tomcat2)运行时,抢占的的不是同一把锁。所以当多个请求通过Nginx分发到不同的tomcat服务器,此时synchronized没办法保证线程安全问题,因为图中这两台tomcat上的synchronized使用的不是同一个锁,就没办法保证线程安全,这时就需要使用分布式锁!
总结:
因为进程具有独立性,各个进程(tomcat)无法访问其他进程的资源,因此无法通过synchronized等线程锁实现线程安全
分布式场景保证线程安全,尽量不要使用synchronized(并发低情况勉强可以使用),应该使用分布式锁,例如:Redisson
redis实现分布式锁底层是基于命令:SET key value NX EX max-lock-time
使用Redisson保证分布式场景下线程安全问题,代码如下:
Redisson分布式锁实现原理: