不会还有人不知道吧redis加锁的常见几种方式

不会还有人不知道吧redis加锁的常见几种方式,第1张

redis加锁的常见几种方式,redis中加锁是比价常见的 *** 作,为了防止并发情况下产生的各种内存分配问题,而选择如何正确高效的加锁,才是我们应该需要知道和了解的,常码字不易,出精品更难,没有特别幸运,那么请先特别努力,别因为懒惰而失败,还矫情地将原因归于自己倒霉。你必须特别努力,才能显得毫不费力

如果key不存在,则将key初始化为0,然后自增1

如何key不存在,则将key设置成value

同setnx加锁

首先,分布式锁和我们平常讲到的锁原理基本一样,目的就是确保在多个线程并发时,只有一个线程在同一刻 *** 作这个业务或者说方法、变量。

在一个进程中,也就是一个jvm或者说应用中,我们很容易去处理控制,在 javautil 并发包中已经为我们提供了这些方法去加锁,比如 synchronized 关键字或者 Lock 锁,都可以处理。

但是如果在分布式环境下,要保证多个线程同时只有1个能访问某个资源,就需要用到分布式锁。这里我们将介绍用Redis的 setnx 命令来实现分布式锁。

其实目前通常所说的 setnx 命令,并非单指redis的 setnx key value 这条命令,这条命令可能会在后期redis版本中删除。

一般代指redis中对 set 命令加上 nx 参数进行使用, set 这个命令,目前已经支持这么多参数可选:

从 Redis 2612 版本开始, SET 命令的行为可以通过一系列参数来修改:

注入bean

这里同时启动5个线程并发往redis中存储 lock 这个key(key可以自定义,但需要一致),同时设置10秒的过期时间。

setIfAbsent 这个函数实现的功能与 setnx 命令一样,代表如果没有这个key则set成功获取到锁,否则set失败没有获取到锁。

获得锁后进行资源的 *** 作,最后释放锁。

执行效果

可以看到同时只有1个线程能够获取到锁。

使用 setnx 命令方式虽然 *** 作比较简单方便,但是会有如下问题:

可以在再次获取锁时,如果锁被占用就get值,判断值是否是当前线程存的随机值,如果是则再次执行 set 命令重新上锁;当然为了保证原子性这些 *** 作都要用 lua 脚本来执行。

可以使用 while 循环重复执行 setnx 命令,并设置一个超时时间退出循环。

可以尽量把锁自动过期的时间设的冗余一些。但也不能彻底解决。

可以在删除锁的时候先get值,判断值是否是当前线程存的随机值,只有相同才执行删锁的 *** 作;当然也要使用 lua 脚本执行来保证原子性。

分布式锁需要满足的特性

综上:使用 setnx 命令来实现分布式锁并不是一个很严谨的方案,如果是Java技术栈,我们可以使用 Redisson 库来解决以上问题,接下来的文章会介绍如何使用。

Redisson实现分布式锁

Redlock实现分布式锁

原理很简单,set 一个 锁-key,如果成功则说明加锁成功,反之则失败。

为了确保分布式锁可用,我们至少要确保锁的实现同时满足以下几个条件:

基于以上条件,采用set扩展参数,保证原子性 *** 作: SET lock-key "lock-client" EX 10086 NX

lock-key "lock-client" 指定 加锁client,解锁时用于判断。

EX 10010 指定过期时间

NX 只在键不存在时,才对键进行设置 *** 作。效果等同于 SETNX 命令。

只不过早期版本redis不支持set的扩展参数,这就需要用到 lua 脚本了

加锁可以在高版本借助set命令实现原子 *** 作,但解锁就不可以了,依然得用到lua脚本。

Redis在26版本推出了 lua 脚本功能,允许开发者使用Lua语言编写脚本传到Redis中执行。使用脚本的好处如下:

需要在获得 lock-key 后判断加锁对象是否为当前client,是,则解锁。Lua 脚本如下:

if rediscall('get', KEYS[1]) == ARGV[1] then return rediscall('del', KEYS[1]) else return 0 end

执行方式:eval;

eval 参数列表: eval lua-script key-num [key1 key2 key3 ] [value1 value2 value3 ] ,参数解析:

eval执行示例: eval "rediscall('set',KEYS[1],ARGV[1])" 1 lua-key lua-val ;

完整解锁执行脚本:

eval "if rediscall('get', KEYS[1]) == ARGV[1] then return rediscall('del', KEYS[1]) else return 0 end" 1 lock-key client-val

----------End----------

1、缓存。缓存现在几乎是所有中大型网站都在用的必杀技,合理的利用缓存不仅能够提升网站访问速度,还能大大降低数据库的压力。Redis提供了键过期功能,也提供了灵活的键淘汰策略,所以,现在Redis用在缓存的场合非常多。(推荐:《Redis视频教程》)

2、排行榜。很多网站都有排行榜应用的,如京东的月度销量榜单、商品按时间的上新排行榜等。Redis提供的有序集合数据类构能实现各种复杂的排行榜应用。

