1、引入包:
2轩端、redis在yml中的配置:
因為本地環(huán)境和測試環(huán)境的redis部署方式不同遵绰,本地為單節(jié)點,測試為集群部署方式忠蝗,所以application-dev.yml和application-test.yml中的redis配置不同现横。
dev.yml中的配置:單節(jié)點的配置方式
test.yml中的配置:集群的配置方式
3、配置RedissonConfig:這里根據不同的環(huán)境讀取不同的redis配置阁最,并創(chuàng)建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()
? ? ? ? ? ? ? ? ? ? // 集群狀態(tài)掃描間隔時間,單位是毫秒
? ? ? ? ? ? ? ? ? ? .setScanInterval(2000);
? ? ? ? ? ? // 添加節(jié)點
? ? ? ? ? ? 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集群節(jié)點
? ? 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的使用:
建議業(yè)務邏輯處理部分的處理時間不宜過長。 默認使用的是非公平鎖
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<String, String> 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) {
? ? ? ? ? ? // 業(yè)務邏輯處理
? ? ? ? ? ? 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<Integer> future = executor.submit(new Callable<Integer>() {
? ? ? ? ? ? ? ? @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告喊、啟動服務,請求這個測試接口壹无,測試結果:
從測試結果可以看出葱绒,多個線程并發(fā)執(zhí)行,最終數據的結果是正確的斗锭,從而實現了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節(jié)點宕機以后帮毁,而且這個鎖正好處于鎖住的狀態(tài)時,這個鎖會出現鎖死的狀態(tài)豺撑。為了避免這種情況的發(fā)生烈疚,Redisson內部提供了一個監(jiān)控鎖的看門狗,它的作用是在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");
? ? // 業(yè)務邏輯處理
? ? 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.-分布式鎖和同步器