两种分布式锁实现方案

 摘要: 两种分布式锁实现方案(一)
一。为何使用分布式锁?
当应用服务器数量超过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);
        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);
                / t 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,性能比较一般;


四。方案2,基于redis的分布式锁
/**
*分布式锁工厂类
*/
public class RedisLockUtil {
private static final Logger logger =  Logger.getLogger(RedisLockUtil.class);
private static Object schemeLock = new Object();
private static Map<String,RedisLockUtil> instances = new ConcurrentHashMap();
public static RedisLockUtil getInstance(String schema){
RedisLockUtil u = instances.get(schema);
if(u==null){
synchronized(schemeLock){
u = instances.get(schema);
if(u == null){
LockObserver lo = new LockObserver(schema);
u = new RedisLockUtil(schema,lo);
instances.put(schema, u);
}
}
}
return u;
}

private Object mutexLock = new Object();
private Map<String,Object> mutexLockMap = new ConcurrentHashMap();
private Map<String,RedisReentrantLock> cache  = new ConcurrentHashMap<String,RedisReentrantLock>();
private DelayQueue<RedisReentrantLock> dq = new DelayQueue<RedisReentrantLock>();
private AbstractLockObserver lo;
public RedisLockUtil(String schema, AbstractLockObserver lo){
Thread th = new Thread(lo);
th.setDaemon(false);
th.setName("Lock Observer:"+schema);
th.start();
clearUselessLocks(schema);
this.lo = lo;
}

public void clearUselessLocks(String schema){
Thread th = new Thread(new Runnable(){
@Override
public void run() {
while(!SystemExitListener.isOver()){
try {
RedisReentrantLock t = dq.take();
if(t.clear()){
String key = t.getKey();
synchronized(getMutex(key)){
cache.remove(key);
}
}
t.resetCleartime();
} catch (InterruptedException e) {
}
}
}

});
th.setDaemon(true);
th.setName("Lock cleaner:"+schema);
th.start();
}

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 RedisReentrantLock getLock(String key,boolean addref){
RedisReentrantLock lock = cache.get(key);
if(lock == null){
synchronized(getMutex(key)){
lock = cache.get(key);
if(lock == null){
lock = new RedisReentrantLock(key,lo);
cache.put(key, lock);
}
}
}
if(addref){
if(!lock.incRef()){
synchronized(getMutex(key)){
lock = cache.get(key);
if(!lock.incRef()){
lock = new RedisReentrantLock(key,lo);
cache.put(key, lock);
}
}
}
}
return lock;
}

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

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

public LockStat lock(String key,int timeout) {
RedisReentrantLock ll = getLock(key,true);
ll.incRef();
try{
if(ll.isOwner(false)){
ll.descrRef();
return LockStat.NONEED;
}
if(ll.lock(timeout)){
return LockStat.SUCCESS;
}else{
ll.descrRef();
if(ll.setCleartime()){
dq.put(ll);
}
return null;
}
}catch(LockNotExistsException e){
ll.descrRef();
return lock(key,timeout);
}catch(RuntimeException e){
ll.descrRef();
throw e;
}
}

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

public void unlock(String key,LockStat stat,boolean keepalive){
if(stat == null) return;
if(LockStat.SUCCESS.equals(stat)){
RedisReentrantLock lock =  getLock(key,false);
boolean candestroy = lock.unlock();
if(candestroy && !keepalive){
if(lock.setCleartime()){
dq.put(lock);
}
}
}
}

public static enum LockStat{
NONEED,
SUCCESS
}
}
/**
*分布式锁本地代理类
*/
public class RedisReentrantLock implements Delayed{
private static final Logger logger =  Logger.getLogger(RedisReentrantLock.class);
    private ReentrantLock reentrantLock = new ReentrantLock();

    private RedisLock redisLock;
    private long timeout = 3*60;
    private CountDownLatch lockcount = new CountDownLatch(1);

