法外狂徒| Redis指东!手把手教你实现Redis分布式锁!

大家好,我是 法外狂徒张三!端午已过,又要开始工作学习啦!

我发现有很多小伙伴对如何在分布式系统中实现分布式锁虽然有概念,但是没有代码的实践。

所以呀,我手写了一个demo来帮助大家加深这方面知识的印象!

Redis怎么实现分布式锁?

  1. 利用setnx+expire命令 (错误的做法)

  2. 使用Lua脚本(包含setnx和expire两条指令,但本文不涉及)

  3. 使用 set key value [EX seconds][PX milliseconds][NX|XX] 命令 (正确做法,本文代码案例使用此方式)

    但是这样依然有问题,因为如果超时时长设置的不够长,在本次加锁后的业务操作耗时较长还未进行完毕,锁到期释放了,就会出现其他线程进行加锁操作,加锁完之后,之前的线程执行完毕,进行释放锁的操作,释放了其他线程的锁,就会出现问题,下面代码简单的使用当前线程的线程名作为value值,在解锁的时候进行比对

上代码!!!!!!

/**
     * 允许丢弃,得到redis锁
     *
     * @param lockKey        锁定键
     * @param expireInSecond 过期时间
     * @return {@link String}
     */
    public String getRedisLock(final String lockKey, final int expireInSecond) throws InterruptedException {
        if (StringUtils.isEmpty(lockKey)) {
            throw new DistributionLockException("key is empty!");
        }

        if (expireInSecond < 0) {
            throw new DistributionLockException("expireInSecond must be bigger than 0");
        }

        try {
            for (int i = 0; i < retryCount; i++) {
                Boolean absent = redisTemplate.opsForValue().setIfAbsent(lockKey, Thread.currentThread().getName(), expireInSecond, TimeUnit.SECONDS);
                // 如果成功,返回当前线程名
                if (Boolean.TRUE.equals(absent)) {
                    return Thread.currentThread().getName();
                } else {
                    logger.warn("get redis lock error: " + i + "time retry");
                }
                // 失败尝试挂起线程,等待重试
                TimeUnit.MILLISECONDS.sleep(timeout);
            }
            // 三次失败则放弃获取
            throw new DistributionLockException(Thread.currentThread().getName() + " cannot acquire lock now ...");
        } catch (Exception e) {
            logger.warn("get redis lock error, exception: " + e.getMessage());
            throw e;
        }
    }
复制代码

释放锁代码

/**
     * 锁释放
     *
     * @param redisKey
     * @param lockValue
     */
    public void unlock(final String redisKey, final String lockValue) {
        if (StringUtils.isEmpty(redisKey)) {
            return;
        }
        if (StringUtils.isEmpty(lockValue)) {
            return;
        }
        try {
            String currLockVal = redisTemplate.opsForValue().get(redisKey);
            if (currLockVal != null && currLockVal.equals(lockValue)) {
                Boolean result = redisTemplate.delete(redisKey);
                if (Boolean.FALSE.equals(result)) {
                    logger.warn(Thread.currentThread().getName() + " unlock redis lock fail");
                } else {
                    logger.info(Thread.currentThread().getName() + " unlock redis lock:" + redisKey + " successfully!");
                }
            }
        } catch (Exception je) {
            logger.warn(Thread.currentThread().getName() + " unlock redis lock error:" + je.getMessage());
        }
    }
复制代码

测试跑一把

@SpringBootApplication(scanBasePackages = {"com.falser.*"})
public class TheblogWebApplication {

    private static Logger logger = LoggerFactory.getLogger(TheblogWebApplication.class);


    public static void main(String[] args) throws InterruptedException {
        SpringApplication.run(TheblogWebApplication.class, args);

        
        StringRedisTemplate redisTemplate = SpringUtils.getObject(StringRedisTemplate.class);

        RedisLock redisLock = new RedisLock(redisTemplate);
        String lockKey = "lock:test";


        CountDownLatch start = new CountDownLatch(1);
        CountDownLatch threadsLatch = new CountDownLatch(2);

        final int lockExpireSecond = 5;
        final int timeoutSecond = 3;

        Runnable lockRunnable = () -> {
            String lockValue = "";
            try {
                //等待发令枪响,防止线程抢跑
                start.await();

                //允许丢数据的简单锁示例
                lockValue = redisLock.getRedisLock(lockKey, lockExpireSecond);


                //不允许丢数据的分布式锁示例
                //lockValue = redisLock.lock(lockKey, lockExpireSecond, timeoutSecond);

                //停一会儿,让后面的线程抢不到锁,从而查看到重试效果
                TimeUnit.SECONDS.sleep(2);
                logger.info(String.format("%s get lock successfully, value:%s", Thread.currentThread().getName(), lockValue));

            } catch (Exception e) {
                e.printStackTrace();
            } finally {
                redisLock.unlock(lockKey, lockValue);
                //执行完后,计数减1
                threadsLatch.countDown();
            }

        };

        Thread t1 = new Thread(lockRunnable, "T1");
        Thread t2 = new Thread(lockRunnable, "T2");

        t1.start();
        t2.start();

        //开始!
        start.countDown();

        //等待所有线程跑完
        threadsLatch.await();

        logger.info("======>搞定了!!!");
    }

}

@Component
class SpringUtils implements ApplicationContextAware {

    private static ApplicationContext applicationContext;

    @Override
    public void setApplicationContext(ApplicationContext applicationContextParam) throws BeansException {
        applicationContext = applicationContextParam;
    }

    public static Object getObject(String id) {
        Object object = null;
        object = applicationContext.getBean(id);
        return object;
    }

    public static <T> T getObject(Class<T> tClass) {
        return applicationContext.getBean(tClass);
    }

    public static Object getBean(String tClass) {
        return applicationContext.getBean(tClass);
    }

    public <T> T getBean(Class<T> tClass) {
        return applicationContext.getBean(tClass);
    }
}
复制代码

运行截图
微信截图_20210601134314.png

当然这里只是简单的单机环境演示,分布式情况下肯定需要考虑更多的地方,我这里只是起到抛砖引玉的效果,有问题的地方希望大佬指出!感谢

© 版权声明
THE END
喜欢就支持一下吧
点赞0 分享