分布式解决方案:
分布式锁常见问题:
1,Zookeeper如何实现分布式锁;
1,重试策略;
2,超时策略;
3,续命设计,续命如何避免死锁问题。
4,性能优化。
5,高可用
6,公平性。
2, 业务超时,一直不释放锁如何处理;
可以采用续命设计,如果业务超时(),续命多次(3次)如果还没有释放锁的情况下,则认为超时
就应该:
1,主动释放锁;
2,事务回滚
3,主动停止线程
4,移除key
代码思路: 记录每次获取锁的线程,锁的信息 事务信息。
业务如果超时:定义: 当前线程获取到锁之后,在规定的时间内需要释放锁,避免其他的jvm一直阻塞等待。
可以采用续命代码设计: 续命多次如果业务还是没有执行完毕的情况下,则认为该锁超时,应主动释放该锁,防止其他jvm一直阻塞等待。
续命代码设计:
1,主动释放锁
2,回滚当前业务逻辑
3,主动停止该线程
4,移除监听;
代码实现
/**
* 续命设计
*/
/**
* 每隔2s执行定时任务
*/
@Scheduled(cron = "0/2 * * * * *")
@Transactional
public synchronized void taskService() {
try {
// 拉取数据库数据 调用第三方短信接口发送短信
// 获取锁
zookeeperTemplateLock.getLock();
//##记录锁的开始执行业务逻辑信息
TransactionStatus begin = translationUtils.begin();
String lockId = UUID.randomUUID().toString();
lockInfoMap.put(UUID.randomUUID().toString(), new LockInfo(lockId, Thread.currentThread(), STATE_START, zookeeperTemplateLock
, translationUtils, begin));
log.info("[{}]正在调用阿里云发送短信", serverPort);
userMapper.insert("mayikt" + System.currentTimeMillis(), 22);
// 模拟业务超时
Thread.sleep(500000);
translationUtils.rollback(begin);
// 释放锁
lockInfoMap.put(UUID.randomUUID().toString(), new LockInfo(lockId, Thread.currentThread(), STATE_STOP,
zookeeperTemplateLock, translationUtils, begin));
zookeeperTemplateLock.unLock();
} catch (Exception e) {
log.error("[e:{}]", e);
TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();
}
}
public MayiktTask() {
scheduledExecutorService.scheduleAtFixedRate(new DetectionAlgorithm(), 0, 5, TimeUnit.SECONDS);
}
/**
* 检测死锁的问题
*/
class DetectionAlgorithm implements Runnable {
@Override
public void run() {
lockInfoMap.forEach((k, lockInfo) -> {
if (STATE_START.equals(lockInfo.getState())) {
// 主动释放session连接
lockInfo.getLock().unLock();
// 事务回滚
TransactionStatus transactionStatus = lockInfo.getTransactionStatus();
lockInfo.getTranslationUtils().rollback(transactionStatus);
log.info("[serverPort:{},lockId:{}]", serverPort, lockInfo + "事务已经回滚...");
// 主动停止线程
lockInfo.getLockThread().interrupt();
// 移除
lockInfoMap.remove(k);
}
});
}
}
//##创建定时任务线程池
private ScheduledExecutorService scheduledExecutorService = Executors.newSingleThreadScheduledExecutor();
3,分布式锁如何避免羊群效应问题?
什么是羊群效应:
当jvm释放锁的时候,会唤醒正在等待的jvm从新进入到获取锁的状态。
如果正在阻塞的等待获取锁的jvm,如果有几十个或者几百个,上千个的情况下zkServer端唤醒所有正在等待的jvm,从新进入到获取锁的状态,唤醒的成本是非常高,有可能会造成我们的zkserver端阻塞。
基于zk实现分布式锁的2种实现方案
1,多个jvm同时在zk上创建一个相同的临时节点,谁能够创建成功,谁就可以拿到这把锁。
存在羊群效应的bug。
2,基于临时顺序编号节点实现,多个jvm同时创建一个临时顺序编号节点,如果当前jvm创建的临时顺序编号节点是最小的情况下,则表示获取锁成功。如果不是最小的情况下,则表示获取锁失败,就会进入到阻塞状态:当前的jvm订阅到我们的上一个节点。;
核心思想: 当我们的jvm释放锁的时候,不会通知一群正在等待获取锁的jvm,而是会通知一个jvm获取锁,效率变高,对我们的zkserver端减去事件通知的压力。
多个jvm枪锁,最终只会有一个jvm能够抢成功。
类似于: juc并发编程中 公平锁实现原理。
伪代码实现:
1,多个jvm同时在zk/localpath 创建一个临时编号节点
2,每个jvm都能够创建到自己独立的临时顺序编号节点。
3,举个例子:
jvm01/localpath01
jvm02/localpath02
jvm03 /localpath03
获取锁的流程:
jvm01--
1,获取当前jvm 创建的临时编号节点localpath01
2,查询到localpath下所有的子节点,实现排序 查找到最小的
3,最小的临时顺序编号节点:localPath01
localPath01==localpath01 如果是最小的节点情况下,则表示获取锁成功。
Jvm02--
1,查询当前jvm创建的临时节点localPath02
2,查询到localpath下所有的子节点,实现排序,查找到最小的
3,最下的临时顺序 编号节点:localPath01
4,localPath02 !=localpath01
5,如果当前自己创建的临时顺序编号节点不是最小的情况下,则会直接阻塞
6.Jvm02订阅到localPath01该临时顺序编号节点:
唤醒以后:
zk服务器端,当localpath01节点删除之后,会通知给jvm02,jvm02从组赛状态到运行状态。
1,获取当前的jvm创建的临时编号节点localpath02
2,查询到localpath下所有的子节点,实现排序,查找到最小的
3,最小的临时顺序编号节点:localpath02
4,localpath02==localpath02 属于最小节点
5,获取锁成功。
羊群效应发生的原因:
如果多个客户端同时订阅同一个节点的情况下,当该节点失效的时候,有可能会通知给所有的客户端,如果客户端有几千或者几万个的情况下,这时会导致zk阻塞效率且非常差。本身我们最终只有一个jvm能够拿到锁,也没有必要通知给所有的客户端。
基于zk实现分布式锁如何解决羊群效应问题:

1,多个jvm首先会先创建一个临时顺序节点,在通过排序的方式找到最小的节点路径
2,如果当前节点为最小的节点的情况下,则表示获取锁。
3,如果当前节点不是为最小的节点的情况下,订阅上一个节点。
串行化
效果优点类似于:公平锁:
4,如何提高分布式锁的效率问题?
避免羊群效应;
5,Redis与Zookeeper 实现分布式锁区别?
6,分布式锁框架Curator源码解读
7,分布式框架Redisson源码解读
8 如何保证分布式锁高可用性问题。
分布式锁有哪些应用场景
1,集群环境中保证定时任务执行的幂等性问题,基于zk案例;
幂等性:执行结果保证唯一不能够重复。
当我们的定时任务服务集群的情况下,有可能会同时重复执行定时任务。
解决思路: 多个jvm集群的定时任务,在触发的时候,获取分布式锁,如果能够获取分布式锁的jvm,就能够执行定时任务,没有获取到分布式锁的jvm就不能够执行定时任务。

2,用户下单库存超卖问题,防止超卖Redis解决。
网友评论