背景

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个客户端获取到了分布式锁
②:容错性问题还有待解决
 
  1.  

关于redis在cluster模式化下的 分布式锁的探索的更多相关文章

  1. redis 的惊群处理和分布式锁的应用例子

    在并发量比较高的情况下redis有很多应用场景,提升查询效率,缓解底层DBio ,下面列举两个平时开发中应用过的两个例子,欢迎各位一起讨论改进. 1 . redis 惊群处理 1.1 方案的由来 Re ...

  2. 【分布式锁】Redis实现可重入的分布式锁

    一.前言 之前写的一篇文章<细说分布式锁>介绍了分布式锁的三种实现方式,但是Redis实现分布式锁关于Lua脚本实现.自定义分布式锁注解以及需要注意的问题都没描述.本文就是详细说明如何利用 ...

  3. redis中使用java脚本实现分布式锁

    转载于:http://www.itxuexiwang.com/a/shujukujishu/redis/2016/0216/115.html?1455860390 edis被大量用在分布式的环境中,自 ...

  4. Redis、Zookeeper实现分布式锁——原理与实践

    Redis与分布式锁的问题已经是老生常谈了,本文尝试总结一些Redis.Zookeeper实现分布式锁的常用方案,并提供一些比较好的实践思路(基于Java).不足之处,欢迎探讨. Redis分布式锁 ...

  5. redis集群+JedisCluster+lua脚本实现分布式锁(转)

    https://blog.csdn.net/qq_20597727/article/details/85235602 在这片文章中,使用Jedis clien进行lua脚本的相关操作,同时也使用一部分 ...

  6. 分布式锁用Redis还是ZooKeeper?(转载)

    文章系网络转载,侵删. 来源:https://zhuanlan.zhihu.com/p/73807097 为什么用分布式锁?在讨论这个问题之前,我们先来看一个业务场景. 图片来自 Pexels 为什么 ...

  7. Redis分布式锁—SETNX+Lua脚本实现篇

    前言 平时的工作中,由于生产环境中的项目是需要部署在多台服务器中的,所以经常会面临解决分布式场景下数据一致性的问题,那么就需要引入分布式锁来解决这一问题. 针对分布式锁的实现,目前比较常用的就如下几种 ...

  8. Redis分布式锁 (图解-秒懂-史上最全)

    文章很长,而且持续更新,建议收藏起来,慢慢读! 高并发 发烧友社群:疯狂创客圈(总入口) 奉上以下珍贵的学习资源: 疯狂创客圈 经典图书 : 极致经典 + 社群大片好评 < Java 高并发 三 ...

  9. 分布式锁用Redis与Zookeeper的使用

    为什么用分布式锁?   在讨论这个问题之前,我们先来看一个业务场景: 系统A是一个电商系统,目前是一台机器部署,系统中有一个用户下订单的接口,但是用户下订单之前一定要去检查一下库存,确保库存足够了才会 ...

随机推荐

  1. 【转】Optimized Surface Loading and Soft Stretching

    FROM:http://lazyfoo.net/tutorials/SDL/05_optimized_surface_loading_and_soft_stretching/index.php Opt ...

  2. win10 hyper-v的开启和关闭

    一.开启: 1. 控制面板->程序->启用或关闭Windows功能,Windows功能中勾选hyper-v功能 2. powershell中使用管理员权限运行下面的命令 bcdedit / ...

  3. 『JVM』我不想知道我是怎么来滴,我就想知道我是怎么没滴

    我是风筝,公众号「古时的风筝」,一个兼具深度与广度的程序员鼓励师,一个本打算写诗却写起了代码的田园码农! 文章会收录在 JavaNewBee 中,更有 Java 后端知识图谱,从小白到大牛要走的路都在 ...

  4. linux开机启动设置的几种方法

    Linux开机自启动的几种方式: 1.chkconfig 以supervisord服务脚本为例: 第1步:把上面的脚本放在/etc/init.d/文件 ln -s ./supervisord  /et ...

  5. Exception in MIPS

    介绍 分支.跳转.异常(包括硬件中断)是三种改变控制流的事件. 同步异常是指程序执行到固定位置必定触发且每次现象一致的异常,如算术溢出异常.未定义指令异常.缺页异常等. 异步异常与当前执行程序无关,如 ...

  6. GitHub 上适合新手的开源项目(Python 篇)

    作者:HelloGitHub-卤蛋 随着 Python 语言的流行,越来越多的人加入到了 Python 的大家庭中.为什么这么多人学 Python ?我要喊出那句话了:"人生苦短,我用 Py ...

  7. 2020 校招,我是如何拿到小米、京东、字节大厂前端offer

    前言 Hi~,我是 2020 届物联网专业毕业生,现就读于杭州.谨以此文来记录我的秋招以及入门前端以来的学习历程,如有错误,希望大家能及时提出! 面试情况 从19年8月初到11月底,前前后后一共面试了 ...

  8. 21 Ajax

    21 Ajax AJAX,Asynchronous JavaScript and XML(异步的 JavaScript 和 XML), 是与在不重新加载整个页面的情况下,与服务器交换数据并更新部分网页 ...

  9. PS中抠图的四种方法介绍

    工具/原料 photoshop 软件(我用的是photoshop cc) 需要抠图的图片 开始的步骤 打开ps 打开图片,ctrl+O 魔棒抠图法 对于前景和后景有明显差别的图片用魔棒抠图法抠图比较容 ...

  10. 看得见的成本!1款工具实现K8S资源成本监控可视化

    本文来自Rancher Labs 关注我们,第一时间获取技术干货 计算Kubernetes成本的复杂性 采用Kubernetes和基于服务的架构可以为企业带来诸多好处,如团队可以更快地迁移以及应用程序 ...