    private String key;
    private AbstractLockObserver observer;

    private int ref = 0;
    private Object refLock = new Object();
    private boolean destroyed = false;

    private long cleartime = -1;

    public RedisReentrantLock(String key,AbstractLockObserver observer) {
     this.key = key;
     this.observer = observer;
     initWriteLock();
    }

public boolean isDestroyed() {
return destroyed;
}

private synchronized void initWriteLock(){
redisLock = new RedisLock(key,new LockListener(){
@Override
public void lockAcquired() {
lockcount.countDown();
}
@Override
public long getExpire() {
return 0;
}

@Override
public void lockError() {
/*synchronized(mutex){
mutex.notify();
}*/
lockcount.countDown();
}
     },observer);
}

public boolean incRef(){
synchronized(refLock){
if(destroyed) return false;
     ref ++;
     }
return true;
}

public void descrRef(){
synchronized(refLock){
     ref --;
     }
}

public boolean clear() {
if(destroyed) return true;
synchronized(refLock){
     if(ref > 0){
     return false;
     }
     destroyed = true;
     redisLock.clear();
     redisLock = null;
     return true;
     }
}

    public boolean lock(long timeout) throws LockNotExistsException{
     if(timeout <= 0) timeout = this.timeout;
     //incRef();
        reentrantLock.lock();//多线程竞争时,先拿到第一层锁
        if(redisLock == null){
         reentrantLock.unlock();
         //descrRef();
         throw new LockNotExistsException();
        }
        try{
         lockcount = new CountDownLatch(1);
         boolean res = redisLock.trylock(timeout);
         if(!res){    
         lockcount.await(timeout, TimeUnit.SECONDS);
//mutex.wait(timeout*1000);
         if(!redisLock.doExpire()){
         reentrantLock.unlock();
return false;
}
         }
         return true;
        }catch(InterruptedException e){
         reentrantLock.unlock();
         return false;
        }
    }

    public boolean lock() throws LockNotExistsException {
     return lock(timeout);
    }

    public boolean unlock(){   
     if(!isOwner(true)) {
     try{
     throw new RuntimeException("big ================================================ error.key:"+key);
     }catch(Exception e){
     logger.error("err:"+e,e);
     }
     return false;
     }
        try{
         redisLock.unlock();
         reentrantLock.unlock();//多线程竞争时,释放最外层锁
        }catch(RuntimeException e){
         reentrantLock.unlock();//多线程竞争时,释放最外层锁
         throw e;
        }finally{
         descrRef();
        }
        return canDestroy();
    }

    public boolean canDestroy(){
     synchronized(refLock){
     return ref <= 0;
     }
    }

