要分析分布式锁这个问题,我们根据黄金圈法则来分析。
黄金圈法则是由美国营销顾问西蒙·斯涅克(Simon Sinek)提出的一种思维模型,用于帮助人们更好地理解和传达信息。黄金圈法则由三个圈组成,分别是:
使用3w分析问题思路来分析分布式锁,可以从以下几个方面进行分析:
分布式锁是控制分布式系统之间同步访问共享资源的机制。在分布式系统中,常常需要协调他们的动作。如果不同的系统或是同一个系统的不同主机之间共享了一个或一组资源,那么访问这些资源的时候,往往需要互斥来防止彼此干扰来保证一致性,在这种情况下,便需要使用到分布式锁。
回答时:先说一下概念。分布式锁是用于在分布式系统中控制对共享资源的访问,以避免数据竞争和并发问题。
分布式锁的实现方法有很多,常见的有以下几种:
回答时:说一下分布式锁以上的实现方式。
一些需要分布式锁的场景:
回答时:说一下分布式锁的应用场景。
Redisson 是一个基于 Redis 的 Java 分布式框架。Redisson 提供了丰富的功能,包括分布式锁、分布式集合、分布式队列等。
以下是使用 Redisson 实现分布式锁的示例:
@Autowired private RedissonClient redissonClient; public String lock() { // 获取锁 RLock lock = redissonClient.getLock("lock"); boolean acquired = lock.tryLock(10,-1,TimeUnit.SECONDS); if (acquired) { // 获取锁成功,执行业务逻辑 return "获取锁成功,执行业务逻辑..."; } else { // 获取锁失败,重试 return "获取锁失败,重试..."; } } public String unlock() { // 释放锁 RLock lock = redissonClient.getLock("lock"); lock.unlock(); return "释放锁成功..."; }
另外,redisson支持锁续期。即在锁键值过期后任务还没执行完成,此时需要把锁键值的时间自动延长。
Redisson提供了的续期机制,只要客户端加锁成功,就会启动一个Watch Dog。可以看到源代码的实现leaseTime不设置为-1时开启监听。如果任务没完成就调用scheduleExpirationRenewal续期方法。
tryLock() 方法用于尝试获取分布式锁,该方法有三个参数:
waitTime 参数表示客户端最多等待多长时间来获取锁。如果在 waitTime 时间内没有获取到锁,则会返回 false。
leaseTime 参数表示锁的过期时间。如果锁在 leaseTime 时间内没有被释放,则会自动释放。如果 leaseTime 设置为 -1,则表示锁的过期时间由 renew() 方法来控制。这样,在业务逻辑执行过程中,可以定期调用 lock.renew() 方法来续期锁的过期时间。
tryLock() 方法的返回值是一个 Boolean 值,表示是否成功获取到锁。如果成功获取到锁,则返回 true。否则,返回 false。
在 SpringBoot 2.7 中,可以通过spring-boot-starter-data-redis 默认依赖是Lettuce。那么Lettuce是如何实现分布式锁呢
以下是 SpringBoot+Lettuce 实现分布式锁的完整代码:
@Componentpublic class RedisLock { private static final String LOCK_SCRIPT = "if redis.call('exists', KEYS[1]) == 0 then/n" + " redis.call('set', KEYS[1], ARGV[1], 'NX', 'PX', ARGV[2]);/n" + " return 1/n" + "else/n" + " return 0/n" + "end"; private final RedisTemplate<String, String> redisTemplate; public RedisLock(RedisTemplate<String, String> redisTemplate) { this.redisTemplate = redisTemplate; } //获得锁 public boolean acquireLock(String key, long timeout) { String uuid = UUID.randomUUID().toString(); Object result = redisTemplate.execute(new DefaultRedisScript(LOCK_SCRIPT, Long.class), Arrays.asList(key), uuid, timeout); return result != null && (long) result == 1; } //释放锁 public void releaseLock(String key, String uuid) { redisTemplate.delete(key); }}
@Servicepublic class TestServcieImpl implements TestServcie{ @Autowired private StringRedisTemplate stringRedisTemplate; // 加锁脚本 private static final String LOCK_SCRIPT = "if redis.call ('setnx', KEYS[1], ARGV[1]) == 1 then return redis.call ('expire', KEYS[1], ARGV[2]) else return 0 end"; // 解锁脚本 private static final String UNLOCK_SCRIPT = "if redis.call ('get', KEYS[1]) == ARGV[1] then return redis.call ('del', KEYS[1]) else return 0 end"; // 加锁方法 public boolean lock(String key, String value, Long expire) { RedisScript<Long> redisScript = new DefaultRedisScript<>(LOCK_SCRIPT, Long.class); Long result = stringRedisTemplate.execute(redisScript, Collections.singletonList(key), value, String.valueOf(expire)); return result.equals(Long.valueOf(1)); } // 解锁方法 public boolean unlock(String key, String value) { RedisScript<Long> redisScript = new DefaultRedisScript<>(UNLOCK_SCRIPT, Long.class); Long result = stringRedisTemplate.execute(redisScript, Collections.singletonList(key), value); return result.equals(Long.valueOf(1)); }}
在上述 demo 中,我们使用 RedisLock 类来封装分布式锁的相关操作。acquireLock() 方法用于获取分布式锁,releaseLock() 方法用于释放分布式锁。
在 ServcieImpl 类中,我们使用 RedisLock 类来获取和释放分布式锁。lock() 方法用于获取锁,unlock() 方法用于释放锁。不像Redisson封装好了相应的方法,Lettuuce如果要实现锁续期就需要自己写监听器及相应的lua脚本。
可以看到不论是Redisson还是Lettuce实现分布式锁都使用的Lua脚本,那我们先来了解一下什么是Lua脚本语言。
Lua 是一个小巧的脚本语言,由巴西里约热内卢天主教大学(Pontifical Catholic University of Rio de Janeiro)里的一个研究小组于 1993 年开发的。Lua 使用标准 C 语言编写并以源代码形式开放,几乎在所有操作系统和平台上都能编译运行。Lua 脚本可以调用 C/C++ 的函数,也可以被 C/C++ 代码调用,所以 Lua 在应用程序中可以被广泛应用。
Lua 的特点如下:
Lua 在游戏开发、Web 开发、嵌入式系统等领域都有广泛的应用。
以下是 Lua 的一些典型应用:
Lua 是一款非常实用的脚本语言,在众多领域都有广泛的应用。
使用 Lua 脚本可以将判断和删除锁的操作合并为一个原子操作,避免了这些问题。Lua 脚本在 Redis 服务器端执行,不会受到网络延迟或者客户端故障的影响,也不会被其他命令打断,因此可以保证操作的原子性。
因此,Redis 命令没有原子性是指多个命令组合起来执行时没有原子性。
以下是使用 Lua 脚本实现分布式锁的示例:
-- 获取锁function acquire_lock(key, uuid, timeout) local value = redis.call("GET", key) if value == nil then redis.call("SET", key, uuid, "NX", "PX", timeout) return 1 else return 0 endend-- 释放锁function release_lock(key, uuid) redis.call("DEL", key)end
上述脚本实现了简单的 SETNX 和 DEL 操作,可以保证同一时刻只有一个客户端可以获取到锁。
在实际使用中,可以根据具体的业务场景来调整 Lua 脚本的实现。
本文链接:http://www.28at.com/showinfo-26-19895-0.html面试官必问的分布式锁面试题,你答得上来吗?
声明:本网页内容旨在传播知识,若有侵权等问题请及时与本网联系,我们将在第一时间删除处理。邮件:2376512515@qq.com
上一篇: 给正在使用Lombok的朋友一些建议
下一篇: 你可能听说过雪花算法