Redis分布式锁
什么分布式锁
顾名思义,就是分布式项目开发中用到的锁,可以用来控制分布式系统之间同步访问共享资源。
在整个系统提供一个全局、唯一的获取锁的“东西”,然后每个系统在需要加锁时,都去问这个“东西”拿到一把锁,这样不同的系统拿到的就可以认为是同一把锁。至于这个“东西”,可以是Redis、Zookeeper,也可以是数据库。
一般来说,分布式锁需要满足的特性有这么几点:
1、互斥性:在任何时刻,对于同一条数据,只有一台应用可以获取到分布式锁;
2、高可用性:在分布式场景下,一小部分服务器宕机不影响正常使用,这种情况就需要将提供分布式锁的服务以集群的方式部署;
3、防止锁超时:如果客户端没有主动释放锁,服务器会在一段时间之后自动释放锁,防止客户端宕机或者网络不可达时产生死锁;
4、独占性:加锁解锁必须由同一台服务器进行,也就是锁的持有者才可以释放锁,不能出现你加的锁,别人给你解锁了。
常见的分布式锁实现方案
实现分布式锁目前有三种流行方案,即基于关系型数据库、Redis、ZooKeeper 的方案。
1、基于关系型数据库,如MySQL 基于关系型数据库实现分布式锁,是依赖数据库的唯一性来实现资源锁定,比如主键和唯一索引等。
缺点:
- 这把锁强依赖数据库的可用性,数据库是一个单点,一旦数据库挂掉,会导致业务系统不可用。
- 这把锁没有失效时间,一旦解锁操作失败,就会导致锁记录一直在数据库中,其他线程无法再获得到锁。
- 这把锁只能是非阻塞的,因为数据的insert操作,一旦插入失败就会直接报错。没有获得锁的线程并不会进入排队队列,要想再次获得锁就要再次触发获得锁操作。
- 这把锁是非重入的,同一个线程在没有释放锁之前无法再次获得该锁。因为数据中数据已经存在了。
2、基于Redis实现
优点:
Redis 锁实现简单,理解逻辑简单,性能好,可以支撑高并发的获取、释放锁操作。
缺点:
Redis 容易单点故障,集群部署,并不是强一致性的,锁的不够健壮;
key 的过期时间设置多少不明确,只能根据实际情况调整;
需要自己不断去尝试获取锁,比较消耗性能。
3、基于zookeeper
优点:
zookeeper 天生设计定位就是分布式协调,强一致性,锁很健壮。如果获取不到锁,只需要添加一个监听器就可以了,不用一直轮询,性能消耗较小。
缺点:
在高请求高并发下,系统疯狂的加锁释放锁,最后 zk 承受不住这么大的压力可能会存在宕机的风险。
redis如何实现分布式锁
1、加锁
使用setnx来加锁。key是锁的唯一标识,按业务来决定命名,value这里设置为test。
setx key test
当一个线程执行setnx返回1,说明key原本不存在,该线程成功得到了锁;当一个线程执行setnx返回0,说明key已经存在,该线程抢锁失败;
2、解锁
有加锁就得有解锁。当得到的锁的线程执行完任务,需要释放锁,以便其他线程可以进入。释放锁的最简单方式就是执行del指令。
del key
释放锁之后,其他线程就可以继续执行setnx命令来获得锁。
3、锁超时
锁超时知道的是:如果一个得到锁的线程在执行任务的过程中挂掉,来不及显式地释放锁,这块资源将会永远被锁住,别的线程北向进来。
所以,setnx的key必须设置一个超时时间,以保证即使没有被显式释放,这把锁也要在一段时间后自动释放。setnx不支持超时参数,所以需要额外指令,
expire key 30
RedLock分布式锁
为了防止master节点加锁完,还没同步到slave时就挂掉,主从切换之后,锁就会丢失,所以出现了一种RedLock(Redis Distributed Lock)的分布式锁。
它的原理是:假设有5个redis节点,客户端用相同的key和随机值在5个节点上请求锁,请求锁的超时时间应小于锁自动释放时间。当在3个(超过半数)redis上请求到锁的时候,才算是真正获取到了锁。如果没有获取到锁,则把部分已锁的redis释放掉。
此种方式具有以下特性:
互斥访问:即永远只有一个 client 能拿到锁
避免死锁:最终 client 都可能拿到锁,不会出现死锁的情况,即使锁定资源的服务崩溃或者分区,仍然能释放锁。
容错性:只要大部分 Redis 节点存活(一半以上),就可以正常提供服务