一、前言

在同一个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的单击我
想学并发的童鞋可以 单击我

原创文章,转载请注明: 转载自并发编程网 – ifeve.com本文链接地址: 使用Redis单实例实现分布式锁

使用Redis单实例实现分布式锁相关推荐

  1. redisson redlock(基于redisson框架和redis集群使用分布式锁)

    一.关于分布式锁的两篇文章 文章1 文章2 二.redis分布式锁存在的问题 redis实现分布式锁有很多种方案,比较完善的方案应该是用setNx + lua进行实现.简单实现如下: java代码-加 ...

  2. redis set 超时_redis分布式锁3种实现方式对比分析总结

    我在这篇文章提到了分布式锁,但没有展开来讲,抛砖引玉,今天就来说说高并发服务编程中的redis分布式锁. 这里罗列出3种redis实现的分布式锁,并分别对比说明各自特点. Redis单实例分布式锁 实 ...

  3. redis和zookeeper实现分布式锁的区别

    Redis实现分布式锁 1.根据lockKey区进行setnx(set not exist,如果key值为空,则正常设置,返回1,否则不会进行设置并返回0)操作,如果设置成功,表示已经获得锁,否则并没 ...

  4. Day137-139.尚品汇:制作SKU、商品详情、项目优化:Redis缓存、redssion分布式锁

    目录 Day5  制作SKU 1. 制作SKU 2. 多表查询如何写? 3. 制作SKU 4. Thymeleaf Day06 商品详情 1. 获取分类信息 2. 获取最新价格信息 3. 获取销售信息 ...

  5. 在 Redis 上实现的分布式锁

    由于近排很忙,忙各种事情,还有工作上的项目,已经超过一个月没写博客了,确实有点惭愧啊,没能每天或者至少每周坚持写一篇博客.这一个月里面接触到很多新知识,同时也遇到很多技术上的难点,在这我将对每一个有用 ...

  6. 挑战Redis单实例内存最大极限,“遭遇”NUMA陷阱!

    我们公司的基础架构部有个云Redis平台,其中Redis实例在申请的时候可以自由选择需要的内存的大小.然后就引发了我的一个思考,Redis单实例内存最大申请到多大比较合适?假设母机是64GB内存的物理 ...

  7. Redis与Zookeeper实现分布式锁的区别

    Redis与Zookeeper实现分布式锁的区别 1.分布式锁解决方案 1.采用数据库 不建议 性能不好 jdbc 2.基于Redis实现分布式锁(setnx)setnx也可以存入key,如果存入ke ...

  8. Redis应用学习——Redis事务与实现分布式锁

    2019独角兽企业重金招聘Python工程师标准>>> 1. Redis事务机制 1. 与MySQL等关系数据库相同,Redis中也有事务机制,Redis的事务实质上是命令的集合,但 ...

  9. Redis使用setnx实现分布式锁及其问题、优化

    最近在工作中用到了分布式锁,然后查了很多分布式锁的实现方式.比较熟悉redis或者说,redis的用法比较简单,所以查了一下redis使用setnx实现分布式锁的方式.其中有一篇文章搜索到的次数最多, ...

  10. Redis 4.0.2分布式锁的Java实现

    简介 Redis分布式锁算法有两种,一种是单个Redis实例下的,一种是多个Redis实例的Redlock算法. 官方推荐Redlock算法,但是这个算法需要比较多的Redis实例而且是完全互相独立, ...

最新文章

  1. android思维导图github,2020年GitHub 上那些优秀Android开源库,这里是Top10!
  2. webuploader 怎么在react中_React 项目性能分析及优化
  3. Rails 4:如何使用带有turbo-links的$(document).ready()
  4. python爬虫今日头条街拍美图开发背景_分析Ajax请求并抓取今日头条街拍美图:爬取详情页的url与实际页面上显示不符...
  5. Oracle中创建、修改、删除序列
  6. MAC usb启动盘制作
  7. SQL Tuning Advisor简单使用
  8. elasticsearch使用Filter过滤查询操作(使用marvel插件)
  9. 手把手打造开源新监控利器check_mk
  10. Unity3D播放背景音乐
  11. keeplive+haproxy+nginx
  12. CFA难度:特许金融分析师CFA难考吗?
  13. 微软应用商店安装包_微软苦恼了!微博UWP客户端停止服务:大家都用浏览器去了?...
  14. 安卓手机重启日志_一加手机CM12安卓5.0版非官方尝鲜版刷机方法介绍【教程】...
  15. 转载:Python 的关键字 yield 有哪些用法和用途?
  16. gcc/g++ 命令的常用选项
  17. TF2.0 API学习(Python)六:函数compute_loss、函数bbox_giou、函数bbox_iou
  18. mysql分组查询 groud by
  19. 房地产开发流程(详细)
  20. 苹果Safari浏览器Safari Technology Preview

热门文章

  1. 【angularjs】pc端使用angular搭建项目,实现导出excel功能
  2. 【BZOJ】3963: [WF2011]MachineWorks
  3. 和最大的连续子数组 Maximum Subarray
  4. 如何远程访问***之easy ***
  5. 大数据与机器学习:实践方法与行业案例.1.4 本章小结
  6. Node.js学习笔记(二)
  7. cocos2d-x 3.1.1 学习笔记[21]cocos2d-x 创建过程
  8. Servlet和JSP学习指南
  9. supesite 更换目录或者域名操作方法
  10. 金鹏GB28181平台对接