背景

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. [C#.NET 拾遗补漏]10:理解 volatile 关键字

    要理解 C# 中的 volatile 关键字,就要先知道编译器背后的一个基本优化原理.比如对于下面这段代码: public class Example { public int x; public v ...

  2. LoRa技术的发展应用和LoRa应用设备

    LoRa技术的发展应用 LORA技术大约在十年前由法国和瑞士开发,到现今LORA技术已经是物联网发展应用中不可缺少的一部分,根据中国物联网研究与发展中心的数据,2025年我国物联网产业规模将达到2万亿 ...

  3. Flask常用扩展(Extentions)

    Flask常用扩展(Extentions) 官网;http://flask.pocoo.org/extensions/ 1.Flask-Script ​ 说明: 一个flask终端运行的解析器 安装: ...

  4. 记一次微信公众号(微信H5)网页对接腾讯云慧眼人脸核验开发笔记

    需求是这样的,客户需要一个在观看学习视频前弹出人脸识别核验真人的功能,客户找了个APP作为参考,但是在微信上第三方人脸识别是无法直接调取到前置摄像头的,都是通过用户自己获取用户的身份信息或者照片,然后 ...

  5. 推动中国制造升级,汽车装配车间生产流水线3D可视化

    前言 随着<中国制造2025>的提出,制造业迎来了全新的发展机遇.更多的企业将制造业信息化技术进行广泛的应用,如 MES 系统.数字孪生以及生产管理可视化等技术的研究应用,已经成为社会各界 ...

  6. 【SpringCloud】04.SpringCloud Eureka Server与Client的创建

    Eureka是Netflix开发的服务发现框架,本身是一个基于REST的服务,主要用于定位运行在AWS域中的中间层服务,以达到负载均衡和中间层服务故障转移的目的.SpringCloud将它集成在其子项 ...

  7. 聚类之k-means附代码

    import osimport sys as sys#reload(sys)#sys.setdefaultencoding('utf-8')from sklearn.cluster import KM ...

  8. Python图书管理系统

    图书管理系统 功能简介 添加图书时,图书ID不能重复,图书名可重复 删除,查询,修改功能,输入图书名之后提供所有的同名的图书,用户可以按照图书序号对具体的一本书进行操作 显示书籍,分行显示,每行一本书 ...

  9. 交换机基于接口划分VLAN(汇聚层设备作为网关)

    组网图形 简介 划分VLAN的方式有:基于接口.基于MAC地址.基于IP子网.基于协议.基于策略(MAC地址.IP地址.接口).其中基于接口划分VLAN,是最简单,最常见的划分方式,如接入层设备作为网 ...

  10. Blog.Core 项目已完成升级.NET5.0

    (是时候拿出来这种图了) 本文首发于公众号,但是会有新的内容加进来,所以就在博客园新开了一篇,望见谅.截止发稿,Blog.Core项目Master分支已经迁移到了5.0,新建了3.1的分支. 开心的锣 ...