详解RedisTemplate下Redis分布式锁引发的系列问题


Posted in Redis onApril 27, 2021

      自己的项目因为会一直抓取某些信息,但是本地会和线上经常一起跑,造成冲突。这其实就是我们常说的分布式集群的问题了,本地和线上的服务器构成了集群以及QPS为2的小并发(其实也不叫并发,不知道拿什么词形容?)。

     首先,分布式集群的问题大家都知道,会造成数据库的插入重复问题,会造成一系列的并发性问题。

     解决的方式呢也大概如下几点,百度以及谷歌上都能搜到的解决方式:

     1:数据库添加唯一索引

     2:设计接口幂等性

     3:依靠中间件使用分布式锁,而分布式锁又分为Redis和Zookeeper

    由于Zookeeper我没怎么接触过,并且我项目中本来就引用了Redis,所以就想着用Redis来做分布式锁,也高端洋气上档次点。

    首先基于Redis的操作,我们必须要保证其原子性,也就是要么全部成功,要么全部失败,先从Redis的客户端入手。

    就Redis客户端而言,我们通过的操作是先使用setnx指令,如果成功则返回1,失败则返回0

详解RedisTemplate下Redis分布式锁引发的系列问题

   可是就分布锁锁而言,一个常用的问题就是如果一个服务setnx成功了,但是在解锁的时候如果发生了宕机或者一些特殊因素,导致无法解锁,那么其他服务将陷入死锁的状态。所以,我们在用 setnx 的同时想着去用 expire 指令对锁进行一个过期操作

 详解RedisTemplate下Redis分布式锁引发的系列问题

   从指令可以看出 setnx 和 expire 指令是分开的,如果在这中间的空隙过程中如果有特殊因素导致指令无法继续,也会导致死锁的产生。

以下参考自老钱的 Redis 深度历险:核心原理与应用实践

   为了解决这个疑难,Redis 开源社区涌现了一堆分布式锁的 library,专门用来解决这个问题。实现方法极为复杂,小白用户一般要费很大的精力才可以搞懂。如果你需要使用分布式锁,意味着你不能仅仅使用 Jedis 或者 redis-py 就行了,还得引入分布式锁的 library。

  为了治理这个乱象,Redis 2.8 版本中作者加入了 set 指令的扩展参数,使得 setnx 和 expire 指令可以一起执行,彻底解决了分布式锁的乱象。从此以后所有的第三方分布式锁 library 可以休息了。

   详解RedisTemplate下Redis分布式锁引发的系列问题

  以上都是基于Redis的操作,但是我们在JAVA中如何去运用分布式锁呢。

  首先在Redis方面我用的是RedisTemplate对Redis进行操作的 ,而RedisTemplate在目前情况下如果不借助于是无法保证其原子性的,所以我们需要借助于Redis的Lua脚本。

   先上Lua脚本的代码

// 加锁
if 
    redis.call('setNx',KEYS[1],ARGV[1]) 
  then 
    if redis.call('get',KEYS[1])==ARGV[1] 
    return redis.call('expire',KEYS[1],ARGV[2]) 
  else 
    return 0 
  end 
end
 
// 解锁
  redis.call('get', KEYS[1]) == ARGV[1] 
then 
  return redis.call('del', KEYS[1]) 
else 
  return 0

    Java调用脚本有两种方式

   1。新建一个脚本文件,在代码中调用其绝对路径地址

     redisScript.setScriptSource(new ResourceScriptSource(new ClassPathResource(地址)));

   2。在Java代码中以字符串的方式传入

    redisScript.setScriptText(脚本);

  我是用的第二种方式实现的,下面是JAVA代码

/**
   * 获取锁
   * @param lockKey
   * @param value
   * @param expireTime:单位-秒
   * @return
   */
  public boolean getLock(String lockKey, String value, int expireTime){
    boolean ret = false;
    try{
      String script = "if redis.call('setNx',KEYS[1],ARGV[1]) then if redis.call('get',KEYS[1])==ARGV[1] then return redis.call('expire',KEYS[1],ARGV[2]) else return 0 end end";
 
      RedisScript<Long> redisScript = new DefaultRedisScript<>(script, Long.class);
 
      Object result = redisTemplate.execute(redisScript,new StringRedisSerializer(),new StringRedisSerializer(), Collections.singletonList(lockKey),value,expireTime + "");
      System.out.println(result + "-----------");
      //Object result = redisTemplate.execute(redisScript, Collections.singletonList(lockKey),value,expireTime + "");
 
      if(SUCCESS.equals(result)){
        return true;
      }
 
    }catch(Exception e){
      e.printStackTrace();
    }
    return ret;
  }
 
  /**
   * 释放锁
   * @param lockKey
   * @param value
   * @return
   */
  public boolean releaseLock(String lockKey, String value){
 
    String script = "if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1]) else return 0 end";
 
    RedisScript<Long> redisScript = new DefaultRedisScript<>(script, Long.class);
 
    Object result = redisTemplate.execute(redisScript,new StringRedisSerializer(),new StringRedisSerializer(), Collections.singletonList(lockKey),value);
    if(SUCCESS.equals(result)) {
      return true;
    }
 
    return false;
  }

   以上代码已经在我的项目中确切可以使用了。但是在使用的过程中遇到了许多问题。

   1:java.lang.IllegalStateException

   在返回值方面,会经常报IllegalStateException。

RedisScript<String> redisScript = new DefaultRedisScript<>(script, String.class);

   用String类型时候,经常会报类型转换异常。我在代码中使用的Long类型接收该类型,在命令行中我们也看到命令行结果返回的是数字0或者1,保险起见我们也可以用Object对象来接收结果集。

 2:ERR value is not an integer or out of range

  这个问题纠结了我一个下午至少,Redis报的异常都是很深的,从跟踪源码的时候看到,我们在调用redisTemplate.execute的方法时候,如果不传序列化的参数的时候,代码默认调用的是 Jdkserializationredisserializer 来进行序列化和反序列化操作,这是jdk自带的序列化操作,使用该序列化的对象必须要实现Serializable接口。所以该序列化接口是用于对实体类的序列化。

   所以在进行 execute 操作的时候,我们传入 Stringredisserializer,该序列化接口是专用于对字符串类型的序列化操作。具体的区别可以去这两个类的源码中看下他们的加密方式。 

因为时间以及个人能力的问题,对部分源码有点未理解,所以没有做到全方位的解读这些异常的原因,以后有机会会将源码细读并分析其异常原因。

到此这篇关于详解RedisTemplate下Redis分布式锁引发的系列问题的文章就介绍到这了,更多相关RedisTemplate 分布式锁内容请搜索三水点靠木以前的文章或继续浏览下面的相关文章希望大家以后多多支持三水点靠木!

Redis 相关文章推荐
Redis持久化与主从复制的实践
Apr 27 Redis
SpringBoot 集成Redis 过程
Jun 02 Redis
深入理解redis中multi与pipeline
Jun 02 Redis
Redis集群新增、删除节点以及动态增加内存的方法
Sep 04 Redis
Redis RDB技术底层原理详解
Sep 04 Redis
基于Redis zSet实现滑动窗口对短信进行防刷限流的问题
Feb 12 Redis
Redis中有序集合的内部实现方式的详细介绍
Mar 16 Redis
 Redis 串行生成顺序编码的方法实现
Apr 03 Redis
redis复制有可能碰到的问题汇总
Apr 03 Redis
Redis批量生成数据的实现
Jun 05 Redis
Redis实现短信验证码登录的示例代码
Jun 14 Redis
Redis配置外网可访问(redis远程连接不上)的方法
Dec 24 Redis
详解Redis实现限流的三种方式
Apr 27 #Redis
在K8s上部署Redis集群的方法步骤
Redis持久化与主从复制的实践
浅谈Redis在直播场景的实践方案
Apr 27 #Redis
redis限流的实际应用
Apr 24 #Redis
Redis安装启动及常见数据类型
redis配置文件中常用配置详解
Apr 14 #Redis
You might like
html中select语句读取mysql表中内容
2006/10/09 PHP
使用XDebug调试及单元测试覆盖率分析
2011/01/27 PHP
PHP安全配置详细说明
2011/09/26 PHP
ThinkPHP模板Switch标签用法示例
2014/06/30 PHP
ThinkPHP中Common/common.php文件常用函数功能分析
2016/05/20 PHP
php empty 函数判断结果为空但实际值却为非空的原因解析
2018/05/28 PHP
实现web打印的各种方法介绍及实现代码
2013/01/09 Javascript
php中给js数组赋值方法
2014/03/10 Javascript
js数值计算时使用parseInt进行数据类型转换(jquery)
2014/10/07 Javascript
Js中使用hasOwnProperty方法检索ajax响应对象的例子
2014/12/08 Javascript
jQuery插件scroll实现无缝滚动效果
2015/04/27 Javascript
基于jQuery+JSON的省市二三级联动效果
2015/06/05 Javascript
JQuery中ajax方法访问web服务实例
2015/07/18 Javascript
BootStrap响应式导航条实例介绍
2016/05/06 Javascript
使用JQuery 加载页面时调用JS的实现方法
2016/05/30 Javascript
Javascript实现汉字和拼音互转的终极方案
2016/10/19 Javascript
JS定时器用法分析【时钟与菜单中的应用】
2016/12/21 Javascript
js禁止表单重复提交
2017/08/29 Javascript
jquery如何实现点击空白处隐藏元素
2017/12/05 jQuery
微信小程序时间标签和时间范围的联动效果
2019/02/15 Javascript
详解Vue.js3.0 组件是如何渲染为DOM的
2020/11/10 Javascript
Python判断变量是否已经定义的方法
2014/08/18 Python
Python3 伪装浏览器的方法示例
2017/11/23 Python
解决Django中多条件查询的问题
2019/07/18 Python
python同步windows和linux文件
2019/08/29 Python
python3通过subprocess模块调用脚本并和脚本交互的操作
2020/12/05 Python
Python3.9.0 a1安装pygame出错解决全过程(小结)
2021/02/02 Python
一款CSS3实现多功能下拉菜单(带分享按)的教程
2014/11/05 HTML / CSS
中医药大学毕业生自荐信
2013/11/08 职场文书
仓库管理专业个人自我评价范文
2013/11/11 职场文书
2016新党章学习心得体会
2016/01/15 职场文书
大学生社会服务心得体会
2016/01/22 职场文书
干货:如何写好工作计划!
2019/05/17 职场文书
Node实现搜索框进行模糊查询
2021/06/28 Javascript
nginx内存池源码解析
2021/11/20 Servers
「月刊Action」2022年5月号封面公开
2022/03/21 日漫