83 zookeeper分布式锁三

常见问题:

使用zk实现分布式锁,,如果主节点宕机:会发生选举的过程,为了数据一致性的问题,在zk选举的过程中整个zk环境无法使用,客户端获取锁的时候,会不断的重试,重试多次会直接抛出异常。
客户端已经在监听的情况下:
zk客户端事件监听到当前的leader节点已经宕机,会自动实现重试。
该过程可能在剩余的节点中选出一个新的leader节点,从新监听新的leade节点,,如果一直没有选出leader节点,为了避免客户端死锁的问题,从新获取锁的状态。

使用zk实现分布式锁,死锁的问题:
curator概念:
Curator是Netflix公司开源的一套zookeeper客户端框架,解决了很多Zookeeper客户端非常底层的细节开发工作,包括连接重连、反复注册Watcher和NodeExistsException异常等等。Patrixck Hunt(Zookeeper)以一句“Guava is to Java that Curator to Zookeeper”给Curator予高度评价。

Curator官网

http://curator.apache.org/

其中Curator提供分布式锁实现方案,解决羊群效应问题。

核心API:

InterProcessMutex:分布式可重入排它锁

InterProcessSemaphoreMutex:分布式排它锁

InterProcessReadWriteLock:分布式读写锁

InterProcessMultiLock:将多个锁作为单个实体管理的容器

Curator环境搭建
Maven依赖

<dependency>
    <groupId>org.apache.curator</groupId>
    <artifactId>curator-recipes</artifactId>
    <version>4.2.0</version>
</dependency>

核心api配置

import org.apache.curator.RetryPolicy;
import org.apache.curator.framework.CuratorFramework;
import org.apache.curator.framework.CuratorFrameworkFactory;
import org.apache.curator.retry.ExponentialBackoffRetry;
import org.apache.curator.utils.CloseableUtils;
import org.springframework.stereotype.Component;

/**
 * @ClassName CuratorZkLock   zk框架实现分布式锁
 * @Author 蚂蚁课堂余胜军 QQ644064779 www.mayikt.com
 * @Version V1.0
 **/
@Component
public class CuratorZkLock {


    // ZooKeeper 服务地址, 单机格式为:(127.0.0.1:2181),
    // 集群格式为:(127.0.0.1:2181)
    private String connectString;
    // Curator 客户端重试策略
    private RetryPolicy retry;
    // Curator 客户端对象
    private CuratorFramework client;

    public CuratorZkLock() throws Exception {
        init();
    }

    public void init() throws Exception {
        // 设置 ZooKeeper 服务地址为本机的 2181 端口
        connectString = "127.0.0.1:2181";
        // 重试策略
        // 初始休眠时间为 1000ms, 最大重试次数为 3
        retry = new ExponentialBackoffRetry(1000, 3);
        // 创建一个客户端, 60000(ms)为 session 超时时间, 15000(ms)为连接超时时间
        client = CuratorFrameworkFactory.newClient(connectString, 60000, 15000, retry);
        client.start();
    }

    public void close() {
        CloseableUtils.closeQuietly(client);
    }

    public CuratorFramework getClient() {
        return client;
    }
}

简单用法
/**

  • 秒杀lock

  • @return
    */
    @RequestMapping("/seckilLock")
    public String seckilLock() throws Exception {
    InterProcessMutex interProcessMutex = null;
    try {
    CuratorFramework client = curatorZkLock.getClient();
    interProcessMutex = new InterProcessMutex(client, lockPath);
    interProcessMutex.acquire();
    log.info("<获取锁成功....>");
    log.info("<执行秒杀扣库存业务逻辑....>");
    CommodityDetails commodityDetails = commodityDetailsMapper.getCommodityDetails(1L);
    Long stock = commodityDetails.getStock();
    if (stock > 0) {
    log.info("<扣库存成功>");
    commodityDetailsMapper.reduceInventory(1l);
    return "ok";
    }
    // 扣库存失败
    log.info("<扣库存失败>");
    return "fail";

    } catch (Exception e) {
    e.printStackTrace();
    log.info("<秒杀出现错误:e:{}>", e);
    return "fail";
    } finally {
    if (interProcessMutex != null)
    interProcessMutex.release();
    }

}

Curator源码解读

  1. 从缓存中查找是否已经创建分布式锁,如果已经创建了分布式锁
    则直接复用(具有可重入性)
  2. 如果缓存中没有,则创建一个分布式锁
  3. 实现原理:创建一个临时节点,获取当前父节点下子节点,如果是为最小的节点,则表示获取锁成功,否则获取锁失败,阻塞等待,则监听上一个节点。
  4. 当上一个节点如果释放锁之后,直接进入到获取锁的状态。
    唤醒使用wait notify技术

获取到当前的线程

Thread currentThread = Thread.currentThread();

从集合中查询当前线程是否有获取过分布式锁;

LockData lockData = threadData.get(currentThread);
if ( lockData != null )
{
// re-entering

如果有获取过分布式锁的情况下,则重入次数就会+1

lockData.lockCount.incrementAndGet();
return true;

}

在集合中没有查询到当前线程有获取过锁,如果没有获取锁的情况下,则创建一个临时顺序编号节点

String lockPath = internals.attemptLock(time, unit, getLockNodeBytes());
if ( lockPath != null )
{

在将该临时顺序编号节点存放到集合缓存。

Key为当前线程 value 分布式锁
LockData newLockData = new LockData(currentThread, lockPath);
threadData.put(currentThread, newLockData);
return true;
}

创建一个临时顺序编号节点

ourPath = driver.createsTheLock(client, path, localLockNodeBytes);

判断该临时顺序编号节点是否属于最小的;如果属于最小的节点

就表示获取锁成功;
hasTheLock = internalLockLoop(startMillis, millisToWait, ourPath);

获取当前节点对应 index下标位置;

int ourIndex = children.indexOf(sequenceNodeName);
validateOurIndex(sequenceNodeName, ourIndex);

0 maxLeases=1;

getsTheLock =true
getsTheLock =true 表示获取锁成功

boolean getsTheLock = ourIndex < maxLeases;

如果getsTheLock =false的情况下,则订阅我们上一个节点

String pathToWatch = getsTheLock ? null : children.get(ourIndex - maxLeases);

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章