redis- 分布式锁

一 设计思路

1. 本地锁:本地多线程并发时,保证只有一个线程在竞争 redis 锁;

2. redis 锁(setNX):分布式部署多个服务器之间并发时,保证只有一个服务器可以竞争到 redis 锁;

3. 本地多线程并发时,先获取本地锁成功,然后再去尝试获取 redis 锁;

redis  锁的实现原理 setNX:redis 的 setNX 操作在设置同一个 key 时,只会有一个成功,当已经存在了该 key 时,setNX 接口会返回 false,也就可以理解为获取锁失败。setNX 成功的即可理解为获取 redis 锁成功,再设置一个超时时间,防止发生异常时死锁。

二 实现类

package com.qbian.common.common;

import lombok.extern.slf4j.Slf4j;
import org.springframework.dao.DataAccessException;
import org.springframework.data.redis.connection.RedisConnection;
import org.springframework.data.redis.core.RedisCallback;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Service;

import javax.annotation.Resource;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.ReentrantLock;

/**
 * @author Qiangqiang.Bian
 * @create 18/8/27
 * @desc redis 实现的分布式锁
 **/
@Service
@Slf4j
public class RedisLock {

    @Resource
    private StringRedisTemplate stringRedisTemplate;

    private final long sleepTimeNanos = 100;

    // 是否存在本地锁
    private volatile Boolean hasLocalLock = Boolean.TRUE;

    // 本地锁
    private final ReentrantLock lock = new ReentrantLock(true);
    private final Condition hasLock = lock.newCondition();

    // 加锁超时时间, 单位 /秒
    private static final long LOCK_TIMEOUT = 10;

    /**
     * 持续获取 redis 锁
     * @param redisLockKey redis lock key
     * @throws InterruptedException 等待时被中断
     */
    public void lock(String redisLockKey) throws InterruptedException {
        final ReentrantLock lock = this.lock;

        // 本地锁已被其它线程占用,等待本地锁释放
        while (!tryLocalLock()) {
            lock.lockInterruptibly();
            try {
                hasLock.await();
            } finally {
                lock.unlock();
            }
        }

        // 当前线程获取到本地锁,尝试获取 redis 锁
        while (!tryRedisLock(redisLockKey))
            Thread.sleep(sleepTimeNanos);
    }

    /**
     * 尝试获取 redis 锁
     * @param redisLockKey redis lock key
     * @return 是否获取成功
     * @throws InterruptedException 等待时被中断
     */
    public boolean tryLock(String redisLockKey) throws InterruptedException {
        // 没有本地锁
        if (!tryLocalLock())
            return false;

        // 存在本地锁,尝试获取 redis 锁
        if (!tryRedisLock(redisLockKey)) {

            // 获取 redis 锁失败,释放本地锁
            releaseLocalLock();
            return false;
        }
        // 获取 redis 锁成功
        return true;
    }

    /**
     * 在超时时间内尝试获取 redis 锁
     * @param redisLockKey redis lock key
     * @param timeoutMillis 超时时长,毫秒
     * @return 是否获取成功
     * @throws InterruptedException 等待时被中断
     */
    public boolean tryLock(String redisLockKey, long timeoutMillis) throws InterruptedException {
        long startTimeMills = System.currentTimeMillis();
        long nanos = TimeUnit.MILLISECONDS.toNanos(timeoutMillis);
        final ReentrantLock lock = this.lock;

        // 没有本地锁,获取本地锁
        while (!tryLocalLock()) {
            if (nanos <= 0) {
                log.info("thread({}) acquire local lock timeout, return .", Thread.currentThread().getName());
                return false;
            }
            lock.lockInterruptibly();
            try {
                nanos = hasLock.awaitNanos(nanos);
            } finally {
                lock.unlock();
            }
        }

        // 存在本地锁,获取 redis 锁
        while (!tryRedisLock(redisLockKey)) {
            Thread.sleep(sleepTimeNanos);

            // 获取 redis 锁超时,释放本地锁
            if (System.currentTimeMillis() - startTimeMills >= timeoutMillis) {
                releaseLocalLock();
                log.info("thread({}) acquire redis lock timeout, return .", Thread.currentThread().getName());
                return false;
            }
        }
        return true;
    }

