# lock-redis **Repository Path**: vigovin/lock-redis ## Basic Information - **Project Name**: lock-redis - **Description**: Redis 分布式锁工具 - **Primary Language**: Java - **License**: Apache-2.0 - **Default Branch**: master - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 2 - **Forks**: 0 - **Created**: 2020-07-31 - **Last Updated**: 2024-06-10 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README
## Redis 实现简单的分布式锁 ### Redis 实现分布式锁需要考虑的问题 互斥性:在任意时刻,只有一个客户端能持有锁 加锁和解锁必须是同一个客户端,在方法退出时应该释放锁 不会发生死锁:即使有一个客户端在持有锁的期间崩溃而没有主动解锁,也能保证后续其他客户端能加锁(通过锁超时机制,注意**上锁的同时设置超时时间,必须保证原子性**) 具有容错性:只要大部分的Redis节点正常运行,客户端就可以加锁和解锁 多把锁的区分方式:一般通过 Key 进行区分 加锁失败是否可以自旋重试 ### 使用方式 下载 jar 包,引入 Maven 依赖,在 application.yml 中配置开启分布式锁 在需要保证在分布式环境中只有一个进程执行的方法上加上注解`@Lock`即可,使用示例如下: ```java @Lock(lockKey = "lock_db", expireTimeout = 30, expireTimeUnit = TimeUnit.SECONDS, tryLockTimeout = 10, tryLockTimeUnit = TimeUnit.SECONDS) public void operateDB() { logger.info("Insert into DB..."); try { TimeUnit.SECONDS.sleep(5); } catch (InterruptedException e) { e.printStackTrace(); } logger.info("Success~"); } ``` 注解参数说明: `lockKey`:锁标识,默认为`全类名+方法名` `expireTimeout`:持有锁超时时间,默认为`1s` `expireTimeUnit`:持有锁超时时间单位,默认为`秒` `tryLockTimeout`:尝试获取锁超时时间(自旋时间),默认为`0` `tryLockTimeUnit`:尝试获取锁超时时间单位,默认为`秒` ### 实现流程 定义分布式锁注解类,指定相关属性和默认值 定义锁服务的相关接口,需要上锁`lock`和解锁`unlock`相关方法 实现锁服务,上锁关键代码如下 ```java Boolean locked = redisTemplate.opsForValue().setIfAbsent(lockKey, requestId, expireTimeout, expireTimeUnit); ``` `setIfAbsent`保证上锁和设置超时时间是一个原子操作,如果上锁后再设置超时时间,如果在上锁后服务挂了,则这个锁永远不会被释放 解锁前需要校验`value`,保证上锁和解锁是同一个客户端操作的,但是校验和解锁的操作又必须是原子的,因为如果在校验后还没解锁的时间内,锁突然过期了,并且锁被其他的进程获取了,那么此时再解锁就是错误的。解锁可以使用`Lua`脚本保证原子性 ```java String script = "if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1]) else return 0 end"; DefaultRedisScript redisScript = new DefaultRedisScript<>(script, Long.class); Long result = redisTemplate.execute(redisScript, Collections.singletonList(lockKey), requestId); ``` 定义切面类,实现在方法进入前上锁、方法退出时解锁的逻辑 ```java @Pointcut("@annotation(com.vigovin.component.Lock)") public void lockPointcut() {} @Around("lockPointcut()") public Object doAround(ProceedingJoinPoint proceedingJoinPoint) throws Throwable { // 尝试上锁... // 上锁失败则直接返回 try { return proceedingJoinPoint.proceed(); } catch (Exception e) { e.printStackTrace(); } finally { // 校验解锁 } } ```
## Reference https://www.w3cschool.cn/redis/redis-yj3f2p0c.html