文章目录
- 非SpringBoot项目
- SpringBoot项目
- 测试
非SpringBoot项目
基于jedis
package com.blog.www.util.lock;
import lombok.extern.slf4j.Slf4j;
import redis.clients.jedis.Jedis;
import java.util.Collections;
import java.util.UUID;
/**
* Redis实现分布式锁,基于 jedis
* <br/>
* 请使用最新实现 {@link RedisLock}
* <br/>
* 分布式锁一般有三种实现方式:1. 数据库乐观锁;2. 基于Redis的分布式锁;3. 基于ZooKeeper的分布式锁。<br/>
* 可靠性:<br/>
* 为了确保分布式锁可用,我们至少要确保锁的实现同时满足以下四个条件:<br/>
* <ul>
* <li>
* 1. 互斥性。在任意时刻,只有一个客户端能持有锁。
* </li>
* <li>
* 2. 不会发生死锁。即使有一个客户端在持有锁的期间崩溃而没有主动解锁,也能保证后续其他客户端能加锁。
* </li>
* <li>
* 3. 具有容错性。只要大部分的Redis节点正常运行,客户端就可以加锁和解锁。
* </li>
* <li>
* 4. 解铃还须系铃人。加锁和解锁必须是同一个客户端,客户端自己不能把别人加的锁给解了。
* </li>
* </ul>
* 参考:<br/>
* <ul>
* <li>
* <a href=''>Redis分布式锁的正确实现方式</a>
* </li>
* <li>
* <a href=''>什么是分布式锁及正确使用redis实现分布式锁</a>
* </li>
* <li>
* <a href=''>基于Spring boot 2.1 使用redisson实现分布式锁</a>
* </li>
* </ul>
*
* @author :leigq
* @date :2019/7/2 11:22
*/
@Slf4j
@Deprecated
public class RedisLockForJedis {
private static final String LOCK_SUCCESS = "OK";
private static final String SET_IF_NOT_EXIST = "NX";
private static final String SET_WITH_EXPIRE_TIME = "PX";
private static final Long RELEASE_SUCCESS = 1L;
/**
* 尝试获取分布式锁
*
* @param jedis Redis客户端
* @param lockKey 锁
* @param requestId 请求标识 可以使用UUID.randomUUID().toString()方法生成
* @param expireTime 超期时间 单位毫秒
* @return 是否获取成功
*/
public static boolean lock(Jedis jedis, String lockKey, String requestId, int expireTime) {
String result = jedis.set(lockKey, requestId, SET_IF_NOT_EXIST, SET_WITH_EXPIRE_TIME, expireTime);
return LOCK_SUCCESS.equals(result);
}
/**
* 释放分布式锁
*
* @param jedis Redis客户端
* @param lockKey 锁
* @param requestId 请求标识 可以使用UUID.randomUUID().toString()方法生成
* @return 是否释放成功
*/
public static boolean unLock(Jedis jedis, String lockKey, String requestId) {
String script = "if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1]) else return 0 end";
Object result = jedis.eval(script, Collections.singletonList(lockKey), Collections.singletonList(requestId));
return RELEASE_SUCCESS.equals(result);
}
/**
* 测试加锁解锁(测试通过)
*/
public static void main(String[] args) {
String requestId = UUID.randomUUID().toString();
/* 单机 jedis连接使用参考:*/
Jedis jedis = new Jedis("127.0.0.1", 6379);
// 设置密码
jedis.auth("111111");
// 加锁
boolean locked = lock(jedis, "lockKey", requestId, 60 * 60 * 1000);
log.warn("locked result is : [{}]", locked);
// 解锁
boolean unLocked = unLock(jedis, "lockKey", requestId);
log.warn("unLocked result is : [{}]", unLocked);
}
}
SpringBoot项目
客户端选用 jedis
或 Lettuce
均可
package com.blog.www.util.lock;
import com.blog.www.config.RedisConfig;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.script.RedisScript;
import org.springframework.stereotype.Service;
import java.util.Collections;
import java.util.Objects;
/**
* Redis实现分布式锁,基于 RedisTemplate
* <br/>
* jedis 实现请看:<a href=''>基于redisTemplate的redis的分布式锁正确打开方式</a>
* <br/>
* 分布式锁一般有三种实现方式:1. 数据库乐观锁;2. 基于Redis的分布式锁;3. 基于ZooKeeper的分布式锁。<br/>
* 可靠性:<br/>
* 为了确保分布式锁可用,我们至少要确保锁的实现同时满足以下四个条件:<br/>
* <ul>
* <li>
* 1. 互斥性。在任意时刻,只有一个客户端能持有锁。
* </li>
* <li>
* 2. 不会发生死锁。即使有一个客户端在持有锁的期间崩溃而没有主动解锁,也能保证后续其他客户端能加锁。
* </li>
* <li>
* 3. 具有容错性。只要大部分的Redis节点正常运行,客户端就可以加锁和解锁。
* </li>
* <li>
* 4. 解铃还须系铃人。加锁和解锁必须是同一个客户端,客户端自己不能把别人加的锁给解了。
* </li>
* </ul>
* 参考:<br/>
* <ul>
* <li>
* <a href=''>Redis分布式锁的正确实现方式</a>
* </li>
* <li>
* <a href=''>springboot的RedisTemplate实现分布式锁</a>
* </li>
* </ul>
* <a href=''>使用示例参考</a>:
* <pre>
* {@link @Autowired}
* private {@link RedisLock} redisLock;
*
* boolean locked = redisLock.lock(lockKey, requestId, expireTime);
* if (locked) {
* // 执行逻辑操作
* ......
* ......
* redisLock.unLock(lockKey, requestId);
* } else {
* // 设置失败次数计数器, 当到达5次时, 返回失败
* int failCount = 1;
* while(failCount <= 5){
* // 等待100ms重试
* try {
* Thread.sleep(100l);
* } catch (InterruptedException e) {
* e.printStackTrace();
* }
* if (redisLock.lock(lockKey, requestId, expireTime)){
* // 执行逻辑操作
* ......
* ......
* redisLock.unLock(lockKey, requestId);
* }else{
* failCount ++;
* }
* }
* throw new RuntimeException("现在创建的人太多了, 请稍等再试");
* }
* </pre>
*
* @author :leigq
* @date :2019/7/2 11:22
*/
@Slf4j
@Service
@SuppressWarnings(value = "unchecked")
public final class RedisLock {
private final RedisTemplate redisTemp;
/**
* 使用 RedisConfig 中的 redisTemp,自定义序列化 及 兼容 java8 时间
* @see RedisConfig#getRedisTemplate(RedisConnectionFactory)
*/
public RedisLock(@Qualifier("redisTemp") RedisTemplate redisTemp) {
this.redisTemp = redisTemp;
}
/**
* 尝试获取分布式锁
*
* @param lockKey 锁key
* @param requestId 请求标识 可以使用UUID.randomUUID().toString()方法生成
* @param expireTime 超期时间 单位秒
* @return 是否获取成功
*/
public boolean lock(String lockKey, String requestId, int expireTime) {
// 使用脚本,保证原子性
RedisScript redisScript = RedisScript.of(LOCK_LUA, Long.class);
Object lockResult = redisTemp.execute(redisScript, Collections.singletonList(lockKey), requestId, expireTime);
log.warn("lock executeResult is [{}]", lockResult);
return Objects.equals(SUCCESS, lockResult);
// 不符合原子性
// Boolean setIfAbsentResult = stringRedisTemplate.opsForValue().setIfAbsent(lockKey, requestId);
// Boolean setExpireResult = stringRedisTemplate.expire(lockKey, expireTime, TimeUnit.SECONDS);
// return setIfAbsentResult && setExpireResult;
}
/**
* 释放分布式锁
*
* @param lockKey 锁key
* @param requestId 请求标识 可以使用UUID.randomUUID().toString()方法生成
* @return 是否释放成功
*/
public boolean unLock(String lockKey, String requestId) {
RedisScript redisScript = RedisScript.of(UNLOCK_LUA, Long.class);
Object unLockResult = redisTemp.execute(redisScript, Collections.singletonList(lockKey), requestId);
log.warn("unLock executeResult is [{}]", unLockResult);
return Objects.equals(SUCCESS, unLockResult);
}
private static final Long SUCCESS = 1L;
// 加锁 Lua 脚本
private static final String LOCK_LUA;
// 解锁 Lua 脚本
private static final String UNLOCK_LUA;
static {
// if redis.call('setNx', KEYS[1], ARGV[1]) then if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('expire', KEYS[1], ARGV[2]) else return 0 end end
LOCK_LUA = "if redis.call('setNx', KEYS[1], ARGV[1]) " +
"then " +
" if redis.call('get', KEYS[1]) == ARGV[1] " +
" then " +
" return redis.call('expire', KEYS[1], ARGV[2]) " +
" else " +
" return 0 " +
" end " +
"end ";
// if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1]) else return 0 end
UNLOCK_LUA = "if redis.call('get', KEYS[1]) == ARGV[1] " +
"then " +
" return redis.call('del', KEYS[1]) " +
"else " +
" return 0 " +
"end ";
}
}
redisTemp
请在这获取:SpringBoot2.0.X配置Redis
测试
package com.blog.www.util.lock;
import com.blog.www.base.BaseApplicationTests;
import org.junit.Test;
import org.springframework.beans.factory.annotation.Autowired;
import java.util.UUID;
/**
* RedisLock Tester.
*
* @author leigq
* @version 1.0
* @since <pre>10/17/2019</pre>
*/
public class RedisLockTest extends BaseApplicationTests {
@Autowired
private RedisLock redisLock;
/**
* Redis分布式锁测试,基于RedisTemplate,测试通过
*/
@Test
public void testLockAndUnLock() throws Exception {
String requestId = UUID.randomUUID().toString();
// 加锁
boolean locked = redisLock.lock("lockKey", requestId, 40);
log.warn("locked result is : [{}]", locked);
// 解锁
boolean unLocked = redisLock.unLock("lockKey", requestId);
log.warn("unLocked result is : [{}]", unLocked);
}
}
BaseApplicationTests.java
package com.blog.www.base;
import org.junit.After;
import org.junit.Before;
import org.junit.runner.RunWith;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;
/**
* 测试基类,其他类继承此类
* <br/>
* @author :leigq
* @date :2019/8/13 17:17
*/
@RunWith(SpringRunner.class)
@SpringBootTest
public abstract class BaseApplicationTests {
protected Logger log = LoggerFactory.getLogger(this.getClass());
private Long time;
@Before
public void setUp() {
this.time = System.currentTimeMillis();
log.info("==> 测试开始执行 <==");
}
@After
public void tearDown() {
log.info("==> 测试执行完成,耗时:{} ms <==", System.currentTimeMillis() - this.time);
}
}
测试结果如下: