关于redis在cluster模式化下的 分布式锁的探索
背景
redis作为一个内存数据库,在分布式的服务的大环境下,占的比重越来越大啦,下面我们和大家一起探讨一下如何使用redis实现一个分布式锁
说明
一个分布式锁至少要满足下面几个条件
1:互斥性
多个客户端竞争的时候,只能有一个客户端能获取锁
2:安全性
谁创建,谁销毁,客户端A创建了分布式锁,只能有A来销毁
3:容错性
某个redis节点挂啦,不会影响客户端创建或者销毁分布式锁
4:避免死锁
客户端A创建了分布式锁因程序异常未释放,不会造成其他客户端再也无法申请到锁
下面我们基于上面四个基本准则一起来设计分布式锁,主要有2个方法,①尝试获取锁,②释放锁
尝试获取锁,
这一段代码中有很多容易犯错的地方
public boolean trylock(String lockKey,String lockValue,Long lockWaitTimeout,Long lockExpirseTimeout){
    int timeout = lockWaitTimeout.intValue();
    while (timeout >= 0){
        String expireTimeout  = String.valueOf(lockExpirseTimeout/1000);
        List<String> keys = new ArrayList<String>();
        keys.add(lockKey);
        List<String> args = new ArrayList<String>();
        args.add(lockValue);
        args.add(expireTimeout);
           //①使用lua脚本,setnx创建锁,并设置过期时间,这里网上大多数教程都是直接将value值设置为过期时间,人工判断,我在这里通过lua脚本给加一个过期时间
           /**
//伪代码
// 如果当前锁不存在,返回加锁成功,
if (jedis.setnx(lockKey, expiresStr) == 1) {
// 若在这里程序突然崩溃,则无法设置过期时间,将发生死锁,建议将2个命令通过lua脚本一起执行,保证原则性
jedis.expire(lockKey, expireTime);
return true;
}
*/
// 如果当前锁不存在,返回加锁成功
String lockLuaScript = setNxLuaScript();
Long exeResult = exeLuaScript(lockLuaScript,keys,args,Long.class);
if (exeResult!=null && exeResult.intValue() == 1){
return true;
}
// 如果锁存在,获取锁的过期时间
String lockTimeStr = get(lockKey);
if (lockTimeStr != null && Long.parseLong(lockTimeStr) < System.currentTimeMillis()){
// 锁已过期,获取上一个锁的过期时间,并设置现在锁的过期时间
String oldLockTimeStr = getAndSet(lockKey,lockValue);
// 考虑多线程并发的情况,只有一个线程的设置值和当前值相同,它才有权利加锁
if (oldLockTimeStr != null && oldLockTimeStr.equals(lockTimeStr)){
//大多数网上源代码中是木有这一行代码的,此行是为了解决高并发情况下,getSet虽然只有一个设置成功,但是value值可能会被覆盖,所以重新设置一下
set(lockKey,lockValue,Long.valueOf(expireTimeout),TimeUnit.SECONDS);
return true;
}
}
int sleepTime=new Random().nextInt(10)*100;
timeout -= sleepTime;
try {
log.info("获取redis分布式锁失败,sleep:{}ms后重新获取",sleepTime);
Thread.sleep(sleepTime);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
// 其他情况,一律返回加锁失败
return false;
}
private String setNxLuaScript(){
    StringBuffer luascript = new StringBuffer();
    luascript.append(" if redis.call('setnx',KEYS[1],ARGV[1]) == 1 then ");
    luascript.append(" redis.call('expire',KEYS[1],ARGV[2]) return 1");
    luascript.append(" else return 0 end");
    return luascript.toString();
}
释放锁
 /**
*网上很多代码都木有考虑,谁创建,谁销毁这个准则
* 通过获取lockKey的值和当初设定oldValue是否一致,来决定客户端是否有权利来释放锁,由于这是2个命令,考虑高并发情况,所以通过lua脚本,将2个命令放在一起执行,保证原子性
*/
public void unlock(String lockKey,String oldValue){
    String luascript = delLuaScript();
    List<String> keys = new ArrayList<String>();
    keys.add(lockKey);
    List<String> args = new ArrayList<String>();
    args.add(oldValue);
    exeLuaScript(luascript,keys,args,Long.class);
}
private String delLuaScript(){
    StringBuffer luascript = new StringBuffer();
    luascript.append(" if redis.call('exists',KEYS[1]) == 1 and redis.call('get',KEYS[1]) == ARGV[1] then");
    luascript.append(" redis.call('del',KEYS[1])  return 1");
    luascript.append(" else return 0 end");
    return luascript.toString();
}
 //执行lua脚本命令
public <T> T exeLuaScript(String luaScript, List<String> keys, List<String> args,Class<T> clz){
      T t = (T)redisTemplate.execute(new RedisCallback<T>(){
        @Override
public T doInRedis(RedisConnection redisConnection) throws DataAccessException {
            Object nativeConnection = redisConnection.getNativeConnection();
            if (nativeConnection instanceof JedisCluster) {
                return (T)((JedisCluster) nativeConnection).eval(luaScript.toString(), keys, args);
            }   // 单机模式
else if (nativeConnection instanceof Jedis) {
                return (T)  ((Jedis) nativeConnection).eval(luaScript.toString(), keys, args);
            }
            return null;
        }
    });
      if(t == null){
          throw new RuntimeException("redis model doesn't support luascript");
      }
      return t;
}
上述代码目前依然存在的问题
①:当业务耗时时间大于分布式锁的过期时间lockExpirseTimeout,会造成同时有2个客户端获取到了分布式锁
②:容错性问题还有待解决
关于redis在cluster模式化下的 分布式锁的探索的更多相关文章
- redis 的惊群处理和分布式锁的应用例子
		在并发量比较高的情况下redis有很多应用场景,提升查询效率,缓解底层DBio ,下面列举两个平时开发中应用过的两个例子,欢迎各位一起讨论改进. 1 . redis 惊群处理 1.1 方案的由来 Re ... 
- 【分布式锁】Redis实现可重入的分布式锁
		一.前言 之前写的一篇文章<细说分布式锁>介绍了分布式锁的三种实现方式,但是Redis实现分布式锁关于Lua脚本实现.自定义分布式锁注解以及需要注意的问题都没描述.本文就是详细说明如何利用 ... 
- redis中使用java脚本实现分布式锁
		转载于:http://www.itxuexiwang.com/a/shujukujishu/redis/2016/0216/115.html?1455860390 edis被大量用在分布式的环境中,自 ... 
- Redis、Zookeeper实现分布式锁——原理与实践
		Redis与分布式锁的问题已经是老生常谈了,本文尝试总结一些Redis.Zookeeper实现分布式锁的常用方案,并提供一些比较好的实践思路(基于Java).不足之处,欢迎探讨. Redis分布式锁 ... 
- redis集群+JedisCluster+lua脚本实现分布式锁(转)
		https://blog.csdn.net/qq_20597727/article/details/85235602 在这片文章中,使用Jedis clien进行lua脚本的相关操作,同时也使用一部分 ... 
- 分布式锁用Redis还是ZooKeeper?(转载)
		文章系网络转载,侵删. 来源:https://zhuanlan.zhihu.com/p/73807097 为什么用分布式锁?在讨论这个问题之前,我们先来看一个业务场景. 图片来自 Pexels 为什么 ... 
- Redis分布式锁—SETNX+Lua脚本实现篇
		前言 平时的工作中,由于生产环境中的项目是需要部署在多台服务器中的,所以经常会面临解决分布式场景下数据一致性的问题,那么就需要引入分布式锁来解决这一问题. 针对分布式锁的实现,目前比较常用的就如下几种 ... 
- Redis分布式锁 (图解-秒懂-史上最全)
		文章很长,而且持续更新,建议收藏起来,慢慢读! 高并发 发烧友社群:疯狂创客圈(总入口) 奉上以下珍贵的学习资源: 疯狂创客圈 经典图书 : 极致经典 + 社群大片好评 < Java 高并发 三 ... 
- 分布式锁用Redis与Zookeeper的使用
		为什么用分布式锁? 在讨论这个问题之前,我们先来看一个业务场景: 系统A是一个电商系统,目前是一台机器部署,系统中有一个用户下订单的接口,但是用户下订单之前一定要去检查一下库存,确保库存足够了才会 ... 
随机推荐
- 1.1:JAVA基础
			JAVA基础面试部分(多线程.算法.网络编程提出去了,详细分类见<面经>) 一.Java底层基础题 JDK和JRE区别? 1.JDK是整个JAVA的核心,包括了Java运行环境JRE,一堆 ... 
- ES & Filebeat 使用 Pipeline 处理日志中的 @timestamp
			使用 Pipeline 处理日志中的 @timestamp Filebeat 收集的日志发送到 ElasticSearch 后,会默认添加一个 @timestamp 字段作为时间戳用于检索,而日志中的 ... 
- 基于ArcGIS ModelBuilder的GDB批量分区裁剪——可保留原始GDB要素集要素类结构
			文章版权由作者pxtgis和博客园共有,若转载请于明显处标明出处:http://www.cnblogs.com/pxtgis/. 一.概述 在数据处理工作中经常遇到批量裁剪ArcGIS文件地理数据库( ... 
- 【转载】HPL与HPCG测试(一)
			来源:HPL与HPCG测试 (一) 一.HPL与HPCG 简介 1.HPL HPL 即 High Performance Linpack,它是针对现代并行计算集群的测试工具.用户不修改测试程序,通过调 ... 
- 阿里P6晋升到P7是一个坎吗?  P7 晋升总结
			作者:程序之心丁仪 来源:https://chengxuzhixin.com/blog/post/P6_jin_sheng_dao_P7_zong_jie.html 公众号停更了挺长一段时间,首先说声 ... 
- Windows 端口被占用,但进程号对应的进程不存在,使用Get-Process来查找进程挺方便的
			Windows上很少安装数据库,这次遇到一个小问题:数据库启动之后提示: 警告: 无法为 "*" 创建监听套接字 致命错误: 无法创建TCP/IP套接字 日志: 数据库系统已关闭 ... 
- git/SQL/正则表达式的在线练习网站
			虽说我没事就喜欢喷应试教育,但我也从应试教育中发现了一个窍门:如果能够以刷题的形式学习某项技能,效率和效果是最佳的.对于技术的学习,我经常面临的困境是,理论知识知道的不少,但是有的场景实在无法模拟,缺 ... 
- SpringBoot第四集:整合JdbcTemplate和JPA(2020最新最易懂)
			SpringBoot第四集:整合JdbcTemplate和JPA(2020最新最易懂) 当前环境说明: Windows10_64 Maven3.x JDK1.8 MySQL5.6 SpringTool ... 
- Scrapy分布式爬虫,分布式队列和布隆过滤器,一分钟搞定?
			使用Scrapy开发一个分布式爬虫?你知道最快的方法是什么吗?一分钟真的能 开发好或者修改出 一个分布式爬虫吗? 话不多说,先让我们看看怎么实践,再详细聊聊细节~ 快速上手 Step 0: 首先安装 ... 
- 利用MultipartFile来进行文件上传
			这个例子实在SpringMVC的基础上完成的,因此在web.xml中需要配置 web.xml <!-- 配置Spring MVC的入口 DispatcherServlet,把所有的请求都提交到该 ... 
