java redis 分布式锁_使用Redis单实例实现分布式锁

一、前言

在同一个jvm进程中时,可以使用JUC提供的一些锁来解决多个线程竞争同一个共享资源时候的线程安全问题,但是当多个不同机器上的不同jvm进程共同竞争同一个共享资源时候,juc包的锁就无能无力了,这时候就需要分布式锁了。常见的有使用zk的最小版本,redis的set函数,数据库锁来实现,本节我们谈谈Redis单实例情况下使用set函数来实现分布式锁。

二、使用Redis单实例实现分布式锁

首先我们来具体看代码:

package com.jiaduo.DistributedLock;

import java.util.Collections;

import redis.clients.jedis.Jedis;

import redis.clients.jedis.JedisPool;

public class DistributedLock{

private static final String LOCK_SUCCESS = "OK";

private static final String SET_IF_NOT_EXIST = "NX";

private static final String SET_WITH_EXPIRE_TIME = "PX";

private static final Long RELEASE_SUCCESS = 1L;

private static void validParam(JedisPool jedisPool, String lockKey, String requestId, int expireTime){

if (null == jedisPool) {

throw new IllegalArgumentException("jedisPool obj is null");

}

if (null == lockKey || "".equals(lockKey)) {

throw new IllegalArgumentException("lock key is blank");

}

if (null == requestId || "".equals(requestId)) {

throw new IllegalArgumentException("requestId is blank");

}

if (expireTime < 0) {

throw new IllegalArgumentException("expireTime is not allowed less zero");

}

}

/**

*

* @param jedis

* @param lockKey

* @param requestId

* @param expireTime

* @return

*/

public static boolean tryLock(JedisPool jedisPool, String lockKey, String requestId, int expireTime){

validParam(jedisPool, lockKey, requestId, expireTime);

Jedis jedis = null;

try {

jedis = jedisPool.getResource();

String result = jedis.set(lockKey, requestId, SET_IF_NOT_EXIST, SET_WITH_EXPIRE_TIME, expireTime);

if (LOCK_SUCCESS.equals(result)) {

return true;

}

} catch (Exception e) {

throw e;

} finally {

if (null != jedis) {

jedis.close();

}

}

return false;

}

/**

*

* @param jedis

* @param lockKey

* @param requestId

* @param expireTime

*/

public static void lock(JedisPool jedisPool, String lockKey, String requestId, int expireTime){

validParam(jedisPool, lockKey, requestId, expireTime);

while (true) {

if (tryLock(jedisPool, lockKey, requestId, expireTime)) {

return;

}

}

}

/**

*

* @param jedis

* @param lockKey

* @param requestId

* @return

*/

public static boolean unLock(JedisPool jedisPool, String lockKey, String requestId){

validParam(jedisPool, lockKey, requestId, 1);

String script = "if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1]) else return 0 end";

Jedis jedis = null;

try {

jedis = jedisPool.getResource();

Object result = jedis.eval(script, Collections.singletonList(lockKey),

Collections.singletonList(requestId));

if (RELEASE_SUCCESS.equals(result)) {

return true;

}

} catch (Exception e) {

throw e;

} finally {

if (null != jedis) {

jedis.close();

}

}

return false;

}

}

首先Redis的 public String set(final String key, final String value, final String nxxx, final String expx,

final int time)方法参数说明:

其中前面两个是key,value值;

nxxx为模式,这里我们设置为NX,意思是说如果key不存在则插入该key对应的value并返回OK,否者什么都不做返回null;

参数expx这里我们设置为PX,意思是设置key的过期时间为time 毫秒

通过tryLock方法尝试获取锁,内部是具体调用Redis的set方法,多个线程同时调用tryLock时候会同时调用set方法,但是set方法本身是保证原子性的,对应同一个key来说,多个线程调用set方法时候只有一个线程返回OK,其它线程因为key已经存在会返回null,所以返回OK的线程就相当与获取到了锁,其它返回null的线程则相当于获取锁失败。

另外这里我们要保证value(requestId)值唯一是为了保证只有获取到锁的线程才能释放锁,这个下面释放锁时候会讲解。

通过lock 方法让使用tryLock获取锁失败的线程本地自旋转重试获取锁,这类似JUC里面的CAS。

Redis有一个叫做eval的函数,支持Lua脚本执行,并且能够保证脚本执行的原子性,也就是在执行脚本期间,其它执行redis命令的线程都会被阻塞。这里解锁时候使用下面脚本:

if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1]) else return 0 end

其中keys[1]为unLock方法传递的key,argv[1]为unLock方法传递的requestId;脚本redis.call(‘get’, KEYS[1])的作用是获取key对应的value值,这里会返回通过Lock方法传递的requetId, 然后看当前传递的RequestId是否等于key对应的值,等于则说明当前要释放锁的线程就是获取锁的线程,则继续执行redis.call(‘del’, KEYS[1])脚本,删除key对应的值。

三、总结

