大家好,又见面了,我是你们的朋友全栈君。
多线程对同一资源的竞争,需要用到锁,例如Java自带的Synchronized、ReentrantLock。 但只能用于单机系统中,如果涉及到分布式环境(多机器)的资源竞争,则需要分布式锁。
分布式锁的主要作用:
分布式锁的主要特性:
数据库表设计:
CREATE TABLE `distributed_lock` (
`id` bigint unsigned NOT NULL AUTO_INCREMENT COMMENT '主键ID',
`resource_name` varchar(200) NOT NULL DEFAULT '' COMMENT '资源名称(唯一索引)',
`owner` varchar(200) NOT NULL DEFAULT '' COMMENT '锁持有者(机器码+线程名称)',
`lock_count` int NOT NULL DEFAULT '0' COMMENT '加锁次数',
`expire_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '锁过期时间',
PRIMARY KEY (`id`),
UNIQUE KEY `uk_resource_name` (`resource_name`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='分布式锁';
owner
和加锁次数字段lock_count
实现可重入:
获取锁,次数加一,释放锁,次数减一,次数为零就删除这把锁。expire_time
字段,通过异步任务检测避免因程序异常或机器宕机导致锁无法释放。由于MySQL并发性能跟不上,所以还可以使用Redis实现分布式锁。 获取锁,并设置过期时间:
// 1. 获取锁
redis.setnx('resource_name1', 'owner1')
// 2. 增加锁过期时间
redis.exprire('resource_name1', 6, TimeUnit.SECONDS)
但是setnx
和exprire
两条命令不是原子的,可能获取锁之后还没来得及设置过期时间就宕机了。
所以可以使用Redis 2.6.12之后提供的一条复合命令:
redis.set('resource_name1', 'owner1',"NX" "EX", 6)
释放锁:
// 释放锁
if ('owner1'.equals(redis.get('resource_name1'))){
redis.del('resource_name1')
}
释放锁时判断锁的持有者,可以避免把其他线程持有的锁给释放掉了。
但是get
和del
两条命令不是原子操作,需要引入Lua脚本把两条命令打包成一条发给Redis执行:
String script = "if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1]) else return 0 end";
redis.eval(script, Collections.singletonList('resource_name1'), Collections.singletonList('owner1'))
此外,实现锁续期的功能,可以使用Redis客户端的Redisson的WatchDog
功能,在我们调用lock
自动唤醒WatchDog
。
zookeeper采用树形节点,类似Linux目录文件结构,同一目录下的节点名称不能重复。
节点有分为四种类型:
**持久节点:**一旦创建,永久存储在服务器上,除非手动删除。 临时节点:生命周期与客户端绑定,客户端断开连接,节点就被自动删除。 **持久顺序节点:**特性同持久节点,只是在节点名称后面追加自增有序数字。 **临时顺序节点:**特性同临时节点,只是在节点名称后面追加自增有序数字。
zookeeper还有个监听-通知机制,客户端可以在资源节点上创建watch事件。当节点发生变化,会通知客户端,客户端可以根据变化做相应的业务处理。
我们可以利用临时顺序节点的特性创建分布式锁,分以下三步:
实现逻辑很简单,我们来分析一下zookeeper实现分布式锁的优点:
版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。
发布者:全栈程序员栈长,转载请注明出处:https://javaforall.cn/196014.html原文链接:https://javaforall.cn