文章已收录Github精选,欢迎Star:https://github.com/yehongzhi
前言
如果在一个分布式系统中,我们从数据库中读取一个数据,然后修改保存,这种情况很容易遇到并发问题。因为读取和更新保存不是一个原子操作,在并发时就会导致数据的不正确。这种场景其实并不少见,比如电商秒杀活动,库存数量的更新就会遇到。如果是单机应用,直接使用本地锁就可以避免。如果是分布式应用,本地锁派不上用场,这时就需要引入分布式锁来解决。
由此可见分布式锁的目的其实很简单,就是为了保证多台服务器在执行某一段代码时保证只有一台服务器执行。
为了保证分布式锁的可用性,至少要确保锁的实现要同时满足以下几点:
- 互斥性。在任何时刻,保证只有一个客户端持有锁。
- 不能出现死锁。如果在一个客户端持有锁的期间,这个客户端崩溃了,也要保证后续的其他客户端可以上锁。
- 保证上锁和解锁都是同一个客户端。
一般来说,实现分布式锁的方式有以下几种:
- 使用MySQL,基于唯一索引。
- 使用ZooKeeper,基于临时有序节点。
- 使用Redis,基于setnx命令。
本篇文章主要讲解Redis的实现方式。
实现思路
Redis实现分布式锁主要利用Redis的setnx
命令。setnx
是SET if not exists
(如果不存在,则 SET)的简写。
127.0.0.1:6379> setnx lock value1 #在键lock不存在的情况下,将键key的值设置为value1
(integer) 1
127.0.0.1:6379> setnx lock value2 #试图覆盖lock的值,返回0表示失败
(integer) 0
127.0.0.1:6379> get lock #获取lock的值,验证没有被覆盖
"value1"
127.0.0.1:6379> del lock #删除lock的值,删除成功
(integer) 1
127.0.0.1:6379> setnx lock value2 #再使用setnx命令设置,返回0表示成功
(integer) 1
127.0.0.1:6379> get lock #获取lock的值,验证设置成功
"value2"
上面这几个命令就是最基本的用来完成分布式锁的命令。
加锁:使用setnx key value
命令,如果key不存在,设置value(加锁成功)。如果已经存在lock(也就是有客户端持有锁了),则设置失败(加锁失败)。
解锁:使用del
命令,通过删除键值释放锁。释放锁之后,其他客户端可以通过setnx
命令进行加锁。
key的值可以根据业务设置,比如是用户中心使用的,可以命令为USER_REDIS_LOCK
,value可以使用uuid保证唯一,用于标识加锁的客户端。保证加锁和解锁都是同一个客户端。
那么接下来就可以写一段很简单的加锁代码:
private static Jedis jedis = new Jedis("127.0.0.1")