    public String getKey() {
return key;
}

public void setKey(String key) {
this.key = key;
}

public boolean isOwner(boolean check) {
     synchronized(refLock){
     if(redisLock == null) {
     logger.error("reidsLock is null:key="+key);
     return false;
     }
     boolean a = reentrantLock.isHeldByCurrentThread();
     boolean b = redisLock.isOwner();
     if(check){
     if(!a || !b){
     logger.error(key+";a:"+a+";b:"+b);
     }
     }
     return a && b;
     }
    }

public boolean setCleartime() {
synchronized(this){
if(cleartime>0) return false;
this.cleartime = System.currentTimeMillis() + 10*1000; 
return true;
}
}

public void resetCleartime(){
synchronized(this){
this.cleartime = -1;
}
}

@Override
public int compareTo(Delayed object) {
if(object instanceof RedisReentrantLock){
RedisReentrantLock t = (RedisReentrantLock)object;
        long l = this.cleartime - t.cleartime;

if(l > 0) return 1 ; //比当前的小则返回1,比当前的大则返回-1,否则为0
else if(l < 0 ) return -1;
else return 0;
}
return 0;
}

@Override
public long getDelay(TimeUnit unit) {
 long d = unit.convert(cleartime - System.currentTimeMillis(), TimeUnit.MILLISECONDS);
     return d;
}

}
/**
*使用Redis实现的分布式锁
*基本工作原理如下:
*1. 使用setnx(key,时间戮+超时),如果设置成功,则直接拿到锁
*2. 如果设置不成功,获取key的值v1(它的到期时间戮),跟当前时间对比,看是否已经超时
*3. 如果超时(说明拿到锁的结点已经挂掉),v2=getset(key,时间戮+超时+1),判断v2是否等于v1,如果相等,加锁成功,否则加锁失败,等过段时间再重试(200MS)
*/
public class RedisLock implements LockListener{
private String key;
private boolean owner = false;
private AbstractLockObserver observer = null;
private LockListener lockListener = null;
private boolean waiting = false;
private long expire;//锁超时时间,以秒为单位
private boolean expired = false;

public RedisLock(String key, LockListener lockListener, AbstractLockObserver observer) {
this.key = key;
this.lockListener = lockListener;
this.observer = observer;
}

public boolean trylock(long expire) {
synchronized(this){
if(owner){
return true;
}
this.expire = expire;
this.expired = false;
if(!waiting){
owner = observer.tryLock(key,expire);
if(!owner){
waiting = true;
observer.addLockListener(key, this);
}
}
return owner;
}
}

public boolean isOwner() {
return owner;
}

public void unlock() {
synchronized(this){
observer.unLock(key);
owner = false;
}
}

public void clear() {
synchronized(this){
if(waiting) {
observer.removeLockListener(key);
waiting = false;
}
}
}

public boolean doExpire(){
synchronized(this){
if(owner) return true;
if(expired) return false;
expired =  true;
clear();
}
return false;
}

@Override
public void lockAcquired() {
synchronized(this){
if(expired){
unlock();
return;
}
owner = true;
waiting = false;
}
lockListener.lockAcquired();
}

@Override
public long getExpire() {
return this.expire;
}

@Override
public void lockError() {
synchronized(this){
owner = false;
waiting = false;
lockListener.lockError();
}
}

}
public class LockObserver extends AbstractLockObserver implements Runnable{
private CacheRedisClient client;
private Object mutex = new Object();
private Map<String,LockListener> lockMap = new ConcurrentHashMap();
private boolean stoped = false;
private long interval = 500;
private boolean terminated = false;
private CountDownLatch doneSignal = new CountDownLatch(1);

public LockObserver(String schema){
client = new CacheRedisClient(schema);

SystemExitListener.addTerminateListener(new ExitHandler(){
public void run() {
stoped = true;
try {
doneSignal.await();
} catch (InterruptedException e) {
}
}
});
}


public void addLockListener(String key,LockListener listener){
if(terminated){
listener.lockError();
return;
}
synchronized(mutex){
lockMap.put(key, listener);
}
}

public void removeLockListener(String key){
synchronized(mutex){
lockMap.remove(key);
}
}

@Override
public void run() {
while(!terminated){
long p1 = System.currentTimeMillis();
Map<String,LockListener> clone = new HashMap();
synchronized(mutex){
clone.putAll(lockMap);
}
Set<String> keyset = clone.keySet();
if(keyset.size() > 0){
ConnectionFactory.setSingleConnectionPerThread(keyset.size());
for(String key : keyset){
LockListener ll = clone.get(key);
try{
    if(tryLock(key,ll.getExpire())) {
     ll.lockAcquired();
     removeLockListener(key);
    }
}catch(Exception e){
ll.lockError();
removeLockListener(key);
}
}
ConnectionFactory.releaseThreadConnection();
}else{
if(stoped){
terminated = true;
doneSignal.countDown();
return;
}
}
try {
long p2 = System.currentTimeMillis();
long cost = p2 - p1;
if(cost <= interval){
Thread.sleep(interval - cost);
}else{
Thread.sleep(interval*2);
}
} catch (InterruptedException e) {
}
}

}


/**
 * 超时时间单位为s!!!
 * @param key
 * @param expire
 * @return
 */
public boolean tryLock(final String key,final long expireInSecond){
if(terminated) return false;
final long tt = System.currentTimeMillis();
final long expire = expireInSecond * 1000;
final Long ne = tt + expire;
List<Object> mm = client.multi(key, new MultiBlock(){
@Override
public void execute() {
transaction.setnxObject(key, ne);
transaction.get(SafeEncoder.encode(key));
}
});
Long res = (Long)mm.get(0);
    if(new Long(1).equals(res)) {
     return true;
    }else{
     byte[] bb = (byte[])mm.get(1);
Long ex = client.deserialize(bb);
     if(ex == null || tt > ex){
     Long old = client.getSet(key, new Long(ne+1));
     if(old == null || (ex == null&&old==null) || (ex!=null&&ex.equals(old))){
     return true;
     }
     }
    }
    return false;
}

public void unLock(String key){
client.del(key);
}
}
使用本方案实现的分布式锁,可以完美地解决锁重入问题;通过引入超时也避免了死锁问题;性能方面,笔者自测试结果如下:

