场景:
最近遇到一个account dubbo服务的并发注册问题,日志中抛出了大量的主键冲突问题。注册用户过程中有多个SQL操作,且这些SQL可以实现单机本地事务。
为何单机锁不能解决问题?正如下图请求是发送到不同的节点上的,单机的锁只能控制单节点的并发请求。
解决思路:
如果加锁操作,会极大的减少主键冲突的日志。开始写了一版最直接的思路,后来发现dubbo服务中很多地方都可能出现并发问题,肖老板指导说可以做的更抽象点,如果jar包的形式提供出来,那么别的工程也可以复用这套逻辑了。所以后来又做了一版抽象,模型主要分成幂等锁和非幂等锁。
系统架构图:
cache能实现分布式锁的原因是所有的putIfAbsent这种操作都放在了单机节点的队列上做串行操作,所以也就避免了分布式锁的问题。
流程图:
非幂等的情况很简单,没有抢到锁直接返回就是。幂等的情况比较复杂,从下面的流程图可以看出有轮询的过程。
缺点:
这个也是我看了别人的分布式锁框架对比发现的,跟别人一对比发现还是有挺多东西可以学习借鉴下的。
1. 对代码侵入性大,最好能直接提供注解的方式,不影响业务逻辑代码。对非幂等的接口比较好处理,但是对于幂等的接口则难度较大,毕竟需要有地方承载轮询的代码啊。
2. 实现单一,仅仅实现nkv,其实可以更抽象一层,然后以nkv,redis zk的方式实现。
3. 我理解幂等其实一般都不用分布式锁了,即使并发请求也没啥关系啊,我们这是奇葩情况。
核心代码:
public interface ConcurrencyService { /** 幂等的并发返回的枚举值定义 */ /** 获取到并发锁 */ final int GET_CONCURRENCY_LOCK = 1; /** 轮询事务对象成功 */ final int GET_TRANSACTION_RET_OBJECT = 2; /** 轮询事务对象失败 */ final int NOT_GET_TRANSACTION_RET_OBJECT = 3; /** * 获取分布式并发的锁(非幂等) * @param key * 请求并发锁的标识 * @return */ public boolean getConcurrencyLock(String key) ; /** * 获取分布式并发的锁(幂等) * @param key * 请求并发锁的标识 * @param interval * 轮询的间隔时间(ms),默认是20ms * @param concurrencyLoopCallBack * 用于轮询抢到分布式锁的事务是否成功的回调方法 * @return */ public int getConcurrencyLock(String key,Long interval, IConcurrencyLoopCallBack concurrencyLoopCallBack) ; }
public interface IConcurrencyLoopCallBack { /** * 用于并发幂等的场景,方法主要用于第三步 * 1. 查询对象不存在 * 2. 获取分布式锁 * 3. 获取锁失败则使用这个方法来进行轮询 * 4. 返回true 则说明验证事务操作成功 * 5. 返回false 则说明轮询失败针对具体业务做不同处理 * @return */ boolean getTransactionRetObject(); }
@Service("concurrencyService") public class ConcurrencyServiceImpl extends CacheSupport<Object> implements ConcurrencyService { /** 缓存在多久之后失效(单位s) */ public final Integer overDate = 5; /** 默认的轮询间隔时间 */ public final Long defaultInterval = 20L; /** 轮询次数 */ public final int tryTimes = 3; @Override public boolean getConcurrencyLock(String key) { NkvClient.NkvOption nkvOption = new NkvClient.NkvOption(); nkvOption.setExpireTime(overDate); return setIfNotExist(key, true, nkvOption); } @Override public int getConcurrencyLock(String key, Long interval, IConcurrencyLoopCallBack callBack) { boolean transactionRet = false; try { // 获取并发控制权 boolean getLock = getConcurrencyLock(key); LogConstant.accountLog.info("cacheKey:" + key + " ,getConcurrencyLock result :" + getLock); if(getLock) { return GET_CONCURRENCY_LOCK; } // 设置默认间隔时间 if(null == interval) { interval = defaultInterval; } int tryingTime = 0; while(!transactionRet && tryingTime < tryTimes) { Thread.sleep(interval); tryingTime ++; transactionRet = callBack.getTransactionRetObject(); LogConstant.accountLog.info("cacheKey:" + key + "the " + tryingTime + "th try , transaction is successful ? " + transactionRet); } } catch (Exception e) { LogConstant.accountLog.error("getConcurrencyLock Exception", e); } if(transactionRet) { return GET_TRANSACTION_RET_OBJECT; } return NOT_GET_TRANSACTION_RET_OBJECT; } }
相关推荐
现在很多项目单机版已经不满足了,分布式变得越受欢迎,同时也带来很多问题,分布式锁也变得没那么容易实现,分享一个redis分布式锁工具类,里面的加锁采用lua脚本(脚本比较简单,采用java代码实现,无须外部调用...
分布式锁处理步骤 第一步:查询是否有分布式锁:
里面包含zk的分布式锁,包括原生客户端API的方式,以及框架的方式。还有red is的原生客户端API方式,以及框架的方式
Redis+RedisTemplate分布式锁
redis实现分布式锁,自旋式加锁,lua原子性解锁
SpringBoot基于redis的分布式锁,有word使用文档,根据文档配置即可使用
主要介绍了Java基于redis实现分布式锁代码实例,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下
003 redis分布式锁 jedis分布式锁 Redisson分布式锁 分段锁
自己整理的如何利用redis实现分布式锁,redis实现分布式锁看这一篇绝对够。
本资源为一步一步实现redis分布式锁的demo,利用redis实现高可用的分布式锁,规避各种坑、坑、坑!
分布式锁
redis实现分布式锁(java/jedis),其中包含工具方法以及使用demo 本资源是利用java的jedis实现 redis实现分布式锁(java/jedis),其中包含工具方法以及使用demo 本资源是利用java的jedis实现
让你对分布式锁理解透彻,基于数据库实现、基于Redis实现、基于ZooKeeper实现,三种分布式锁实现方案详解!
zookeeper做分布式锁
zookeeper通过使用curator实现分布式锁来保证数据的一致性。 zookeeper通过使用curator实现分布式锁来保证数据的一致性。
介绍分布式锁的机制以及业务场景,系统介绍了分布式基础的特性,比较系统介绍了锁和事务的区别,对开源的分布式锁如redis实现分布式锁的机制方案进行阐述
基于zookeeper的分布式锁简单实现,包含测试代码,实用工具类
解决方案里面包括两个Console项目,一个是ZookeeperNet的基本使用,另一个是用ZookeeperNet实现分布式锁的实例。
redisson实现分布式锁,基于aop注解形式使用。
一款基于Spring Boot , 用起来还挺顺手的分布式锁 一款基于Spring Boot , 用起来还挺顺手的分布式锁 一款基于Spring Boot , 用起来还挺顺手的分布式锁 一款基于Spring Boot , 用起来还挺顺手的分布式锁 一款基于...