关于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是一个电商系统,目前是一台机器部署,系统中有一个用户下订单的接口,但是用户下订单之前一定要去检查一下库存,确保库存足够了才会 ...
随机推荐
- 利用数据库拿shell的一些姿势
0x01.利用MySQL命令导出getshell 利用条件: 1.拥有网站的写入权限 2.Secure_file_priv参数为空或者为指定路径 3.知道网站的绝对路径 方法: 通过into outf ...
- SpringBoot连接多数据源(HBASE,KUDU,ORACLE集成和开发库)
前提:1.连接hadoop需要本地安装 winutils.exe 并在程序指定,不然程序会报错 IOException: HADOOP_HOME or hadoop.home.dir are not ...
- POI Excel 导入导出重点
HSSF是指2007年以前的,XSSF是指2007年版本以上的 这个还是比较好用的,这些总结来自Apache的官方向导的点点滴滴 详细的请参考http://poi.apache.org/spreads ...
- symfony3.4 配置高效的邮箱swiftmailer插件
网站经常需要邮件发送,symfony里面swiftmailerBundle是一个非常并且稳定的发送邮件的第三方插件. 我们来看一下symfony里面的邮件怎么配置. 第一步我们先下载bundle: c ...
- Redis发布订阅使用方法
Redis发布订阅 发布订阅模式中发布消息的为publisher即发布者,接收消息的为subscriber即订阅者.在Redis中,所有的消息通过channel即频道进行发布,一个发布者可以向多个ch ...
- TCP连接性能指标之TCP关闭过程(四次挥手)
TCP关闭过程(四次挥手): ESTABLISHED: 当前建立连接状态 CLOSE_WAIT:Server端收到来自Client端的FIN包后,发送ACK回Client端,进入CLOSE_WAIT ...
- Simulink中封装子系统
学习目的: 使用simulink封装一个子系统,并将封装子系统放入到自定义的库中,可供建模时重复使用 功能:封装一个能够检测输入信号下降沿跳变的边沿检测模块,该模块可支持双击时修改内部参数.封装完成后 ...
- css 两边宽度固定中间自适应宽度
#content_left{ //左 width:73px; height:100px; vertical-align:middle; margin-right:-100px; float:lef ...
- OJ-2:区间问题【九度1554】
题目描述: 给定一个数组,判断数组内是否存在一个连续区间,使其和恰好等于给定整数k. 输入: 输入包含多组测试用例,每组测试用例由一个整数n(1<=n<=10000)开头,代表数组的大 ...
- Android Google官方文档(cn)解析之——Intents and Intent filter
应用程序核心组件中的三个Activity,service,还有broadcast receiver都是通过一个叫做intent的消息激活的.Intent消息传送是在相同或不同的应用程序中的组件之间后运 ...