Redis 分布式客户端 Redisson 分布式锁快速入门

目录

Redisson 分布式 Redis 客户端

分布式锁需求分析 与 主流实现方式

Redisson  分布式锁快速入门

Redisson 分布式锁常用 API

自定义 Redisson  配置选项

YML 文件方式配置(推荐方式)


Redisson 分布式 Redis 客户端

1、Redisson 是一个在 Redis 的基础上实现的 Java 驻内存数据网格(In-Memory Data Grid),它不仅提供了一系列的分布式的 Java 常用对象,还提供了许多分布式服务,如 (BitSet, Set, Multimap, SortedSet, Map, List, Queue, BlockingQueue, Deque, BlockingDeque, Semaphore, Lock, AtomicLong, CountDownLatch, Publish / Subscribe, Bloom filter, Remote service, Spring cache, Executor service, Live Object service, Scheduler service) 。

2、Redisson 底层采用的是 Netty 框架,支持 Redis 2.8 以上版本,支持 Java1.6+ 以上版本。Redis 命令和 Redisson 对象匹配列表。

3、个人理解:Redisson、Jedis、Lettuce 是三个不同的操作 Redis 的客户端,Jedis、Lettuce 的 API 更侧重对 Reids 数据库的 CRUD(增删改查),而 Redisson API 侧重于分布式开发,比如它的分布式锁。Spring Boot 为 Lettuce 和 Jedis 客户端库提供基本的自动配置,且默认使用 Lettuce 作为客户端,对于现在微服务开发,项目通常都是分布式多实例部署,分布式锁通常都会用到,实现分布式锁的方式有很多,Redisson 解决方案就是其中一种,此时只需要添加 Redisson 依赖即可轻松使用,不用担心与 Jedis 或 Lettuce 冲突。

4、通过 Redisson 官方文档可以发现 Redisson 有很多分布式功能,本文暂时以分布式锁进行练习。

Redisson github 开源地址:https://github.com/redisson/redisson/

Redisson 官方中文文档: 目录 · redisson/redisson Wiki · GitHub

Redisson 官方示例:https://github.com/redisson/redisson-examples

Redis 分布式客户端 Redisson 分布式锁快速入门_第1张图片

分布式锁需求分析 与 主流实现方式

Redis 分布式客户端 Redisson 分布式锁快速入门_第2张图片


1、当两个用户同时请求,落在同一个系统的不同节点上时,如果使用 Java 原生的锁机制(synchronized或ReentrantLock ),则是无效的,因为图中的两个 A 系统节点,运行在两个不同的 JVM 中,加的锁只对属于自己 JVM 里面的线程有效。

2、此时,解决办法是使用分布式锁,分布式锁的思路是:为整个系统提供一个全局、唯一的锁。比较常用的是 Redission,以及 基于 zookeeper 实现分布式锁。本文主要介绍前者。

3、Redisson 分布式锁不仅使用非常简单,而且可靠性高:

redisson 所有指令都通过 lua 脚本执行,redis 支持 lua 脚本原子性执行

redisson 设置 key 的默认过期时间为 30s,当某个客户端持有锁超过了30s时怎么办呢?redisson 的 watchdog (看门狗)会在获取锁之后,每隔 10 秒自动将 key 的超时时间设为 30s,所以一直持有锁也不会出现 key 过期。

redisson 的“看门狗”逻辑保证了没有死锁发生,比如机器宕机了,看门狗也就没了,此时自然也就不会延长 key 的过期时间,到了 30s 之后就会自动过期了,其他线程可以获取到锁。

Redis 分布式客户端 Redisson 分布式锁快速入门_第3张图片

分布式锁主流实现方式
1、基于数据库记录,进入时写数据,退出时删记录
2、数据库行锁,比如分布式 quartz,它是一把排它锁
3、基于 Redis,自己直接使用 redis,或者使用第三方的框架,如 Redisson

4、基于 zookeeper

5、redis 获取锁是轮训机制,客户端每隔一段时间去获取一下,锁释放后会有多个调用者争抢;zk 是监听机制,有变动会接到通知,除了非公平锁,也可以实现公平锁。

Redisson  分布式锁快速入门

1、以如下的示例进行验收分布式锁,当没有锁的情况下,用户重复请求时,后台重复执行业务代码。

