文章目录

  • 背景
  • 实现方案
  • 实现要点
  • 核心代码
    • OneByOneTemplate模板实现
      • 模板
        • 定义模板接口
        • 定义模板回调接口
        • 具体模板实现
    • 锁实现
      • 加锁
      • 锁释放
  • 优点

背景

随着互联网项目的访问量增大,对系统的要求越来越高。应运而生出分布式系统,高可用集群等技术。而且已经非常成熟,在公司里面访问量再小的应用标配都是2台集群,单机应用已经一去不复返了。
java的多线程加锁的方式已经没有办法支撑这种分布式应用,因为java的线程锁,只起作用于当前运行的JVM中,多个JVM之间是相互分隔,无法控制的。

实现方案

核心就是将锁置于一个集中式的地方管理,让锁只有一把,所有集群应用争夺的就是这把锁。
因为是集中式访问,所以要求的性能较高-轻快,且要保证高可用-稳定:业内选择有比较多,zookeeper,redis等等,早期还有用DB(淘汰了)。
这边介绍下当前项目中设计的利用redis实现的。

实现要点

  1. 利用redis的setNX命令的原子性操作来进行加锁。如果单纯取锁,判断,再加锁存在不原子的情况,极端情况下会产生并发。
  2. 利用redis的lua脚本来释放锁,同样保证原子性
  3. 保证释放锁的动作,必须是当前锁的拥有者
  4. OneByOne阻塞等待:在现实应用中,尽可能的在防止业务并发的情况,保证业务的成功,所以排他锁(获取不到锁就失败)这种场景反而不是最多的。最多的是OneByOne,将并发转串行,尽量等待前一个业务完成之后,后一个业务能继续执行,而不是直接失败。 那势必需要设计一个阻塞的机制,这个阻塞等待并不像需要流量消峰那样用队列来做,只要释放可以有多个竞争者抢锁,抢到就继续执行。

核心代码

OneByOneTemplate模板实现

加锁,释放锁排队等等都是一个标准的流程,所以可以建立一个模板。调用者只要关注加锁代码块的业务逻辑实现。

模板

提供两个方法,一个是默认超时时间及排队时间。另一个可以指定锁的超时时间以及排队等待锁的时间。

定义模板接口
public interface OneByOneTemplate {<T> T execute(OneByOne oneByOne, CallBack<T> callBack);<T> T execute(OneByOne oneByOne, boolean waitInQueue, int timeoutMsecs, int expireMsecs, CallBack<T> callBack);
}
定义模板回调接口

业务实现逻辑,实现回调接口,并且实现回调方法即可

