三种分布式锁

写在前面

在看这篇文章之前先要弄懂啥是分布式?啥是分布式,我的理解就是一个系统部署在多台服务器上,每一台都能提供服务,这样子所有服务器就是分布式系统,分布式的好处就是可以聚合资源,使一台机器完成不了的事让两台机器或多台机器来完成。这是最早的分布式,一台机器宕机了,其他的机器仍然可以提供服务。

一文弄懂"分布式锁"

多线程情况下对共享资源的操作需要加锁,避免数据被写乱,在分布式系统种,这个问题也是存在的,此时就需要一个分布式锁服务。常见的分布式锁实现一般是基于DB、Redis、zoomkeeper。下面笔者会按照顺序分析这3种分布式锁的设计与是实现。
分布式锁的实现有多种方式,但是不管怎样,分布式锁一般要有以下的特点:

排他性:任意时刻,只能有一个client能获取到锁
容错型:分布式锁服务一般要满足AP,也就是说,只要分布式锁服务集群节点大部分存活,client就可以进行加锁解锁操作
避免死锁: 分布式锁一定能得到释放,即使client在释放之前崩溃或者网络不可达
除了以上特点之外,分布式锁最好也能满足可重入、高性能、阻塞锁特性(AQS这种,能够及时从阻塞状态唤醒)等,下面就话不多说,赶紧上(开发分布式锁的设计和实现的)车~

DB锁

在数据库新建一张表用于控制并发控制,表结构可以如下所示:

CREATE TABLE `lock_table`(
	`id` int(11) unsigned NOT NULL COMMENT '主键',
	`key_id` bigint(20) NOT NULL COMMENT '分布式key',
	`memo` varchar(43) NOT NULL DEFAULT'' COMMENT '可记录操作内容',
	`update_time` datetime NOT NULL COMMENT '更新时间',
	PRIMARY KEY (`id`,`key_id`),
	UNIQUE KEY `key_id` (`key_id`) USING BTREE
) ENGINE = InnoDB DEFAULT CHARSET = utf-8

key_id作为分布式key用于并发控制,memo可用来记录一些操作内容(比如memo可用来支持重入特性,标记下当期加锁的client和加锁次数)。将key_id设置为唯一索引,保证了针对同一个key_id只有一个加锁(数据插入)能成功。此时lock和unlock伪代码如下:

def lock:
	exec sql : insert into lock_table( key_id,memo,update_time) values(key_id, memo, NOW())
	if result = true;
		return true
	else:
		return false
def unlock:
	exec sql:delete from lock_table where key_id = 'key_id' and memo = 'memo'

注意,伪代码中的lock操作是非阻塞锁,也就是tryLock,如果想实现阻塞(或者阻塞超时)枷锁,只修反复执行lock伪代码直到加锁成功为止即可。基于DB的分布式锁其实有一个问题,那就是如果加锁成功后,client端宕机或者由于网络原因导致没有解锁,那么其他client就无法对该key_id进行加锁并且无法释放了。为了能够让锁失效,需要在应用层加上定时任务,去删除过期还未解锁的记录,比如删除2分钟前未解锁的伪代码如下

def clear_timeout_lock:
	exec sqldelete from lock_table where update_time < ADDTIME(NOW(),'-00:02:00')

因为单实例DB的TPS一般为几百,所以基于DB的分布式性能上限一般也是1k以下,一般在并发量不大的场景下该分布式锁是满足需求的,不会出现性能问题。不过DB作为分布式锁服务需要考虑单点问题,对于分布式系统来说是不允许出现单点的,一般通过数据库的同步复制,以及使用vip切换Master就能解决这个问题。
以上DB分布式锁是通过insert来实现的,如果加锁的数据已经在数据库中存在,那么用select xxx where key_id = xxx for update来做也是可以的。

Redis锁

Redis锁是通过以下命令对资源进行加锁

set key_id key_value NX PX expireTime

其中,set nx命令只会在key不存在时给key进行赋值,px用来设置key过期时间,key_value一般是随机值,用来保证释放锁的安全性(释放时会判断是否是之前设置过的随机值,只有是才释放锁)。由于资源设置了过期时间,一点时间后锁就会自动释放。

set nx 保证并发加锁时只有一个client能设置成功(Redis内部是单线程,并且数据存在内存中,也就是说redis内部执行命令是不会有多线程同步问题的),此时lock / unlock伪代码如下:

def lock:
	if(redis.call('set',KEYS[1],ARGV[1],'ex',ARGV(2),'nx'))then
		return true
	end
		return false
def unlock:
	if(redis.call('get',KEYS[1])===ARGV[1]) then 
		redis.call('del',KEYS[1])
		return true
	end
		return false
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值