使用zookeeper开发分布式锁组件

最近一个半月一直比较忙,业务中使用了分布式锁来控制操作特定数据,考虑到重用性,使用curator框架封装zookeeper目录获取和释放分布式锁的模块。记录下来,以后就可以封装类似的组件了。

首先还是要把zookeeper安装好,具体步骤就不介绍了。网上很多。

1,先建立连接配置

@Data
@ConfigurationProperties(prefix = ZOOKEEPER_CONFIG_PRE)
public class CuratorConfiguration {
    private int retryCount;
    private int elapsedTimeMs;
    private String connectString;
    private int sessionTimeoutMs;
    private int connectionTimeoutMs;

}

使用到的配置文件前缀:这个是要在使用到该组件的项目里配置,毕竟每个项目的zk环境可能不同,不能都用一个

public interface ConfigDef {

    /*配置文件的处理前缀*/
    String ZOOKEEPER_CONFIG_PRE="zookeeper";

}

2.使用连接配置建立客户端连接

package com.kamowl.kamo.cloud.base.zookeeper.config;

import org.apache.curator.framework.CuratorFramework;
import org.apache.curator.framework.CuratorFrameworkFactory;
import org.apache.curator.retry.RetryNTimes;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import static com.kamowl.kamo.cloud.base.zookeeper.config.ConfigDef.ZOOKEEPER_CONFIG_PRE;

/**
 * @author dushan
 * 使用连接工厂
 * 通过配置文件参数创建连接
 * @Description
 * @Date 2019-12-11 20:54
 */
@Configuration
@ConditionalOnClass({CuratorFrameworkFactory.class,CuratorFramework.class})
@EnableConfigurationProperties(CuratorConfiguration.class)
@ConditionalOnProperty(prefix = ZOOKEEPER_CONFIG_PRE, value = "enable", havingValue = "true")
public class ZookeeperAutoConfigure {
    @Autowired
    private CuratorConfiguration curatorConfiguration;

    @Bean
    public CuratorFramework curatorFramework() {
        return CuratorFrameworkFactory.newClient(
                curatorConfiguration.getConnectString(),
                curatorConfiguration.getSessionTimeoutMs(),
                curatorConfiguration.getConnectionTimeoutMs(),
                new RetryNTimes(curatorConfiguration.getRetryCount(), curatorConfiguration.getElapsedTimeMs()));

    }
}

3、建立连接后直接使用工具,使用工具可以大大简化开发工作,不然我们自己写就会费劲

package com.kamowl.kamo.cloud.base.zookeeper.config;

import org.apache.curator.framework.CuratorFramework;
import org.apache.curator.framework.recipes.cache.PathChildrenCache;
import org.apache.curator.framework.recipes.cache.PathChildrenCacheEvent;
import org.apache.zookeeper.CreateMode;
import org.apache.zookeeper.ZooDefs;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.util.concurrent.CountDownLatch;

/**
 * @author dushan
 * @date 2019-12-1211:04
 */
@Service
public class DistributedLockByCurator implements InitializingBean {
    private static final Logger logger = LoggerFactory.getLogger(DistributedLockByCurator.class);

    private final static String ROOT_PATH_LOCK = "rootlock";
    private CountDownLatch countDownLatch = new CountDownLatch(1);
    @Autowired
    private CuratorFramework curatorFramework;

    /**
     * 获取分布式锁
     *
     * @param lockName 锁实体目录,譬如用户钱包锁wallet-1
     **/
    public void acquireDistributeLock(String lockName) {
        String keyPath = "/" + ROOT_PATH_LOCK + "/" + lockName;
        while (true) {
            try {
                curatorFramework
                        .create()
                        .creatingParentsIfNeeded()
                        .withMode(CreateMode.EPHEMERAL)
                        .withACL(ZooDefs.Ids.OPEN_ACL_UNSAFE)
                        .forPath(keyPath);
                logger.info("success to acquire lock for path:{}", keyPath);
                break;
            } catch (Exception e) {
                logger.info("failed to acquire lock for path:{}", keyPath);
                logger.info("while try again ------------");
                try {
                    if (countDownLatch.getCount() <= 0) {
                        countDownLatch = new CountDownLatch(1);
                    }
                    countDownLatch.await();
                } catch (InterruptedException e1) {
                    e1.printStackTrace();
                }
            }
        }
    }

    /**
     * 释放分布式锁
     * 只删除叶子节点就可以
     */

