Redis系列之客户端Redisson

这篇具有很好参考价值的文章主要介绍了Redis系列之客户端Redisson。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

概述

官方推荐的客户端,支持Redis单实例、Redis哨兵、Redis Cluster、Redis master-slave等各种部署架构。
GitHub,

功能:

  • 分布式锁

分布式锁

使用Redisson提供的分布式锁的一个最常见场景,应用部署为多个节点,然后使用Spring提供的原生@Scheduled任务调度功能;而没有使用xxl-job等轻量级分布式任务调度系统(底层基于数据库悲观锁)

@Scheduled(cron = "0 0 8 * * ?")
public void execute() {
    RLock lock = redissonClient.getLock("myLock");
    try {
        boolean isLock = lock.tryLock(1, 5, TimeUnit.MINUTES);
        if (!isLock) {
            log.warn("job正在执行!");
            return;
        }
        log.info("任务开始执行!");
    } catch (Exception e) {
        log.error("执行失败:", e);
        lock.unlock();
    }
}

通过lock.tryLock()查看源码,一步步往里看:

<T> RFuture<T> tryLockInnerAsync(long leaseTime, TimeUnit unit, long threadId, RedisStrictCommand<T> command) {
    this.internalLockLeaseTime = unit.toMillis(leaseTime);
    return this.commandExecutor.evalWriteAsync(this.getName(), LongCodec.INSTANCE, command, "if (redis.call('exists', KEYS[1]) == 0) then redis.call('hset', KEYS[1], ARGV[2], 1); redis.call('pexpire', KEYS[1], ARGV[1]); return nil; end; if (redis.call('hexists', KEYS[1], ARGV[2]) == 1) then redis.call('hincrby', KEYS[1], ARGV[2], 1); redis.call('pexpire', KEYS[1], ARGV[1]); return nil; end; return redis.call('pttl', KEYS[1]);", Collections.singletonList(this.getName()), new Object[]{this.internalLockLeaseTime, this.getLockName(threadId)});
}

稍微格式化一下,方便阅读:

if (redis.call('exists', KEYS[1]) == 0) 
	then redis.call('hset', KEYS[1], ARGV[2], 1);
	redis.call('pexpire', KEYS[1], ARGV[1]);
	return nil;
end;
if (redis.call('hexists', KEYS[1], ARGV[2]) == 1)
	then redis.call('hincrby', KEYS[1], ARGV[2], 1);
	redis.call('pexpire', KEYS[1], ARGV[1]);
	return nil;
end;
return redis.call('pttl', KEYS[1]);

也就是说,需要执行一段Lua脚本:

  • KEYS[1]代表加锁的Key,即myLock
  • ARGV[1]代表加锁Key的生存时间,默认30秒
  • ARGV[2]代表加锁客户端ID,格式UUID:n,如:e197fb92-deeb-4f9d-9d34-51b9b09f0bd7:1,其中n表示Redis Cluster集群节点

第一段if判断语句,用exists myLock判断一下,如果要加锁的Key不存在,则通过命令hset myLock e197fb92-deeb-4f9d-9d34-51b9b09f0bd7:1 1加锁,即设置一个Hash数据结构。命令执行后会生成类似如下数据结构:

myLock:
{
"e197fb92-deeb-4f9d-9d34-51b9b09f0bd7:1": 1
}

接着执行pexpiremyLock 30000命令,设置myLock这个锁Key的生存时间是30秒,加锁完成。

watch dog自动延期机制
客户端1加锁的Key默认过期时间30秒,客户端1只要加锁成功,就会启动一个watchdog后台线程,每隔10秒检查一下,如果客户端1还持有锁Key,就会不断的延长锁Key的生存时间。

释放锁
执行lock.unlock(),即释放分布式锁,执行一次lock.unlock(),对myLock数据结构中的加锁次数减1。
加锁次数未0,说明此客户端已经不再持有锁,触发删除所del myLock命令。其他客户端即可尝试加锁。

缺点

上面那种方案最大的问题,就是如果你对某个Redis Master实例,写入myLock这种锁Key的Value,此时会异步复制给对应的Master Slave实例。

但是这个过程中一旦发生Redis Master宕机,主备切换,Redis Slave变为Redis Master。

会导致客户端2尝试加锁时,在新的Redis Master上完成加锁,客户端1也以为自己成功加锁。

此时就会导致多个客户端对一个分布式锁完成加锁。这时系统在业务语义上一定会出现问题,导致各种脏数据的产生。

所以这个就是Redis Cluster,或是redis master-slave架构的主从异步复制导致的Redis分布式锁的最大缺陷:在Redis Master实例宕机的时候,可能导致多个客户端同时完成加锁。

