redis實現分布式鎖-redisson

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.-分布式鎖和同步器

https://www.cnblogs.com/qdhxhz/p/11046905.html

https://www.cnblogs.com/cjsblog/p/11273205.html

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市绳慎,隨后出現的幾起案子纵竖,更是在濱河造成了極大的恐慌,老刑警劉巖杏愤,帶你破解...
    沈念sama閱讀 219,188評論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件靡砌,死亡現場離奇詭異,居然都是意外死亡珊楼,警方通過查閱死者的電腦和手機乏奥,發(fā)現死者居然都...
    沈念sama閱讀 93,464評論 3 395
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來亥曹,“玉大人邓了,你說我怎么就攤上這事∠钡桑” “怎么了骗炉?”我有些...
    開封第一講書人閱讀 165,562評論 0 356
  • 文/不壞的土叔 我叫張陵,是天一觀的道長蛇受。 經常有香客問我句葵,道長,這世上最難降的妖魔是什么兢仰? 我笑而不...
    開封第一講書人閱讀 58,893評論 1 295
  • 正文 為了忘掉前任乍丈,我火速辦了婚禮,結果婚禮上把将,老公的妹妹穿的比我還像新娘轻专。我一直安慰自己,他們只是感情好察蹲,可當我...
    茶點故事閱讀 67,917評論 6 392
  • 文/花漫 我一把揭開白布请垛。 她就那樣靜靜地躺著,像睡著了一般洽议。 火紅的嫁衣襯著肌膚如雪宗收。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,708評論 1 305
  • 那天亚兄,我揣著相機與錄音混稽,去河邊找鬼。 笑死审胚,一個胖子當著我的面吹牛匈勋,可吹牛的內容都是我干的。 我是一名探鬼主播菲盾,決...
    沈念sama閱讀 40,430評論 3 420
  • 文/蒼蘭香墨 我猛地睜開眼颓影,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了懒鉴?” 一聲冷哼從身側響起诡挂,我...
    開封第一講書人閱讀 39,342評論 0 276
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎临谱,沒想到半個月后璃俗,有當地人在樹林里發(fā)現了一具尸體,經...
    沈念sama閱讀 45,801評論 1 317
  • 正文 獨居荒郊野嶺守林人離奇死亡悉默,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 37,976評論 3 337
  • 正文 我和宋清朗相戀三年城豁,在試婚紗的時候發(fā)現自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片抄课。...
    茶點故事閱讀 40,115評論 1 351
  • 序言:一個原本活蹦亂跳的男人離奇死亡唱星,死狀恐怖雳旅,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情间聊,我是刑警寧澤攒盈,帶...
    沈念sama閱讀 35,804評論 5 346
  • 正文 年R本政府宣布,位于F島的核電站哎榴,受9級特大地震影響型豁,放射性物質發(fā)生泄漏。R本人自食惡果不足惜尚蝌,卻給世界環(huán)境...
    茶點故事閱讀 41,458評論 3 331
  • 文/蒙蒙 一迎变、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧飘言,春花似錦衣形、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,008評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至般妙,卻和暖如春纪铺,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背碟渺。 一陣腳步聲響...
    開封第一講書人閱讀 33,135評論 1 272
  • 我被黑心中介騙來泰國打工鲜锚, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人苫拍。 一個月前我還...
    沈念sama閱讀 48,365評論 3 373
  • 正文 我出身青樓芜繁,卻偏偏與公主長得像,于是被迫代替她去往敵國和親绒极。 傳聞我的和親對象是個殘疾皇子骏令,可洞房花燭夜當晚...
    茶點故事閱讀 45,055評論 2 355

推薦閱讀更多精彩內容