本文使用redis单实例结合redis的set方法和eval函数实现了一个简单的分布式锁,但是这个实现还是明显有问题的。虽然使用set方法设置了超时时间,以避免线程获取到锁后redis挂了后锁没有被释放的情况,但是超时时间设置为多少合适那?如果设置太小,可能会存在线程获取锁后执行业务逻辑时间大于锁超时时间,那么就会存在逻辑还没执行完,锁已经因为超时自动释放了,而其他线程可能获取到锁,那么之前获取锁的线程的业务逻辑的执行就没有保证原子性。

另外还有一个问题是Lock方法里面是自旋调用tryLock进行重试,这就会导致像JUC中的AtomicLong一样,在高并发下多个线程竞争同一个资源时候造成大量线程占用cpu进行重试操作。这时候其实可以随机生成一个等待时间,等时间到后在进行重试,以减少潜在的同时对一个资源进行竞争的并发量。

最后

想了解JDK NIO和更多Netty基础的可以单击我

想了解更多关于粘包半包问题单击我

更多关于分布式系统中服务降级策略的知识可以单击 单击我

想系统学dubbo的单击我

想学并发的童鞋可以 单击我

d0c1501a6d8bb921cf36400dc89de69f.png

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/409631.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

设计一个按优先数调度算法实现处理器调度的程序_计算机中的程序都是怎么运行的,来深入了解一下吧...

在现代计算机操作系统中&#xff0c;总是会保持多道程序环境。一个作业被提交后&#xff0c;通常经过作业调度和进程调度后&#xff0c;才能获得处理机。有时为提高内存利用率&#xff0c;还会设置中程调度。那我们先来了解一下处理机调度的层次吧。高级调度&#xff0c;又称作…

mysql 查看锁_SQL-mysql锁等待与死锁

一 前言本篇是MYSQL高级进阶篇内容第二篇&#xff0c;学习本篇的基础是知识追寻者之前发布过的文章&#xff0c;尤其是《MYSQL架构入门篇》重中之重&#xff1b;《SQL-你真的了解什么SQL么&#xff1f;》《SQL-小白最佳入门sql查询一》《SQL-小白最佳入门sql查询二》《SQL- 多年…

(转)HBase二级索引与Join

二级索引与索引Join是Online业务系统要求存储引擎提供的基本特性。RDBMS支持得比较好&#xff0c;NOSQL阵营也在摸索着符合自身特点的最佳解决方案。 这篇文章会以HBase做为对象来探讨如何基于Hbase构建二级索引与实现索引join。文末同时会列出目前已知的包括0.19.3版secondary…

mysql主要的收获_MySQL性能测试大总结

以下的文章主要是介绍MySQL性能测试的结论&#xff0c;我们大家都知道MySQL数据库在实际实用度主要是取决于MySQL数据库的性能&#xff0c;以下的文章主要就是对MySQL性能测试的一个总结&#xff0c;望你看完之后会有所收获。好像是zdnet的实验室做得一个权威测试吧sqlserver在…

RDD模型

Spark是Berkeley大学AMP&#xff08;stands for Algorithms, Machines, and People&#xff09;实验室开发的一个项目。它是一个基于RDD&#xff08;Resilient Distributed Datasets&#xff09;模型&#xff0c;能够支持计算MapReduce模式的作业&#xff0c;主要用于迭代计算和…

ConcurrentHashMap,一个更快的HashMap

ConcurrentHashMap 是 Doug Lea 的 util.concurrent 包的一部分&#xff0c;它提供比 Hashtable 或者 synchronizedMap 更高程度的并发性。而且&#xff0c;对于大多数成功的 get() 操作它会设法避免完全锁定&#xff0c;其结果就是使得并发应用程序有着非常好的吞吐量。这个月…

python处理图片隐写分析_Python3简单实现隐写术

下载W3Cschool手机App&#xff0c;0基础随时随地学编程>>戳此了解导语利用Python简单实现图片隐写术。。。或者说是水印技术。。。说实话&#xff0c;真的只是简单实现。。。没什么技术含量。。。可以说是入门级的隐写术/水印技术。。。Lets Go?相关文件密码: 9ffy开发工…

流程图中的虚线含义_还在为画流程图烦恼,焦躁?介绍一款画图神器,让你爱上画图!...

在团队协作过程中最常见的就是开会、开会最常用的就是图&#xff0c;而图中最常见的就是流程图&#xff0c;时序图&#xff0c;类图等下面介绍一款画图神器&#xff1a;PlantUMLPlantUML是一个开源项目&#xff0c;支持快速绘制&#xff1a;时序图用例图类图活动图组件图状态图…

有限状态自动机java实现_用java开发编译器之:Thompson构造,将正则表达式转换为有限状态自动机...

阅读博客的朋友可以到我的网易云课堂中&#xff0c;通过视频的方式查看代码的调试和执行过程&#xff1a;上一节&#xff0c;我们通过代码&#xff0c;实现了一个有限状态自动机&#xff0c;并将其应用于对整形和浮点数的识别。构造有限状态自动机&#xff0c;并驱动它&#xf…