举个栗子:
高并发下争夺共享资源,比如秒杀对于库存这种共享资源需要用到分布式锁,如果不用分布式锁很可能造成超卖。
分布式锁也是锁
-
在单体应用的时候,如果多个线程要访问共享资源的时候,我们通常线程间加锁的机制,在某一个时刻,只有一个线程可以对这个资源进行操作,其他线程需要等待锁的释放,Java中也有一些处理锁的机制,比如synchronized。
-
而到了分布式的环境中,当某个资源可以被多个系统访问使用到的时候,为了保证大家访问这个数据是一致性的,那么就要求再同一个时刻,只能被一个系统使用,这时候线程之间的锁机制就无法起到作用了,因为分布式环境中,系统是会部署到不同的机器上面的,那么就需要【分布式锁】了。
什么时候需要使用分布式锁
总结来看,当有多个客户端需要访问并操作同一个资源,还需要保持这个资源一致性的时候,就需要使用【分布式锁】,让多客户端互斥的对共享资源进行访问。
举个例子来说明一下:
-
有多个批处理任务,两台机器同时处理,如果不加任何控制的话,很有可能同一个批处理被两台机器分别处理一遍;如果使用分布式锁,在领取任务的时候,一个任务只会被一台机器领到,这样就不会造成任务的重复执行;
-
再多思考一些,如果A/B两台机器,任务1被A机器领取到进行处理,在处理到一半的时候,A机器挂掉了,那么这个批处理任务也就无法顺利执行了,除非A机器可以恢复。
这时候就可以知道分布式锁需要做哪些工作了
- 排他性:在同一时间只会有一个客户端能获取到锁,其它客户端无法同时获取;
- 避免死锁:锁在一段时间内有效,超过这个时间后会被释放(正常释放或异常释放);
- 高可用:获取或释放锁的机制必须高可用且性能佳。
使用场景
当你的后端服务是以集群形式存在的时候,是一定需要分布式锁的。集群与分布式不同,而这里的分布式与分布式锁也不是同一回事儿。集群可以指多台服务器实现了同样的需求,比如有三台Tomcat,都负责查询模块;而分布式指多台服务器各自不同的功能点,多台功能的整合对外是一个完整的服务,比如一台Tomcat负责查询,一台负责下单。
说回集群,当后端集群要去访问同一个资源的时候,就需要对该资源加锁,保证同一时刻只能有一个对象来修改该资源数据,如果不加锁会导致什么情况呢?
举一个例子:
有两个线程(分别叫T1,T2)做的都是同样的事情,拿到一个叫做A的资源,然后对其进行+1操作。由于线程之间是不会互相通信的,于是就有可能出现下面这种情况:
T1拿到A,读入内存,此时A值为T;
T2拿到A,读入内存,此时A值为T;
T1进行+1操作,此时A实际值为T+1;
T2进行+1操作,此时A的实际值仍然为T+1;
然而,此时A经过两个线程执行+1操作,应该为T+2才对的,所以可以看出,如果没有分布式锁,就会出现数据不一致的问题。如果是上面这种简单的计算还好,如果是你的银行账户,没用分布式锁,此时有两个人给你打钱,结果只有其中一个人的到账了,另一个人的被作为无主钱财被银行充公了,肯定是不行的吧。
所以,保障数据一致性和准确性就是分布式锁的重要性。
如何实现
在这里可以给大家介绍一个用redis来实现分布式锁的方案。
Redis对外开放了一个非常厉害的api,目前经常被大家用来做分布式锁,是绝对的线程安全,这个函数就是SET key field value加上NX参数。这个NX参数可是了不得,通常来说,set函数是不管field字段是否存在,只要写入成功就会返回1,但是如果增加了NX参数,那么如果field值在redis中已经存在,就会返回nil,否则才返回1。因此可以通过这个函数来执行加锁操作,如果返回值不为nil,则加锁成功,否则代表有其他线程在操作数据,当前请求需要等待。
不仅如此,为了避免死锁,SET还有一个参数为EX,即EX毫秒后,field会自动清空。
此外,还有PX,XX参数,具体含义见如下文档。
非分布式环境下,用锁可以解决什么?
多线程环境下,共享资源的线程安全问题!这个时候的共享资源通常是在单机里面的多线程里存在竞争,从JAVA内存模型来看,可以通过锁住对象,锁住方法,锁住代码块等方式,避免共享资源的竞争!
而在分布式环境中,共享资源所要经过的代码,方法,都不是在一个JVM里面,也不是同一个进程!通过锁方法,代码块的方式不能解决共享资源的竞争,需要分布式的锁!
分布式锁通常在以下场景中使用:
1,全局ID的生成;
2,全局配置文件的修改;
3,分布式服务中的秒杀问题;
4,分布式环境下的重复提交;
分布式锁通常有以下实现方式:
1,使用数据库的唯一主键来实现锁!
2,使用redis的指令:通常使用setnx方法,incr方法等进行实现
3,使用zookeeper:使用api生成临时节点实现锁!