基于redis实现分布式锁(单节点)
文章目录
- 基于redis实现分布式锁
-
- 获取锁
-
- 代码实现
- 解锁
-
- 代码实现
- 完整代码
基于redis实现分布式锁
1、适用于单节点的分布式锁
2、多节点的分布式锁可使用redlock等框架实现
分布式锁需要解决如下几个问题
问题1:获取锁的唯一性(多个线程不能同时获取一个锁)
问题2:锁要有过期时间(一个线程获取锁后宕机,导致其它线程都获取不到锁)
问题3:释放锁时,只能释放自己(本线程)创建的锁
获取锁
key: 锁名,业务唯一
value: 使用线程id + 时间戳
1、redis的SETNX是key不存在时,才能set成功【问题1】
2、锁需要添加过期时间,并且获取锁和设置过期时间要保证原子性,这里使用Lua脚本(Lua脚本在redis执行是原子性的)执行【问题2】
代码实现
/* 获取锁保证原子性*/private static final String SET_KEY_LUA = "if redis.call('setnx',KEYS[1],ARGV[1]) == 1 then" +" return redis.call('expire',KEYS[1],ARGV[2]) else return 0 end";/* 获取锁* @param expire 锁过期时间,单位秒* @return*/public Boolean lock(Integer expire) {if (expire == null || expire <= 0) {expire = DEFAULT_EXPIRE;}RedisScript<Long> redisScript = new DefaultRedisScript(SET_KEY_LUA, Long.class);Long result = (Long) redisTemplate.execute(redisScript, Arrays.asList(key), value, String.valueOf(expire));return SUCCESS.equals(result);}
解锁
解锁时检查value值是否和当前线程的value一致,一致才能解锁【问题3】
代码实现
/* 释放锁保证原子性*/private static final String DELETE_LUA = "if redis.call('get',KEYS[1]) == ARGV[1] then" +" return redis.call('del', KEYS[1])" +" else" +" return 0" +" end";/* 解锁*/public void unlock() {RedisScript<Long> redisScript = new DefaultRedisScript(DELETE_LUA, Long.class);redisTemplate.execute(redisScript, Arrays.asList(key), value);}
完整代码
Lock.java
import lombok.Data;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.script.DefaultRedisScript;
import org.springframework.data.redis.core.script.RedisScript;import java.util.Arrays;/* 锁*/
@Data
public class Lock {private String key;private String value;private RedisTemplate redisTemplate;/* 获取锁保证原子性*/private static final String SET_KEY_LUA = "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 DELETE_LUA = "if redis.call('get',KEYS[1]) == ARGV[1] then" +" return redis.call('del', KEYS[1])" +" else" +" return 0" +" end";private static final Long SUCCESS = 1L;// 默认过期时间5分钟private static final Integer DEFAULT_EXPIRE = 5 * 60;public Lock(String key, RedisTemplate redisTemplate) {this.key = key;this.value = Thread.currentThread().getName() + "-" + System.currentTimeMillis();this.redisTemplate = redisTemplate;}/* 获取锁,默认60秒过期* @return*/public Boolean lock() {return this.lock(DEFAULT_EXPIRE);}/* 获取锁* @param expire 锁过期时间,单位秒* @return*/public Boolean lock(Integer expire) {if (expire == null || expire <= 0) {expire = DEFAULT_EXPIRE;}RedisScript<Long> redisScript = new DefaultRedisScript(SET_KEY_LUA, Long.class);Long result = (Long) redisTemplate.execute(redisScript, Arrays.asList(key), value, String.valueOf(expire));return SUCCESS.equals(result);}/* 解锁*/public void unlock() {RedisScript<Long> redisScript = new DefaultRedisScript(DELETE_LUA, Long.class);redisTemplate.execute(redisScript, Arrays.asList(key), value);}
}
LockService.java
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;@Service
@Slf4j
public class LockServiceImpl implements LockService {@Autowiredprivate RedisTemplate redisTemplate;@Overridepublic Lock getLock(String key) {return new Lock(key, redisTemplate);}
}
使用
String key = "xxx";Integer expire = 5;Lock lock = lockService.getLock(key);if (!lock.lock(expire)) {log.warn("未获取到分布式锁, key: {}", key);return null;}log.info("获取到分布式锁, key: {}", key);try {... ...} finally {lock.unlock();log.info("分布式锁已释放, key: {}", key);}