热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

redis分布式锁的实现总结

在Java中,关于锁我想大家都很熟悉。在并发编程中,我们通过锁,来避免由于竞争而造成的数据不一致问题。通常我们以进程锁synchroniz

在Java中,关于锁我想大家都很熟悉。在并发编程中,我们通过锁,来避免由于竞争而造成的数据不一致问题。通常我们以进程锁synchronized 、Lock来实现它,对于分布式程序,就不能用进程锁了,这时候常用的是分布式锁。

什么是分布式锁

分布式锁,是一种思想,它的实现方式有很多。比如,我们将沙滩当做分布式锁的组件,那么它看起来应该是这样的:

加锁

在沙滩上踩一脚,留下自己的脚印,就对应了加锁操作。其他进程或者线程,看到沙滩上已经有脚印,证明锁已被别人持有,则等待。

解锁

把脚印从沙滩上抹去,就是解锁的过程。

锁超时

为了避免死锁,我们可以设置一阵风,在单位时间后刮起,将脚印自动抹去。

分布式锁的实现有很多,比如基于数据库、memcached、Redis、系统文件、zookeeper等。它们的核心的理念跟上面的过程大致相同。基于数据库可以用乐观锁和悲观锁处理分页式锁,乐观锁使用对比记录version号来实现,悲观锁使用类似“select * where * for update”行锁实现。

本文讨论的是基于redis实现分页式锁的问题,别的方面不做详说,有相关需求可以参考和查阅别的资料。

Redis分布式锁原理

加锁

加锁实际上就是在redis中,给Key键设置一个值,为避免死锁,并给定一个过期时间。

SET lock_key random_value NX PX 5000

值得注意的是:
random_value 是客户端生成的唯一的字符串。
NX 代表只在键不存在时,才对键进行设置操作。
PX 5000 设置键的过期时间为5000毫秒。

这样,如果上面的命令执行成功,则证明客户端获取到了锁。

解锁

解锁的过程就是将Key键删除。但也不能乱删,不能说客户端1的请求将客户端2的锁给删除掉。这时候random_value的作用就体现出来。

为了保证解锁操作的原子性,我们用LUA脚本完成这一操作。先判断当前锁的字符串是否与传入的值相等,是的话就删除Key,解锁成功。

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

jedis实现(单节点)


/*** 获取分布式锁:一分命令,保证事务的一致性。* @param lockKey* @param requestId* @param expireTime* @return*/public static boolean getDistributeLock(String lockKey, String requestId, long expireTime) {Jedis jedis = null;try {jedis = getResource();String result = jedis.set(lockKey,requestId,"NX","PX",expireTime);if ("OK".equals(result)) {return true;}} catch (Exception e) {logger.error("getDistributeLock {}", lockKey, e);} finally {returnResource(jedis);}return false;}/*** 释放分布式锁:使用lua脚本,一个命令实现对带有标志的锁的释放* @param lockKey* @param requestId* @return*/public static boolean releaseDistributeLock(String lockKey, String requestId) {Jedis jedis = null;try {jedis = getResource();String script = "if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1]) else return 0 end";Object result = jedis.eval(script, Collections.singletonList(lockKey), Collections.singletonList(requestId));Long RELEASE_SUCCESS = 1L;if (RELEASE_SUCCESS.equals(result)) {return true;}} catch (Exception e) {logger.error("releaseDistributeLock {}", lockKey, e);} finally {returnResource(jedis);}return false;}

注意:这里的requestId,类似客户端口请求id,每次请求都是不同的可以使用uuid,测试和使用可以参考后面的”测试和说明“部分。

缺点:在集群包括主从、哨兵模式、集群模式不可用;锁不具有可重入性。

redisson实现(通用)

Redisson是一个在Redis的基础上实现的Java驻内存数据网格(In-Memory Data Grid)。它不仅提供了一系列的分布式的Java常用对象,还提供了许多分布式服务。其中包括(BitSet, Set, Multimap, SortedSet, Map, List, Queue, BlockingQueue, Deque, BlockingDeque, Semaphore, Lock, AtomicLong, CountDownLatch, Publish / Subscribe, Bloom filter, Remote service, Spring cache, Executor service, Live Object service, Scheduler service) Redisson提供了使用Redis的最简单和最便捷的方法。Redisson的宗旨是促进使用者对Redis的关注分离(Separation of Concern),从而让使用者能够将精力更集中地放在处理业务逻辑上。

