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,为你讲述不一样的码农生活。