关闭

两种分布式锁实现方案(一)

2503人阅读 评论(0) 收藏 举报
分类:

一。为何使用分布式锁?
当应用服务器数量超过1台,对相同数据的访问可能造成访问冲突(特别是写冲突)。单纯使用关系数据库比如MYSQL的应用可以借助于事务来实现锁,也可以使用版本号等实现乐观锁,最大的缺陷就是可用性降低(性能差)。对于GLEASY这种满足大规模并发访问请求的应用来说,使用数据库事务来实现数据库就有些捉襟见肘了。另外对于一些不依赖数据库的应用,比如分布式文件系统,为了保证同一文件在大量读写操作情况下的正确性,必须引入分布式锁来约束对同一文件的并发操作。

二。对分布式锁的要求
1.高性能(分布式锁不能成为系统的性能瓶颈)
2.避免死锁(拿到锁的结点挂掉不会导致其它结点永远无法继续)
3.支持锁重入

三。方案1,基于zookeeper的分布式锁

/**
 * DistributedLockUtil.java
 * 分布式锁工厂类,所有分布式请求都由该工厂类负责
 */
public class DistributedLockUtil {
    private static Object schemeLock = new Object();
    private static Object mutexLock = new Object();
    private static Map<String, Object> mutexLockMap = new ConcurrentHashMap();
    private String schema;
    private Map<String, DistributedReentrantLock> cache = new ConcurrentHashMap<String, DistributedReentrantLock>();

    private static Map<String, DistributedLockUtil> instances = new ConcurrentHashMap();

    public static DistributedLockUtil getInstance(String schema) {
        DistributedLockUtil u = instances.get(schema);
        if (u == null) {
            synchronized (schemeLock) {
                u = instances.get(schema);
                if (u == null) {
                    u = new DistributedLockUtil(schema);
                    instances.put(schema, u);
                }
            }
        }
        return u;
    }

    private DistributedLockUtil(String schema) {
        this.schema = schema;
    }

    private Object getMutex(String key) {
        Object mx = mutexLockMap.get(key);
        if (mx == null) {
            synchronized (mutexLock) {
                mx = mutexLockMap.get(key);
                if (mx == null) {
                    mx = new Object();
                    mutexLockMap.put(key, mx);
                }
            }
        }
        return mx;
    }

    private DistributedReentrantLock getLock(String key) {
        DistributedReentrantLock lock = cache.get(key);
        if (lock == null) {
            synchronized (getMutex(key)) {
                lock = cache.get(key);
                if (lock == null) {
                    lock = new DistributedReentrantLock(key, schema);
                    cache.put(key, lock);
                }
            }
        }
        return lock;
    }

    public void reset() {
        for (String s : cache.keySet()) {
            getLock(s).unlock();
        }
    }

    /**
     * 尝试加锁
     * 如果当前线程已经拥有该锁的话,直接返回false,表示不用再次加锁,此时不应该再调用unlock进行解锁
     *
     * @param key
     * @return
     * @throws InterruptedException
     * @throws KeeperException
     */
    public LockStat lock(String key) throws InterruptedException, KeeperException {
        if (getLock(key).isOwner()) {
            return LockStat.NONEED;
        }
        getLock(key).lock();
        return LockStat.SUCCESS;
    }

    public void clearLock(String key) throws InterruptedException, KeeperException {
        synchronized (getMutex(key)) {
            DistributedReentrantLock l = cache.get(key);
            l.clear();
            cache.remove(key);
        }
    }

    public void unlock(String key, LockStat stat) throws InterruptedException, KeeperException {
        unlock(key, stat, false);
    }

    public void unlock(String key, LockStat stat, boolean keepalive) throws InterruptedException, KeeperException {
        if (stat == null) return;
        if (LockStat.SUCCESS.equals(stat)) {
            DistributedReentrantLock lock = getLock(key);
            boolean hasWaiter = lock.unlock();
            if (!hasWaiter && !keepalive) {
                synchronized (getMutex(key)) {
                    lock.clear();
                    cache.remove(key);
                }
            }
        }
    }

    public static enum LockStat {
        NONEED,
        SUCCESS
    }
}

/**
 * DistributedReentrantLock.java
 * 本地线程之间锁争用,先使用虚拟机内部锁机制,减少结点间通信开销
 */