    public boolean releaseDistributeLock(String path) {
        try {
            String keyPath = "/" + ROOT_PATH_LOCK + "/" + path;
            if (curatorFramework.checkExists().forPath(keyPath) != null) {
                curatorFramework.delete().guaranteed()
                        .forPath(keyPath);
            }
        } catch (Exception e) {
            logger.error("failed to release lock :{}");
            return false;
        }
        return true;
    }


    /**
     * 创建 watcher 事件
     */
    private void addWatcher(String path) throws Exception {
        String keyPath;
        if (path.equals(ROOT_PATH_LOCK)) {
            keyPath = "/" + path;
        } else {
            keyPath = "/" + ROOT_PATH_LOCK + "/" + path;
        }
        final PathChildrenCache cache = new PathChildrenCache(curatorFramework, keyPath, false);
        cache.start(PathChildrenCache.StartMode.POST_INITIALIZED_EVENT);
        cache.getListenable().addListener((client, event) -> {
            if (event.getType().equals(PathChildrenCacheEvent.Type.CHILD_REMOVED)) {
                String oldPath = event.getData().getPath();
                logger.info("success to release lock for path:{}", oldPath);
                if (oldPath.contains(path)) {
                    //释放计数器,让当前的请求获取锁
                    countDownLatch.countDown();
                }
            }
        });
    }

    /**
     * 创建父节点,并创建永久节点
     * 命名空间
     */
    @Override
    public void afterPropertiesSet() {
        curatorFramework.start();
        curatorFramework = curatorFramework.usingNamespace("lock-namespace");
        String path = "/" + ROOT_PATH_LOCK;
        try {
            if (curatorFramework.checkExists().forPath(path) == null) {
                curatorFramework.create()
                        .creatingParentsIfNeeded()
                        .withMode(CreateMode.PERSISTENT)
                        .withACL(ZooDefs.Ids.OPEN_ACL_UNSAFE)
                        .forPath(path);
            }
            addWatcher(ROOT_PATH_LOCK);
            logger.info("root path 的 watcher 事件创建成功");
        } catch (Exception e) {
            logger.error("connect zookeeper fail,please check the log >> {}", e.getMessage(), e);
        }
    }


}

4、使用到的依赖以及打包方式加进来

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <parent>
        <artifactId>kamo-cloud-base</artifactId>
        <groupId>com.kamowl</groupId>
        <version>1.0-SNAPSHOT</version>
    </parent>
    <modelVersion>4.0.0</modelVersion>

    <artifactId>kamo-cloud-base-distribute</artifactId>
    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-configuration-processor</artifactId>
            <optional>true</optional>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-autoconfigure</artifactId>
        </dependency>

        <dependency>
            <groupId>org.apache.zookeeper</groupId>
            <artifactId>zookeeper</artifactId>
            <version>3.4.10</version>
            <exclusions>
                <exclusion>
                    <groupId>org.slf4j</groupId>
                    <artifactId>slf4j-log4j12</artifactId>
                </exclusion>
                <exclusion>
                    <groupId>org.slf4j</groupId>
                    <artifactId>slf4j-api</artifactId>
                </exclusion>
                <exclusion>
                    <artifactId>log4j</artifactId>
                    <groupId>log4j</groupId>
                </exclusion>
            </exclusions>
        </dependency>



        <dependency>
            <groupId>org.apache.curator</groupId>
            <artifactId>curator-recipes</artifactId>
            <version>2.12.0</version>
            <exclusions>
                <exclusion>
                    <groupId>org.slf4j</groupId>
                    <artifactId>slf4j-log4j12</artifactId>
                </exclusion>
            </exclusions>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

    </dependencies>
</project>

整理完毕,该工具可以被项目组其他使用到分布式锁的成员引用,引用方式也很简单

只需要添加改模快组件就好了,下面举个栗子

<dependency>
    <groupId>com.kamowl</groupId>
    <artifactId>kamo-cloud-base-distribute</artifactId>
    <version>1.0-SNAPSHOT</version>
</dependency>

用的时候把依赖加进来

@Autowired
private DistributedLockByCurator distributedLockByCurator;
distributedLockByCurator.acquireDistributeLock(lock12);
distributedLockByCurator.acquireDistributeLock(lock34);

务必在finally中释放,否则异常不释放锁,下次再获取就失败了

finally {
    //释放操作货主钱包的分布式锁
    distributedLockByCurator.releaseDistributeLock();
    //释放操作司机钱包的分布式锁
    distributedLockByCurator.releaseDistributeLock();
}
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值