2、第一步:项目中导入 Redisson 依赖。Redission 官网分布式锁和同步器



    org.redisson
    redisson
    3.13.4

3、第二步:配置 RedissonClient 实例

无论 Reids 是单机部署、还是主从复制、哨兵模式、云托管、集群部署等等,Redisson 都提供了相应的[配置方法]。本节以 redis 服务器单机部署为例。

import org.redisson.Redisson;
import org.redisson.api.RedissonClient;
import org.redisson.config.Config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
/**
 * Redisson 配置类
 *
 * @author wangMaoXiong
 * @version 1.0
 * @date 2020/9/24 19:28
 */
@Configuration
public class RedissonConfig {
    //redis 服务器单机部署时,创建 RedissonClient 实例,交由 Spring 容器管理
    @Bean
    public RedissonClient redissonClient() {
        /**
         * Config:Redisson 配置基类,SingleServerConfig:单机部署配置类,MasterSlaveServersConfig:主从复制部署配置
         * SentinelServersConfig:哨兵模式配置,ClusterServersConfig:集群部署配置类。
         * useSingleServer():初始化 redis 单服务器配置。即 redis 服务器单机部署
         * setAddress(String address):设置 redis 服务器地址。格式 -- 主机:端口,不写时,默认为 127.0.0.1:6379
         * setDatabase(int database): 设置连接的 redis 数据库,默认为 0
         * setPassword(String password):设置 redis 服务器认证密码,没有时设置为 null,默认为 null
         * RedissonClient create(Config config): 使用提供的配置创建同步/异步 Redisson 实例
         * Redisson 类实现了 RedissonClient 接口,真正需要使用的就是这两个 API
         */
        Config config = new Config();
        config.useSingleServer()
                .setAddress("redis://127.0.0.1:6379")
                .setDatabase(2)
                .setPassword(null);

        RedissonClient redissonClient = Redisson.create(config);
        return redissonClient;
    }
}

Config 以及 XxxServerConfig  配置类中提供了许多的配置项,不过通常大部分默认即可,为了演示方便,才写死在代码中,实际中应该是由配置文件配置。

4、第四步:分布式锁使用

加锁之后,对于同一个支付订单,当用户重复请求时,因为执行业务前已经上锁了,所以后续请求必须等待前面的请求执行完成,释放锁之后,才能继续执行。(只是为了演示方便,才在控制层加锁,实际中应该在业务层操作)

    /**
     * RedissonClient.getLock(String name):可重入锁
     * boolean tryLock(long waitTime, long leaseTime, TimeUnit unit):尝试获取锁
     * 1、waitTime:获取锁时的等待时间,超时自动放弃,线程不再继续阻塞,方法返回 false
     * 2、leaseTime:获取到锁后,指定加锁的时间,超时后自动解锁
     * 3、如果成功获取锁,则返回 true,否则返回 false。
     * 

* http://localhost:8080/redisson/payment3?orderNumber=8856767 * * @param orderNumber * @return */ @GetMapping("redisson/payment3") public String payment3(@RequestParam Integer orderNumber) { String result = "订单【" + orderNumber + "】支付成功."; logger.info("用户请求支付订单【" + orderNumber + "】."); String key = "com.wmx.wmxredis.controller.RedissonController.payment3_" + orderNumber; /** * getLock(String name):按名称返回锁实例,实现了一个非公平的可重入锁,因此不能保证线程获得顺序 * lock():获取锁,如果锁不可用,则当前线程将处于休眠状态,直到获得锁为止 */ RLock lock = redissonClient.getLock(key); boolean tryLock = false; try { tryLock = lock.tryLock(30, 180, TimeUnit.SECONDS); } catch (InterruptedException e) { e.printStackTrace(); } if (!tryLock) { return "订单【" + orderNumber + "】正在支付中,请耐心等待!"; } try { logger.info("查询支付状态"); TimeUnit.SECONDS.sleep(40); logger.info("开始支付订单【" + orderNumber + "】"); TimeUnit.SECONDS.sleep(40); lock.unlock(); } catch (Exception e) { e.printStackTrace(); result = "订单【" + orderNumber + "】支付失败:" + e.getMessage(); } finally { logger.info("结束支付订单【" + orderNumber + "】"); /** * boolean isLocked():检查锁是否被任何线程锁定,被锁定时返回 true,否则返回 false. * unlock():释放锁, Lock 接口的实现类通常会对线程释放锁(通常只有锁的持有者才能释放锁)施加限制, * 如果违反了限制,则可能会抛出(未检查的)异常。如果锁已经被释放,重复释放时,会抛出异常。 */ if (lock.isLocked()) { lock.unlock(); } } return result; }

