前言
现在实际项目开发的过程中,我们无法避免去使用锁的技术,特别是现在项目很多都是分布式开发,对于这种架构的项目我们的锁应该怎么去使用呢?下面我们详细了解一下关于分布式锁的知识点。
一、什么是锁?
在计算机科学中,锁(lock)或者说互斥()是一种同步机制,用于在有多个线程执行的环境中控制对资源的访问限制。锁的主要作用就是互斥排他,控制并发(比如试衣间只能有一个人进去,要是同时进去两个人可能会发生一些故事)。
二、什么是分布式锁?
分布式锁是一种控制分布式系统之间同步访问共享资源的方式。在分布式系统中,不同系统或者是同一个系统不同主机共享了同一个资源,为了保证数据的一致性,往往需要进行互斥操作来进行控制以防止互相干扰。这时就使用到了分布式锁。
三、常见的三种分布式锁实现
目前我们对于分布式锁的实现常见的有三种方式:基于数据库、基于redis缓存、基于zookeeper
(1)基于数据库
此种方式比较好理解,我们可以借助数据库的乐观锁和悲观锁来实现。
1)乐观锁+版本号(version)机制
此种实现方式就是在数据库中引入一个version版本号字段,当我们去更改该条数据的时候同时将版本号读出来,更改完数据之后,版本号字段+1,检查此时数据库中的该条记录的版本号字段是否还是上一次读取出来的值,如果是,则将最新的数据连同+1之后的版本号更新到数据库中,如果不是,说明在此线程修改数据的同时,已经有其他线程对该条数据做了更新操作。那么此线程的更新就会失败,然后重新去数据库中查询最新的数据。
举个栗子:
老王和他老婆都知道老王工资卡的账号和密码。某天老王想去取点钱,卡上有5000,他老婆正好同一时间也去取钱。他俩同时查询到该卡的余额,上面显示还有5000,而且版本号都是1。老王先取出2000,此时余额还有3000,版本号字段由1变成了2。他老婆要取出4000,但是没有成功,因为余额在更新的时候发现数据库的版本号是2,但是她刚才查出来的版本号是1,取钱失败。重新查询之后发现余额不再是刚才的5000,而是3000了。
通过这样一种机制,我们可以保证任何时候都只能有一个线程操作某一资源。从而保证数据一致性。
2)悲观锁
这种机制就是对事务处理持悲观态度,在对数据进行修改前,尝试为该记录加上排他锁,如果加锁失败,说明又其他线程正在操作该记录,如果加锁成功,就可以对该数据进行处理。
通过在sql语句之后添加 for update 可以对该数据加锁。
SELECT * FROM products WHERE id='3' FOR UPDATE;
(2)基于redis缓存
redis是单进程单线程的网络模型,本身具有原子性。而基于redis实现分布式锁也就是依赖了redis的原子性。
在说此种实现方式之前,先了解以下redis的NX命令:
set key value NX
set key value NX 等同于 setNX key value
NX:只有在redis中不存在该key的时候,才会set value成功,否则无法对该key进行设置操作
当有很多个线程并发操作同一个key的时候,永远只会有一个线程操作成功,当这个线程执行完业务逻辑之后,删除掉该key值。相当于释放了锁,其他的线程才能继续执行。不过需要注意的是,在删除key之前先核对一下目前该key对应的value值是否和开始的value值一致。一致则可以删除key。
另外redis中还有一个命令:PX——可以用以设置过期时间
set key value NX PX 1000
上面指令代表设置key的过期时间为1000毫秒,到1000毫秒的时候,该key自动被删除。
(3)基于zookeeper实现
此种实现方式大致的思想就是:在对业务逻辑进行加锁的时候,在zookeeper上的某个指定节点目录下,生成一个唯一有序的临时节点。在判断是否获取到锁的时候只需要判断自己是否是这些有序节点中序号最小的一个,如果不是,则说明没有获取到锁,此时找到比自己小的那个节点,并调用exist()方法对其进行监控,如果此节点不存在(被删除了),再次判断自己是否是有序节点中序号最小的一个,如果是,则表名获取到锁,如果不是,则重复之前的步骤,对比自己小的节点进行监控。