public class DistributedReentrantLock {
    private static final Logger logger = Logger.getLogger(DistributedReentrantLock.class);
    private ReentrantLock reentrantLock = new ReentrantLock();

    private WriteLock writeLock;
    private long timeout = 3 * 60 * 1000;

    private final Object mutex = new Object();
    private String dir;
    private String schema;

    private final ExitListener exitListener = new ExitListener() {
        @Override
        public void execute() {
            initWriteLock();
        }
    };

    private synchronized void initWriteLock() {
        logger.debug("初始化writeLock");
        writeLock = new WriteLock(dir, new LockListener() {

            @Override
            public void lockAcquired() {
                synchronized (mutex) {
                    mutex.notify();
                }
            }

            @Override
            public void lockReleased() {
            }

        }, schema);

        if (writeLock != null && writeLock.zk != null) {
            writeLock.zk.addExitListener(exitListener);
        }

        synchronized (mutex) {
            mutex.notify();
        }
    }

    public DistributedReentrantLock(String dir, String schema) {
        this.dir = dir;
        this.schema = schema;
        initWriteLock();
    }

    public void lock(long timeout) throws InterruptedException, KeeperException {
        reentrantLock.lock();//多线程竞争时,先拿到第一层锁
        try {
            boolean res = writeLock.trylock();
            if (!res) {
                synchronized (mutex) {
                    mutex.wait(timeout);
                }
                if (writeLock == null || !writeLock.isOwner()) {
                    throw new InterruptedException("锁超时");
                }
            }
        } catch (InterruptedException e) {
            reentrantLock.unlock();
            throw e;
        } catch (KeeperException e) {
            reentrantLock.unlock();
            throw e;
        }
    }

    public void lock() throws InterruptedException, KeeperException {
        lock(timeout);
    }

    public void destroy() throws KeeperException {
        writeLock.unlock();
    }


    public boolean unlock() {
        if (!isOwner()) return false;
        try {
            writeLock.unlock();
            reentrantLock.unlock();//多线程竞争时,释放最外层锁
        } catch (RuntimeException e) {
            reentrantLock.unlock();//多线程竞争时,释放最外层锁
            throw e;
        }

        return reentrantLock.hasQueuedThreads();
    }


    public boolean isOwner() {
        return reentrantLock.isHeldByCurrentThread() && writeLock.isOwner();
    }

    public void clear() {
        writeLock.clear();
    }

}

/**
 * WriteLock.java
 * 基于zk的锁实现
 * 一个最简单的场景如下:
 * 1.结点A请求加锁,在特定路径下注册自己(会话自增结点),得到一个ID号1
 * 2.结点B请求加锁,在特定路径下注册自己(会话自增结点),得到一个ID号2
 * 3.结点A获取所有结点ID,判断出来自己是最小结点号,于是获得锁
 * 4.结点B获取所有结点ID,判断出来自己不是最小结点,于是监听小于自己的最大结点(结点A)变更事件
 * 5.结点A拿到锁,处理业务,处理完,释放锁(删除自己)
 * 6.结点B收到结点A变更事件,判断出来自己已经是最小结点号,于是获得锁。
 */
public class WriteLock extends ZkPrimative {
    private static final Logger LOG = Logger.getLogger(WriteLock.class);

    private final String dir;
    private String id;
    private LockNode idName;
    private String ownerId;
    private String lastChildId;
    private byte[] data = {0x12, 0x34};
    private LockListener callback;

    public WriteLock(String dir, String schema) {
        super(schema, true);
        this.dir = dir;
    }

    public WriteLock(String dir, LockListener callback, String schema) {
        this(dir, schema);
        <a href = "http://www.nbso.ca/" > nbso online casino reviews</a > this.callback = callback;
    }

    public LockListener getLockListener() {
        return this.callback;
    }

    public void setLockListener(LockListener callback) {
        this.callback = callback;
    }

    public synchronized void unlock() throws RuntimeException {
        if (zk == null || zk.isClosed()) {
            return;
        }
        if (id != null) {
            try {
                zk.delete(id, -1);
            } catch (InterruptedException e) {
                LOG.warn("Caught: "e, e);
                //set that we have been interrupted.
                Thread.currentThread().interrupt();
            } catch (KeeperException.NoNodeException e) {
                // do nothing
            } catch (KeeperException e) {
                LOG.warn("Caught: "e, e);
                throw (RuntimeException) new RuntimeException(e.getMessage()).
                        initCause(e);
            } finally {
                if (callback != null) {
                    callback.lockReleased();
                }
                id = null;
            }
        }
    }