在基于NIO的Netty框架上,充分利用Redis提供的一系列优势,

问题

ClassNotFoundException: org.nustaq.serialization.FSTConfiguration

Caused by: org.springframework.beans.BeanInstantiationException: Failed to instantiate [org.redisson.api.RedissonClient]: Factory method 'redisson' threw exception; nested exception is java.lang.NoClassDefFoundError: Lorg/nustaq/serialization/FSTConfiguration;
Caused by: java.lang.ClassNotFoundException: org.nustaq.serialization.FSTConfiguration

出现上述报错的原因在于,配置文件里有Fst相关引用:

threads: 0
nettyThreads: 0
codec: !<org.redisson.codec.FstCodec> {}
transportMode: "NIO"

解决方案,pom.xml文件里新增:

<dependency>
	<groupId>de.ruedigermoeller</groupId>
	<artifactId>fst</artifactId>
	<version>2.57</version>
</dependency>

attempt to unlock lock, not locked by current thread by node id

java.lang.IllegalMonitorStateException: attempt to unlock lock, not locked by current thread by node id: 633dfc8a-b388-4ba1-ad64-75b491d0c5f2 thread-id: 118
    at org.redisson.misc.RedissonPromise.trySuccess(RedissonPromise.java:88)
    at org.redisson.command.CommandAsyncService.handleReference(CommandAsyncService.java:1067)
    at org.redisson.command.CommandAsyncService.handleSuccess(CommandAsyncService.java:1059)
    at org.redisson.command.CommandAsyncService.checkAttemptFuture(CommandAsyncService.java:1041)
    at org.redisson.command.CommandAsyncService$12.operationComplete(CommandAsyncService.java:805)
    at org.redisson.misc.RedissonPromise.trySuccess(RedissonPromise.java:88)
    at org.redisson.client.handler.CommandDecoder.completeResponse(CommandDecoder.java:448)
    at org.redisson.client.handler.CommandDecoder.handleResult(CommandDecoder.java:443)
    at org.redisson.client.handler.CommandDecoder.decode(CommandDecoder.java:354)
    at org.redisson.client.handler.CommandDecoder.decodeCommand(CommandDecoder.java:128)
    at org.redisson.client.handler.CommandDecoder.decode(CommandDecoder.java:108)

解决方案:

finally {
    if (lock.isLocked() && lock.isHeldByCurrentThread()) {
        lock.unlock();
    }
}

Command (SET), params [] succesfully sent, but channel [] has been closed

详细的报错信息:

org.springframework.data.redis.RedisConnectionFailureException: Command (SET), params [] succesfully sent, but channel [] has been closed!
at org.redisson.spring.data.connection.RedissonExceptionConverter.convert(RedissonExceptionConverter.java:40)
	at org.redisson.spring.data.connection.RedissonExceptionConverter.convert(RedissonExceptionConverter.java:35)
	at org.springframework.data.redis.PassThroughExceptionTranslationStrategy.translate(PassThroughExceptionTranslationStrategy.java:44)
	at org.redisson.spring.data.connection.RedissonConnection.transform(RedissonConnection.java:237)
	at org.redisson.spring.data.connection.RedissonConnection.syncFuture(RedissonConnection.java:232)
	at org.redisson.spring.data.connection.RedissonConnection.sync(RedissonConnection.java:462)
	at org.redisson.spring.data.connection.RedissonConnection.write(RedissonConnection.java:828)
	at org.redisson.spring.data.connection.RedissonConnection.set(RedissonConnection.java:596)
	at org.springframework.data.redis.connection.DefaultStringRedisConnection.set(DefaultStringRedisConnection.java:946)
    at org.springframework.data.redis.core.RedisTemplate.execute(RedisTemplate.java:224)
	at org.springframework.data.redis.core.RedisTemplate.execute(RedisTemplate.java:184)
	at org.springframework.data.redis.core.AbstractOperations.execute(AbstractOperations.java:95)
	at org.springframework.data.redis.core.DefaultValueOperations.set(DefaultValueOperations.java:236)	

参考GitHub-issue:

Redis connection is closed for some reason. Try to set pingConnectionInterval: 60000.

解决方法:在redisson.yml文件里新增配置:pingConnectionInterval: 60000

RedisResponseTimeoutException: Redis server response timeout occured after 3 retry attempts. Command: params:

解决方法同上,新增配置。文章来源地址https://www.toymoban.com/news/detail-709086.html

参考