500线程 tps = 35000
[root@DB1 benchtest-util]# target/benchtest/bin/TestFastRedis /data/config/util/config_0_11.properties lock 500 500000
线程总时间:6553466;平均:13.106932
实际总时间:13609; 平均:0.027218

TPS达到35000,比方案1强了整整一个数量级;
五。总结
本文介绍了两种分布式锁的实现方案;方案1最大的优势在于避免结点挂掉后导致的死锁;方案2最大的优势在于性能超强;在实际生产过程中,结合自身情况来决定最适合的分布式锁方案是架构师的必修课。
http://rdc.gleasy.com/%E4%B8%A4%E7%A7%8D%E5%88%86%E5%B8%83%E5%BC%8F%E9%94%81%E5%AE%9E%E7%8E%B0%E6%96%B9%E6%A1%882.html

四。方案2,基于redis的分布式锁
/**
*分布式锁工厂类
*/
public class RedisLockUtil {
private static final Logger logger =  Logger.getLogger(RedisLockUtil.class);
private static Object schemeLock = new Object();
private static Map<String,RedisLockUtil> instances = new ConcurrentHashMap();
public static RedisLockUtil getInstance(String schema){
RedisLockUtil u = instances.get(schema);
if(u==null){
synchronized(schemeLock){
u = instances.get(schema);
if(u == null){
LockObserver lo = new LockObserver(schema);
u = new RedisLockUtil(schema,lo);
instances.put(schema, u);
}
}
}
return u;
}

private Object mutexLock = new Object();
private Map<String,Object> mutexLockMap = new ConcurrentHashMap();
private Map<String,RedisReentrantLock> cache  = new ConcurrentHashMap<String,RedisReentrantLock>();
private DelayQueue<RedisReentrantLock> dq = new DelayQueue<RedisReentrantLock>();
private AbstractLockObserver lo;
public RedisLockUtil(String schema, AbstractLockObserver lo){
Thread th = new Thread(lo);
th.setDaemon(false);
th.setName("Lock Observer:"+schema);
th.start();
clearUselessLocks(schema);
this.lo = lo;
}

public void clearUselessLocks(String schema){
Thread th = new Thread(new Runnable(){
@Override
public void run() {
while(!SystemExitListener.isOver()){
try {
RedisReentrantLock t = dq.take();
if(t.clear()){
String key = t.getKey();
synchronized(getMutex(key)){
cache.remove(key);
}
}
t.resetCleartime();
} catch (InterruptedException e) {
}
}
}

});
th.setDaemon(true);
th.setName("Lock cleaner:"+schema);
th.start();
}

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 RedisReentrantLock getLock(String key,boolean addref){
RedisReentrantLock lock = cache.get(key);
if(lock == null){
synchronized(getMutex(key)){
lock = cache.get(key);
if(lock == null){
lock = new RedisReentrantLock(key,lo);
cache.put(key, lock);
}
}
}
if(addref){
if(!lock.incRef()){
synchronized(getMutex(key)){
lock = cache.get(key);
if(!lock.incRef()){
lock = new RedisReentrantLock(key,lo);
cache.put(key, lock);
}
}
}
}
return lock;
}

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

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

public LockStat lock(String key,int timeout) {
RedisReentrantLock ll = getLock(key,true);
ll.incRef();
try{
if(ll.isOwner(false)){
ll.descrRef();
return LockStat.NONEED;
}
if(ll.lock(timeout)){
return LockStat.SUCCESS;
}else{
ll.descrRef();
if(ll.setCleartime()){
dq.put(ll);
}
return null;
}
}catch(LockNotExistsException e){
ll.descrRef();
return lock(key,timeout);
}catch(RuntimeException e){
ll.descrRef();
throw e;
}
}

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

public void unlock(String key,LockStat stat,boolean keepalive){
if(stat == null) return;
if(LockStat.SUCCESS.equals(stat)){
RedisReentrantLock lock =  getLock(key,false);
boolean candestroy = lock.unlock();
if(candestroy && !keepalive){
if(lock.setCleartime()){
dq.put(lock);
}
}
}
}

public static enum LockStat{
NONEED,
SUCCESS
}
}
/**
*分布式锁本地代理类
*/
public class RedisReentrantLock implements Delayed{
private static final Logger logger =  Logger.getLogger(RedisReentrantLock.class);
    private ReentrantLock reentrantLock = new ReentrantLock();

