redis实现分布式锁-redisson

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<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) {

            // 业务逻辑处理

            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、启动服务,请求这个测试接口,测试结果:

从测试结果可以看出,多个线程并发执行,最终数据的结果是正确的,从而实现了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

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 219,589评论 6 508
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 93,615评论 3 396
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 165,933评论 0 356
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,976评论 1 295
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,999评论 6 393
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,775评论 1 307
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,474评论 3 420
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,359评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,854评论 1 317
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 38,007评论 3 338
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 40,146评论 1 351
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,826评论 5 346
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,484评论 3 331
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 32,029评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 33,153评论 1 272
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 48,420评论 3 373
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 45,107评论 2 356

推荐阅读更多精彩内容