Redis 分布式锁
大家项目中都会使用到分布式锁把,通常用来做数据的有序操作场景,比如一笔订单退款(如果可以退多次的情况)。或者用户多端下单。
Maven 依赖
我主要是基于 Spring-Boot 2.1.2
+ Jedis
进行实现
<?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>2.1.2.RELEASE</version> </parent> <groupId>cn.edu.cqvie</groupId> <artifactId>redis-lock</artifactId> <version>1.0-SNAPSHOT</version> <properties> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <java.version>1.8</java.version> <redis.version>2.9.0</redis.version> <spring-test.version>5.0.7</spring-test.version> </properties> <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-autoconfigure</artifactId> </dependency> <dependency> <groupId>org.springframework.data</groupId> <artifactId>spring-data-redis</artifactId> </dependency> <dependency> <groupId>redis.clients</groupId> <artifactId>jedis</artifactId> <version>${redis.version}</version> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-logging</artifactId> </dependency> <dependency> <groupId>org.slf4j</groupId> <artifactId>log4j-over-slf4j</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> </dependency> </dependencies> <build> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> </plugin> </plugins> </build> </project>
配置文件
application.properties
配置文件内容如下:
spring.redis.host=127.0.0.1 spring.redis.port=6379 spring.redis.password= spring.redis.timeout=30000 spring.redis.jedis.pool.max-active=8 spring.redis.jedis.pool.min-idle=2 spring.redis.jedis.pool.max-idle=4 logging.level.root=INFO
接口定义
接口定义,对于锁我们核心其实就连个方法 lock
和 unlock
.
public interface RedisLock { long TIMEOUT_MILLIS = 30000; int RETRY_MILLIS = 30000; long SLEEP_MILLIS = 10; boolean tryLock(String key); boolean lock(String key); boolean lock(String key, long expire); boolean lock(String key, long expire, long retryTimes); boolean unlock(String key); }
分布式锁实现
我的实现方式是通过 setnx 方式实现了,如果存在 tryLock
逻辑的话,会通过 自旋
的方式重试
// AbstractRedisLock.java 抽象类 public abstract class AbstractRedisLock implements RedisLock { @Override public boolean lock(String key) { return lock(key, TIMEOUT_MILLIS); } @Override public boolean lock(String key, long expire) { return lock(key, TIMEOUT_MILLIS, RETRY_MILLIS); } } // 具体实现 @Component public class RedisLockImpl extends AbstractRedisLock { private Logger logger = LoggerFactory.getLogger(getClass()); @Autowired private RedisTemplate<String, String> redisTemplate; private ThreadLocal<String> threadLocal = new ThreadLocal<String>(); private static final String UNLOCK_LUA; static { StringBuilder sb = new StringBuilder(); sb.append("if redis.call(\"get\",KEYS[1]) == ARGV[1] "); sb.append("then "); sb.append(" return redis.call(\"del\",KEYS[1]) "); sb.append("else "); sb.append(" return 0 "); sb.append("end "); UNLOCK_LUA = sb.toString(); } @Override public boolean tryLock(String key) { return tryLock(key, TIMEOUT_MILLIS); } public boolean tryLock(String key, long expire) { try { return !StringUtils.isEmpty(redisTemplate.execute((RedisCallback<String>) connection -> { JedisCommands commands = (JedisCommands) connection.getNativeConnection(); String uuid = UUID.randomUUID().toString(); threadLocal.set(uuid); return commands.set(key, uuid, "NX", "PX", expire); })); } catch (Throwable e) { logger.error("set redis occurred an exception", e); } return false; } @Override public boolean lock(String key, long expire, long retryTimes) { boolean result = tryLock(key, expire); while (!result && retryTimes-- > 0) { try { logger.debug("lock failed, retrying...{}", retryTimes); Thread.sleep(SLEEP_MILLIS); } catch (InterruptedException e) { return false; } result = tryLock(key, expire); } return result; } @Override public boolean unlock(String key) { try { List<String> keys = Collections.singletonList(key); List<String> args = Collections.singletonList(threadLocal.get()); Long result = redisTemplate.execute((RedisCallback<Long>) connection -> { Object nativeConnection = connection.getNativeConnection(); if (nativeConnection instanceof JedisCluster) { return (Long) ((JedisCluster) nativeConnection).eval(UNLOCK_LUA, keys, args); } if (nativeConnection instanceof Jedis) { return (Long) ((Jedis) nativeConnection).eval(UNLOCK_LUA, keys, args); } return 0L; }); return result != null && result > 0; } catch (Throwable e) { logger.error("unlock occurred an exception", e); } return false; } }