在线演示源码:src/main/java/com/wmx/wmxredis/redisson/RedissonController.java · 汪少棠/wmx-redis - Gitee.com

Redisson 分布式锁常用 API

更多详细信息参考官网:分布式锁和同步器

Redis 分布式客户端 Redisson 分布式锁快速入门_第4张图片

Redisson 类实现 RedissonClient 接口

RLock lock = redisson.getLock("key");
lock.lock();
//业务代码
lock.lock();
//业务代码
lock.unlock();
lock.unlock();

可重入锁(Reentrant Lock)

按名称返回锁实例,实现了一个非公平的可重入锁,因此不能保证线程获得顺序

可重复锁是指可以多次对同一个 key 的进行 lock,加锁几次,同样就得解锁(unlock)几次。

RLock fairLock = redisson.getFairLock("key");

 可重入公平锁(Fair Lock)

保证了当多个 Redisson 客户端线程同时请求加锁时,优先分配给先发出请求的线程。所有请求线程会在一个队列中排队,当某个线程出现宕机时,Redisson 会等待5秒后继续下一个线程

联锁(MultiLock):将多个 RLock 对象关联为一个联锁,每个 RLock 对象实例可以来自于不同的 Redisson 实例。

RLock lock1 = redissonInstance1.getLock("lock1");
RLock lock2 = redissonInstance2.getLock("lock2");
RLock lock3 = redissonInstance3.getLock("lock3");

RedissonMultiLock lock = new RedissonMultiLock(lock1, lock2, lock3);
// 同时加锁:lock1 lock2 lock3
// 所有的锁都上锁成功才算成功。
lock.lock();
...
lock.unlock();

红锁(RedLock):也可以用来将多个 RLock 对象关联为一个红锁,每个RLock对象实例可以来自于不同的Redisson实例,在大部分节点上加锁成功就算成功。

RedissonRedLock lock = new RedissonRedLock(lock1, lock2, lock3);
// 给lock1,lock2,lock3加锁,只要在大部分节点上加锁成功就算成功。如果没有手动解开的话,10秒钟后将会自动解开
lock.lock(10, TimeUnit.SECONDS);

// 为加锁等待100秒时间,并在加锁成功10秒钟后自动解开
boolean res = lock.tryLock(100, 10, TimeUnit.SECONDS);
...
lock.unlock();

读写锁(ReadWriteLock):分布式可重入读写锁允许同时有多个读锁和一个写锁处于加锁状态。

RReadWriteLock rwlock = redisson.getReadWriteLock("anyRWLock");
// 最常见的使用方法
rwlock.readLock().lock();
// 或
rwlock.writeLock().lock();

RLock 接口常用 API
void lock.lock(); 获取锁,如果锁不可用,则当前线程将处于休眠状态,直到获得锁为止
void lock(long var1, TimeUnit var3)

获取锁,如果锁不可用,则当前线程将处于休眠状态,直到获得锁为止。

加锁以后在指定的时间后自动解锁,可以无需再调用 unlock 方法手动解锁

boolean tryLock(long waitTime, long leaseTime, TimeUnit unit) 尝试获取锁,如果成功获取锁,则返回 true,否则返回 false。
waitTime:获取锁时的等待时间,超时则放弃,线程不再继续阻塞,返回 false
leaseTime:获取到锁后,指定加锁的时间,超时后自动解锁
void unlock(); 释放锁, Lock 接口的实现类通常会对线程释放锁(通常只有锁的持有者才能释放锁)施加限制,如果违反了限制,则可能会抛出(未检查的)异常。
注意:如果锁已经被释放了,重复释放时,会抛出异常.
boolean forceUnlock(); 强制释放锁,即使锁已经被释放时,重复强制释放也不会抛出异常!
boolean isLocked() 检查锁是否被任何线程锁定,被锁定时返回 true,否则返回 false.
String getName() 获取锁的名称,即锁的 key.
boolean isHeldByThread(long threadId) 检查锁是否由指定线程Id的线程持有,是则返回 true,否则返回 false
boolean isHeldByCurrentThread() 检查锁是否由当前线程持有,是则返回 true,否则返回 false
long remainTimeToLive();

