基于zookeep实现分布式锁的实际场景运用

首先我们先来看看使用zk实现分布式锁的原理,在zk中是使用文件目录的格式存放节点内容,其中节点类型分为:

  • 持久节点(PERSISTENT ):节点创建后,一直存在,直到主动删除了该节点。
  • 临时节点(EPHEMERAL):生命周期和客户端会话绑定,一旦客户端会话失效,这个节点就会自动删除。
  • 序列节点(SEQUENTIAL ):多个线程创建同一个顺序节点时候,每个线程会得到一个带有编号的节点,节点编号是递增不重复的,如下图:

 

方式1:基于zookeep不能重复创建一个节点的特性来实现,如图:

 

 

 

存在问题:如果有100个线程同时竞争该锁,即这100线程中都需要基于zookeep中的watch机制观察该节点,当该节点释放后,需要同时通知这100个线程去公平获取锁,会存在“惊群”现象;

 

方式2 :基于zookeep的序列节点;

比如现在要对订单001加锁,线程A已经获取到锁,线程B、线程C、线程D等待;

则zookeep会有默认机制按照顺序在001下创建时序节点,在获取001锁时找该节点下的最小节点并获取锁,释放锁后删除该节点在寻找下一个最小节点获取锁,

 

 

 

 

 这里基于Curator对第二种方式实现:

引入依赖:

<dependency>
            <groupId>org.apache.curator</groupId>
            <artifactId>curator-recipes</artifactId>
            <version>2.5.0</version>
            <exclusions>
                <exclusion>
                    <artifactId>zookeeper</artifactId>
                    <groupId>org.apache.zookeeper</groupId>
                </exclusion>
            </exclusions>
        </dependency>

代码: 

@Component
public class CuratorSingleton {
    private static final Logger log = LoggerFactory.getLogger(CuratorSingleton.class);

    private static CuratorFramework client = null;
    private static final String ZK_HOST = YmlUtil.getYmlValue("zookeeper.zookeeperUrl");

    private CuratorSingleton() throws InterruptedException {
        if (client == null) {
            synchronized (CuratorSingleton.class) {
                if (client == null) {
                    start();
                }
            }
        }
    }

    /**
     * 单例模式获取CuratorFramework对象
     * @return
     * @throws IOException
     * @throws InterruptedException
     */
    public static CuratorFramework getInstance() throws InterruptedException {
        if (client == null) {
            synchronized (CuratorSingleton.class) {
                if (client == null) {
                    start();
                }
            }
        } else {
            if ("STOPPED".equals(client.getState().toString())) {
                synchronized (CuratorSingleton.class) {
                    if ("STOPPED".equals(client.getState().toString())) {
                        start();
                    }
                }
            }
        }
        return client;
    }

    private static void start() throws InterruptedException {
        RetryPolicy retryPolicy = new RetryUntilElapsed(30000, 1000);
        client = CuratorFrameworkFactory.newClient(ZK_HOST, retryPolicy);
        client.start();
        client.getZookeeperClient().blockUntilConnectedOrTimedOut();
    }


    /**
     * @Description 删除一个文件夹下的空节点 一个文件夹就是一种业务对象
     * @return void
     * @throws
     * @Author zhanglei
     * @Date 20:44 2020/4/21
     * @Param [folder]
     **/
    public static void clearOneFolder(String folder) throws Exception {
        final CuratorFramework curator = getInstance();
        for (String nodePath : curator.getChildren().forPath(folder)) {
            //四级路径
            String nodeFullPath = folder + "/" + nodePath;
            List<String> folders = curator.getChildren().forPath(nodeFullPath);
            if (CollectionUtils.isEmpty(folders)) {
                try {
                    //没有子目录直接删除
                    deleteNode(nodeFullPath);
                } catch (Exception e) {
                    log.info("fail to delete: {}", nodeFullPath, e);
                }
                continue;
            }
            for (String path : folders) {
                //删除第五级目录
                String pathFive = nodeFullPath + "/" + path;
                try {
                    deleteNode(pathFive);
                } catch (Exception e) {
                    log.info("fail to delete: {}", pathFive, e);
                }
            }
            try {//删除第四级目录
                deleteNode(nodeFullPath);
            } catch (Exception e) {
                log.info("fail to delete: {}", nodeFullPath, e);
            }
        }
    }