    private class LockWatcher implements Watcher {
        public void process(WatchedEvent event) {
            LOG.debug("Watcher fired on path: "event.getPath()" state: "
                    event.getState()" type "event.getType());
            try {
                trylock();
            } catch (Exception e) {
                LOG.warn("Failed to acquire lock: "e, e);
            }
        }
    }

    private void findPrefixInChildren(String prefix, ZooKeeper zookeeper, String dir)
            throws KeeperException, InterruptedException {
        List<String> names = zookeeper.getChildren(dir, false);
        for (String name : names) {
            if (name.startsWith(prefix)) {
                id = dir "/" name;
                if (LOG.isDebugEnabled()) {
                    LOG.debug("Found id created last time: "id);
                }
                break;
            }
        }
        if (id == null) {
            id = zookeeper.create(dir"/"prefix, data,
                    acl, EPHEMERAL_SEQUENTIAL);

            if (LOG.isDebugEnabled()) {
                LOG.debug("Created id: "id);
            }
        }

    }

    public void clear() {
        if (zk == null || zk.isClosed()) {
            return;
        }
        try {
            zk.delete(dir, -1);
        } catch (Exception e) {
            LOG.error("clear error: "e, e);
        }
    }

    public synchronized boolean trylock() throws KeeperException, InterruptedException {
        if (zk == null) {
            LOG.info("zk 是空");
            return false;
        }
        if (zk.isClosed()) {
            LOG.info("zk 已经关闭");
            return false;
        }
        ensurePathExists(dir);

        LOG.debug("id:"id);
        do {
            if (id == null) {
                long sessionId = zk.getSessionId();
                String prefix = "x-" sessionId "-";
                idName = new LockNode(id);
                LOG.debug("idName:"idName);
            }
            if (id != null) {
                List<String> names = zk.getChildren(dir, false);
                if (names.isEmpty()) {
                    LOG.warn("No children in: "dir" when we've just "
                            "created one! Lets recreate it...");
                    id = null;
                } else {
                    SortedSet<LockNode> sortedNames = new TreeSet<LockNode>();
                    for (String name : names) {
                        sortedNames.add(new LockNode(dir"/"name));
                    }
                    ownerId = sortedNames.first().getName();
                    LOG.debug("all:"sortedNames);
                    SortedSet<LockNode> lessThanMe = sortedNames.headSet(idName);
                    LOG.debug("less than me:"lessThanMe);
                    if (!lessThanMe.isEmpty()) {
                        LockNode lastChildName = lessThanMe.last();
                        lastChildId = lastChildName.getName();
                        if (LOG.isDebugEnabled()) {
                            LOG.debug("watching less than me node: "lastChildId);
                        }
                        Stat stat = zk.exists(lastChildId, new LockWatcher());
                        if (stat != null) {
                            return Boolean.FALSE;
                        } else {
                            LOG.warn("Could not find the"
                                    " stats for less than me: "lastChildName.getName());
                        }
                    } else {
                        if (isOwner()) {
                            if (callback != null) {
                                callback.lockAcquired();
                            }
                            return Boolean.TRUE;
                        }
                    }
                }
            }
        }
        while (id == null);
        return Boolean.FALSE;
    }

    public String getDir() {
        return dir;
    }

    public boolean isOwner() {
        return id != null && ownerId != null && id.equals(ownerId);
    }

    public String getId() {
        return this.id;
    }
}


使用本方案实现的分布式锁,可以很好地解决锁重入的问题,而且使用会话结点来避免死锁;性能方面,根据笔者自测结果,加锁解锁各一次算是一个操作,本方案实现的分布式锁,TPS大概为2000-3000,性能比较一般;

0
0

查看评论
* 以上用户言论只代表其个人观点,不代表CSDN网站的观点或立场
    个人资料
    • 访问:7461683次
    • 积分:64600
    • 等级:
    • 排名:第31名
    • 原创:252篇
    • 转载:2676篇
    • 译文:3篇
    • 评论:700条
    文章分类
    最新评论