    /**
     * 释放 redis 锁
     * @param redisLockKey redis lock key
     * @throws InterruptedException 等待时被中断
     */
    public void unlock(String redisLockKey) throws InterruptedException {
        stringRedisTemplate.delete(redisLockKey);
        log.info("thread({}) release redis lock .", Thread.currentThread().getName());
        releaseLocalLock();
    }

    /**
     * -----------------------------------------------------------------------------------
     * -----------------------------------------------------------------------------------
     */

    /**
     * 释放本地锁
     * @throws InterruptedException 等待时被中断
     */
    private void releaseLocalLock() throws InterruptedException {
        final ReentrantLock lock = this.lock;
        lock.lockInterruptibly();
        try {
            hasLocalLock = Boolean.TRUE;
            hasLock.signal();
            log.info("thread({}) release local lock .", Thread.currentThread().getName());
        } finally {
            lock.unlock();
        }
    }

    /**
     * 尝试获取本地锁
     * @return 获取锁是否成功
     * @throws InterruptedException 等待时被中断
     */
    private boolean tryLocalLock() throws InterruptedException {
        final ReentrantLock lock = this.lock;
        lock.lockInterruptibly();
        try {
            if (hasLocalLock) {
                log.info("thread({}) get the local lock successfully .", Thread.currentThread().getName());
                hasLocalLock = Boolean.FALSE;
                return true;
            }
            log.info("thread({}) failed to acquire local lock .", Thread.currentThread().getName());
            return false;
        } finally {
            lock.unlock();
        }
    }

    /**
     * 尝试获取 redis 锁
     * @param redisLockKey redis lock key
     * @return 获取锁是否成功
     */
    private boolean tryRedisLock(String redisLockKey) {
        String threadName = Thread.currentThread().getName();

        if (stringRedisTemplate.execute(new RedisCallback<Boolean>() {
            @Override
            public Boolean doInRedis(RedisConnection connection) throws DataAccessException {
                return connection.setNX(redisLockKey.getBytes(), (redisLockKey + "-" + Thread.currentThread().getName()).getBytes());
            }
        })) {
            // 设置加锁超时时长
            stringRedisTemplate.expire(redisLockKey, LOCK_TIMEOUT, TimeUnit.SECONDS);
            log.info("thread({}) get the redis lock successfully .", threadName);
            return true;
        }

        log.info("thread({}) failed to acquire redis lock, waiting .", threadName);
        return false;
    }
}

三 测试方法 

package com.qbian.redis;

import com.qbian.BaseTest;
import com.qbian.common.common.RedisLock;
import lombok.extern.slf4j.Slf4j;
import org.junit.Test;

import javax.annotation.Resource;

/**
 * @author Qiangqiang.Bian
 * @create 18/8/27
 * @desc
 **/
@Slf4j
public class RedisLockTest extends BaseTest {

    @Resource
    private RedisLock redisLock;

    private static final String LOCK_KEY = "java:demo:lock:test";

    private static int j = 0;

    @Test
    public void redisLockTest() {
        for (int i = 0; i < 10; ++ i) {
            new Thread(() -> {
                try {
                    if (redisLock.tryLock(LOCK_KEY, 1000)) {
                        try {
                            log.info("----------> j={}", ++ j);
                        } finally {
                            try {
                                redisLock.unlock(LOCK_KEY);
                            } catch (InterruptedException e) {
                                e.printStackTrace();
                            }
                        }
                    }
                } catch (InterruptedException e) {
                    log.error("等待时被中断,e={}", e);
                }
            }, "job-" + i).start();
        }
    }

}

四  测试结果

