为什么80%的码农都做不了架构师?>>>   

前言:

随着笔者的颜值不断提高,用户量的日益增长,传统的单机方案已经不能满足产品的需求。笔者在网上寻遍方案,发现均为人云亦云,一份以毫秒为精度的轮询分布式锁被转发转载上万次。然,该方案没法满足笔者性能要求。故此,笔者研发ELock插件,并发布本文章。

其实集群也好,分布式服务也好。当我们不能保证团队成员的整体素质,那么在某些业务上,分布式锁自然没法避免。

公认开发原则:能不使用分布式锁的,尽可能不使用

举个例子,一个商品交易,需要检查库存、检查余额、扣库存、扣款、变更订单状态。可能很多人觉得,在分布式环境下一定要分布式锁才能安全。

致此,笔者提供一种简单的方案:

订单处理{if(订单状态!=待支付){return 该订单已处理;}if(库存不足){return 库存不足;}if(余额不足){return 余额不足;}事务管理(rollbackFor = Exception.class){//修改订单状态int changeLine = 执行语句( update 订单表 set status=已支付 where status=待支付 and orderId=订单号);if(changeLine < 1){return 该订单已处理;}//扣库存changeLine = 执行语句(update 商品表 set 库存=库存-订单信息.购买数量 where 库存>订单信息.购买数量 and 商品ID = ?);if(changeLine != 1){throw CustomRuntimeException("库存不足");}//扣款changeLine = 执行语句(update 用户余额表 set 余额=余额-订单信息.订单金额 where 余额 > 订单信息.订单金额 and 订单信息.订单金额 > 0 and 用户ID = ?);if(changeLine != 1){throw CustomRuntimeException("余额不足");}}
}

我们仔细来分析一下如上的整个逻辑

1、当一个业务进入逻辑体,先检查订单状态、余额和库存,不满足条件则返回错误(可阻挡非并发情况下的大部分业务流入事物)

2、进入事物后,先变更订单状态,如果变更失败,直接返回错误

3、当订单状态变更成功,则扣取库存,扣取库存失败必须抛出异常,让第二步的订单状态回滚。

4、扣取库存后,则进行扣款,当扣款失败,则抛出异常(由于在业务体走到这里,已经扣取了库存,本处不能return,需抛出异常,让事物回滚)

特别注意:语句中,通过where来进行余额不足和库存不足的条件判断。通过执行语句返回的影响行数,来判断是否扣取成功。 在以上流程中,我们发现,即便不使用分布式锁,也无并发问题。

===========================================================

以上介绍了在常见的业务中如何规避分布式锁,下面介绍一下笔者的高性能分布式锁

友情提示:切勿觉得笔者以上理论是拆自己的台,笔者作为互联网技术人,希望各位技术人能够将产品质量做到最好,少加班,多回家陪陪家人

ELock介绍

ELock是笔者闲暇之余写的一套分布式锁插件,代码非常精简、并且以非轮询阻塞的方式进行加锁控制。适用于面向用户的互联网产品,目前用在一套用户量为7位数的直播系统中。  源码地址:https://gitee.com/coodyer/Coody-Framework/tree/original/coody-elock

Maven引用代码(可关注更新情况):

<dependency><groupId>org.coody.framework</groupId><artifactId>coody-elock</artifactId><!--更新于2019-01-22 10:23:00 --><version>alpha-1.2.4</version>
</dependency>
  1. 初始化JedisPool       

//直接传入连接池初始化(注:无密码请传null)
new ELockCache().initJedisPool(JediPool);
//传入ip、端口、密码、超时时间初始化
new ELockCache().initJedisPool(host, port, secretKey, timeOut);
//传入ip、端口、密码、超时时间、配置器初始化
new ELockCache().initJedisPool(host, port, secretKey, timeOut, jedisPoolConfig);
  1. 加锁

ELocker.lock(key, expireSecond);
  1. 释放锁

ELocker.unLock(key);

注意: 加锁代码(ELocker.lock(key, expireSecond);)。需try{}catch{}包围,并在finally释放锁(ELocker.unLock(key);)

try {ELocker.lock(key, 100);for (int i = 0; i < 10; i++) {System.out.println(Thread.currentThread().getId() + ">>" + i);Thread.sleep(100l);}
} catch (InterruptedException e) {e.printStackTrace();
} finally {ELocker.unLock(key);
}

6. 测试代码

import java.util.ArrayList;
import java.util.List;import org.coody.framework.elock.ELocker;
import org.coody.framework.elock.redis.ELockCache;/*** 分布式锁测试* @author Coody**/
public class ELockTest {//要加锁的keystatic String key = "TESTLOCK_1";static {//初始化jedis连接new ELockCache().initJedisPool("127.0.0.1", 16379, "123456", 10000);}public static void main(String[] args) {List<Thread> threads = new ArrayList<Thread>();for (int i = 0; i < 10; i++) {Thread thread = new Thread(new Runnable() {[@Override](https://my.oschina.net/u/1162528)public void run() {test();}});threads.add(thread);}//启动十个线程for (Thread thread : threads) {thread.start();}}//要锁的方法private static void test() {try {ELocker.lock(key, 100);for (int i = 0; i < 10; i++) {System.out.println(Thread.currentThread().getId() + ">>" + i);Thread.sleep(100l);}} catch (InterruptedException e) {e.printStackTrace();} finally {ELocker.unLock(key);}}
}

执行效果:

===========================================================

以上介绍了这套锁的基本使用,下面开始介绍一下这套锁在Spring下的花样玩法

1、配置分布式锁中使用的缓存

  <bean id="eLockCache" class="org.coody.framework.elock.redis.ELockCache" lazy-init="false"><property name="jedisPool" ref="jedisPool" /></bean>

2、配置分布式锁切面

 <!-- 配置切面的bean --><bean id="eLockInterceptor" class="org.coody.framework.elock.aspect.ELockAspect"></bean><!-- 配置AOP --><aop:config><!-- 配置切面表达式 --><aop:pointcutexpression="@annotation(org.coody.framework.elock.annotation.ELock)"id="eLockPointcut" /><!-- 配置切面和通知 order:越小优先级越高 --><aop:aspect id="logAspect" ref="eLockInterceptor"><aop:around method="rdLockForAspectj" pointcut-ref="eLockPointcut" /></aop:aspect></aop:config>

致此,分布式锁配置完成,开始进入我们的花样玩法。

NO1. 使用注解添加分布式锁:

 @ELock(name = "USER_MODIFY_LOCK", fields = "userId", waitTime = 20)public void delUser(String userId) {userDao.delUser(userId);}

在ELock注解中,name代表key名字,field代表拼接的字段。

当fields所有字段长度超过32时,elock将会对key进行md5获取摘要作为缓存的key,即name:key。

本处fields支持选择对象的字段,即:方法参数名.字段值(如:userInfo.userId)

本处fields支持多个字段,fields={"userInfo.userId","orderInfo.orderId"}

当不指定key时,elock将会根据包名、类名、方法名和方法参数生成key

当不指定fields时,elock不会拼接任何多余参数,则该方法变成全局同步方法

如图:

NO2. 使用锁执行器添加分布式锁

public void delUser(String userId) throws InterruptedException {String key="USER_MODIFY_LOCK"+userId;Integer code=new AbstractLockAble(key,20) {@Overridepublic Object doService() {return userDao.delUser(userId);}}.invoke();}

通过 返回值=new AbstractLockAble(锁名称,超时时间){}.invoke()的方式,覆盖doService方法,将需要加锁的代码块放置doService方法里面执行。

如图:

===========================================================

以上介绍了通过注解进行加锁和通过执行器进行加锁的操作,如果在项目中觉得两种方式不可取,可采用上文中常规方式。

本处介绍下这套锁为何高性能。

笔者曾经百度搜索Java分布式锁实现,发现所提供方案都如出一辙(由于没有作图工具,就随便写下流程)。

1、尝试获得锁

2、死循环轮询获得锁

3、执行业务

4、释放锁

在网上查到的方案,相信很多小朋友都知道,不知道是谁通过这种方式来做分布式锁,然后被一大堆网友转载。

这种方案是可以实现锁,但是不适用于对外的互联网产品。

重大问题地雷:当多个线程尝试获得锁,只有一个线程会执行,剩下的线程都在轮询获得锁。这里我们假设时间精度为1ms,那就意味着每个线程每秒钟最多轮询1000次。然而在分布式锁中,我们需要借助中介容器去进行尝试获得锁的操作,如redis zookeeper。故此,我们假设这个key有100个线程,第一个线程执行卡住,那么,1个线程在执行业务,99个线程在以每秒钟1000的频次对中间容器发起ddos攻击。故此,如上方案不适用于对外的互联网产品。

介绍下笔者的方案:

1、尝试获得锁

2、线程入列并暂停

3、执行业务

4、发送消息释放锁,并唤醒下一个线程(轮询至第1步)

我们知道,redis也好,zookeeper也好,都有消息订阅机制。当业务流入的时候,获取锁失败的线程,都进入了挂起的状态,那么此时有一个线程在执行。当这个线程执行完毕后,发送消息,这时候所有的应用程序都收到了这个消息,并尝试获得锁,以此往复,实现业务体执行权限

作者:Coody

版权:©2014-2020 Test404 All right reserved. 版权所有

反馈邮箱:644556636@qq.com

问题反馈群:Java泛太平洋研究中心 218481849

转载于:https://my.oschina.net/hooker/blog/3002621

使用ELock实现高性能分布式锁(非轮询)相关推荐

  1. 如何设计一款高性能分布式锁,实现数据的安全访问?

    查看原文:如何设计一款高性能分布式锁,实现数据的安全访问? 随着互联网技术的飞速发展,分布式已经成为一个绕不开的话题,分布式环境下,"高并发访问共享资源"的场景并不少见,带来的问题 ...

  2. 高性能分布式锁-redisson

    RedLock算法-使用redis实现分布式锁服务 译自Redis官方文档 在多线程共享临界资源的场景下,分布式锁是一种非常重要的组件. 许多库使用不同的方式使用redis实现一个分布式锁管理. 其中 ...

  3. 死锁终结者:顺序锁和轮询锁!

    作者 | 王磊 来源 | Java中文社群(ID:javacn666) 转载请联系授权(微信ID:GG_Stone 死锁(Dead Lock)指的是两个或两个以上的运算单元(进程.线程或协程),都在等 ...

  4. Apache ZooKeeper - 使用ZK实现分布式锁(非公平锁/公平锁/共享锁 )

    文章目录 什么是分布式锁 分布式死锁 分类 排他锁 共享锁 实现 创建锁 获取锁 释放锁 Demo Jmeter配置 方案零 缺陷版本 方案一 非公平锁方案 缺陷 (羊群效应) 方案二 公平锁方案 方 ...

  5. linux 用mutex定义一个linkedlist,一个高性能无锁非阻塞链表队列

    这个是一个用c++ 11标准实现的无锁非阻塞链表队列,通过增加一个dummy节点,解偶合链表头指针和尾指针.使得当只有一个生产者和一个消费者时,进队和出队都无需加锁,进队操作的是尾指针,出队操作的是头 ...

  6. 一个高性能无锁非阻塞链表队列

    这个是一个用c++ 11标准实现的无锁非阻塞链表队列,通过增加一个dummy节点,解偶合链表头指针和尾指针.使得当只有一个生产者和一个消费者时,进队和出队都无需加锁,进队操作的是尾指针,出队操作的是头 ...

  7. wpf 轮询mysql数据库_WPF非轮询方式实时更新数据库变化SqlDependency

    public partial class Page1 : Page { private static string connStr; SqlDataReader sdr; public Page1() ...

  8. 【分布式锁】三种分布式锁的实现【原创】

    分布式锁 0x00 概述 0x02 实现方式 0x03 分布式锁:基于数据库 1. 实现思想 A. 悲观锁(排他锁) B. 乐观锁 2. 优缺点 0x04 分布式锁:基于Zookeeper 1. 实现 ...

  9. nx set 怎么实现的原子性_基于Redis的分布式锁实现

    前言 本篇文章主要介绍基于Redis的分布式锁实现到底是怎么一回事,其中参考了许多大佬写的文章,算是对分布式锁做一个总结 分布式锁概览 在多线程的环境下,为了保证一个代码块在同一时间只能由一个线程访问 ...

最新文章

  1. Mysql数据库按照varchar字符串类型排序和按照int整型类型排序的区别和注意点及解决方案...
  2. 分布式系统设计原理与方案Dubbo+Zookeeper+Spring整合
  3. python 爬网页 发布wordpress 包含图片_使用python批量插入wordpress-从理清表结构开始...
  4. 学学python(4):面向对象及类,模块
  5. c#如何wmf图片转换成png图片_每日一学:如何将png图片转换为jpg图片
  6. 由C过渡到C++-入门知识点
  7. QML工作笔记-Image中fillMode的使用
  8. 用计算机进行实时自动采集,计算机的五大主要用途
  9. lnmp编译安装mysql_LNMP一键包不安装mysql | 厘米天空
  10. 易筋SpringBoot 2.1 | 第廿六篇:SpringBoot访问缓存抽象Cache
  11. vue 设置表单必填项
  12. 052RINEX中N文件示例说明
  13. server是什么意思
  14. 论文:A Real-Time Cross-modality Correlation Filtering Method for Referring Expression Comprehension
  15. Mac 下修改文件的 md5 值
  16. 格律诗的基本知识【一小时学会写格律诗】
  17. win10 linux安卓模拟器,WIN10电脑安卓模拟器逍遥安卓唯一真正支持
  18. android判断webview加载完成,android webView判断是否加载完成的2种办法
  19. Tomcat 输入http://localhost:8080网页打不开
  20. git init 命令详解

热门文章

  1. salt的grains工具和pillar工具使用详解
  2. CactiEZ-完美篇
  3. Bootstrap实战---Dplayer播放器(影片)
  4. 夜幕里的“非凡一步”,数字传播时代创意之路
  5. 【网络科普】网络类型:PAN、LAN、WLAN、CAN、MAN、SAN和WAN
  6. AutoCAD软件使用:快捷键
  7. 函数定义——一元二次方程求根
  8. 茂名市 2021 年高中信息技术学科素养展评
  9. lm做自相关做几阶_LM检验结果残差是2阶自相关和12阶自相关,如何处理?
  10. aop配置异常日志管理