    /**
     * @Description 安全删除由于锁造成的空节点 确保节点存在、没有子节点、没有数据、最近修改时间在两小时前
     * @return void
     * @throws
     * @Author zhanglei
     * @Date 20:44 2020/4/21
     * @Param [path]
     **/
    public static void deleteNode(String path) throws Exception {
        final CuratorFramework curator = getInstance();
        Stat stat = curator.checkExists().forPath(path);
        if (stat == null) {
            return;
        }
        if (stat.getNumChildren() > 0) {
            return;
        }
        if (curator.getData().forPath(path).length > 0) {
            return;
        }
        if (System.currentTimeMillis() - stat.getMtime() < 3600 * 2000) {
            return;
        }
        curator.delete().inBackground().forPath(path);
    }
}
public class DistributedLockBean {
    private String      granularity; //主节点地址,以谁为粒度加锁(例如按客户加锁,则传入cusId)
    private boolean     lockSuccess; //是否获取锁成功
    private LockService lockService; //具体调用方法
    private Object      result;     //方法返回结果
    private int         maxLease;   //最大信号量(信号量锁)
    private int         qty;        //消耗信号量(信号量锁)

    public String getGranularity() {
        return granularity;
    }

    /**
     * 设置锁业务的粒度
     * 
     * @param granularity
     */
    public void setGranularity(String granularity) {
        this.granularity = "/distributeLock/" + granularity;
    }

    protected boolean isLockSuccess() {
        return lockSuccess;
    }

    protected void setLockSuccess(boolean lockSuccess) {
        this.lockSuccess = lockSuccess;
    }
    /**
     * 在锁内执行的操作
     * @return
     */
    public LockService getLockService() {
        return lockService;
    }

    public void setLockService(LockService lockService) {
        this.lockService = lockService;
    }

    protected Object getResult() {
        return result;
    }

    protected void setResult(Object result) {
        this.result = result;
    }

    public int getMaxLease() {
        return maxLease;
    }

    public void setMaxLease(int maxLease) {
        this.maxLease = maxLease;
    }

    public int getQty() {
        return qty;
    }

    public void setQty(int qty) {
        this.qty = qty;
    }
}
@Component
public class DistributedLockService {

    private static final Logger log = LoggerFactory.getLogger(DistributedLockService.class);

    /**
     * 通过分布式锁执行相应的业务操作
     * 
     * @throws Exception
     */
    public Object doServiceByLock(DistributedLockBean dlBean) throws Exception {
        return doServiceByLock(dlBean,false);
    }
    
    private void releaseLock(InterProcessMutex lock, CuratorFramework client) {
        try {
            lock.release();
            log.debug("释放锁成功");
        } catch (Exception e) {
            client.close();
            log.error("释放锁失败", e);
        }
    }
    
