写BUG的派大星

Patrick Star

  • 首页
  • 归档

  • 搜索
设计模式 Gis Kafka Druid 微信小程序 Java 开源项目源码 物体识别 机器学习 Mybatis 微服务 Feign OpenVPN CSS Streamsets CDH SpringCloud SpringBoot maven 分布式 Shell Tree Linux js WebSocket 多线程 集群 Hadoop 大数据 JDK ElasticSearch MySQL 数据库 Redis Http Nginx

SpringBoot中基于Redis的分布式锁实现(通过Redisson)

发表于 2020-04-28 | 分类于 Java | 0 | 阅读次数 2406

在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 报的是空指针,具体异常如下

image.png

Redis的连接问题

在安装Redis后(通过yum/dnf安装),需要修改配置文件vi /etc/redis.conf,

  1. 将bind 127.0.0.1 修改为 bind 0.0.0.0
  2. 将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

  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.

  1. 上面的新问题主要是密码的配置问题,出现这个问题是因为redis服务器中没有设置密码。所以在代码中也不需要调用setPassword()方法,需先判断password是否为空。
  • 本文作者: Patrick
  • 本文链接: https://www.write1bug.cn/archives/springboot中基于redis的分布式锁实现通过redisson
  • 版权声明: 本博客所有文章除特别声明外,均采用CC BY-NC-SA 3.0 许可协议。转载请注明出处!
# 设计模式 # Gis # Kafka # Druid # 微信小程序 # Java # 开源项目源码 # 物体识别 # 机器学习 # Mybatis # 微服务 # Feign # OpenVPN # CSS # Streamsets # CDH # SpringCloud # SpringBoot # maven # 分布式 # Shell # Tree # Linux # js # WebSocket # 多线程 # 集群 # Hadoop # 大数据 # JDK # ElasticSearch # MySQL # 数据库 # Redis # Http # Nginx
CentOS8搭建分布式Hadoop3.2.1环境(hdfs+yarn)
Cloudera Manager安装与搭建(CentOS7,CDH6.1.0)
  • 文章目录
  • 站点概览
Patrick

Patrick

不是在改BUG,就是在写BUG。

52 日志
9 分类
36 标签
RSS
E-mail
Creative Commons
© 2018 — 2023 Patrick
人生如逆旅|我亦是行人
鲁ICP备18043140号-1