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

四。方案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;
        }
    }

    <a href="http://www.nbso.ca/">
    nbso online
    casino reviews
    </a>

    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最大的优势在于性能超强;在实际生产过程中,结合自身情况来决定最适合的分布式锁方案是架构师的必修课。



  • 1
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Redis分布式锁可以通过多种方式实现。其中一种方式是使用Redisson分布式锁。Redisson解决了「锁过期释放,业务没执行完」的问题。它通过在加锁时启动一个后台线程,每隔一段时间检查锁是否还被持有,如果是,则延长锁的生存时间。这样可以确保在业务逻辑执行期间锁不会过期。\[1\] 除了Redisson,还有其他的分布式锁实现方式。其中一种方式是使用SETNX + EXPIRE命令,通过设置一个键值对来表示锁的状态,并设置过期时间来自动释放锁。另一种方式是使用Lua脚本,通过原子性的执行SETNX和EXPIRE两条指令来实现加锁和设置过期时间。还有一种方式是使用Redis的扩展命令SET EX PX NX,通过设置过期时间和唯一随机值来实现加锁。此外,还有Redlock和Redission等开源框架可以实现多机的分布式锁。\[2\] 需要注意的是,分布式锁实现方式需要考虑一些问题,比如加锁后业务逻辑还未执行完成锁已经过期,这会导致其他客户端拿到锁。如果是单节点,这个问题不大,但是在集群环境下,加锁首先会落盘到master节点,然后再复制到slave节点。如果在复制之前master节点挂掉,就会导致锁丢失的问题。为了解决这些问题,Redis官方推荐使用Redisson分布式锁。\[3\] #### 引用[.reference_title] - *1* *2* [Redis实现分布式锁的7种方案](https://blog.csdn.net/qszfly/article/details/126100421)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v91^insert_down28v1,239^v4^insert_chatgpt"}} ] [.reference_item] - *3* [Redis分布式锁的正确实现方式](https://blog.csdn.net/yaomingyang/article/details/104965554)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v91^insert_down28v1,239^v4^insert_chatgpt"}} ] [.reference_item] [ .reference_list ]

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值