获取锁的过期时间(毫秒),如果锁不存在,则返回 -2,如果锁存在但没有指定失效时间,则返回 -1。

自定义 Redisson  配置选项

上面的例子中将参数写死在代码中只是为了演示方便,现在优化一下,采用配置文件进行配置。

一:自定义配置属性 src/main/java/com/wmx/wmxredis/redisson/RedssionProperties.java · 汪少棠/wmx-redis - Gitee.com

1、Config 以及 XxxServerConfig 配置类中提供了许多的配置项,不过通常大部分默认即可,可以抽取其中比较常用的选项自定义一个配置类。这些属性可以参考 {@link SingleServerConfig}、{@link BaseConfig}、{@link Config}。

2、这些属性也可以参考 Redssion 官网配置方法。

import org.redisson.config.BaseConfig;
import org.redisson.config.Config;
import org.redisson.config.SingleServerConfig;
import org.springframework.boot.context.properties.ConfigurationProperties;
/**
 * @author wangMaoXiong
 * 

* 1、自定义 Redssion 配置属性,这些属性可以参考 {@link SingleServerConfig}、{@link BaseConfig}、{@link Config},根据需要添加或者减少 * 2、Redssion 官网配置方法:https://github.com/redisson/redisson/wiki/2.-%E9%85%8D%E7%BD%AE%E6%96%B9%E6%B3%95 */ @ConfigurationProperties(prefix = "redisson") public class RedssionProperties { //Redis 服务器地址 private String address; //用于Redis连接的数据库索引 private int database = 0; //Redis身份验证的密码,如果不需要,则应为null private String password; //Redis最小空闲连接量 private int connectionMinimumIdleSize = 24; //Redis连接最大池大小 private int connectionPoolSize = 64; //Redis 服务器响应超时时间,Redis 命令成功发送后开始倒计时(毫秒) private int timeout = 3000; //连接到 Redis 服务器时超时时间(毫秒) private int connectTimeout = 10000; //省略 getter、setter 方法未粘贴 }

二:自定义配置类 src/main/java/com/wmx/wmxredis/redisson/RedissonConfig.java · 汪少棠/wmx-redis - Gitee.com

@Configuration
@EnableConfigurationProperties(RedssionProperties.class)
public class RedissonConfig {
    private final RedssionProperties redssionProperties;
    /**
     * 通过构造器从 Spring 容器中获取 {@link RedssionProperties}实例
     *
     * @param redssionProperties
     */
    public RedissonConfig(RedssionProperties redssionProperties) {
        this.redssionProperties = redssionProperties;
    }
    /**
     * redis 服务器单机部署时,创建 RedissonClient 实例,交由 Spring 容器管理
     * 只有当配置了 redisson.type=stand-alone 时,才继续生成 RedissonClient 实例并交由 Spring 容器管理
     *
     * @return
     */
    @Bean
    @ConditionalOnProperty(prefix = "redisson", name = "type", havingValue = "stand-alone")
    public RedissonClient redissonClient() {
        /**
         * Config:Redisson 配置基类,SingleServerConfig:单机部署配置类,MasterSlaveServersConfig:主从复制部署配置
         * SentinelServersConfig:哨兵模式配置,ClusterServersConfig:集群部署配置类。
         * useSingleServer():初始化 redis 单服务器配置。即 redis 服务器单机部署
         * setAddress(String address):设置 redis 服务器地址。格式 -- redis://主机:端口,不写时,默认为 redis://127.0.0.1:6379
         * setDatabase(int database): 设置连接的 redis 数据库,默认为 0
         * setPassword(String password):设置 redis 服务器认证密码,没有时设置为 null,默认为 null
         * RedissonClient create(Config config): 使用提供的配置创建同步/异步 Redisson 实例
         * Redisson 类实现了 RedissonClient 接口,真正需要使用的就是这两个 API
         */
        Config config = new Config();
        config.useSingleServer()
                .setAddress(redssionProperties.getAddress())
                .setDatabase(redssionProperties.getDatabase())
                .setPassword(redssionProperties.getPassword())
                .setConnectionPoolSize(redssionProperties.getConnectionPoolSize())
                .setConnectionMinimumIdleSize(redssionProperties.getConnectionMinimumIdleSize())
                .setTimeout(redssionProperties.getTimeout())
                .setConnectTimeout(redssionProperties.getConnectTimeout());
        RedissonClient redissonClient = Redisson.create(config);
        return redissonClient;
    }
}

三:全局 src/main/resources/application.yml · 汪少棠/wmx-redis - Gitee.com


#自定义分布式 Redis 客户端 Redisson 配置
redisson:
  type: stand-alone  #redis服务器部署类型,stand-alone:单机部署、cluster:机器部署.
  address: redis://127.0.0.1:6379 #redis服务器地址,单机时必须是redis://开头.

本文虽然只是演示了 Redis 单机部署模式,但是其他 Redis 模式部署时也是同理,都可以参考 Redisson 配置方法 修改可得。

YML 文件方式配置(推荐方式)

1、将配置信息写死在程序代码里显然是不合适的,上面的自定义配置虽然也是一种很好的解决方式,但是官方提供了更好的办法,可以直接从 yml 配置文件中读取配置(推荐方式)

2、先提供一个 yml 文件,比如 redisson-config.yml,文件名称随意,下面是单机版配置,其它方式配置可以参考官网,比如 集群模式。

# org.redisson.Config类的配置参数,适用于所有Redis组态模式(单机,集群和哨兵)
threads: 0
nettyThreads: 0
codec: ! {}
transportMode: NIO

# Redis 单机部署时 Redisson 文件方式配置
singleServerConfig:
  address: "redis://127.0.0.1:6379" #节点地址
  password: null #密码,默认null
  database: 15 #数据库编号,默认0
  idleConnectionTimeout: 10000 #连接空闲超时,单位毫秒,默认10000
  connectTimeout: 10000 #连接超时,单位毫秒,默认10000
  timeout: 3000 #命令等待超时,单位毫秒,默认3000
  retryAttempts: 3 #命令失败重试次数,默认3
  retryInterval: 1500 #命令重试发送时间间隔,单位毫秒,默认1500
  subscriptionsPerConnection: 5 #单个连接最大订阅数量,默认5
  clientName: null #客户端名称,默认null,
  subscriptionConnectionMinimumIdleSize: 1 #发布和订阅连接的最小空闲连接数,默认1
  subscriptionConnectionPoolSize: 50 #发布和订阅连接池大小,默认50
  connectionMinimumIdleSize: 32 #最小空闲连接数,默认32
  connectionPoolSize: 64 #连接池大小,默认64
  dnsMonitoringInterval: 5000 #DNS监测时间间隔,单位毫秒,默认5000
  sslEnableEndpointIdentification: true #启用SSL终端识别,默认true
  sslProvider: JDK #SSL实现方式,默认JDK
  sslTruststore: null #SSL信任证书库路径,默认null
  sslTruststorePassword: null #SSL信任证书库密码,默认null
  sslKeystore: null #SSL钥匙库路径,默认null
  sslKeystorePassword: null #SSL钥匙库密码,默认null

src/main/resources/redisson-config.yml · 汪少棠/wmx-redis - Gitee.com

3、然后在配置类中直接读取即可,这样无论 redis 是怎样部署,只需要修改配置文件即可,完全不用修改任何代码,推荐方式。

    /**
     * Config fromYAML :从 yaml 文件读取 redisson 配置对象
     * String toYAML() :将当前配置转换为YAML格式
     */
    @Bean
    public RedissonClient redissonClient() throws IOException {
        URL resource = RedissonConfig2.class.getClassLoader().getResource("redisson-config.yml");
        Config config = Config.fromYAML(resource);
        RedissonClient redissonClient = Redisson.create(config);
        log.info("Redisson 配置:{}", config.toYAML());
        return redissonClient;
    }

src/main/java/com/wmx/wmxredis/redisson/RedissonConfig2.java · 汪少棠/wmx-redis - Gitee.com

4、原生 org.redisson.redisson 依赖就已经提供了 fromYAML 的功能。

你可能感兴趣的:(Redis,redis,分布式,数据库)