Redisson底层采用的是Netty 框架。支持Redis 2.8以上版本,支持Java1.6+以上版本。它里面也实现了分布式锁,而且包含多种类型的锁:可重入锁,公平锁等。

具体实现如下:

JedisUtil提供

//从配置类中获取redisson对象private static Redisson redisson = JedisConfig.getRedisson();//加锁 Redisson:适用单机、主从、哨兵和集群//同步方法,等待锁返回执行 所以涉及锁使用的,可以放在线程池中进行public static boolean acquire(String lockName){//声明key对象String key = lockName;//获取锁对象RLock mylock = redisson.getLock(key);//加锁,并且设置锁过期时间,防止死锁的产生mylock.lock(2, TimeUnit.MINUTES); // 分钟//加锁成功return true;}//锁的释放 Redisson:适用单机、主从、哨兵和集群//同步方法,等待锁返回执行 所以涉及锁使用的,可以放在线程池中进行public static void release(String lockName){//必须是和加锁时的同一个keyString key = lockName;//获取所对象RLock mylock = redisson.getLock(key);//释放锁(解锁)mylock.unlock();}

JedisConfig提供


private static Config config = new Config();
//声明redisso对象
private static Redisson redisson = null;static{//可以用"redis://"来启用SSL连接if (IS_CLUSTER.equals(CLUSTER_USED)) {//集群log.info("Redisson redis lock init cluster config:"+server1+";"+server2+";"+server3+";"+server4+";"+server5+";"+server6);config.useClusterServers().addNodeAddress("redis://".concat(server1),"redis://".concat(server2), "redis://".concat(server3),"redis://".concat(server4),"redis://".concat(server5), "redis://".concat(server6)).setScanInterval(5000);} else {//单机log.info("Redisson redis lock init single node config:"+server1+";"+server2+";"+server3+";"+server4+";"+server5+";"+server6);config.useSingleServer().setAddress("redis://".concat(poolHost).concat(":").concat(poolPort));}//得到redisson对象redisson = (Redisson) Redisson.create(config);}/*** Redisson redis分布式锁处理对象* @return*/public static Redisson getRedisson() {return redisson;}

测试和说明

测试和使用,可以参考下面的junit测试用例。

@Slf4j
public class JedisUtilTest extends SpringTxTestCase {private static Logger logger &#61; LoggerFactory.getLogger(JedisUtils.class);/*** 单机版本&#xff1a;加解锁功能*/&#64;Testpublic void testSingleRedisLockAndUnlock(){JedisUtils.getDistributeLock("lockKey","requestId",JedisConfig.JEDIS_EXPIRE);{for (int i &#61; 0; i <5; i&#43;&#43;) {boolean result &#61; JedisUtils.getDistributeLock("lockKey","requestId",JedisConfig.JEDIS_EXPIRE);System.out.println(Thread.currentThread().getName()&#43;"&#xff1a;lock result:"&#43;result);JedisUtils.releaseDistributeLock("lockKey","requestId");boolean result1 &#61; JedisUtils.getDistributeLock("lockKey","requestId",JedisConfig.JEDIS_EXPIRE);System.out.println(Thread.currentThread().getName()&#43;"&#xff1a;unlock result1:"&#43;result1);}}}/*** 单机版本&#xff1a;锁测试*/&#64;Testpublic void testSingleRedisLock(){{final CyclicBarrier cbRef &#61; new CyclicBarrier(10);final ReentrantLock reentrantLock&#61;new ReentrantLock();for(int i&#61;0;i<10;i&#43;&#43;){Thread t&#61; new Thread(new Runnable() {&#64;Overridepublic void run() {try {System.out.println(Thread.currentThread().getName() &#43; "准备");cbRef.await();//10个线程等待在这里 才开始执行下面的//reentrantLock.lock();//tryGetDistributedLock("hello","hello",10000);boolean result &#61; JedisUtils.getDistributeLock("lockKey","requestId",JedisConfig.JEDIS_EXPIRE);System.out.println(Thread.currentThread().getName()&#43;"&#61;&#61;&#61;lock result:"&#43;result);JedisUtils.releaseDistributeLock("lockKey",UUID.randomUUID().toString());boolean result1 &#61; JedisUtils.getDistributeLock("lockKey","requestId",JedisConfig.JEDIS_EXPIRE);System.out.println(Thread.currentThread().getName()&#43;"&#61;&#61;&#61;lock result1:"&#43;result);} catch (Exception e) {e.printStackTrace();}finally {//reentrantLock.unlock();}}});t.start();}//这一段可以不要try {Thread.sleep(2000);System.out.println(Thread.currentThread().getName() &#43; "起跑");System.out.println( cbRef.getParties()&#43;"--" &#43;cbRef.getNumberWaiting());} catch (Exception e){e.printStackTrace();}}}/*** 单机版本redis&#xff1a;测试分布式锁的使用方法*/&#64;Testpublic void testUseOfSingleRedisLock() throws InterruptedException {final CountDownLatch countDownLatch &#61; new CountDownLatch(10);String data2Deal &#61; "data to deal";final CyclicBarrier cbRef &#61; new CyclicBarrier(10);for(int i&#61;0;i<10;i&#43;&#43;){Thread t&#61; new Thread(new Runnable() {&#64;Overridepublic void run() {System.out.println(Thread.currentThread().getName() &#43; "准备");try {cbRef.await();//10个线程等待在这里 才开始执行下面的&#43;} catch (InterruptedException e) {e.printStackTrace();} catch (BrokenBarrierException e) {e.printStackTrace();}final ReentrantLock reentrantLock&#61;new ReentrantLock();reentrantLock.lock();try {String data2Deal &#61; "data to deal&#xff1a;" &#43; Thread.currentThread().getName();useOfSingleRedisLock(data2Deal);} catch (Exception e){e.printStackTrace();} finally {reentrantLock.unlock();}countDownLatch.countDown();}});t.start();}countDownLatch.await();System.out.println("所有线程都执行完了……");}/*** 分布式锁的使用方法&#xff1a;单机redis cluster包括(集群和哨兵)不适用。* &#64;param data2Deal*/public void useOfSingleRedisLock(String data2Deal){String requestId &#61; UUID.randomUUID().toString();if(JedisPoolUtils.getDistributeLock("lock_key", requestId, 1000*60*5)){try {methonNeedDisLock(data2Deal);} catch (Exception e) {logger.error("分布式锁业务处理失败&#xff01;",e);e.printStackTrace();} finally {JedisPoolUtils.releaseDistributeLock("lock_key",requestId);}} else {try {Thread.sleep(1000);useOfSingleRedisLock(data2Deal);} catch (InterruptedException e) {logger.error(e.getMessage());}}}/** 需要分布式锁的业务代码*/public void methonNeedDisLock(String data2Deal){System.out.println("分布式锁业务处理方法:"&#43;data2Deal);}/*** 测试分布式锁(Redisson)的使用方法&#xff1a;redis单机和哨兵、集群都适用* 测试说明&#xff1a;开启1000个线程&#xff0c;对count进行累加*/int count &#61; 0;&#64;Testpublic void testRedisLock() throws InterruptedException {int clientcount &#61;1000;final CountDownLatch countDownLatch &#61; new CountDownLatch(clientcount);ExecutorService executorService &#61; Executors.newFixedThreadPool(clientcount);long start &#61; System.currentTimeMillis();for (int i &#61; 0;i
参考

https://blog.csdn.net/u014353343/article/details/88921212

https://www.jianshu.com/p/828aa3b44564

https://www.jianshu.com/p/47fd7f86c848

臭味相投的朋友们&#xff0c;我在这里:

猿in小站:http://www.yuanin.net

csdn博客:https://blog.csdn.net/jiabeis

简书:https://www.jianshu.com/u/4cb7d664ec4b

微信免费订阅号“猿in”
猿in


推荐阅读
  • Centos下安装memcached+memcached教程
    本文介绍了在Centos下安装memcached和使用memcached的教程,详细解释了memcached的工作原理,包括缓存数据和对象、减少数据库读取次数、提高网站速度等。同时,还对memcached的快速和高效率进行了解释,与传统的文件型数据库相比,memcached作为一个内存型数据库,具有更高的读取速度。 ... [详细]
  • 一、Hadoop来历Hadoop的思想来源于Google在做搜索引擎的时候出现一个很大的问题就是这么多网页我如何才能以最快的速度来搜索到,由于这个问题Google发明 ... [详细]
  • 本文总结了初学者在使用dubbo设计架构过程中遇到的问题,并提供了相应的解决方法。问题包括传输字节流限制、分布式事务、序列化、多点部署、zk端口冲突、服务失败请求3次机制以及启动时检查。通过解决这些问题,初学者能够更好地理解和应用dubbo设计架构。 ... [详细]
  • 云原生应用最佳开发实践之十二原则(12factor)
    目录简介一、基准代码二、依赖三、配置四、后端配置五、构建、发布、运行六、进程七、端口绑定八、并发九、易处理十、开发与线上环境等价十一、日志十二、进程管理当 ... [详细]
  • ZooKeeper 学习
    前言相信大家对ZooKeeper应该不算陌生。但是你真的了解ZooKeeper是个什么东西吗?如果别人面试官让你给他讲讲ZooKeeper是个什么东西, ... [详细]
  • Hadoop源码解析1Hadoop工程包架构解析
    1 Hadoop中各工程包依赖简述   Google的核心竞争技术是它的计算平台。Google的大牛们用了下面5篇文章,介绍了它们的计算设施。   GoogleCluster:ht ... [详细]
  • 在真实开发中,因为需求是不断变化的,说不定什么时候就需要往模型里添加新的字段,添加新的模型,甚至是大规模的重构; ... [详细]
  • 大厂首发!思源笔记docker
    JVMRedisJVM面试内存模型以及分区,需要详细到每个区放什么?GC的两种判定方法GC的三种收集方法:标记清除、标记整理、复制算法的 ... [详细]
  • 我们在之前的文章中已经初步介绍了Cloudera。hadoop基础----hadoop实战(零)-----hadoop的平台版本选择从版本选择这篇文章中我们了解到除了hadoop官方版本外很多 ... [详细]
  • 电信网为不能访问联通服务器的网站_老板说网站慢,我们总结了三大阶段提升性能...
    作者:李平来源:https:www.cnblogs.comleefreemanp3998757.html前言在前一篇随笔《大型网站系统架构的演化》中&# ... [详细]
  • python zookeeeper 学习和操作
    1.zookeeeper介绍ZooKeeper是一个为分布式应用所设计的分布的、开源的协调服务,它主要是用来解决分布式应用中经常遇到的一些数据管理问题,简化分布式应用协调及其管理的 ... [详细]
  • 本文由编程笔记#小编为大家整理,主要介绍了python面试题——数据库和缓存(46题)相关的知识,希望对你有一定的参考价值。1、列举常见的关系型数据库和非关系型都有那些? ... [详细]
  • Java开发实战讲解!字节跳动三场技术面+HR面
    二、回顾整理阿里面试题基本就这样了,还有一些零星的问题想不起来了,答案也整理出来了。自我介绍JVM如何加载一个类的过程,双亲委派模型中有 ... [详细]
  • Zookeeper 总结与面试题汇总
    Zookeeper总结与面试题汇总,Go语言社区,Golang程序员人脉社 ... [详细]
  • 安装mysqlclient失败解决办法
    本文介绍了在MAC系统中,使用django使用mysql数据库报错的解决办法。通过源码安装mysqlclient或将mysql_config添加到系统环境变量中,可以解决安装mysqlclient失败的问题。同时,还介绍了查看mysql安装路径和使配置文件生效的方法。 ... [详细]
author-avatar
thofarq
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有