上图分析:java
(1)变量A存在JVM一、JVM二、JVM3三个JVM内存中(这个变量A主要体现是在一个类中的一个成员变量,是一个有状态的对象),若是不加任何控制的话,变量A同时都会在JVM一、JVM二、JVM3中分配一块内存; (2)三个请求发过来同时对这个变量进行操做,显然结果是不一样的。 (3)即便不是同时发过来,三个请求分别操做三个不一样JVM内存区域的数据,变量A之间不存在共享,也不具备可见性,处理的结果也是不对的。 (4)若是咱们业务中存在这种场景的话,咱们就须要一种方法解决这个问题。
建立一个表:mysql
DROP TABLE IF EXISTS `method_lock`; CREATE TABLE `method_lock` ( `id` int(11) unsigned NOT NULL AUTO_INCREMENT COMMENT '主键', `method_name` varchar(64) NOT NULL COMMENT '锁定的方法名', `desc` varchar(255) NOT NULL COMMENT '备注信息', `update_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, PRIMARY KEY (`id`), UNIQUE KEY `uidx_method_name` (`method_name`) USING BTREE ) ENGINE=InnoDB AUTO_INCREMENT=3 DEFAULT CHARSET=utf8 COMMENT=‘锁定中的方法';
想要执行某个方法,就使用这个方法名向表中插入数据:redis
INSERT INTO method_lock (method_name, desc) VALUES ('methodName', ‘测试的methodName');
成功插入则获取锁,执行完成后删除对应的行数据释放锁:sql
delete from method_lock where method_name ='methodName';
使用基于数据库的这种实现方式很简单,可是对于分布式锁应该具有的条件来讲,它有一些问题须要解决及优化:typescript
(1)由于是基于数据库实现的,数据库的可用性和性能将直接影响分布式锁的可用性及性能,因此,数据库须要双机部署、数据同步、主备切换。 (2)不具有可重入的特性,由于同一个线程在释放锁以前,行数据一直存在,没法再次成功插入数据,因此,须要在表中新增一列,用于记录当前获取到锁的机器和线程信息,在再次获取锁的时候,先查询表中机器和线程信息是否和当前机器和线程信息相同,若相同则直接获取锁; (3)没有锁失效机制,由于有可能出现成功插入数据后,服务器宕机了,对应的数据没有被删除,当服务恢复后一直获取不到锁,因此,须要在锁中新增一列,用于记录失效时间,而且须要有定时任务清除这些失效的数据; (4)不具有阻塞锁特性,获取不到锁直接返回失败,因此须要优化获取逻辑,循环屡次去获取。
选择redis分布式锁的缘由:数据库
(1)redis有很高的性能; (2)redis对此支持的命令较好,实现起来比较方便
使用分布式锁的时候主要用到的命令介绍:缓存
(1)SETNX
SETNX key val:当且仅当key不存在时,set一个key为val的字符串,返回1;若key存在,则什么都不作,返回0。 (2)expire expire key timeout:当key设置一个超时时间,单位为second,超过这个时间锁会自动释放,避免死锁。 (3)delete delete key:删除key
实现思想:服务器
(1)获取锁的时候,使用setnx加锁,并使用expire命令给锁加一个超时时间,超过该时间则自动释放锁,锁的value值为一个随机生成的UUID,经过此在释放锁的时候进行判断。 (2)获取锁的时候还设置一个获取的超时时间,若超过这个时间则放弃获取锁。 (3)释放锁的时候,经过UUID判断是否是该锁,如果该锁,则执行delete进行锁释放。
基于ZooKeeper实现分布式锁的步骤以下:多线程
(1)建立一个目录mylock; (2)线程A想获取锁就在mylock目录下建立临时顺序节点; (3)获取mylock目录下全部的子节点,而后获取比本身小的兄弟节点,若是不存在,则说明当前线程顺序号最小,得到锁; (4)线程B获取全部节点,判断本身不是最小节点,设置监听比本身小的节点; (5)线程A处理完,删除本身的节点,线程B监听到变动事件,判断本身是否是最小节点,若是是则得到锁。