在Java中,关于锁我想大家都很熟悉。在并发编程中,我们通过锁,来避免由于竞争而造成的数据不一致问题。通常,我们以synchronized 、Lock来使用它。
但是Java中的锁,只能保证在同一个JVM进程内中执行。如果在分布式集群环境下呢?
分布式锁,是一种思想,它的实现方式有很多。比如,我们将沙滩当做分布式锁的组件,那么它看起来应该是这样的:
在沙滩上踩一脚,留下自己的脚印,就对应了加锁操作。其他进程或者线程,看到沙滩上已经有脚印,证明锁已被别人持有,则等待。
把脚印从沙滩上抹去,就是解锁的过程。
为了避免死锁,我们可以设置一阵风,在单位时间后刮起,将脚印自动抹去。
分布式锁的实现有很多,比如基于数据库、memcached、Redis、系统文件、zookeeper等。它们的核心的理念跟上面的过程大致相同。
我们先来看如何通过单节点Redis实现一个简单的分布式锁。
背景:在当应用服务启动的时候,需要进行数据初始化,如果部署至多容器节点的时候,节点在启动的时候会概率触发多次初始化逻辑;所以,需要通过锁来限制初始化操作之后执行一次。
@Component public class RedisLockUtil { @Autowired private StringRedisTemplate stringRedisTemplate; private static long DEFALT_TIMEOUT = 1L; /** * @description 上锁 * @param key 锁标识 * @param value 线程标识 * @param timeout 超时时间,秒 * @updateTime 2021/9/14 */ public boolean lock(String key, String value, long timeout) { return stringRedisTemplate.opsForValue().setIfAbsent(key, value, timeout, TimeUnit.SECONDS); } /** * @description 上锁(排队等待) * @param key 锁标识 * @param value 线程标识 * @updateTime 2021/9/14 */ public boolean lock(String key, String value) { while (true) { // 执行set命令 Boolean absent = stringRedisTemplate.opsForValue().setIfAbsent(key, value, DEFALT_TIMEOUT, TimeUnit.MINUTES); // 是否成功获取锁 if (absent) { return true; } } } /** * @description 解锁 * @param key 锁标识 * @param value 线程标识 * @updateTime 2021/9/14 */ public boolean unlock(String key, String value) { String currentValue = stringRedisTemplate.opsForValue().get(key); if(StrUtil.isNotBlank(currentValue) && currentValue.equals(value) ){ return stringRedisTemplate.opsForValue().getOperations().delete(key); } return false; } }
@Override public void onApplicationEvent(ApplicationStartedEvent event) { //上锁 long time = System.currentTimeMillis(); boolean result = redisLock.lock(INIT_KEY, String.valueOf(time), TIMEOUT); if (log.isInfoEnabled()) { log.info("获得锁的结果:" + result + ";获得锁的时间戳:" + String.valueOf(time)); } if(!result){ throw new IllegalArgumentException("已存在初始化!!!"); } try { ... 初始化逻辑代码 ... } catch (Exception e) { log.error("初始化失败!", e); } finally { //释放锁 redisLock.unlock(INIT_KEY, String.valueOf(time)); if (log.isInfoEnabled()) { log.info("释放锁的时间戳" + String.valueOf(time)); } } }