1、引入包:
2、redis在yml中的配置:
因为本地环境和测试环境的redis部署方式不同,本地为单节点,测试为集群部署方式,所以application-dev.yml和application-test.yml中的redis配置不同。
dev.yml中的配置:单节点的配置方式
test.yml中的配置:集群的配置方式
3、配置RedissonConfig:这里根据不同的环境读取不同的redis配置,并创建RedissonClient 。
import lombok.extern.slf4j.Slf4j;
import org.redisson.Redisson;
import org.redisson.api.RedissonClient;
import org.redisson.config.ClusterServersConfig;
import org.redisson.config.Config;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.io.DefaultResourceLoader;
import org.springframework.core.io.Resource;
import org.yaml.snakeyaml.Yaml;
import java.io.IOException;
import java.io.InputStream;
import java.util.Map;
/**
* redisson配置
*/
@Configuration
@Slf4j
public class RedissonConfig {
@Value("${env}")
private String env;
@Bean
public RedissonClient redissonClient() {
Config config = new Config();
String redisNodes = this.getRedisNodes();
if (env.equals("dev")) {
config.useSingleServer().setAddress("redis://127.0.0.1:6379");
} else if (env.equals("test")) {
String[] redisNodeList = redisNodes.replace("[","").replace("]","").split(",");
// 指定使用集群部署方式
ClusterServersConfig clusterServersConfig = config.useClusterServers()
// 集群状态扫描间隔时间,单位是毫秒
.setScanInterval(2000);
// 添加节点
for (String node : redisNodeList) {
clusterServersConfig.addNodeAddress("redis://"+node.trim());
}
} else if (env.equals("prod")){
String[] redisNodeList = redisNodes.replace("[","").replace("]","").split(",");
ClusterServersConfig clusterServersConfig = config.useClusterServers().setScanInterval(2000);
for (String node : redisNodeList) {
clusterServersConfig.addNodeAddress("redis://"+node.trim());
}
}
RedissonClient client = Redisson.create(config);
return client;
}
// 读取application-test.yml文件中的redis集群节点
private String getRedisNodes() {
Map map = null;
Yaml yaml = new Yaml();
//文件路径是相对类目录(src/main/java)的相对路径
Resource resource = new DefaultResourceLoader().getResource("classpath:application-test.yml");
try {
InputStream inputStream = resource.getInputStream();
map = (Map) yaml.load(inputStream);
// map: {spring={redis={timeout=30000, password=null, cluster={nodes=[127.0.0.1:6001, 127.0.0.1:6002, 127.0.0.1:6003, 127.0.0.1:7001, 127.0.0.1:7002, 127.0.0.1:7003], max-redirects=3}, database=0, lettuce={pool={max-active=1000, max-idle=10, max-wait=-1, min-idle=5}}}}, env=test}
String nodes = ((Map)((Map)((Map) map.get("spring")).get("redis")).get("cluster")).get("nodes").toString();
System.out.println(nodes);
return nodes;
} catch (IOException e) {
e.printStackTrace();
}
return null;
}
}
4、使用RedissonClient :
@Autowired
private RedissonClient redissonClient;
5、RedissonClient的使用:
建议业务逻辑处理部分的处理时间不宜过长。 默认使用的是非公平锁
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.util.concurrent.TimeUnit;
/**
* @ClassName: RedissonLockService
* @Description: 测试redisson分布式锁
*/
@Service
@Slf4j
public class RedissonLockService {
@Autowired
private RedissonClient redissonClient;
@Autowired
private RedisTemplate
/**
* 取出数据,逻辑处理后保存
*/
@Transactional
public Integer getAndSave() throws Exception {
Integer result = null;
// 得到具体的锁
RLock lock = redissonClient.getLock("test-redisson-lock");
// 尝试加锁,最多等待15秒,上锁以后10秒自动解锁
Boolean lockRes = lock.tryLock(15, 10, TimeUnit.SECONDS);
// 如果拿到了锁
if (lockRes) {
// 业务逻辑处理
try {
String value = redisTemplate.opsForValue().get("redissionValue");
if (StringUtils.isNotEmpty(value)) {
int addValue = Integer.valueOf(value) + 1;
result = addValue;
redisTemplate.opsForValue().set("redissionValue", String.valueOf(addValue));
} else {
result = 1;
redisTemplate.opsForValue().set("redissionValue", "1");
}
} catch (Exception e) {
throw new Exception(e);
} finally {
// 释放锁
lock.unlock();
}
}
return result;
}
}
6、测试方法:使用线程池,开启多个线程,对redis中的值进行100次的+1操作。
import com.loong.redis.service.RedissonLockService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
/**
* @ClassName: TestRedissonController
* @Description: 测试Redisson
* @author: sunzf
* @date: 2021/11/23
*/
@Slf4j
@RestController
@RequestMapping("/redisson")
public class TestRedissonController {
@Autowired
private RedissonLockService redissonLockService;
/**
* 开启多个线程,测试redisson分布式锁
*/
@GetMapping("/test/lock")
public void testLock() {
ExecutorService executor = Executors.newFixedThreadPool(5);
for (int i = 1; i <= 100; i++) {
Future
@Override
public Integer call() throws Exception {
Integer result = 0;
try {
result = redissonLockService.getAndSave();
long currentThreadId = Thread.currentThread().getId();
System.out.println("当前线程ID: " + currentThreadId + ", 结果: " + result);
} catch (Exception e) {
log.error("测试异常, e: {}", e.getMessage());
}
return result;
}
});
}
executor.shutdown();
}
}
7、启动服务,请求这个测试接口,测试结果:
从测试结果可以看出,多个线程并发执行,最终数据的结果是正确的,从而实现了Redisson的基本使用。
当前线程ID: 94, 结果: 1
当前线程ID: 94, 结果: 2
当前线程ID: 95, 结果: 3
当前线程ID: 94, 结果: 4
当前线程ID: 95, 结果: 5
当前线程ID: 95, 结果: 6
当前线程ID: 95, 结果: 7
当前线程ID: 95, 结果: 8
当前线程ID: 95, 结果: 9
当前线程ID: 95, 结果: 10
当前线程ID: 95, 结果: 11
当前线程ID: 95, 结果: 12
当前线程ID: 95, 结果: 13
当前线程ID: 95, 结果: 14
当前线程ID: 95, 结果: 15
当前线程ID: 95, 结果: 16
当前线程ID: 95, 结果: 17
当前线程ID: 95, 结果: 18
当前线程ID: 95, 结果: 19
当前线程ID: 95, 结果: 20
当前线程ID: 95, 结果: 21
当前线程ID: 95, 结果: 22
当前线程ID: 95, 结果: 23
当前线程ID: 95, 结果: 24
当前线程ID: 95, 结果: 25
当前线程ID: 95, 结果: 26
当前线程ID: 95, 结果: 27
当前线程ID: 95, 结果: 28
当前线程ID: 95, 结果: 29
当前线程ID: 95, 结果: 30
当前线程ID: 95, 结果: 31
当前线程ID: 95, 结果: 32
当前线程ID: 95, 结果: 33
当前线程ID: 95, 结果: 34
当前线程ID: 95, 结果: 35
当前线程ID: 95, 结果: 36
当前线程ID: 95, 结果: 37
当前线程ID: 95, 结果: 38
当前线程ID: 95, 结果: 39
当前线程ID: 95, 结果: 40
当前线程ID: 95, 结果: 41
当前线程ID: 98, 结果: 42
当前线程ID: 98, 结果: 43
当前线程ID: 98, 结果: 44
当前线程ID: 98, 结果: 45
当前线程ID: 98, 结果: 46
当前线程ID: 98, 结果: 47
当前线程ID: 98, 结果: 48
当前线程ID: 98, 结果: 49
当前线程ID: 98, 结果: 50
当前线程ID: 98, 结果: 51
当前线程ID: 98, 结果: 52
当前线程ID: 98, 结果: 53
当前线程ID: 98, 结果: 54
当前线程ID: 98, 结果: 55
当前线程ID: 98, 结果: 56
当前线程ID: 98, 结果: 57
当前线程ID: 98, 结果: 58
当前线程ID: 98, 结果: 59
当前线程ID: 98, 结果: 60
当前线程ID: 98, 结果: 61
当前线程ID: 98, 结果: 62
当前线程ID: 98, 结果: 63
当前线程ID: 98, 结果: 64
当前线程ID: 98, 结果: 65
当前线程ID: 98, 结果: 66
当前线程ID: 98, 结果: 67
当前线程ID: 98, 结果: 68
当前线程ID: 98, 结果: 69
当前线程ID: 98, 结果: 70
当前线程ID: 97, 结果: 71
当前线程ID: 97, 结果: 72
当前线程ID: 94, 结果: 73
当前线程ID: 94, 结果: 74
当前线程ID: 94, 结果: 75
当前线程ID: 94, 结果: 76
当前线程ID: 94, 结果: 77
当前线程ID: 94, 结果: 78
当前线程ID: 94, 结果: 79
当前线程ID: 94, 结果: 80
当前线程ID: 94, 结果: 81
当前线程ID: 94, 结果: 82
当前线程ID: 94, 结果: 83
当前线程ID: 94, 结果: 84
当前线程ID: 94, 结果: 85
当前线程ID: 94, 结果: 86
当前线程ID: 94, 结果: 87
当前线程ID: 94, 结果: 88
当前线程ID: 94, 结果: 89
当前线程ID: 94, 结果: 90
当前线程ID: 94, 结果: 91
当前线程ID: 94, 结果: 92
当前线程ID: 94, 结果: 93
当前线程ID: 94, 结果: 94
当前线程ID: 94, 结果: 95
当前线程ID: 94, 结果: 96
当前线程ID: 97, 结果: 97
当前线程ID: 95, 结果: 98
当前线程ID: 98, 结果: 99
当前线程ID: 96, 结果: 100
redis中的结果:
其他要点:
1、大家都知道,如果负责储存这个分布式锁的Redisson节点宕机以后,而且这个锁正好处于锁住的状态时,这个锁会出现锁死的状态。为了避免这种情况的发生,Redisson内部提供了一个监控锁的看门狗,它的作用是在Redisson实例被关闭前,不断的延长锁的有效期。默认情况下,看门狗的检查锁的超时时间是30秒钟,也可以通过修改Config.lockWatchdogTimeout来另行指定。
修改lockWatchdogTimeout :
Config config = new Config();
config.setLockWatchdogTimeout(50000L);
注意:看门狗可能会影响性能。
2、Redisson非公平锁的第二种用法:lock.lock()
/**
* 锁的第二种用法:
* 取出数据,逻辑处理后保存
*/
@Transactional
public Integer getAndSave2() throws Exception {
Integer result = null;
// 得到具体的锁
RLock lock = redissonClient.getLock("test-redisson-lock-2");
// 业务逻辑处理
try {
// 加锁以后10秒钟自动解锁
// 无需调用unlock方法手动解锁
lock.lock(10, TimeUnit.SECONDS);
String value = redisTemplate.opsForValue().get("redissionValue2");
if (StringUtils.isNotEmpty(value)) {
int addValue = Integer.valueOf(value) + 1;
result = addValue;
redisTemplate.opsForValue().set("redissionValue2", String.valueOf(addValue));
} else {
result = 1;
redisTemplate.opsForValue().set("redissionValue2", "1");
}
} catch (Exception e) {
throw new Exception(e);
} finally {
// 释放锁
lock.unlock();
}
return result;
}
参考:
https://github.com/redisson/redisson/wiki/8.-distributed-locks-and-synchronizers
https://github.com/redisson/redisson/wiki/8.-分布式锁和同步器
https://www.cnblogs.com/qdhxhz/p/11046905.html
https://www.cnblogs.com/cjsblog/p/11273205.html