基于redis实现的锁机制,主要是依赖redis自身的原子操作(因为redis是单线程)。
-
原子操作命令:SET user_key user_value NX PX 100
-
NX:只在在键不存在时,才对键进行设置操作
-
SET key value NX 效果等同于 SETNX key value
-
PX millisecond:设置键的过期时间为millisecond毫秒,当超过这个时间后,设置的键会自动失效。
-
redis从2.6.12版本开始,SET命令才支持这些参数。
-
逻辑:设置一个redis缓存数据作为分布式锁,因为操作是原子性,所以肯定线程安全,设置完拿到锁之后去处理业务逻辑,处理完之后释放掉锁。每次新的线程执行这段相同的逻辑之前,都要去判断是否已有锁,如果存在锁则无法处理逻辑,等待锁释放之后在进行操作。
设置一个过期时间,这样就算有异常原因没有释放锁,到期了之后会自动删除锁。
可能发生的问题:
- 问题:由于业务时间很长,锁自己过期了自动删除,有可能把别人正在持有的锁删除了。
解决:占锁的时候,value值指定为uuid等,每个人匹配是自己的锁才能删除。 - 问题: 如果判断正好是当前value值可以删除,正要删除锁的时候,锁已经过期,别人设置了新的值,那么我们删除的正好是别人的锁。
解决:查询value值和删除锁操作也要保证必须是原子性的,使用redis+Lua脚本完成。
final String script = "if redis.call(\"get\",\"" + lock + "\") == \"" + uid +
"\"then return redis.call(\"del\",\"" + lock + "\") else return 0 end ";
jedis.eval(script);
这段lua脚本是用c语言写的,redis在调用的时候判断和删除是原子操作。
- 问题:锁如何自动续期:
解决:1、设置一个比业务耗时更长的过期时间。2、未设置过期时机制,间,使用看门狗
机制,只要占锁成功,就会自动启动一个定时任务,从新给锁设置过期时间,新的过期时间就是看门狗的默认时间,每隔10秒都会自动给锁续上新的10s。
redis分布式锁的优化(例如秒杀库存系统 -> 快并且准):
场景:客户下单 -> 拿到redis分布式锁 -> 查询库存(库存充足) -> 下单 -> 生成订单或者扣款等 -> 减库存 -> 解锁
假设从拿锁到解锁之间需要20毫秒,那么一秒钟只能下单50个。
解决方案1:
使用分段锁,例如把1000个库存平分在10个字段中,每个库存100个,可以使用随机算法去访问这10个库存段,每个下单流程和上面场景中一样,这样就差不多能提高10倍性能,参考:(https://zhuanlan.zhihu.com/p/268290754?utm_source=wechat_session)
解决方案2:
个人想法,可以使用生产者消费者模式,先把总库存容量塞进队列容量1000,每次有订单过来减去队列容量,直到队列为空,后续的生成订单等流程单独设计模块,如果有生成订单失败或者付款失败的队列容量在加上。
网友评论