到了这里,关于Redis系列之客户端Redisson的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • Redis系列学习文章分享---第一篇(Redis快速入门之初始Redis--NoSql+安装redis+客户端+常用命令)

    欢迎小伙伴交流学习,这是第一篇(Redis快速入门之初始Redis–NoSql+安装redis+客户端+常用命令),后续持续更新 Redis是一种键值型的NoSql数据库,这里有两个: 键值型 NoSql 其中 键值型 ,是指Redis中存储的数据都是以key、value对的形式存储,而value的形式多种多样,可以

    2024年02月20日
    浏览(54)
  • [Redis]Redis客户端

    Redis 给我们提供了三种客户端,分别是: 命令行客户端 图形化桌面客户端 编程客户端 在 Redis 安装完成之后,就有了 Redis 命令行客户端。即 Redis 安装完成后就自带了命令行客户端。 Redis 自带的命令行客户端就是 redis-cli。 option:选项 其中常见的options有: -h 127.0.0.1 :指定

    2024年02月06日
    浏览(51)
  • Redis客户端介绍

    redis安装完成后有自带的命令行客户端,即redis-cli,使用方式如下 options ——参数 -h 127.0.0.1 :指定要连接的redis节点的ip地址,默认值127.0.0.1 -p 6379 :指定要连接的redis节点的端口,默认值6379 -a xxxxxx :指定redis的访问密码 … commonds ——redis的操作命令 ping :心跳测试,服务器

    2024年02月06日
    浏览(45)
  • 【Redis入门篇】| Redis的Java客户端

    目录 一: Redis的Java客户端 1. Jedis快速入门 2. Jedis连接池 3. SpringDataRedis快速入门 4. RedisSerializer配置 5. StringRedisTemplate 图书推荐 在Redis官网中提供了各种语言的客户端,地址: https://redis.io/resources/clients/ Jedis: 以 Redis 命令作为方法名称,学习成本低,简单实用。但是 Jedis 实

    2024年02月03日
    浏览(98)
  • redis教程 二 redis客户端Jedis使用

    在Redis官网中提供了各种语言的客户端,地址:https://redis.io/docs/clients/ 其中Java客户端也包含很多但在开发中用的最多的还是Jedis,接下来就让我们以Jedis开始我们的快速实战。 入门案例详细步骤 案例分析: 创建工程: 创建一个maven管理的java项目 引入依赖: 在pom.xml文件下添

    2024年02月05日
    浏览(48)
  • Redis客户端 - RedisSerializer

    原文首更地址,阅读效果更佳! Redis客户端 - RedisSerializer | CoderMast编程桅杆 https://www.codermast.com/database/redis/redistemplate-redis-serializer.html 前景回顾 在上一篇中,我们实现了一个简单的案例,操作一个 String 类型的数据,插入了一个 name = codermast 的数据到Redis。 使用redis-cli客户端

    2024年02月09日
    浏览(54)
  • redis 登录客户端命令

    Redis 命令用于在 redis 服务上执行操作。 要在 redis 服务上执行命令需要一个 redis 客户端。Redis 客户端在我们之前下载的的 redis 的安装包中。 语法 Redis 客户端的基本语法为: $ redis-cli 实例 以下实例讲解了如何启动 redis 客户端: 启动 redis 客户端,打开终端并输入命令 redis

    2023年04月08日
    浏览(51)
  • Redis学习路线(3)——Redis的Java客户端

    一、如何使用Redis的Java客户端 官方文档: https://redis.io/docs/clients/java/ Java-Redis客户端 使用场景 Jeids 以Redis命令作为方法名称,学习成本低,简单实现,但是Jedis实例是线程不安全的,多线程环境下需要基于连接池使用。 lettuce Lettuce 是基于Netty实现的,支持同步、异步和响应式

    2024年02月15日
    浏览(41)
  • Redis的Java客户端

    以下是redis.io官网所推荐使用前五的Java客户端 Java客户端 特点 Jedis 以Redis命令作为方法名称,学习成本低,简单实用,但是Jedis实例是线程不安全的,多线程环境下需要基于连接池来使用 lettuce Lettuce是基于Netty实现的,支持同步、异步和响应式编程方式,并且是线程安全的。

    2024年02月13日
    浏览(42)
  • Redis中的Java客户端

    Jedis是一个Java实现的Redis客户端连接工具。 Jedis使用非常简单,直接引入依赖。基于默认参数的Jedis连接池,初始化连接池类(使用默认连接池参数)JedisPool,获取一个Jedis连接Jedis jedis=jp.getResource()。 Jedis是线程不安全的,多线程使用同一个Jedis实例,会出现并发问题,原因是

    2024年01月17日
    浏览(46)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包