    private RedisLock redisLock;
    private long timeout = 3*60;
    private CountDownLatch lockcount = new CountDownLatch(1);

    private String key;
    private AbstractLockObserver observer;

    private int ref = 0;
    private Object refLock = new Object();
    private boolean destroyed = false;

    private long cleartime = -1;

    public RedisReentrantLock(String key,AbstractLockObserver observer) {
     this.key = key;
     this.observer = observer;
     initWriteLock();
    }

public boolean isDestroyed() {
return destroyed;
}

private synchronized void initWriteLock(){
redisLock = new RedisLock(key,new LockListener(){
@Override
public void lockAcquired() {
lockcount.countDown();
}
@Override
public long getExpire() {
return 0;
}

@Override
public void lockError() {
/*synchronized(mutex){
mutex.notify();
}*/
lockcount.countDown();
}
     },observer);
}

public boolean incRef(){
synchronized(refLock){
if(destroyed) return false;
     ref ++;
     }
return true;
}

public void descrRef(){
synchronized(refLock){
     ref --;
     }
}

public boolean clear() {
if(destroyed) return true;
synchronized(refLock){
     if(ref > 0){
     return false;
     }
     destroyed = true;
     redisLock.clear();
     redisLock = null;
     return true;
     }
}

    public boolean lock(long timeout) throws LockNotExistsException{
     if(timeout <= 0) timeout = this.timeout;
     //incRef();
        reentrantLock.lock();//多线程竞争时,先拿到第一层锁
        if(redisLock == null){
         reentrantLock.unlock();
         //descrRef();
         throw new LockNotExistsException();
        }
        try{
         lockcount = new CountDownLatch(1);
         boolean res = redisLock.trylock(timeout);
         if(!res){    
         lockcount.await(timeout, TimeUnit.SECONDS);
//mutex.wait(timeout*1000);
         if(!redisLock.doExpire()){
         reentrantLock.unlock();
return false;
}
         }
         return true;
        }catch(InterruptedException e){
         reentrantLock.unlock();
         return false;
        }
    }

    public boolean lock() throws LockNotExistsException {
     return lock(timeout);
    }

