spring boot 整合 redis 实现分布式锁
利用redis setnx
命令的特性实现分布式锁
-
SETNX key value
只在键
key
不存在的情况下, 将键key
的值设置为value
。若键
key
已经存在, 则SETNX
命令不做任何动作。SETNX
是『SET if Not eXists』(如果不存在,则 SET)的简写。 -
返回值
命令在设置成功时返回
1
, 设置失败时返回0
-
设置key 过期时间,防止死锁
具体实现代码
-
lock接口
public interface Lock { /** 获取锁,直至成功 */ String getLock(String key, Long ttl); /** * 尝试获取锁,成功获取锁之后,会返回一个非空的字符串,解锁时需要传 * @param key * @param ttl 占有锁的时间,单位秒 * @return */ String tryLock(String key, Long ttl); /** * 释放锁 * @param key 获取锁时对应的key * @param value 获取锁时返回的非空字符串 * @return */ Boolean unLock(String key, String value);
-
redis实现
@Component @Slf4j public class RedisDistributedLock implements Lock { private static final String commandSet="set"; private static final String commandNX="NX"; private static final String commandEX="EX"; private static final String script="if redis.call(\"get\",KEYS[1]) == ARGV[1]\n" + "then\n" + " return redis.call(\"del\",KEYS[1])\n" + "else\n" + " return 0 end"; @Autowired private RedisTemplate redisTemplate; /** * * @param key * @param ttl 占有锁的时间,单位秒 * @return */ @Override public String tryLock(String key, Long ttl) { String value = UUID.fastUUID().toString(); Boolean isSuccess = (Boolean) redisTemplate.execute((RedisCallback<Boolean>) redisConnection -> { RedisSerializer valueSerializer = redisTemplate.getValueSerializer(); RedisSerializer keySerializer = redisTemplate.getKeySerializer(); Object result = redisConnection.execute(commandSet, keySerializer.serialize(key), valueSerializer.serialize(value), commandNX.getBytes(StandardCharsets.UTF_8), commandEX.getBytes(StandardCharsets.UTF_8), String.valueOf(ttl).getBytes(StandardCharsets.UTF_8)); return !ObjectUtils.isEmpty(result); }); return ObjectUtils.nullSafeEquals(isSuccess, Boolean.TRUE) ? value : null; } /** * 使用lua脚本,确保原子性操作 * @param key 获取锁时对应的key * @param value 获取锁时返回的非空字符串 * @return */ @Override public Boolean unLock(String key, String value) { DefaultRedisScript<Boolean> defaultRedisScript = new DefaultRedisScript<>(); defaultRedisScript.setResultType(Boolean.class); defaultRedisScript.setScriptText(script); return (Boolean) redisTemplate.execute(defaultRedisScript, Arrays.asList(key), value); } //获取锁 直至成功 @Override public String getLock(String key, Long ttl) { String lockUuid; while (StrUtil.isBlank(lockUuid = this.tryLock(key, ttl))) { try { Thread.sleep(100); } catch (Exception e) { log.error("RedisDistributedLock--getLock --异常"); } } return lockUuid; }
-
测试
@SpringBootTest @RunWith(SpringRunner.class) public class AppApiApplicationTest { @Inject private Lock lock; @Test public void testLock(){ String lockKey="member:15814841700"; String tryLock = lock.tryLock(lockKey, 60L); if (StrUtil.isEmpty(tryLock)){ throw new BusinessException("重复请求"); } try { System.out.println("执行任务---"); }catch (Exception e){ }finally { lock.unLock(lockKey,tryLock); } }
其他实现方案
-
redisson
也是基于redis实现
-
zookeeper
基于zk节点特性(持久顺序节点)+ watch机制