2018-08-27 20:56:16.346 18004 [job-0] INFO  com.qbian.common.common.RedisLock - thread(job-0) get the local lock successfully . 
2018-08-27 20:56:16.347 18005 [job-1] INFO  com.qbian.common.common.RedisLock - thread(job-1) failed to acquire local lock . 
2018-08-27 20:56:16.348 18006 [job-2] INFO  com.qbian.common.common.RedisLock - thread(job-2) failed to acquire local lock . 
2018-08-27 20:56:16.354 18012 [job-3] INFO  com.qbian.common.common.RedisLock - thread(job-3) failed to acquire local lock . 
2018-08-27 20:56:16.355 18013 [job-4] INFO  com.qbian.common.common.RedisLock - thread(job-4) failed to acquire local lock . 
2018-08-27 20:56:16.356 18014 [job-5] INFO  com.qbian.common.common.RedisLock - thread(job-5) failed to acquire local lock . 
2018-08-27 20:56:16.358 18016 [job-6] INFO  com.qbian.common.common.RedisLock - thread(job-6) failed to acquire local lock . 
2018-08-27 20:56:16.361 18019 [job-7] INFO  com.qbian.common.common.RedisLock - thread(job-7) failed to acquire local lock . 
2018-08-27 20:56:16.361 18019 [job-8] INFO  com.qbian.common.common.RedisLock - thread(job-8) failed to acquire local lock . 
2018-08-27 20:56:16.363 18021 [job-9] INFO  com.qbian.common.common.RedisLock - thread(job-9) failed to acquire local lock .
2018-08-27 20:56:16.535 18193 [job-0] INFO  com.qbian.common.common.RedisLock - thread(job-0) get the redis lock successfully . 
2018-08-27 20:56:16.535 18193 [job-0] INFO  com.qbian.redis.RedisLockTest - ----------> j=1 
2018-08-27 20:56:16.537 18195 [job-0] INFO  com.qbian.common.common.RedisLock - thread(job-0) release redis lock . 
2018-08-27 20:56:16.537 18195 [job-0] INFO  com.qbian.common.common.RedisLock - thread(job-0) release local lock . 
2018-08-27 20:56:16.537 18195 [job-1] INFO  com.qbian.common.common.RedisLock - thread(job-1) get the local lock successfully . 
2018-08-27 20:56:16.539 18197 [job-1] INFO  com.qbian.common.common.RedisLock - thread(job-1) get the redis lock successfully . 
2018-08-27 20:56:16.539 18197 [job-1] INFO  com.qbian.redis.RedisLockTest - ----------> j=2 
2018-08-27 20:56:16.540 18198 [job-1] INFO  com.qbian.common.common.RedisLock - thread(job-1) release redis lock . 
2018-08-27 20:56:16.540 18198 [job-1] INFO  com.qbian.common.common.RedisLock - thread(job-1) release local lock . 
2018-08-27 20:56:16.541 18199 [job-2] INFO  com.qbian.common.common.RedisLock - thread(job-2) get the local lock successfully . 
2018-08-27 20:56:16.543 18201 [job-2] INFO  com.qbian.common.common.RedisLock - thread(job-2) get the redis lock successfully . 
2018-08-27 20:56:16.543 18201 [job-2] INFO  com.qbian.redis.RedisLockTest - ----------> j=3 
2018-08-27 20:56:16.544 18202 [job-2] INFO  com.qbian.common.common.RedisLock - thread(job-2) release redis lock . 
2018-08-27 20:56:16.544 18202 [job-2] INFO  com.qbian.common.common.RedisLock - thread(job-2) release local lock . 
2018-08-27 20:56:16.544 18202 [job-3] INFO  com.qbian.common.common.RedisLock - thread(job-3) get the local lock successfully . 
2018-08-27 20:56:16.546 18204 [job-3] INFO  com.qbian.common.common.RedisLock - thread(job-3) get the redis lock successfully . 
2018-08-27 20:56:16.546 18204 [job-3] INFO  com.qbian.redis.RedisLockTest - ----------> j=4 
2018-08-27 20:56:16.547 18205 [job-3] INFO  com.qbian.common.common.RedisLock - thread(job-3) release redis lock . 
2018-08-27 20:56:16.547 18205 [job-3] INFO  com.qbian.common.common.RedisLock - thread(job-3) release local lock . 
2018-08-27 20:56:16.548 18206 [job-4] INFO  com.qbian.common.common.RedisLock - thread(job-4) get the local lock successfully . 
2018-08-27 20:56:16.550 18208 [job-4] INFO  com.qbian.common.common.RedisLock - thread(job-4) get the redis lock successfully . 
2018-08-27 20:56:16.550 18208 [job-4] INFO  com.qbian.redis.RedisLockTest - ----------> j=5 
2018-08-27 20:56:16.550 18208 [job-4] INFO  com.qbian.common.common.RedisLock - thread(job-4) release redis lock . 
2018-08-27 20:56:16.551 18209 [job-4] INFO  com.qbian.common.common.RedisLock - thread(job-4) release local lock . 
2018-08-27 20:56:16.551 18209 [job-5] INFO  com.qbian.common.common.RedisLock - thread(job-5) get the local lock successfully . 
2018-08-27 20:56:16.552 18210 [job-5] INFO  com.qbian.common.common.RedisLock - thread(job-5) get the redis lock successfully . 
2018-08-27 20:56:16.553 18211 [job-5] INFO  com.qbian.redis.RedisLockTest - ----------> j=6 
2018-08-27 20:56:16.554 18212 [job-5] INFO  com.qbian.common.common.RedisLock - thread(job-5) release redis lock . 
2018-08-27 20:56:16.554 18212 [job-5] INFO  com.qbian.common.common.RedisLock - thread(job-5) release local lock . 
2018-08-27 20:56:16.554 18212 [job-6] INFO  com.qbian.common.common.RedisLock - thread(job-6) get the local lock successfully . 
2018-08-27 20:56:16.556 18214 [job-6] INFO  com.qbian.common.common.RedisLock - thread(job-6) get the redis lock successfully . 
2018-08-27 20:56:16.556 18214 [job-6] INFO  com.qbian.redis.RedisLockTest - ----------> j=7 
2018-08-27 20:56:16.557 18215 [job-6] INFO  com.qbian.common.common.RedisLock - thread(job-6) release redis lock . 
2018-08-27 20:56:16.557 18215 [job-6] INFO  com.qbian.common.common.RedisLock - thread(job-6) release local lock . 
2018-08-27 20:56:16.557 18215 [job-7] INFO  com.qbian.common.common.RedisLock - thread(job-7) get the local lock successfully . 
2018-08-27 20:56:16.559 18217 [job-7] INFO  com.qbian.common.common.RedisLock - thread(job-7) get the redis lock successfully . 
2018-08-27 20:56:16.559 18217 [job-7] INFO  com.qbian.redis.RedisLockTest - ----------> j=8 
2018-08-27 20:56:16.560 18218 [job-7] INFO  com.qbian.common.common.RedisLock - thread(job-7) release redis lock . 
2018-08-27 20:56:16.560 18218 [job-7] INFO  com.qbian.common.common.RedisLock - thread(job-7) release local lock . 
2018-08-27 20:56:16.560 18218 [job-8] INFO  com.qbian.common.common.RedisLock - thread(job-8) get the local lock successfully . 
2018-08-27 20:56:16.562 18220 [job-8] INFO  com.qbian.common.common.RedisLock - thread(job-8) get the redis lock successfully . 
2018-08-27 20:56:16.562 18220 [job-8] INFO  com.qbian.redis.RedisLockTest - ----------> j=9 
2018-08-27 20:56:16.563 18221 [job-8] INFO  com.qbian.common.common.RedisLock - thread(job-8) release redis lock . 
2018-08-27 20:56:16.563 18221 [job-8] INFO  com.qbian.common.common.RedisLock - thread(job-8) release local lock . 
2018-08-27 20:56:16.563 18221 [job-9] INFO  com.qbian.common.common.RedisLock - thread(job-9) get the local lock successfully . 
2018-08-27 20:56:16.565 18223 [job-9] INFO  com.qbian.common.common.RedisLock - thread(job-9) get the redis lock successfully . 
2018-08-27 20:56:16.566 18224 [job-9] INFO  com.qbian.redis.RedisLockTest - ----------> j=10 
2018-08-27 20:56:16.568 18226 [job-9] INFO  com.qbian.common.common.RedisLock - thread(job-9) release redis lock . 
2018-08-27 20:56:16.568 18226 [job-9] INFO  com.qbian.common.common.RedisLock - thread(job-9) release local lock . 

猜你喜欢

转载自blog.csdn.net/qbian/article/details/82082073