public interface CallBack<T> {T invoke();
}
具体模板实现
public class OneByOneTemplateImpl implements OneByOneTemplate {private static final int DEFAULT_TIME_OUT_MSECS = 10000;private static final int DEFAULT_EXPIRE_MSECS = 30000;@Overridepublic <T> T execute(OneByOne oneByOne, CallBack<T> callBack) {return execute(oneByOne, Boolean.TRUE, DEFAULT_TIME_OUT_MSECS, DEFAULT_EXPIRE_MSECS, callBack);}@Overridepublic <T> T execute(OneByOne oneByOne, boolean waitInQueue, int timeoutMsecs, int expireMsecs, CallBack<T> callBack) {// 需要排队if (waitInQueue) {// 当参数timeoutMsecs取值小于等于零时,则使用默认的排队10秒if (timeoutMsecs <= 0) {timeoutMsecs = DEFAULT_TIME_OUT_MSECS;} else {timeoutMsecs = 0;}// 不需要排队} else {timeoutMsecs = 0;}// 当参数expireMsecs取值小于等于零时,则使用默认的有效期30秒if (expireMsecs <= 0) {expireMsecs = DEFAULT_EXPIRE_MSECS;} return invoke(oneByOne, timeoutMsecs, expireMsecs, callBack);}private <T> T invoke(OneByOne oneByOne, int timeoutMsecs, int expireMsecs, CallBack<T> callBack) {final String key = RedisCacheKeyConstants.REDIS_ONE_BY_ONE + oneByOne.getBizType() + "_" + oneByOne.getBizId();SedisLock sedisLock = new SedisLock(RedisUtil.redisClient, key, timeoutMsecs, expireMsecs);try {if (sedisLock.acquire()) { // 启用锁return callBack.invoke();} else {throw new AppException("bizType:" + oneByOne.getBizType() + ",bizId:" + oneByOne.getBizId() + ",并发执行!");}} catch (InterruptedException e) {throw new AppException("");} finally {sedisLock.release();}}}

锁实现

声明redis锁的类,这段代码是在上面模板里面。这边列出来主要是为了控制释放锁时的owner

SedisLock sedisLock = new SedisLock(RedisUtil.redisClient, key, timeoutMsecs, expireMsecs);

加锁

加锁过程中会进行锁等待排队,利用的是轮询+wait()
redis中存储的这个锁的key,并且要存放这个对象的随机属性,owner属性可以是一个UUID。

public synchronized boolean acquire() throws InterruptedException {int timeout = timeoutMsecs;while (timeout >= 0) {if ("OK".equals(redisClient.execute(new ShardedJedisAction<String>() {@Overridepublic String doAction(ShardedJedis jedis) {Jedis j = jedis.getShard(lockKey);// redis中不存在就设置lockKey对应的值,同时设置毫秒级过期时间return j.set(lockKey, owner, "NX", "PX", expireMsecs);}}))) {// lock acquiredlocked = true;return true;}int spinWatingTime = random.nextInt(200) + 1;timeout -= spinWatingTime;wait(spinWatingTime);}return false;
}

轮询200内的随机毫秒进行一次尝试获取锁,并且排队时间减去相应的时间,一直等待时间小于0则不再尝试。
这边random.nextInt(200)获取的值是0-199,容易忽略会导致wait(0),造成线程长期占用。所以需要+1

锁释放

需要校验这个锁是否加锁状态,并且判断是否是锁拥有者
利用redis支持的LUA脚本来将get和del两个动作做成原子性

public void release() {if (locked) {// 判断锁拥有者和释放锁final String script = "if redis.call('get',KEYS[1]) == ARGV[1] then return redis.call('del',KEYS[1]) else return 0 end";Object result = redisClient.execute(new ShardedJedisAction<Object>() {//@Overridepublic Object doAction(ShardedJedis jedis) {List<String> keysList = Collections.singletonList(lockKey);List<String> argsList = Collections.singletonList(owner);Jedis jedisKey = jedis.getShard(lockKey);return jedisKey.eval(script, keysList, argsList);}});if (1 == (Long) result) {locked = false;}}
}

这样整体上一个OneByOne的分布式防并发锁就完成了。性能方面基于redis的高性能读写来说还是比较好的。

优点

  • 基于redis的锁机制,读写性能较高,提高系统的性能。
  • redis的原子性的特性的保证,为高并发下的锁处理变的稳定且简洁
  • 锁等待的设计能很好的提升业务的成功率,超时时间和等待时间设置合适的时间,可以达到很好的吞吐量和成功率。

Redis分布式锁实现OneByOne组件相关推荐

  1. redis分布式锁 在集群模式下如何实现_收藏慢慢看系列:简洁实用的Redis分布式锁用法...

    在微服务中很多情况下需要使用到分布式锁功能,而目前比较常见的方案是通过Redis来实现分布式锁,网上关于分布式锁的实现方式有很多,早期主要是基于Redisson等客户端,但在Spring Boot2. ...

  2. 简洁实用的Redis分布式锁用法

    在微服务中很多情况下需要使用到分布式锁功能,而目前比较常见的方案是通过Redis来实现分布式锁,网上关于分布式锁的实现方式有很多,早期主要是基于Redisson等客户端,但在Spring Boot2. ...

  3. **Java有哪些悲观锁的实现_80% 人不知道的 Redis 分布式锁的正确实现方式(Java 版)...

    点击上方"小哈学Java",选择"星标" 回复"资源",领取全网最火的Java核心知识总结 来源:http://sina.lt/gfZU 前 ...

  4. Redis分布式锁 Spring Schedule实现任务调度

    一看到标题就知道,这一篇博客又是总结分布式工作环境中集群产生的问题,个人觉得分布式没有那么难以理解,可能也是自己见识比较浅,对我来说,分布式只是一种后端业务演进时的一种工作方式,而真正实现这种工作方式 ...

  5. Redis分布式锁【正确实现方式】

    前言 分布式锁一般有三种实现方式:1. 数据库乐观锁:2. 基于Redis的分布式锁:3. 基于ZooKeeper的分布式锁.本篇博客将介绍第二种方式,基于Redis实现分布式锁.虽然网上已经有各种介 ...

  6. Redis分布式锁的正确实现方式(Java版)

    转自:https://wudashan.cn/2017/10/23/Redis-Distributed-Lock-Implement/ 前言 分布式锁一般有三种实现方式:1. 数据库乐观锁:2. 基于 ...

  7. 分布式锁原理——redis分布式锁,zookeeper分布式锁

    首先分布式锁和我们平常讲到的锁原理基本一样,目的就是确保,在多个线程并发时,只有一个线程在同一刻操作这个业务或者说方法.变量. 在一个进程中,也就是一个jvm 或者说应用中,我们很容易去处理控制,在j ...

  8. 京东秒杀系统模块的Redis分布式锁深度剖析,没给你讲明白你打我

    1|0背景 目前开发过程中,按照公司规范,需要依赖框架中的缓存组件.不得不说,做组件的大牛对CRUD操作的封装,连接池.缓存路由.缓存安全性的管控都处理的无可挑剔.但是有一个小问题,该组件没有对分布式 ...

  9. 大家所推崇的Redis分布式锁真的就万无一失吗?

    在单实例JVM中,常见的处理并发问题的方法有很多,比如synchronized关键字进行访问控制.volatile关键字.ReentrantLock等常用方法.但是在分布式环境中,上述方法却不能在跨J ...

最新文章

  1. Java基础类库四则运算_00JAVA语法基础_四则运算 01
  2. 【重庆】2021年下半年软考报考时间及通知
  3. 37条常用Linux Shell命令组合
  4. 拥有一台你的轻量应用服务器Lighthouse
  5. 大华供应链管理平台_files_锦江全球采购平台SRM系统2.0版本上线 打造更智能的供应链...
  6. 基于JAVA+SpringMVC+Mybatis+MYSQL的个人相册管理系统
  7. asp.net Framework 与 asp.net core 知识
  8. Python实现Excel与XML之间的转换
  9. PixelShuffler原理
  10. ibm服务器vga没有信号,华硕主板vga和boot灯常亮显示器无信号
  11. 运用 Hightopo 融合基于 HTML5 WebGL 2D / 3D ,搭建的智慧工厂可视化管理系统
  12. fabric 1.3.1 ,全手动部署到5台机器上.支持 kafka 模式的共识机制和 couchdb 存储,以及 fabric ca , fabric explorer的使用
  13. 人工智能在物流行业的发展与应用
  14. UVA1723 Intervals
  15. 一个可供创业公司借鉴的持续集成技术实践
  16. originPro2021(3)添加图例导出图片图例不完全
  17. 现流行的第三方库及名称
  18. nodejs实现公众号服务URL绑定验证,nodejs实现公众号关注并回发送一条消息,以及关注之后的聊天自动回复消息
  19. 已拿 offer!一个非 985/211 的普通二本学生从毕业季到职场的面经分享!
  20. Spring aop 循环依赖 Is there an unresolvable circular reference?

热门文章

  1. MD5散列算法原理及实现
  2. 毕业设计-基于微信小程序的精准健康管理系统
  3. 【JY】 ABAQUS子程序UEL的有限元原理与应用
  4. linux查看文件字节数
  5. 文末送书|那些数学不好的程序员?最后都如何了
  6. 学习笔记 | 树的最近公共祖先
  7. macOS系统安装gnuplot(解决Terminal type set to unknown)
  8. 等保三级安全要求简要攻略-安全计算环境
  9. 动画css ---无限旋转
  10. PV操作——理发师问题(简要模版)