基于数据库 基于数据库(MySQL)的方案,一般分为3类:基于表记录、乐观锁和悲观锁 ###### 基于表记录 用表主键或表字段加唯一性索引便可实现,如下; ``` CREATE TABLE `database_lock` ( `id` BIGINT NOT NULL AUTO_INCREMENT, `resource` int NOT NULL COMMENT '锁定的资源', `description` varchar(1024) NOT NULL DEFAULT "" COMMENT '描述', PRIMARY KEY (`id`), UNIQUE KEY `uiq_idx_resource` (`resource`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='数据库分布式锁表'; ``` 想获得锁插入一条数据 ``` INSERT INTO database_lock(resource, description) VALUES (1, 'lock'); ``` 解锁删除数据: ``` DELETE FROM database_lock WHERE resource=1; ``` 这种实现方式非常的简单,但是需要注意以下几点: - 这种锁没有失效时间,一旦释放锁的操作失败就会导致锁记录一直在数据库中,其它线程无法获得锁。这个缺陷也很好解决,比如可以做一个定时任务去定时清理。 - 这种锁的可靠性依赖于数据库。建议设置备库,避免单点,进一步提高可靠性。 - 这种锁是非阻塞的,因为插入数据失败之后会直接报错,想要获得锁就需要再次操作。如果需要阻塞式的,可以弄个for循环、while循环之类的,直至INSERT成功再返回。 - 这种锁也是非可重入的,因为同一个线程在没有释放锁之前无法再次获得锁,因为数据库中已经存在同一份记录了。想要实现可重入锁,可以在数据库中添加一些字段,比如获得锁的主机信息、线程信息等,那么在再次获得锁的时候可以先查询数据,如果当前的主机信息和线程信息等能被查到的话,可以直接把锁分配给它。 - 在 MySQL 数据库中采用主键冲突防重,在大并发情况下有可能会造成锁表现象 ###### 基于乐观锁 可基于MVCC机制实现 - 优点:在检测数据冲突时并不依赖数据库本身的锁机制,不会影响请求的性能,执行上面的set()方法就只会导致两种结果: - 当前没有锁(key不存在),那么就进行加锁操作,并对锁设置个有效期,同时value表示加锁的客户端。 - 已有锁存在,不做任何操作。 ###### Redisson实现分布式锁 使用流程如下,创建Redisson实例(单机或哨兵模式),然后通过getLock获取锁,后续是进行lock和unlock操作。 ``` // 1. Create config object Config config = new Config(); config.useClusterServers() // use "rediss://" for SSL connection .addNodeAddress("redis://127.0.0.1:7181"); // 2. Create Redisson instance // Sync and Async API RedissonClient redisson = Redisson.create(config); // 3. Get Redis based implementation of java.util.concurrent.locks.Lock RLock lock = redisson.getLock("myLock"); ``` 具体使用例子可参考:https://www.cnblogs.com/milicool/p/9201271.html #### 基于zookeeper ###### zookeeper基本锁原理 利用临时节点与watch机制,每个锁占用一个普通节点/lock,当需要获取锁时,在/lock目录下创建一个临时节点,创建成功则表示获取锁成功,失败则watch /lock节点,有删除操作后再去争锁。 临时节点 - 好处:在于当进程挂掉后能自动上锁的节点自动删除,即取消锁 - 缺点: 所有取锁失败的进程都监听父节点,很容易发生羊群效应,即当释放锁后所有等待进程一起来创建节点,并发量很大 ###### zookeeper锁优化原理 上锁改为创建临时有序节点,每个上锁的节点均能创建节点成功,只是其序号不同,只有序号最小的可以拥有锁,如果这个节点序号不是最小的则watch序号比本身小的前一个节点。 步骤: - 在/lock节点下创建一个有序临时节点。