    /**
     * 通过分布式锁执行相应的业务操作
     * 
     * @param dlBean
     * @param releaseLockAfterTransaction 在事务提交后才释放锁。<br>警告:如果是true,需要注意这个方法不能在一个循环中调用,如果各循环体中包含相同的granularity,有死锁的危险!!!
     * @return
     * @throws CuratorException
     */
    public Object doServiceByLock(DistributedLockBean dlBean,boolean releaseLockAfterTransaction) throws Exception {
        try {
            final CuratorFramework client = CuratorSingleton.getInstance();
            final InterProcessMutex lock = new InterProcessMutex(client, dlBean.getGranularity());
            if (lock.acquire(60, TimeUnit.SECONDS)) {// 等待60s,如果60s未获得锁,则不再获取锁
                try {
                    log.debug("获取锁成功,开始执行业务操作");
                    Object result = dlBean.getLockService().doService();
                    dlBean.setResult(result);
                    dlBean.setLockSuccess(true);
                } finally {
                    if(releaseLockAfterTransaction && TransactionHookUtil.isInTransaction()){
                        // 如果参数指定在事务之后释放,并且当前在事务中
                        TransactionHookUtil.registAfterCompletionHook(new TransactionHookUtil.CompletionHook() {
                            @Override
                            public void execute(int status) {
                                releaseLock(lock, client);
                            }
                        });
                    }else{ 
                        // 直接释放锁
                        releaseLock(lock, client);
                    }
                }
            }
            if (!dlBean.isLockSuccess()) {
                throw new CuratorException("获取锁失败,lock path:"+dlBean.getGranularity());
            }
        } catch (Exception e) {
            log.error(e.getMessage(),e);
            throw e;
        }
        return dlBean.getResult();
    }
    
    /**
     *
     * @Description  分布式锁-信号量
     * @Date 11:05 2019/3/28
     * @Param 
     * @return 
     **/
    public Object doServiceBySemaphoreLock(DistributedLockBean dlBean) throws Exception {
        try {
            final CuratorFramework client = CuratorSingleton.getInstance();
            InterProcessSemaphoreV2 interProcessSemaphoreV2=new InterProcessSemaphoreV2(client,  dlBean.getGranularity(), dlBean.getMaxLease());
            Collection< Lease > leases = interProcessSemaphoreV2.acquire(dlBean.getQty(),60,TimeUnit.SECONDS);
            if(leases != null) {// 等待60s,如果60s未获得锁,则不再获取锁
                try {
                    log.debug("获取信号量锁成功,开始执行业务操作");
                    Object result = dlBean.getLockService().doService();
                    dlBean.setResult(result);
                    dlBean.setLockSuccess(true);
                } finally {
                    // 直接释放锁
                    releaseSemaphoreLock(interProcessSemaphoreV2,leases, client);
                }
            }
            if (!dlBean.isLockSuccess()) {
                throw new CuratorException("获取信号量锁失败,lock path:"+dlBean.getGranularity());
            }
        } catch (Exception e) {
            log.error(e.getMessage(),e);
            throw e;
        }
        return dlBean.getResult();
    }
    
    /**
     * 
     * @Description  释放信号量锁
     * @Date 11:05 2019/3/28
     * @Param 
     * @return 
     **/
    private void releaseSemaphoreLock(InterProcessSemaphoreV2 interProcessSemaphoreV2,Collection<Lease> leases, CuratorFramework client) {
        try {
            interProcessSemaphoreV2.returnAll(leases);
            log.debug("释放信号量锁成功");
        } catch (Exception e) {
            client.close();
            log.error("释放信号量锁失败", e);
        }
    }
}
public interface LockService {
    public Object doService() throws Exception;
}

使用:

     DistributedLockBean distributedLockBean = new DistributedLockBean();
        distributedLockBean.setGranularity(DistributedLockConstant.ONLINE_STATISTICS+onlineInfo.getSessionId());
        distributedLockBean.setLockService(new LockService() {
            @Override
            public Object doService() throws Exception {
                // doServicereturn null;
            }
        });
        try{
            distributedLockService.doServiceByLock(distributedLockBean,true);
        }catch (Exception e){
            log.info(e.getMessage());
        }

 

注意,这里由Curator创建的节点在锁释放后并没有删除,建议执行定时任务手动删除,直接调用CuratorSingleton类中删除方法即可,可删除指定节点,也可删除文件夹i下空节点;

 

posted @ 2020-04-21 21:31  zlAdmin  阅读(238)  评论(0)    收藏  举报