    public boolean unlock(){   
     if(!isOwner(true)) {
     try{
     throw new RuntimeException("big ================================================ error.key:"+key);
     }catch(Exception e){
     logger.error("err:"+e,e);
     }
     return false;
     }
        try{
         redisLock.unlock();
         reentrantLock.unlock();//多线程竞争时,释放最外层锁
        }catch(RuntimeException e){
         reentrantLock.unlock();//多线程竞争时,释放最外层锁
         throw e;
        }finally{
         descrRef();
        }
        return canDestroy();
    }

    public boolean canDestroy(){
     synchronized(refLock){
     return ref <= 0;
     }
    }

    public String getKey() {
return key;
}

public void setKey(String key) {
this.key = key;
}

public boolean isOwner(boolean check) {
     synchronized(refLock){
     if(redisLock == null) {
     logger.error("reidsLock is null:key="+key);
     return false;
     }
     boolean a = reentrantLock.isHeldByCurrentThread();
     boolean b = redisLock.isOwner();
     if(check){
     if(!a || !b){
     logger.error(key+";a:"+a+";b:"+b);
     }
     }
     return a && b;
     }
    }

public boolean setCleartime() {
synchronized(this){
if(cleartime>0) return false;
this.cleartime = System.currentTimeMillis() + 10*1000; 
return true;
}
}

public void resetCleartime(){
synchronized(this){
this.cleartime = -1;
}
}

@Override
public int compareTo(Delayed object) {
if(object instanceof RedisReentrantLock){
RedisReentrantLock t = (RedisReentrantLock)object;
        long l = this.cleartime - t.cleartime;

if(l > 0) return 1 ; //比当前的小则返回1,比当前的大则返回-1,否则为0
else if(l < 0 ) return -1;
else return 0;
}
return 0;
}

@Override
public long getDelay(TimeUnit unit) {
 long d = unit.convert(cleartime - System.currentTimeMillis(), TimeUnit.MILLISECONDS);
     return d;
}

}
/**
*使用Redis实现的分布式锁
*基本工作原理如下:
*1. 使用setnx(key,时间戮+超时),如果设置成功,则直接拿到锁
*2. 如果设置不成功,获取key的值v1(它的到期时间戮),跟当前时间对比,看是否已经超时
*3. 如果超时(说明拿到锁的结点已经挂掉),v2=getset(key,时间戮+超时+1),判断v2是否等于v1,如果相等,加锁成功,否则加锁失败,等过段时间再重试(200MS)
*/
public class RedisLock implements LockListener{
private String key;
private boolean owner = false;
private AbstractLockObserver observer = null;
private LockListener lockListener = null;
private boolean waiting = false;
private long expire;//锁超时时间,以秒为单位
private boolean expired = false;

public RedisLock(String key, LockListener lockListener, AbstractLockObserver observer) {
this.key = key;
this.lockListener = lockListener;
this.observer = observer;
}

public boolean trylock(long expire) {
synchronized(this){
if(owner){
return true;
}
this.expire = expire;
this.expired = false;
if(!waiting){
owner = observer.tryLock(key,expire);
if(!owner){
waiting = true;
observer.addLockListener(key, this);
}
}
return owner;
}
}

public boolean isOwner() {
return owner;
}

public void unlock() {
synchronized(this){
observer.unLock(key);
owner = false;
}
}

public void clear() {
synchronized(this){
if(waiting) {
observer.removeLockListener(key);
waiting = false;
}
}
}

public boolean doExpire(){
synchronized(this){
if(owner) return true;
if(expired) return false;
expired =  true;
clear();
}
return false;
}

@Override
public void lockAcquired() {
synchronized(this){
if(expired){
unlock();
return;
}
owner = true;
waiting = false;
}
lockListener.lockAcquired();
}

@Override
public long getExpire() {
return this.expire;
}

@Override
public void lockError() {
synchronized(this){
owner = false;
waiting = false;
lockListener.lockError();
}
}

}
public class LockObserver extends AbstractLockObserver implements Runnable{
private CacheRedisClient client;
private Object mutex = new Object();
private Map<String,LockListener> lockMap = new ConcurrentHashMap();
private boolean stoped = false;
private long interval = 500;
private boolean terminated = false;
private CountDownLatch doneSignal = new CountDownLatch(1);

public LockObserver(String schema){
client = new CacheRedisClient(schema);

SystemExitListener.addTerminateListener(new ExitHandler(){
public void run() {
stoped = true;
try {
doneSignal.await();
} catch (InterruptedException e) {
}
}
});
}


public void addLockListener(String key,LockListener listener){
if(terminated){
listener.lockError();
return;
}
synchronized(mutex){
lockMap.put(key, listener);
}
}

public void removeLockListener(String key){
synchronized(mutex){
lockMap.remove(key);
}
}

@Override
public void run() {
while(!terminated){
long p1 = System.currentTimeMillis();
Map<String,LockListener> clone = new HashMap();
synchronized(mutex){
clone.putAll(lockMap);
}
Set<String> keyset = clone.keySet();
if(keyset.size() > 0){
ConnectionFactory.setSingleConnectionPerThread(keyset.size());
for(String key : keyset){
LockListener ll = clone.get(key);
try{
    if(tryLock(key,ll.getExpire())) {
     ll.lockAcquired();
     removeLockListener(key);
    }
}catch(Exception e){
ll.lockError();
removeLockListener(key);
}
}
ConnectionFactory.releaseThreadConnection();
}else{
if(stoped){
terminated = true;
doneSignal.countDown();
return;
}
}
try {
long p2 = System.currentTimeMillis();
long cost = p2 - p1;
if(cost <= interval){
Thread.sleep(interval - cost);
}else{
Thread.sleep(interval*2);
}
} catch (InterruptedException e) {
}
}

}


/**
 * 超时时间单位为s!!!
 * @param key
 * @param expire
 * @return
 */
public boolean tryLock(final String key,final long expireInSecond){
if(terminated) return false;
final long tt = System.currentTimeMillis();
final long expire = expireInSecond * 1000;
final Long ne = tt + expire;
List<Object> mm = client.multi(key, new MultiBlock(){
@Override
public void execute() {
transaction.setnxObject(key, ne);
transaction.get(SafeEncoder.encode(key));
}
});
Long res = (Long)mm.get(0);
    if(new Long(1).equals(res)) {
     return true;
    }else{
     byte[] bb = (byte[])mm.get(1);
Long ex = client.deserialize(bb);
     if(ex == null || tt > ex){
     Long old = client.getSet(key, new Long(ne+1));
     if(old == null || (ex == null&&old==null) || (ex!=null&&ex.equals(old))){
     return true;
     }
     }
    }
    return false;
}

public void unLock(String key){
client.del(key);
}
}
使用本方案实现的分布式锁,可以完美地解决锁重入问题;通过引入超时也避免了死锁问题;性能方面,笔者自测试结果如下:

500线程 tps = 35000
[root@DB1 benchtest-util]# target/benchtest/bin/TestFastRedis /data/config/util/config_0_11.properties lock 500 500000
线程总时间:6553466;平均:13.106932
实际总时间:13609; 平均:0.027218

TPS达到35000,比方案1强了整整一个数量级;
五。总结
本文介绍了两种分布式锁的实现方案;方案1最大的优势在于避免结点挂掉后导致的死锁;方案2最大的优势在于性能超强;在实际生产过程中,结合自身情况来决定最适合的分布式锁方案是架构师的必修课。

https://my.oschina.net/gaowm/blog/522479
http://rdc.gleasy.com/%E4%B8%A4%E7%A7%8D%E5%88%86%E5%B8%83%E5%BC%8F%E9%94%81%E5%AE%9E%E7%8E%B0%E6%96%B9%E6%A1%882.html 

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值