什么是分布式锁?
分布式锁是控制分布式系统或不同系统之间共同访问共享资源的一种锁实现,如果不同的系统或同一个系统的不同主机之间共享了某个资源时,往往需要互斥来防止彼此干扰来保证一致性。
分布式锁需要具备哪些条件?
- 互斥性:在任意一个时刻,只有一个客户端持有锁。
- 无死锁:即便持有锁的客户端崩溃或者其他意外事件,锁仍然可以被获取。
- 容错:只要大部分Redis节点都活着,客户端就可以获取和释放锁。
场景
以前大学照着网上的项目视频做商城的时候,用到Redis。不过基本上都是用来当缓存,但是实际上的应用远不止缓存,所以今天分享一个分布式锁的场景和应用。
在逛商城的时候进行购物支付,基本都是分布式系统,那么用户支付的时候就要上锁,保证不能多线程操作,Redis分布式锁就差不多是这么一个位置:
从业务的角度考虑是非常合理的,它保证了查询及插入数据整个流程的原子性,防止查到脏数据,使得支付流程是一个串行化操作。
接下来就来讲一个Redis分布式锁的一个知识点。
为什么要使用分布式锁?
在实际项目中见过分布式锁后,就不难理解为什么要用分布式锁了。总的来说就是分布式系统要访问共享资源,为了避免并发访问资源带来的错误,我们为共享资源添加一把锁,让各个访问互斥,保证并发访问的安全性,这就是使用分布式锁的原因。
Redis中分布式锁的实现
命令格式:
1 | SETNX key value |
将key的值设为value,当且仅当key不存在。若给定的key已经存在,则SETNX不做任何动作。SETNX是SET if Not eXists的简写。
返回值:
返回整数,具体为
- 1,当key的值被设置
- 0,当key的值没被设置
Redis中使用分布式锁很简单,只要使用setnx指令对某个key上锁就行:
1 | setnx lock test //上锁 |
当然我们还可以在上锁之后使用expire指令给锁设置过期时间。
假如我们的程序不使用指令解锁,靠redis设置时间过期来解锁,貌似会出问题。假如我们的服务进程在执行setnx之后和执行expire指令之前挂掉了,那么这个锁岂不是永远都不会被释放?
没错,这确实是个问题,当时人们在Redis的开源社区提出一堆解决方案专门来解决这个问题,可实现方式都极为复杂。后来Redis的作者在Redis2.8版本中假如了set指令的扩展参数,使得setnx指令和expire指令能够同时执行,具体使用像下面一样:
1 | set lock test ex 5 nx |
从此以后,Redis成为了分布式锁的宠儿。
集群Redis的分布式锁
在Redis的分布式环境中,Redis 的作者提供了RedLock 的算法来实现一个分布式锁。
加锁
- 获取当前Unix时间,以毫秒为单位。
- 依次尝试从N个实例,使用相同的key和随机值获取锁。在步骤2,当向Redis设置锁时,客户端应该设置一个网络连接和响应超时时间,这个超时时间应该小于锁的失效时间。例如你的锁自动失效时间为10秒,则超时时间应该在5-50毫秒之间。这样可以避免服务器端Redis已经挂掉的情况下,客户端还在死死地等待响应结果。如果服务器端没有在规定时间内响应,客户端应该尽快尝试另外一个Redis实例。
- 客户端使用当前时间减去开始获取锁时间(步骤1记录的时间)就得到获取锁使用的时间。当且仅当从大多数(这里是3个节点)的Redis节点都取到锁,并且使用的时间小于锁失效时间时,锁才算获取成功。
- 如果取到了锁,key的真正有效时间等于有效时间减去获取锁所使用的时间(步骤3计算的结果)。
- 如果因为某些原因,获取锁失败(没有在至少N/2+1个Redis实例取到锁或者取锁时间已经超过了有效时间),客户端应该在所有的Redis实例上进行解锁(即便某些Redis实例根本就没有加锁成功)。
解锁
向所有的Redis实例发送释放锁命令即可,不用关心之前有没有从Redis实例成功获取到锁.
总结
这次对Redis分布式锁的探索算是加深了自己对Redis的理解,但是Redis的用处远远不止缓存和分布式锁,后面慢慢摸索吧。