在Java中通过Redisson可以比较方便的操作Redis,实现分布式锁。(可以将更多的精力放在业务上,而不是锁的实现、锁的逻辑漏洞等。)
添加Redisson的Maven依赖
<dependency>
<groupId>org.redisson</groupId>
<artifactId>redisson</artifactId>
<version>3.2.0</version>
</dependency>
注入RedissonClient的bean
// 网上找的Redis集群配置方式(SSL加密连接的协议是rediss://)
@Bean
public RedissonClient redisson() throws IOException {
Config config = new Config();
config.useClusterServers()
.setScanInterval(2000) // cluster state scan interval in milliseconds
.addNodeAddress("redis://127.0.0.1:7000", "redis://127.0.0.1:7001")
.addNodeAddress("redis://127.0.0.1:7002");
return Redisson.create(config);
}
// 单机Redis
@Bean
public RedissonClient redisson() throws IOException {
Config config = new Config();
config.useSingleServer().setAddress("192.168.0.63:6379");
return Redisson.create(config);
}
能看出来,Redis单机与集群的配置方式差别不大,一个是通过useClusterServers()方法,一个是通过useSingleServer()方法
分布式锁的使用
@Autowired
private RedissonClient redissonclient;
public void test(){
RLock lock = redisson.getLock("anyLock"); // getLock的参数是锁的名称,如方法名等。
lock.lock();
//业务代码
lock.unlock();
}
上面的lock.lock()是最简单的一种分布式锁的运用。
然而还有一些稍复杂的情形:
- 锁的过期时间
- 没有获取到锁时采取的策略,(如等待一段时间后重试 或者放弃获取锁)。
- 等待加锁的队列中的优先级(如同一线程重复加锁时是否可以优先拿到锁)。
等等各种不同场景,也有一些封装好的方法提供使用,可以参考下面的工具类样例:
package com.hsshy.beam.distributedlock.redis;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import java.util.concurrent.TimeUnit;
/**
* redis分布式锁帮助类
* @author 科帮网 By https://blog.52itstyle.com
*/
public class RedissLockUtil {
private static RedissonClient redissonClient;
public void setRedissonClient(RedissonClient locker) {
redissonClient = locker;
}
/**
* 加锁
* @param lockKey
* @return
*/
public static RLock lock(String lockKey) {
RLock lock = redissonClient.getLock(lockKey);
lock.lock();
return lock;
}
/**
* 释放锁
* @param lockKey
*/
public static void unlock(String lockKey) {
RLock lock = redissonClient.getLock(lockKey);
lock.unlock();
}
/**
* 释放锁
* @param lock
*/
public static void unlock(RLock lock) {
lock.unlock();
}
/**
* 带超时的锁
* @param lockKey
* @param timeout 超时时间 单位:秒
*/
public static RLock lock(String lockKey, int timeout) {
RLock lock = redissonClient.getLock(lockKey);
lock.lock(timeout, TimeUnit.SECONDS);
return lock;
}
/**
* 带超时的锁
* @param lockKey
* @param unit 时间单位
* @param timeout 超时时间
*/
public static RLock lock(String lockKey, TimeUnit unit , int timeout) {
RLock lock = redissonClient.getLock(lockKey);
lock.lock(timeout, unit);
return lock;
}
/**
* 尝试获取锁
* @param lockKey
* @param waitTime 最多等待时间
* @param leaseTime 上锁后自动释放锁时间
* @return
*/
public static boolean tryLock(String lockKey, int waitTime, int leaseTime) {
RLock lock = redissonClient.getLock(lockKey);
try {
return lock.tryLock(waitTime, leaseTime, TimeUnit.SECONDS);
} catch (InterruptedException e) {
return false;
}
}
/**
* 尝试获取锁
* @param lockKey
* @param unit 时间单位
* @param waitTime 最多等待时间
* @param leaseTime 上锁后自动释放锁时间
* @return
*/
public static boolean tryLock(String lockKey, TimeUnit unit, int waitTime, int leaseTime) {
RLock lock = redissonClient.getLock(lockKey);
try {
return lock.tryLock(waitTime, leaseTime, unit);
} catch (InterruptedException e) {
return false;
}
}
}
遇到的一些坑
版本冲突
Redisson与SpringBoot的版本有些许的依赖关系,如果版本不匹配可能导致Bean注入失败。
在本地中SpringBoot为2.0.0.RELEASE、 Redisson版本为3.2.0时启动失败,为2.13.0时启动成功。
若不修改Redisson版本,仅将SpringBoot为2.2.2.RELEASE时也没有报错。
冲突时Log最下面的caused by 报的是空指针,具体异常如下
Redis的连接问题
在安装Redis后(通过yum/dnf安装),需要修改配置文件vi /etc/redis.conf
,
- 将bind 127.0.0.1 修改为 bind 0.0.0.0
- 将protected-mode yes 修改为 protected-mode no
bean的注入
@Bean
public RedissonClient redisson() throws IOException {
Config config = new Config();
config.useSingleServer().setAddress("redis://"+redisHost+":"+redisPort).setPassword(password);
return Redisson.create(config);
}
一开始使用如上代码进行配置。
测试时redis地址为192.168.0.63 端口为6379,无密码
异常:java.lang.IllegalArgumentException: port out of range:-1
- 由
"redis://"
引起的异常,此时会报"Hostname can't be null"
或"java.lang.IllegalArgumentException: port out of range:-1"
,将代码中的"redis://"
删除后错误类型变为Can't init enough connections amount! Only 0 from 1 were initialized. Server: /192.168.0.63:6379
异常:Can't init enough connections amount! Only 0 from 1 were initialized.
- 上面的新问题主要是密码的配置问题,出现这个问题是因为redis服务器中没有设置密码。所以在代码中也不需要调用setPassword()方法,需先判断password是否为空。