分布式锁几乎是在微服务架构下的标配。在前面的系列已经讲过如何通过redisson实现分布式锁(redisson本身已经封装好了,开箱即用),不过得引入redisson的依赖,这里将配合lua脚本自行实现redis的分布式锁。同时还将实现zookeeper版的分布式锁
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-data-redisartifactId>
dependency>
application.yml配置
spring:
redis:
host: ${myurl.redis}
database: 0
password: root1234567890
BaseRedisConfig.java
import com.lk.basics.application.component.redis.RedisLock;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.StringRedisTemplate;
@Configuration
public class BaseRedisConfig {
@Bean
public RedisLock redisLock(StringRedisTemplate stringRedisTemplate) {
return new RedisLock(stringRedisTemplate);
}
@Bean
public StringRedisTemplate stringRedisTemplate(RedisConnectionFactory connectionFactory) {
return new StringRedisTemplate(connectionFactory);
}
}
RedisLock.java
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.redis.core.script.DefaultRedisScript;
import java.util.Collections;
import java.util.Objects;
import java.util.UUID;
import java.util.concurrent.Callable;
import java.util.concurrent.ThreadLocalRandom;
import java.util.concurrent.TimeUnit;
import java.util.function.Consumer;
import java.util.function.Function;
/**
* 自行实现redis分布式锁,
* 直接使用的StringRedisTemplate(来自spring-data-redis)。通过配合Lua脚本实现分布式锁
*/
public class RedisLock {
private static final Logger log = LoggerFactory.getLogger(RedisLock.class);
public static final long DEFAULT_WAIT_TIME = 5 * 1000L;
private final StringRedisTemplate stringRedisTemplate;
private String keyPrefix = "";
public RedisLock(StringRedisTemplate stringRedisTemplate) {
this.stringRedisTemplate = stringRedisTemplate;
}
public RedisLock(StringRedisTemplate stringRedisTemplate, String keyPrefix) {
Objects.requireNonNull(stringRedisTemplate, "stringRedisTemplate require non null");
this.stringRedisTemplate = stringRedisTemplate;
if (keyPrefix != null) {
this.keyPrefix = keyPrefix;
}
}
/**
* 获取锁并执行任务, 并提供失败后回调方法
*
* @param lockKey 锁的key
* @param task 获取锁后执行的任务
* @param failedCallback 获取失败 或 执行失败 后的回调, 若获取锁失败,则参数为null. 如果 failedCallback 为 null, 则默认将异常抛出
* @param waitTimeInMills 获取锁等待时间. <=0 不等待, 单位 ms
* @param expireTimeInMills 锁超时时间
*/
public void lockAndDo(String lockKey, Runnable task, Consumer<Exception> failedCallback, long waitTimeInMills, long expireTimeInMills) {
Objects.requireNonNull(task, "task require non null");
String lockId = UUID.randomUUID().toString();
boolean locked = waitLock(lockKey, lockId, waitTimeInMills, expireTimeInMills);
if (failedCallback == null) {
failedCallback = getDefaultFailedCallbackThrow(lockKey);
}
try {
if (locked) {
task.run();
} else {
failedCallback.accept(null);
}
} catch (Exception e) {
failedCallback.accept(e);
} finally {
unLock(lockKey, lockId);
}
}
/**
* 获取锁并执行任务, 返回执行后的结果, 或失败后回调后的结果
*
* @param lockKey 锁的key
* @param task 获取锁后执行的任务
* @param failedCallback 获取失败 或 执行失败 后的回调, 若获取锁失败,则参数为null. 如果 failedCallback 为 null, 则默认将异常抛出
* @param waitTimeInMills 获取锁等待时间. <=0 不等待, 单位 ms
* @param expireTimeInMills 锁超时时间
* @param 执行任务后的返回值
*/
public <T> T lockAndDo(String lockKey, Callable<T> task, Function<Exception, T> failedCallback, long waitTimeInMills, long expireTimeInMills) {
Objects.requireNonNull(task, "task require non null");
String lockId = UUID.randomUUID().toString();
boolean locked = waitLock(lockKey, lockId, waitTimeInMills, expireTimeInMills);
if (failedCallback == null) {
failedCallback = getDefaultFailedCallbackThrowForFunc(lockKey);
}
try {
if (locked) {
return task.call();
} else {
return failedCallback.apply(null);
}
} catch (Exception e) {
return failedCallback.apply(e);
} finally {
unLock(lockKey, lockId);
}
}
private Consumer<Exception> getDefaultFailedCallbackThrow(String lockKey) {
return e -> {
if (e == null) {
throw new RuntimeException("获取锁失败: lockKey=" + lockKey);
}
throw new RuntimeException(e);
};
}
private <T> Function<Exception, T> getDefaultFailedCallbackThrowForFunc(String lockKey) {
return e -> {
if (e == null) {
throw new RuntimeException("获取锁失败: lockKey=" + lockKey);
}
throw new RuntimeException(e);
};
}
public boolean waitLock(String lockKey, String lockId, long expireTimeInMills) {
return waitLock(lockKey, lockId, DEFAULT_WAIT_TIME, expireTimeInMills);
}
/**
* 阻塞式获取锁
*
* @param lockKey 锁的key
* @param lockId 锁的id, 防止被误删, uuid
* @param waitTimeInMills 获取锁等待时间, 在该时间内不断重试, <=0 不等待; 单位: ms
* @param expireTimeInMills 锁超时时间, 单位: ms
* @return 是否成功获取锁
*/
public boolean waitLock(String lockKey, String lockId, long waitTimeInMills, long expireTimeInMills) {
long lastWaitTime = waitTimeInMills;
while (true) {
boolean locked = tryLock(lockKey, lockId, expireTimeInMills);
if (locked) {
return true;
}
if (lastWaitTime <= 0) {
return false;
}
// 平均间隔每100ms 尝试加一次锁,
long sleepTime = 75 + ThreadLocalRandom.current().nextInt(50);
lastWaitTime -= sleepTime;
try {
Thread.sleep(sleepTime);
} catch (InterruptedException ignore) {
}
}
}
/**
* 尝试获取锁, 获取失败直接返回
*
* @param lockKey 锁的key
* @param lockId 锁的id, 防止 误删
* @param expireTimeInMills 锁超时时间, 单位: ms
* @return 是否获取到锁
*/
public boolean tryLock(String lockKey, String lockId, long expireTimeInMills) {
try {
String key = keyPrefix + lockKey;
Boolean lock = stringRedisTemplate.opsForValue().setIfAbsent(key, lockId, expireTimeInMills, TimeUnit.MILLISECONDS);
return lock != null && lock;
} catch (Exception e) {
log.warn("redis lock error key={}, value={}", lockKey, lockId, e);
}
return false;
}
private static final String UN_LOCK_SCRIPT =
"if redis.call('get', KEYS[1]) == ARGV[1] " +
"then return redis.call('del', KEYS[1]) " +
"else return 0 end";
private static final Long UN_LOCK_SUCCESS = 1L;
public boolean unLock(String lockKey, String lockId) {
try {
String key = keyPrefix + lockKey;
DefaultRedisScript<Long> defaultRedisScript = new DefaultRedisScript<>();
defaultRedisScript.setScriptText(UN_LOCK_SCRIPT);
defaultRedisScript.setResultType(Long.class);
Long execute = stringRedisTemplate.execute(defaultRedisScript, Collections.singletonList(key), lockId);
return UN_LOCK_SUCCESS.equals(execute);
} catch (Exception e) {
log.warn("redis unlock error key={}, value={}", lockKey, lockId, e);
}
return false;
}
}
@Resource
private RedisLock redisLock;
@GetMapping("/redisLock/lock")
public String redisLockTest() throws InterruptedException {
//加锁并处理业务
String redisLockKey = "redisLock_key";
long waitTimeInMills = 30 * 1000L;
long expireTimeInMills = 30 * 1000L;
redisLock.lockAndDo(redisLockKey,
() -> {
log.info("开始====》完成一些业务逻辑");
try {
Thread.sleep(1000 * 10);
} catch (InterruptedException e) {
e.printStackTrace();
}
log.info("已经====》完成上述业务逻辑");
}
, e -> log.error("There have some error", e)
, waitTimeInMills
, expireTimeInMills);
return "执行正常";
}
<dependency>
<groupId>org.apache.zookeepergroupId>
<artifactId>zookeeperartifactId>
<version>3.4.14version>
dependency>
application.yml
zookeeper:
address: ${myurl.zk} #zookeeper Server地址,如果有多个,使用","隔离。例如 ip1:port1,ip2:port2,ip3:port3
retryCount: 5 #重试次数
elapsedTimeMs: 5000 #重试间隔时间
sessionTimeoutMs: 30000 #Session超时时间
connectionTimeoutMs: 10000 #连接超时时间
ZookeeperProperties.java
import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Configuration;
@Data
@Configuration
@ConfigurationProperties(prefix = "zookeeper")
public class ZookeeperProperties {
/** 重试次数 */
private int retryCount;
/** 重试间隔时间 */
private int elapsedTimeMs;
/**连接地址 */
private String address;
/**Session过期时间 */
private int sessionTimeoutMs;
/**连接超时时间 */
private int connectionTimeoutMs;
}
ZookeeperConfig.java
import com.lk.basics.infrastructure.component.ZookeeperProperties;
import org.apache.curator.retry.RetryNTimes;
import org.apache.curator.framework.CuratorFramework;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.apache.curator.framework.CuratorFrameworkFactory;
@Configuration
public class ZookeeperConfig {
/**
* 创建 CuratorFramework 对象并连接 Zookeeper
*
* @param zookeeperProperties 从 Spring 容器载入 zookeeperProperties Bean 对象,读取连接 ZK 的参数
* @return CuratorFramework
*/
@Bean(initMethod = "start")
public CuratorFramework curatorFramework(ZookeeperProperties zookeeperProperties) {
return CuratorFrameworkFactory.newClient(
zookeeperProperties.getAddress(),
zookeeperProperties.getSessionTimeoutMs(),
zookeeperProperties.getConnectionTimeoutMs(),
new RetryNTimes(zookeeperProperties.getRetryCount(),
zookeeperProperties.getElapsedTimeMs()));
}
}
ZkLock.java
import lombok.extern.slf4j.Slf4j;
import org.apache.curator.framework.CuratorFramework;
import org.apache.curator.framework.recipes.locks.InterProcessMutex;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import java.util.concurrent.TimeUnit;
@Slf4j
@Component
public class ZkLock {
@Autowired
CuratorFramework curatorFramework;
/**
* 节点名称
*/
public static final String NODE_PATH = "/lock-space/%s";
/**
* 尝试获取分布式锁
*
* @param key 分布式锁 key
* @param expireTime 超时时间
* @param timeUnit 时间单位
* @return 超时时间单位
*/
public InterProcessMutex tryLock(String key, int expireTime, TimeUnit timeUnit) {
try {
InterProcessMutex mutex = new InterProcessMutex(curatorFramework, String.format(NODE_PATH, key));
boolean locked = mutex.acquire(expireTime, timeUnit);
if (locked) {
log.info("申请锁(" + key + ")成功");
return mutex;
}
} catch (Exception e) {
log.error("申请锁(" + key + ")失败,错误:{}", e);
}
log.warn("申请锁(" + key + ")失败");
return null;
}
/**
* 释放锁
*
* @param key 分布式锁 key
* @param lockInstance InterProcessMutex 实例
*/
public void unLock(String key, InterProcessMutex lockInstance) {
try {
lockInstance.release();
log.info("解锁(" + key + ")成功");
} catch (Exception e) {
log.error("解锁(" + key + ")失败!");
}
}
}
@Resource
private ZkLock zkLock;
@GetMapping("/lock")
public String redissonLock() throws InterruptedException {
String zkLockKey = "redisLock_key";
//尝试加锁
final InterProcessMutex lock = zkLock.tryLock(zkLockKey, 30, TimeUnit.SECONDS);
//成功获取到锁
if (lock != null) {
// 业务代码
log.info("开始====》完成一些业务逻辑");
Thread.sleep(1000 * 10);
log.info("已经====》完成上述业务逻辑");
zkLock.unLock(zkLockKey, lock);//解锁
}
return "执行正常";
}