3、计数器。什么是计数器,如电商网站商品的浏览量、视频网站视频的播放数等。为了保证数据实时效,每次浏览都得给1,并发量高时如果每次都请求数据库 *** 作无疑是种挑战和压力。Redis提供的incr命令来实现计数器功能,内存 *** 作,性能非常好,非常适用于这些计数场景。

4、分布式会话。集群模式下,在应用不多的情况下一般使用容器自带的session复制功能就能满足,当应用增多相对复杂的系统中,一般都会搭建以Redis等内存数据库为中心的session服务,session不再由容器管理,而是由session服务及内存数据库管理。

5、分布式锁。在很多互联网公司中都使用了分布式技术,分布式技术带来的技术挑战是对同一个资源的并发访问,如全局ID、减库存、秒杀等场景,并发量不大的场景可以使用数据库的悲观锁、乐观锁来实现,但在并发量高的场合中,利用数据库锁来控制资源的并发访问是不太理想的,大大影响了数据库的性能。可以利用Redis的setnx功能来编写分布式的锁,如果设置返回1说明获取锁成功,否则获取锁失败,实际应用中要考虑的细节要更多。

Redis有一系列的命令,特点是以NX结尾,NX是Not eXists的缩写,如SETNX命令就应该理解为:SET if Not eXists。这系列的命令非常有用,这里讲使用SETNX来实现分布式锁。

用SETNX实现分布式锁

利用SETNX非常简单地实现分布式锁。例如:某客户端要获得一个名字foo的锁,客户端使用下面的命令进行获取:

SETNX lockfoo <current Unix time + lock timeout + 1>

如返回1,则该客户端获得锁,把lockfoo的键值设置为时间值表示该键已被锁定,该客户端最后可以通过DEL lockfoo来释放该锁。

如返回0,表明该锁已被其他客户端取得,这时我们可以先返回或进行重试等对方完成或等待锁超时。

解决死锁

上面的锁定逻辑有一个问题:如果一个持有锁的客户端失败或崩溃了不能释放锁,该怎么解决?我们可以通过锁的键对应的时间戳来判断这种情况是否发生了,如果当前的时间已经大于lockfoo的值,说明该锁已失效,可以被重新使用。

发生这种情况时,可不能简单的通过DEL来删除锁,然后再SETNX一次,当多个客户端检测到锁超时后都会尝试去释放它,这里就可能出现一个竞态条件,让我们模拟一下这个场景:

C0 *** 作超时了,但它还持有着锁,C1和C2读取lockfoo检查时间戳,先后发现超时了。

C1 发送DEL lockfoo

C1 发送SETNX lockfoo 并且成功了。

C2 发送DEL lockfoo

C2 发送SETNX lockfoo 并且成功了。

这样一来,C1,C2都拿到了锁!问题大了!

幸好这种问题是可以避免D,让我们来看看C3这个客户端是怎样做的:

C3发送SETNX lockfoo 想要获得锁,由于C0还持有锁,所以Redis返回给C3一个0

C3发送GET lockfoo 以检查锁是否超时了,如果没超时,则等待或重试。

反之,如果已超时,C3通过下面的 *** 作来尝试获得锁:

GETSET lockfoo <current Unix time + lock timeout + 1>

通过GETSET,C3拿到的时间戳如果仍然是超时的,那就说明,C3如愿以偿拿到锁了。

如果在C3之前,有个叫C4的客户端比C3快一步执行了上面的 *** 作,那么C3拿到的时间戳是个未超时的值,这时,C3没有如期获得锁,需要再次等待或重试。留意一下,尽管C3没拿到锁,但它改写了C4设置的锁的超时值,不过这一点非常微小的误差带来的影响可以忽略不计。

注意:为了让分布式锁的算法更稳键些,持有锁的客户端在解锁之前应该再检查一次自己的锁是否已经超时,再去做DEL *** 作,因为可能客户端因为某个耗时的 *** 作而挂起, *** 作完的时候锁因为超时已经被别人获得,这时就不必解锁了。

示例伪代码

根据上面的代码,我写了一小段Fake代码来描述使用分布式锁的全过程:

# get lock

lock = 0

while lock != 1:

timestamp = current Unix time + lock timeout + 1

lock = SETNX lockfoo timestamp

if lock == 1 or (now() > (GET lockfoo) and now() > (GETSET lockfoo timestamp)):

break;

else:

sleep(10ms)

# do your job

do_job()

# release

if now() < GET lockfoo:

DEL lockfoo

是的,要想这段逻辑可以重用,使用python的你马上就想到了Decorator,而用Java的你是不是也想到了那谁?AOP + annotation?行,怎样舒服怎样用吧,别重复代码就行。

以上就是关于不会还有人不知道吧redis加锁的常见几种方式全部的内容,包括:不会还有人不知道吧redis加锁的常见几种方式、Redis的Setnx命令实现分布式锁、高并发场景Redis分布式锁实现方式等相关内容解答,如果想了解更多相关内容,可以关注我们,你们的支持是我们更新的动力!

欢迎分享,转载请注明来源:内存溢出

原文地址: http://outofmemory.cn/web/10129164.html

(0)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
上一篇 2023-05-05
下一篇 2023-05-05

发表评论

登录后才能评论

评论列表(0条)

保存