首页技术文章正文

+使用Redis单实例实现分布式锁【黑马java培训】

更新时间:2019年07月26日 10时57分13秒 来源:黑马程序员论坛



为什么使用redis分布式锁
在同一个jvm进程中时,可以使用JUC提供的一些锁来解决多个线程竞争同一个共享资源时候的线程安全问题,但是当多个不同机器上的不同jvm进程共同竞争同一个共享资源时候,juc包的锁就无能无力了,这时候就需要分布式锁了。常见的有使用zk的最小版本,redis的set函数,数据库锁来实现。
使用分布式锁
  1. package com.jiaduo.DistributedLock;

  2. import java.util.Collections;

  3. import redis.clients.jedis.Jedis;
  4. import redis.clients.jedis.JedisPool;

  5. public class DistributedLock {

  6.     private static final String LOCK_SUCCESS = "OK";
  7.     private static final String SET_IF_NOT_EXIST = "NX";
  8.     private static final String SET_WITH_EXPIRE_TIME = "PX";
  9.     private static final Long RELEASE_SUCCESS = 1L;

  10.     private static void validParam(JedisPool jedisPool, String lockKey, String requestId, int expireTime) {
  11.         if (null == jedisPool) {
  12.             throw new IllegalArgumentException("jedisPool obj is null");
  13.         }

  14.         if (null == lockKey || "".equals(lockKey)) {
  15.             throw new IllegalArgumentException("lock key  is blank");
  16.         }

  17.         if (null == requestId || "".equals(requestId)) {
  18.             throw new IllegalArgumentException("requestId is blank");
  19.         }

  20.         if (expireTime < 0) {
  21.             throw new IllegalArgumentException("expireTime is not allowed less zero");
  22.         }
  23.     }

  24.     /**
  25.      *
  26.      * @param jedis
  27.      * @param lockKey
  28.      * @param requestId
  29.      * @param expireTime
  30.      * @return
  31.      */
  32.     public static boolean tryLock(JedisPool jedisPool, String lockKey, String requestId, int expireTime) {

  33.         validParam(jedisPool, lockKey, requestId, expireTime);

  34.         Jedis jedis = null;
  35.         try {

  36.             jedis = jedisPool.getResource();
  37.             String result = jedis.set(lockKey, requestId, SET_IF_NOT_EXIST, SET_WITH_EXPIRE_TIME, expireTime);

  38.             if (LOCK_SUCCESS.equals(result)) {
  39.                 return true;
  40.             }
  41.         } catch (Exception e) {
  42.             throw e;
  43.         } finally {
  44.             if (null != jedis) {
  45.                 jedis.close();
  46.             }
  47.         }

  48.         return false;
  49.     }

  50.     /**
  51.      *
  52.      * @param jedis
  53.      * @param lockKey
  54.      * @param requestId
  55.      * @param expireTime
  56.      */
  57.     public static void lock(JedisPool jedisPool, String lockKey, String requestId, int expireTime) {

  58.         validParam(jedisPool, lockKey, requestId, expireTime);

  59.         while (true) {
  60.             if (tryLock(jedisPool, lockKey, requestId, expireTime)) {
  61.                 return;
  62.             }
  63.         }
  64.     }

  65.     /**
  66.      *
  67.      * @param jedis
  68.      * @param lockKey
  69.      * @param requestId
  70.      * @return
  71.      */
  72.     public static boolean unLock(JedisPool jedisPool, String lockKey, String requestId) {

  73.         validParam(jedisPool, lockKey, requestId, 1);

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

  75.         Jedis jedis = null;
  76.         try {

  77.             jedis = jedisPool.getResource();
  78.             Object result = jedis.eval(script, Collections.singletonList(lockKey),
  79.                     Collections.singletonList(requestId));

  80.             if (RELEASE_SUCCESS.equals(result)) {
  81.                 return true;
  82.             }

  83.         } catch (Exception e) {
  84.             throw e;
  85.         } finally {
  86.             if (null != jedis) {
  87.                 jedis.close();
  88.             }
  89.         }

  90.         return false;

  91.     }

  92. }
复制代码
首先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命令的线程都会被阻塞。这里解锁时候使用下面脚本:
  1. 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进行重试操作。这时候其实可以随机生成一个等待时间,等时间到后在进行重试,以减少潜在的同时对一个资源进行竞争的并发量。
转载于http://ifeve.com/redis-distributedlock/

推荐了解热门学科

java培训 Python人工智能 Web前端培训 PHP培训
区块链培训 影视制作培训 C++培训 产品经理培训
UI设计培训 新媒体培训 产品经理培训 Linux运维
大数据培训 智能机器人软件开发




传智播客是一家致力于培养高素质软件开发人才的科技公司“黑马程序员”是传智播客旗下高端IT教育品牌。自“黑马程序员”成立以来,教学研发团队一直致力于打造精品课程资源,不断在产、学、研3个层面创新自己的执教理念与教学方针,并集中“黑马程序员”的优势力量,针对性地出版了计算机系列教材50多册,制作教学视频数+套,发表各类技术文章数百篇。

传智播客从未停止思考

传智播客副总裁毕向东在2019IT培训行业变革大会提到,“传智播客意识到企业的用人需求已经从初级程序员升级到中高级程序员,具备多领域、多行业项目经验的人才成为企业用人的首选。”

中级程序员和初级程序员的差别在哪里?
项目经验。毕向东表示,“中级程序员和初级程序员最大的差别在于中级程序员比初级程序员多了三四年的工作经验,从而多出了更多的项目经验。“为此,传智播客研究院引进曾在知名IT企业如阿里、IBM就职的高级技术专家,集中研发面向中高级程序员的课程,用以满足企业用人需求,尽快补全IT行业所需的人才缺口。

何为中高级程序员课程?

传智播客进行了定义。中高级程序员课程,是在当前主流的初级程序员课程的基础上,增加多领域多行业的含金量项目,从技术的广度和深度上进行拓展“我们希望用5年的时间,打造上百个高含金量的项目,覆盖主流的32个行业。”传智播客课程研发总监于洋表示。




黑马程序员热门视频教程【点击播放】

Python入门教程完整版(懂中文就能学会) 零起点打开Java世界的大门
C++| 匠心之作 从0到1入门学编程 PHP|零基础入门开发者编程核心技术
Web前端入门教程_Web前端html+css+JavaScript 软件测试入门到精通


在线咨询 我要报名
和我们在线交谈!