5、具备非阻塞锁特性,即没有获取到锁将直接返回获取锁失败。
三、分布式锁的三种实现方式
=================
目前几乎很多大型网站及应用都是分布式部署的,分布式场景中的数据一致性问题一直是一个比较重要的话题。分布式的CAP理论告诉我们“任何一个分布式系统都无法同时满足一致性、可用性和分区容错性,最多只能满足两项。”所以,很多系统在设计之初就要对这三者进行取舍。在互联网领域的绝大多数的场景中,都需要牺牲掉一致性来换取系统的高可用性,系统往往只需要保证最终一致性,只要这个最终时间是在用户可以接受的范围内即可。
在很多场景中,为了保证数据的最终一致性,需要很多的技术方案来支持,比如分布式事务、分布式锁等。
分布式锁的三种实现方式:
1、基于数据库实现分布式锁;
2、基于缓存(Redis)实现分布式锁;
3、基于Zookeeper实现分布式;
尽管有这三种方案,但是不同的业务也要根据自己的情况进行选型,他们之间没有最好只有更适合!
四、基于数据库实现分布式锁
=================
基于数据库的实现方式的核心思想是:在数据库中创建一个表,表中包含方法名等字段,并在方法名字段上创建唯一索引,想要执行某个方法,就使用这个方法名向表中插入数据,成功插入则获取锁,执行完成后删除对应的行数据释放锁。
1、创建一个表
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=‘锁定中的方法’;
2、想要执行某个方法,就要使用这个方法名向表中插入数据
INSERT INTO method_lock (method_name, desc) VALUES (‘methodName’, ‘测试的methodName’);
因为我们对method_name做了唯一性约束,这里如果有多个请求同时提交到数据库的话,数据库会保证只有一个操作可以成功,那么我们就可以认为操作成功的那个线程获得了该方法的锁,可以执行方法体内容。
3、成功插入则获取锁,执行完毕后删除对应的行数据释放锁
delete from method_lock where method_name =‘methodName’;
注意:这只是使用基于数据库的一种方法,使用数据库实现分布式锁还有很多其它的方法。
4、存在的一些问题
(1)因为是基于数据库实现的,数据库的高可用性和性能将直接影响分布式锁的可用性和性能,所以,数据库需要双机热备、数据同步、准备切换。
(2)不具备可重入的特性,因为同一线程在释放锁之前,行数据一直存在,无法再次成功插入数据,所以,需要在表中新增一列,用于记录当前获取到锁的机器和线程信息,在再次获取锁的时候,先查询表中机器和线程信息是否是当前机器和线程,若相同则直接获取锁。
(3)没有锁失效机制,因为有可能出现成功插入数据后,服务器宕机了,对应的数据没有被删除,当服务恢复后一直获取不到锁,所以,需要在表中新增一列,用于记录失效时间,并且需要定时消除这些失效的数据。
(4)不具备阻塞锁特性,获取不到锁直接返回失败,所以需要优化获取逻辑,循环多次去获取。
(5)在实施的过程中遇到各种不同的问题,为了解决这些问题,实现方式将越来越复杂,依赖数据库需要一定的资源开销,性能问题需要考虑。
五、基于缓存(Redis)实现分布式锁
=======================
1、使用Redis实现分布式锁原因
(1)Redis有很高的性能;
(2)Redis命令对此支持较好,实现起来比较方便
2、使用命令简介
(1) setnx
SETNX key val:当key不存在时,set一个key为val的字符串,返回1;若key存在,则什么都不做,返回0。
(2)expire
expire key timeout:为key设置一个超时时间,单位是秒,超过这个时间锁会自动释放,避免死锁。
(3)delete
删除key。
3、实现思想
(1)获取锁的时候,使用 setnx 加锁,并使用expire命令为锁添加一个超时时间,超过该时间则自动释放锁,锁的值为一个随机生成的UUID,通过此在释放锁的时候进行判断。
(2)获取锁的时候还设置了一个获取的超时时间,若超过这个时间则放弃获取锁。
(3)释放锁的时候,通过UUID判断是不是该锁,若是该锁,则执行delete进行锁释放。
4、分布式锁的简单代码
/**
-
分布式锁的简单实现代码
-
Created by 素小暖 on 2020/2/12.
*/
public class DistributedLock {
private final JedisPool jedisPool;
public DistributedLock(JedisPool jedisPool) {
this.jedisPool = jedisPool;
}
/**
-
加锁
-
@param lockName 锁的key
-
@param acquireTimeout 获取超时时间
-
@param timeout 锁的超时时间
-
@return 锁标识
*/
public String lockWithTimeout(String lockName, long acquireTimeout, long timeout) {
Jedis conn = null;
String retIdentifier = null;
try {
// 获取连接
conn = jedisPool.getResource();
// 随机生成一个value
String identifier = UUID.randomUUID().toString();
// 锁名,即key值
String lockKey = “lock:” + lockName;
// 超时时间,上锁后超过此时间则自动释放锁
int lockExpire = (int) (timeout / 1000);
// 获取锁的超时时间,超过这个时间则放弃获取锁
long end = System.currentTimeMillis() + acquireTimeout;
while (System.currentTimeMillis() < end) {
if (conn.setnx(lockKey, identifier) == 1) {
conn.expire(lockKey, lockExpire);
// 返回value值,用于释放锁时间确认
retIdentifier = identifier;
return retIdentifier;
}
// 返回-1代表key没有设置超时时间,为key设置一个超时时间
if (conn.ttl(lockKey) == -1) {
conn.expire(lockKey, lockExpire);
}
try {
Thread.sleep(10);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}
} catch (JedisException e) {
e.printStackTrace();
} finally {
if (conn != null) {
conn.close();
}
先自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。
深知大多数初中级Java工程师,想要提升技能,往往是自己摸索成长或者是报班学习,但对于培训机构动则近万的学费,着实压力不小。自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!
因此收集整理了一份《Java开发全套学习资料》送给大家,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。
由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频
如果你觉得这些内容对你有帮助,可以扫码领取!
读者福利
分享一份自己整理好的Java面试手册,还有一些面试题pdf
不要停下自己学习的脚步
5)]
由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频
如果你觉得这些内容对你有帮助,可以扫码领取!
读者福利
分享一份自己整理好的Java面试手册,还有一些面试题pdf
不要停下自己学习的脚步
[外链图片转存中…(img-xKEL942B-1711477128955)]
[外链图片转存中…(img-th81yKsX-1711477128955)]
需要更多Java资料的小伙伴可以帮忙点赞+关注,点击传送门,即可免费领取!