本文采用Redis官网提供的RedLock来实现分布式同步锁,实现了单机模式和哨兵集群模式两种。
安全和可靠性保证
在描述我们的设计之前,我们想先提出三个属性,这三个属性在我们看来,是实现高效分布式锁的基础。
- 安全属性:互斥,不管任何时候,只有一个客户端能持有同一个锁。
- 效率属性A:不会死锁,最终一定会得到锁,就算一个持有锁的客户端宕掉或者发生网络分区。
- 效率属性B:容错,只要大多数Redis节点正常工作,客户端应该都能获取和释放锁。
1、RedLock原理
-
1.获取当前时间(单位是毫秒)
-
2.轮流用相同的key和随机值在N个节点上请求锁,在这一步里,客户端在每个master上请求锁时会有一个和总的锁释放时间相比小的多的超时时间。比如如果锁自动释放时间是10秒钟,那每个节点锁请求的超时时间可能是5-50毫秒的范围,这个可以防止一个客户端在某个宕掉的master节点上阻塞过长时间,如果一个master节点不可用了,我们应该尽快尝试下一个master节点
-
3.客户端计算第二步中获取锁所花的时间,只有当客户端在大多数master节点上成功获取了锁(在这里是3个),而且总共消耗的时间不超过锁释放时间,这个锁就认为是获取成功了
-
4.如果锁获取成功了,那现在锁自动释放时间就是最初的锁释放时间减去之前获取锁所消耗的时间
-
5.如果锁获取失败了,不管是因为获取成功的锁不超过一半(N/2+1)还是因为总消耗时间超过了锁释放时间,客户端都会到每个master节点上释放锁,即便是那些他认为没有获取成功的锁。
更为详细的介绍请参见:官网文档http://ifeve.com/redis-lock/
2、maven导入依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-configuration-processor</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.redisson</groupId>
<artifactId>redisson</artifactId>
<version>3.7.5</version>
</dependency>
主要配置文件
@Configuration
@ConfigurationProperties(prefix = "redisson")
@ConditionalOnProperty("redisson.password")
@Data
public class RedissonProperties {
/**
* 连接超时时长
*/
private int timeout = 3000;
/**
* ip
*/
private String address;
/**
* 密码
*/
private String password;
/**
* 连接库
*/
private int database = 0;
/**
* 连接池大小
*/
private int connectionPoolSize = 64;
/**
* 最小连接数
*/
private int connectionMinimumIdleSize = 10;
/**
* 备用服务器连接数
*/
private int slaveConnectionPoolSize = 250;
/**
* 主服务器连接数
*/
private int masterConnectionPoolSize = 250;
/**
* 哨兵地址
*/
private String[] sentinelAddresses;
/**
* 主服务器名称
*/
private String masterName;
}
分布式锁初始化类
@Configuration
@ConditionalOnClass(Config.class)
@EnableConfigurationProperties(RedissonProperties.class)
public class RedissonAutoConfiguration {
@Autowired
private RedissonProperties redissonProperties;
@Bean
@ConditionalOnProperty(name="redission.master-name")
RedissonClient redissonSentinel(){
Config config = new Config();
SentinelServersConfig sentinelServersConfig = config.useSentinelServers()
.setDatabase(redissonProperties.getDatabase())
.addSentinelAddress(redissonProperties.getSentinelAddresses())
.setMasterName(redissonProperties.getMasterName())
.setTimeout(redissonProperties.getTimeout())
.setMasterConnectionPoolSize(redissonProperties.getMasterConnectionPoolSize())
.setSlaveConnectionPoolSize(redissonProperties.getSlaveConnectionPoolSize());
if(StringUtils.isNotBlank(redissonProperties.getPassword())){
sentinelServersConfig.setPassword(redissonProperties.getPassword());
}
return Redisson.create(config);
}
@Bean
@ConditionalOnProperty(name = "redisson.address")
RedissonClient redissonClient(){
Config config = new Config();
SingleServerConfig singleServerConfig = config.useSingleServer()
.setAddress("redis://" + StringUtils.trim(redissonProperties.getAddress()))
.setDatabase(redissonProperties.getDatabase())
.setTimeout(redissonProperties.getTimeout())
.setConnectionPoolSize(redissonProperties.getConnectionPoolSize())
.setConnectionMinimumIdleSize(redissonProperties.getConnectionMinimumIdleSize());
if( StringUtils.isNotEmpty(redissonProperties.getPassword()) ){
singleServerConfig.setPassword(redissonProperties.getPassword());
}
return Redisson.create(config);
}
@Bean
@ConditionalOnBean(RedissonClient.class)
DistributedLock distributedLock(RedissonClient redissonClient){
DistributedLock lock = new RedissonDistributedLocker(redissonClient);
return lock;
}
}
根据配置动态生成哨兵集群模式和单机模式。主要对Redis集群进行配置后根据Redisson初始化Redisson连接。
分布式锁接口
/**
* 基于Redisson的分布式锁接口
* @author liumeng
*/
public interface DistributedLock {
/**
* 获取锁
* @param lockKey
* @return
*/
RLock lock(String lockKey);
/**
* 获取锁,设置锁超时时长
* @param lockKey
* @param leaseTime
* @return
*/
RLock lock(String lockKey, long leaseTime);
/**
* 获取锁,设置锁超时时长
* @param lockKey
* @param leaseTime
* @param timeUnit
* @return
*/
RLock lock(String lockKey, long leaseTime, TimeUnit timeUnit);
/**
* 尝试获取锁
* @param lockKey
* @param waitTime
* @param leaseTime
* @param timeUnit
* @return
*/
boolean tryLock(String lockKey, long waitTime, long leaseTime, TimeUnit timeUnit);
/**
* 释放锁
* @param lockKey
*/
void unLock(String lockKey);
/**
* 释放锁
* @param rLock
*/
void unLock(RLock rLock);
}
分布式锁接口实现类
/**
* @author liumeng
*/
@Slf4j
public class RedissonDistributedLocker implements DistributedLock {
private RedissonClient redissonClient;
public RedissonDistributedLocker(RedissonClient redissonClient){
this.redissonClient = redissonClient;
}
@Override
public RLock lock(String lockKey) {
RLock rLock = this.getRLock(lockKey);
rLock.lock();
return rLock;
}
@Override
public RLock lock(String lockKey, long leaseTime) {
return this.lock(lockKey, leaseTime, TimeUnit.SECONDS);
}
@Override
public RLock lock(String lockKey, long leaseTime, TimeUnit timeUnit) {
RLock rLock = this.getRLock(lockKey);
rLock.lock(leaseTime, timeUnit);
return rLock;
}
@Override
public boolean tryLock(String lockKey, long waitTime, long leaseTime, TimeUnit timeUnit) {
RLock rLock = this.getRLock(lockKey);
try {
return rLock.tryLock(waitTime, leaseTime, timeUnit);
} catch (InterruptedException e) {
log.error("", e);
}
return false;
}
@Override
public void unLock(String lockKey) {
RLock rLock = this.getRLock(lockKey);
rLock.unlock();
}
@Override
public void unLock(RLock rLock) {
if( null == rLock ){
throw new NullPointerException("rLock cannot be null.");
}
rLock.unlock();
}
private RLock getRLock(String lockKey) {
if( null == redissonClient ){
throw new NullPointerException("redisson client is null.");
}
return redissonClient.getLock(lockKey);
}
}