基於官方推薦的Redisson實現Redis分佈式鎖

RedLock 簡介

在不同進程需要互斥地訪問共享資源時,分佈式鎖是一種非常有用的技術手段。實現高效的分佈式鎖有三個屬性需要考慮:

安全屬性:互斥,不管什麼時候,只有一個客戶端持有鎖
效率屬性A:不會死鎖
效率屬性B:容錯,只要大多數redis節點能夠正常工作,客戶端端都能獲取和釋放鎖。
解鈴還須繫鈴人。加鎖和解鎖必須是同一個客戶端,客戶端自己不能把別人加的鎖給解了。  

Redlock是redis官方提出的實現分佈式鎖管理器的算法。這個算法會比一般的普通方法更加安全可靠。
Redisson提供了很多種類型的分佈式鎖和分佈式同步器,如下:

- 8.1. 可重入鎖(Reentrant Lock) --例子以實現
- 8.2. 公平鎖(Fair Lock)        --例子以實現
- 8.3. 聯鎖(MultiLock)
- 8.4. 紅鎖(RedLock)
- 8.5. 讀寫鎖(ReadWriteLock)
- 8.6. 信號量(Semaphore)
- 8.7. 可過期性信號量(PermitExpirableSemaphore)
- 8.8. 閉鎖(CountDownLatch)

詳情可查看下面的參考鏈接

分佈式鎖實現

@Component
public class RedisLocker implements DistributedLocker {

    private final static String LOCKER_PREFIX = "lock:";

    /**
     * The Redisson client.
     */
    @Autowired
    RedissonClient redissonClient;

    @Override
    public <T> T lock(String resourceName, AcquiredLockWorker<T> worker) throws Exception {
        return fairLock(resourceName, worker, 100);
    }

    @Override
    public <T> T tryLock(String resourceName, AcquiredLockWorker<T> worker, int lockTime) throws Exception {
        RLock lock = redissonClient.getLock(LOCKER_PREFIX + resourceName);
        // (可重入鎖)最多等待100秒,鎖定後經過lockTime秒後自動解鎖
        boolean success = lock.tryLock(100, lockTime, TimeUnit.SECONDS);
        if (success) {
            try {
                return worker.invokeAfterLockAcquire();
            } finally {
                lock.unlock();
            }
        }
        throw new UnableToAcquireLockException();
    }

    @Override
    public <T> T fairLock(String resourceName, AcquiredLockWorker<T> worker, int lockTime) throws Exception {
        RLock lock = redissonClient.getFairLock(LOCKER_PREFIX + resourceName);
        // (公平鎖)最多等待100秒,鎖定後經過lockTime秒後自動解鎖
        boolean success = lock.tryLock(100, lockTime, TimeUnit.SECONDS);
        if (success) {
            try {
                return worker.invokeAfterLockAcquire();
            } finally {
                lock.unlock();
            }
        }
        throw new UnableToAcquireLockException();
    }

}

配置客戶端

@Configuration
public class RedissonConfiguration {

    /**
     * Gets client.
     *
     * @return the client
     */
    @Bean
    public RedissonClient redissonClient() {
        //如果是默認本地6379,則可不必配置,否則參照
        // https://github.com/redisson/redisson/wiki/2.-%E9%85%8D%E7%BD%AE%E6%96%B9%E6%B3%95  配置
        return Redisson.create();
    }
}

獲取鎖後的業務處理

/**
 * 獲取鎖後需要做的邏輯
 *
 * @param <T> the type parameter
 * @author Liaozihong
 */
public interface AcquiredLockWorker<T> {
    /**
     * Invoke after lock aquire t.
     *
     * @return the t
     * @throws Exception the exception
     */
    T invokeAfterLockAcquire() throws Exception;
}

測試類

@RestController
@Slf4j
public class RedissonLockTestApi {
    /**
     * The Distributed locker.
     */
    @Autowired
    RedisLocker distributedLocker;

    /**
     * Test redlock string.
     * 併發下分佈式鎖測試API
     *
     * @return the string
     * @throws Exception the exception
     */
    @RequestMapping(value = "/redlock")
    public String testRedlock() throws Exception {
        CountDownLatch startSignal = new CountDownLatch(1);
        CountDownLatch doneSignal = new CountDownLatch(5);
        // 測試5個併發
        for (int i = 0; i < 5; ++i) {
            new Thread(new Worker(startSignal, doneSignal)).start();
        }
        startSignal.countDown(); // let all threads proceed
        doneSignal.await();
        System.out.println("All processors done. Shutdown connection");
        return "redlock";
    }

    /**
     * Worker
     * <p/>
     * Created in 2018.12.05
     * <p/>
     *
     * @author Liaozihong
     */
    class Worker implements Runnable {
        private final CountDownLatch startSignal;
        private final CountDownLatch doneSignal;

        /**
         * Instantiates a new Worker.
         *
         * @param startSignal the start signal
         * @param doneSignal  the done signal
         */
        Worker(CountDownLatch startSignal, CountDownLatch doneSignal) {
            this.startSignal = startSignal;
            this.doneSignal = doneSignal;
        }

        @Override
        public void run() {
            try {
                startSignal.await();
                //嘗試加鎖
                distributedLocker.lock("test", new AcquiredLockWorker<Object>() {

                    @Override
                    public Object invokeAfterLockAcquire() {
                        doTask();
                        return "success";
                    }

                });
            } catch (Exception e) {
                log.warn("獲取鎖出現異常", e);
            }
        }

        /**
         * Do task.
         */
        void doTask() {
            System.out.println(Thread.currentThread().getName() + " 搶到鎖!");
            Random random = new Random();
            int _int = random.nextInt(200);
            System.out.println(Thread.currentThread().getName() + " sleep " + _int + "millis");
            try {
                Thread.sleep(_int);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread().getName() + " 釋放鎖!");
            doneSignal.countDown();
        }
    }
}

運行結果:

Thread-26 搶到鎖!
Thread-26 sleep 181millis
Thread-26 釋放鎖!
Thread-25 搶到鎖!
Thread-25 sleep 189millis
Thread-25 釋放鎖!
Thread-27 搶到鎖!
Thread-27 sleep 42millis
Thread-27 釋放鎖!
Thread-29 搶到鎖!
Thread-29 sleep 97millis
Thread-29 釋放鎖!
Thread-28 搶到鎖!
Thread-28 sleep 45millis
Thread-28 釋放鎖!
All processors done. Shutdown connection

源碼 GitHub:https://github.com/liaozihong/SpringBoot-Learning/tree/master/SpringBoot-Redis-Distributed-Redlock
參考鏈接:
如何用Redlock實現分佈式鎖
分佈式鎖和同步器

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章