分布式锁的简单设计

分布式锁的简单设计

在一些在线秒杀或者是抢购的活动中,商品的份额往往是有限制配额的,所以对于秒杀抢购系统来说保证商品份额不被多抢尤为重要,
单点服务系统中做到这点一般来说仅通过语言本身的特性来实现是比较容易保证和实现的,
但是对于集群或者分布式的系统来说,仅仅是通过语言本身的特性是比较难于保证的,需要借助第三方组件来保证这一类型的需求

单点服务

在java语言中,可以采用java本身的关键字或者实现类来进行设计
* synchronized
* Lock

集群和分布式

对于这一类系统,有多种的同步方案来保证商品不被多抢。
* 基于数据库
* 基于ZooKeeper
* 基于redis

这里不讨论各自性能如何,因为目前接触到的系统并没有遇到性能瓶颈,并发量也不高,而本身系统又集成又redis,所以这里只是简单说说采用redis来进行设计的锁
用过redis的都知道,redis有个特性比较适合同步锁的设置,就是它的所有操作都是原子操作,也就是说不管在什么系统中,只要你在同一个redis db的同一操作都能保证原子性
所以在分布式系统中只要我们通过redis的一些操作就能设计出一个比较简单的分布式锁,同时也能比较简单解决并发时候产生的一些问题

基于redis指令

redis的指令操作都具有原子性,这就保证了在同一时间内有且仅有一个操作,我们可以利用这一特性来

redis中有个指令非常契合我们的需求:
指令SETNX:
SETNX key value
将 key 的值设为 value ,当且仅当 key 不存在。
若给定的 key 已经存在,则 SETNX 不做任何动作。
SETNX 是『SET if Not eXists』(如果不存在,则 SET)的简写。
返回值:
设置成功,返回 1
设置失败,返回 0


redis> EXISTS job                # job 不存在
(integer) 0

redis> SETNX job "programmer"    # job 设置成功
(integer) 1

redis> SETNX job "code-farmer"   # 尝试覆盖 job ,失败
(integer) 0

redis> GET job                   # 没有被覆盖
"programmer"

可以看到,这个指令只是一个简单的值设置操作,当且仅当key不存在的时候才会成功的设置该key的值。
所以就可以把该指令当作锁来使用,因为不管什么时候都有且仅有一个设置能成功,我们设定只有设置成功才能进行下一步的操作,只要将抢购奖品操作放在设置成功之后来惊醒操作,就可以保证了类似抢购活动中奖品的数量能统一的派送,而且不会超派,然后派送完一个奖品后就释放掉这个锁,让其它线程有机会拿到这个锁。

当然锁的设计没这么简单,还要花点时间考虑下怎么来实现,可以看看Redisson锁设计的原理,这里就不说了,还有用这个指令来操作其实是有一定的风险的,因为有可能会造成死锁,也就是说这个锁在操作过程中应用发生异常,导致锁不能正常释放!

基于eval脚本操作

在redis中同样原子操作的还有eval脚本操作,对于同一个脚本的执行是具原子性的,所以我们就可以通过脚本来实现我们的商品奖品抢购流程,而不用在业务代码实现,这样我们的抢购其实就具备原子性,说回锁设计,其实也可以利用这一特性,通过脚本来设计锁会比单纯的指令更加精细,也可以适应更加复杂的业务特性

实现在这里也不多说了,只要原理通了实现倒是次要的!

总结

总的来说,业务上锁一般都是要求同一个时间点只能有一个有效操作,这样才能保护关键的操作不出现意外,根据这个要求来怎么样实现都可以,当然懒得自己弄也没关系,现在那么多开源的第三方组件足以应付我们的业务需求,这里说那么多只是总结下这一类问题这一类组件是怎么个回事(原理)。

由于我目前接触的系统以及业务也不复杂,见识也有限,有可能我自己的理解是错的,这个有看到的人也可以留言指出。
这里推荐一下有个比较轻量级的框架Redission,不过这个框架也有一些问题,稳定性也不是非常好,但是对于一般的轻应用来说我感觉是可以应付的,好了,不废话了!

猜你喜欢

转载自blog.csdn.net/zlw420123/article/details/53841440