1. 简介
随着业务发展的需要,原有的单机部署的系统逐渐演变成了分布式集群系统,由于分布式系统多线程,多进程分布在不同的机器上,这使得原有的并发控制策略失效,单纯的Java API并不能提供分布式锁的能力(就是Java中的本地锁Lock以及synchronized)。 为了解决这个问题就需要一种能跨JVM的互斥机制来控制共享资源的访问,这就是分布式锁要解决的问题。
1.1. 几种主流的分布式锁
1.基于数据库实现分布式锁
2.基于缓存(Redis等)
3.基于Zookeeper
2. Redis 为什么可以做分布式锁?
Redis之所以能可以做分布式锁,主要有如下几个原因:
1.Redis集群是独立于业务系统的,
2.Redis是单进程的,同一时刻只能有一个线程获取到同一个锁,在该锁释放之前其他线程不能获取。
3. 使用Redis实现分布式锁
3.1. 直接使用setnx设置锁
在Redis中有个专门的命令用于设置锁 setnx <key> <value> ,该命令的特定是如果Redis服务中不存在该key则可以设置成功。否则,则设置不成功。如下图设置了key为lockkey的锁,第一次可以设置成功,再次设置的话则设置失败,通过del命令来释放锁,只有锁释放了才能再次设置该锁。
当然这样设置的锁是有问题的,如果忘记删除锁,或者说删除锁出现了异常,则这个锁会一直存在,永不过期。这显然是有问题的。
3.2 给分布式锁设置过期时间
为了解决锁不过期的问题,可以在设置锁的时候同时给锁设置过期时间。设置的命令是set <key> <value> nx ex <ttl>
。其中:<key>
是锁的键,<value>
是锁的值,<ttl>
是锁的过期时间,单位是秒。
如上图,设置了键为sku的锁,过期时间设置为10秒,在这10秒内不能重新设置该锁,10秒后又可以重新设置该锁。
4. 在Java代码中使用Redis分布式锁
通用的redis分布式锁的写法,调用setIfAbsent方法设置分布式锁的键和值,设置过期时间是10秒。之所以将值设置成唯一的UUID值,通过调用delete方法删除锁,删除之前首先需要判断被删除的锁的值和当前的UUID值是否相等,这样就可以防止锁被误删的情况,后面会详细讲解。
@RestController public class RedisLockTestController { @Autowired private StringRedisTemplate redisTemplate; @RequestMapping("/v1/lock/test") public String testLock() throws InterruptedException { String uuid = UUID.randomUUID().toString(); // 获取锁 Boolean lock = redisTemplate.opsForValue().setIfAbsent("lock", uuid, 10, TimeUnit.SECONDS); try { if (lock) { // 业务操作 Thread.sleep(5); } } finally { String value = redisTemplate.opsForValue().get("lock"); if (uuid.equalsIgnoreCase(value)) { redisTemplate.delete("lock"); } } return "success"; } }
这样的方式可以实现分布式锁,但是还有个问题,
1.业务还没有执行完锁就过期了怎么办?
2.锁删除的时候报错了怎么办?
下一篇文章我会详细介绍。
总结
本文简单介绍了Redis分布式锁的问题。