这是我参与8月更文挑战的第20天
分布式锁应该具备哪些条件
- 在分布式系统环境下,一个方法在同一时间只能被一个机器的一个线程执行
- 高可用的获取锁与释放锁
- 高性能的获取锁与释放锁
- 具备可重入特性(可理解为重新进入,由多于一个任务并发使用,而不必担心数据错误)
- 具备锁失效机制,防止死锁
- 具备非阻塞锁特性,即没有获取到锁将直接返回获取锁失败
互斥性。在任意时刻,只有一个客户端能持有锁。
不会发生死锁。即使有一个客户端在持有锁的期间崩溃而没有主动解锁,也能保证后续其他客户端能加锁。
具有容错性。只要大部分的Redis节点正常运行,客户端就可以加锁和解锁。
解铃还须系铃人。加锁和解锁必须是同一个客户端,客户端自己不能把别人加的锁给解了。
分布式锁的实现有哪些
- Memcached:利用 Memcached 的 add 命令。此命令是原子性操作,只有在 key 不存在的情况下,才能 add 成功,也就意味着线程得到了锁。
- Redis:和 Memcached 的方式类似,利用 Redis 的 setnx 命令。此命令同样是原子性操作,只有在 key 不存在的情况下,才能 set 成功。
- Zookeeper:利用 Zookeeper 的顺序临时节点,来实现分布式锁和等待队列。Zookeeper 设计的初衷,就是为了实现分布式锁服务的。
- Chubby:Google 公司实现的粗粒度分布式锁服务,底层利用了 Paxos 一致性算法。
通过数据库实现分布式锁
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;
通过redis实现分布式锁
通过Redis setnx方法来实现分布式锁,如果存在则返回1,如果失败则返回0,删除锁通过del方法来实现;
死锁场景:如果在上锁和解锁中间的业务代码出现异常,那么其他线程一直获取不到锁,而当前线程一直不释放锁,就会产生死锁
多线程并发访问数据产生的同步问题: 假如部署了集群redis服务,主节点突然挂掉了。同时主节点中有把锁还没有来得及同步到从节点。这样就会导致系统中同样-一把锁被两个客户端同时持有,不安全性由此产生。
redis官方为 了解决这个问题,推出了Redlock算法解决这 被两个客户端同时持有,不安全性由此产生.红纹夜蛾(redis官方为了解决这个问题,推出了redlock算法解决这)
通过zookeeper实现分布式锁
实现思路:
-
为每一个执行线程创建一个有序节点-->在指定目录下创建zookeeper临时节点;
-
在创建完节点后,取全部的节点重新进行排序-->getChildren()方法,利用treeSet进行排序;
-
判断当前节点是否是第一个节点,如果是第一个节点则获取锁。
//操作失败重试机制 1000毫秒间隔 重试3次 RetryPolicy retryPolicy = new ExponentialBackoffRetry(1000, 3); //创建Curator客户端 CuratorFramework client = CuratorFrameworkFactory.newClient("192.168.1.18:2181", retryPolicy); //开始 client.start(); /** * 这个类是线程安全的,一个JVM创建一个就好 * mylock 为锁的根目录,我们可以针对不同的业务创建不同的根目录 */ final InterProcessMutex lock = new InterProcessMutex(client, "/mylock"); try { //阻塞方法,获取不到锁线程会挂起。 lock.acquire(); System.out.println("已经获取到锁"); Thread.sleep(10000); } catch (Exception e) { e.printStackTrace(); } finally{ //释放锁,必须要放到finally里面,已确保上面方法出现异常时也能够释放锁。 lock.release(); } Thread.sleep(10000); client.close();