Java 正确实现 redis 分布式锁
1 源起
因为项目中有需要一个需求 ,就是在分布式的环境下 ,要根据一个 key,去获取一把分布式锁。所以。。。。。
2 我想要的效果
我想要的其实很简单, 就是根据一个 key 获取一个 分布式锁就行了 比如这样
// 获取分布式锁对象
RedisDistributeLock locker = new DefaultRedisDistributeLock();
// 锁定
locker.lock(jedis, "test1");
// TODO 业务逻辑
// 解锁
locker.release(jedis, "test1");
3 撸起袖子开干
3.1 导入 jedis 依赖
<dependency>
<groupId>redis.clients</groupId>
<artifactId>jedis</artifactId>
<version>2.9.0</version>
</dependency>
3.2 RedisDistributeLock 接口
RedisDistributeLock 定义了一些分布式锁的接口 实现, 本人能力有限,只实现了 非公平锁
package org.study.distributed_look.redis.way2;
import redis.clients.jedis.Jedis;
/** * @Author: huangwenjun * @Description: * @Date: Created in 13:45 2018/5/22 **/
public interface RedisDistributeLock {
/** * 公平锁, 只能说 本机的公平 * @param jedis * @param key * @param uuid */
void fairLock(Jedis jedis, String key, String uuid);
/** * 非公平锁 * @param jedis * @param key * @param uuid */
void unfairLock(Jedis jedis, String key, String uuid);
/** * 锁 默认非公平 * @param jedis * @param key * @param uuid */
void lock(Jedis jedis, String key, String uuid);
/** * 解锁 * @param jedis * @param key * @param uuid */
void release(Jedis jedis, String key, String uuid);
}
3.3 DefaultRedisDistributeLock
DefaultRedisDistributeLock 是我封装的 默认的分布式锁的实现
package org.study.distributed_look.redis.way2;
import org.study.distributed_look.redis.RedisTool;
import redis.clients.jedis.Jedis;
/** * @Author: huangwenjun * @Description: * @Date: Created in 13:48 2018/5/22 **/
public class DefaultRedisDistributeLock implements RedisDistributeLock {
/** * 默认 非公平锁 */
private static final Boolean DEFALUT_FAIR = false;
/** * 过期时间 默认 10 秒, 太短会导致锁不住, 如果业务无法在指定过期时间内 完成, 则必须加长过期时间 */
private static final Integer DEFAULT_EXPIRE_TIME = 10000;
private Boolean isFair;
private Integer expireTime;
public DefaultRedisDistributeLock() {
isFair = DEFALUT_FAIR;
expireTime = DEFAULT_EXPIRE_TIME;
}
public DefaultRedisDistributeLock(boolean isFair, Integer expireTime) {
isFair = isFair;
expireTime = expireTime;
}
@Override
public void lock(Jedis jedis, String key, String uuid) {
if (isFair) {
fairLock(jedis, key, uuid);
} else {
unfairLock(jedis, key, uuid);
}
}
@Override
public void fairLock(Jedis jedis, String key, String uuid) {
// 通过一个队列维护, 参照 AQS 实现
}
@Override
public void release(Jedis jedis, String key, String uuid) {
try {
while (true) {
boolean released = RedisTool.releaseDistributedLock(jedis, key, uuid);
if (released) {
break;
}
}
} finally {
jedis.close();
}
}
@Override
public void unfairLock(Jedis jedis, String key, String uuid) {
while (true) {
boolean locked = RedisTool.tryGetDistributedLock(jedis, key, uuid, expireTime);
if (locked) {
break;
}
}
}
}
3.4 RedisTool
RedisTool 是我参照网上一个大神的代码, 正确的实现了 redis 分布式锁的 获取 和 释放
package org.study.distributed_look.redis;
import redis.clients.jedis.Jedis;
import java.util.Collections;
/** * * redis 分布式锁实现 * * @Author: huangwenjun * @Description: * @Date: Created in 15:00 2018/5/21 **/
public class RedisTool {
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 请求标识 * @param expireTime 超期时间 * @return 是否获取成功 */
public static boolean tryGetDistributedLock(Jedis jedis, String lockKey, String requestId, int expireTime) {
String result = jedis.set(lockKey, requestId, SET_IF_NOT_EXIST, SET_WITH_EXPIRE_TIME, expireTime);
if (LOCK_SUCCESS.equals(result)) {
return true;
}
return false;
}
/** * 释放分布式锁 * @param jedis Redis客户端 * @param lockKey 锁 * @param requestId 请求标识 * @return 是否释放成功 */
public static boolean releaseDistributedLock(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));
if (RELEASE_SUCCESS.equals(result)) {
return true;
}
return false;
}
}
4 必须有测试啊!!!
4.1 测试代码
package org.study.distributed_look.redis.way2;
import redis.clients.jedis.Jedis;
import redis.clients.jedis.JedisPool;
/** * @Author: huangwenjun * @Description: * @Date: Created in 14:10 2018/5/22 **/
public class TestRedisDistributeLock {
public static void main(String[] args) {
for (int i = 1; i < 10; i ++) {
new TestLock().start();
}
}
static class TestLock extends Thread {
static RedisDistributeLock locker = new DefaultRedisDistributeLock();
JedisPool jedisPool = new JedisPool();
@Override
public void run() {
Jedis jedis = jedisPool.getResource();
locker.lock(jedis, "test1", "qwerqwer");
// TODO 模拟业务
System.out.println(Thread.currentThread().getName() + " get lock.....");
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + " release lock.....");
locker.release(jedis, "test1", "qwerqwer");
}
}
}
4.2 输出
Thread-4 get lock.....
Thread-4 release lock.....
Thread-2 get lock.....
Thread-2 release lock.....
Thread-7 get lock.....
Thread-7 release lock.....
Thread-9 get lock.....
Thread-9 release lock.....
Thread-0 get lock.....
Thread-0 release lock.....
Thread-8 get lock.....
Thread-8 release lock.....
Thread-5 get lock.....
Thread-5 release lock.....
Thread-6 get lock.....
Thread-6 release lock.....
Thread-3 get lock.....
Thread-3 release lock.....
4.3 优化策略
一些优化策略
- 1 这里为了简单起见, 没有优化 jedis 的连接池, 生产环境 必须手动设置 jedis 连接池
- 2 locker.lock(jedis, “test1”, “qwerqwer”); 第三个 参数可以用 uuid, 至于为什么要有第三个参数 参见
5 整合到业务中
// 获取分布式锁对象
RedisDistributeLock locker = new DefaultRedisDistributeLock();
// 锁定
locker.lock(jedis, "test1", "uuid");
// TODO 业务逻辑
// 解锁
locker.release(jedis, "test1", "uuid");
和之前设想的基本一致, 如果有人发现问题 欢迎指正