一。为何使用分布式锁?
当应用服务器数量超过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,性能比较一般;