redis如何防止并发?

Redis本身io是单线程只是网络处理是多线程,所以底层是原子操作,可以防止并发


对于很多系统而言,都有集群处理。在集群中使用quartz或者task处理任务的时候,一般有三种选择:

1.多实例顺序执行

2.单实例执行

3.多实例并行执行

创保目前考虑的是1情况。

redis对于并发较小的情况下,进行锁控制,效果是较为理想的。目前创保只有八台实例,适用于该方案。

1.首先,redis提供了一个setnx方法,该方法执行后会返回key值在设置之前是否存在。

这是进行并发控制的基础,但并不够。

2.在利用setnx进行key值设定后,如果instance发生异常(不仅仅是exception)会导致该锁无法正常释放。

所以,我们需要为锁设置一个失效时间,即expire命令。

由于redis有顺序执行命令的特性,如果我们setnx方法与expire命令并不是以一个原子粒度执行的话,将会导致一些不可控的现象发生。

3.所以,我们使用了multi方法进行命令绑定,将setnx与expire进行绑定,保障同一原子粒度执行。

4.同时,为了避免不必要的设置损耗,同时为了兼容对于锁的value值进行特别设置的情况,我们进行了exists判断。

同时,在使用该锁的时候,也有一些规范需要遵守。

在获取到锁之后,才能进行业务逻辑处理。

在业务逻辑处理之后,必须手动释放锁。

未获取到锁的instance不允许释放锁。

过期时间必须设置,推荐设置时间为业务逻辑执行时间的1.5倍左右或任务间隔的2~3倍左右。


1.Redis写入是单线程的,单个命令执行不存在并发问题

2.如果是get命令,然后判断再进行set,那就有并发问题,set值不正确,举例:库存系统,get库存大于零,则库存减一再set库存,并发条件下,get得到的库存有可能是一样的,所以set回去的库存也是一样的,所以库存少减了,导致商品实际库存不足,多卖的情况,秒杀活动那就会比较危险了

3.解决办法,加入一个分布式锁


Redis是当前炙手可热的NoSQL数据库,几乎已经成为高并发、高可用系统的标配了。对Redis响应快的认知不能仅仅停留在基于内存和单线程的层面。

在一些对高并发请求有限制的系统或者功能里,比如说秒杀活动,或者一些网站返回的当前用户过多,请稍后尝试。这些都是通过对同一时刻请求数量进行了限制,一般用作对后台系统的保护,防止系统因为过大的流量冲击而崩溃。对于系统崩溃带来的后果,显然还是拒绝一部分请求更能被维护者所接受。

而在各种限流中,除了系统自身设计的带锁机制的计数器外,利用Redis实现显然是一种既高效安全又便捷方便的方式。

客户端加锁(ReentrantLock或synchronized)

但此方式只限于单机加锁,无法解决分布式系统的并发竞争问题。

乐观锁(redis的命令watch)

当执行多键值事务操作时,Redis不仅要求这些键值需要落在同一个Redis实例上,还要求落在同一个slot上,所以redis的事务比较鸡肋,不过可以想办法遵循redis内部的分片算法把设计到的所有key分到同一个slot。

redis的setnx实现分布式锁

要设置超时时间,防止抢占到锁的客户端因失败、崩溃或其他原因没有办法释放锁而造成死锁。

如有不同观点,欢迎发表评论。如果喜欢我的回答,欢迎“点赞、分享”。


1.问题描述

并发竞争key这个问题简单讲就是:

同时有多个客户端去set一个key。

示例场景1

例如有多个请求一起去对某个商品减库存,通常操作流程是:

取出当前库存值

计算新库存值

写入新库存值

假设当前库存值为20,现在有2个连接都要减5,结果库存值应该是10才对,但存在下面这种情况:

示例场景2

比如有3个请求有序的修改某个key,按正常顺序的话,数据版本应该是1-&;2-&;3,最后应该是3。

但如果第二个请求由于网络原因迟到了,数据版本就变为了1-&;3-&;2,最后值为2,出问题了。

2.解决方案

2.1乐观锁

乐观锁适用于大家一起抢着改同一个key,对修改顺序没有要求的场景。

watch命令可以方便的实现乐观锁。

需要注意的是,如果你的redis使用了数据分片的方式,那么这个方法就不适用了。

watch命令会监视给定的每一个key,当exec时如果监视的任一个key自从调用watch后发生过变化,则整个事务会回滚,不执行任何动作。

2.2分布式锁

适合分布式环境,不用关心redis是否为分片集群模式。

在业务层进行控制,操作redis之前,先去申请一个分布式锁,拿到锁的才能操作。

分布式锁的实现方式很多,比如ZooKeeper、Redis等。

2.3时间戳

适合有序需求场景,例如A需要把key设置为a,然后B设置为b,C再设置为c,最后的值应该是c。

这时就可以考虑使用时间戳的方式:

A=&;setkey1B=&;setkey1C=&;setkey1

就是在写入时保存一个时间戳,写入前先比较自己的时间戳是不是早于现有记录的时间戳,如果早于,就不写入了。

假设B先执行了,key1的值为,当A执行时,发现自己的时间戳11:01早于现有值,就不执行set操作了。

2.4消息队列

在并发量很大的情况下,可以通过消息队列进行串行化处理。这在高并发场景中是一种很常见的解决方案。

3.小结

“Redis并发竞争”问题就是高并发写同一个key时导致的值错误。

常用的解决方法:

乐观锁,注意不要在分片集群中使用

分布式锁,适合分布式系统环境

时间戳,适合有序场景

消息队列,串行化处理

作者:夕阳雨晴,欢迎关注我的号:偶尔美文,主流Java,为你讲述不一样的码农生活。


原始地址:/dongtai/7630.html