文档章节

利用Redis实现的一种分布式锁

疏影横斜
 疏影横斜
发布于 03/30 21:50
字数 1215
阅读 16
收藏 0

分布式锁的一种实现方式,能够处理[锁时间],[等待时间],该方法在生产环境中使用一年多,还算是稳定~~

需要依赖lombok,Redis,Spring redis

package **;

import lombok.extern.slf4j.Slf4j;
import org.springframework.data.redis.core.RedisCallback;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.StringRedisSerializer;

import java.util.Random;
import java.util.concurrent.TimeUnit;

/**
 * Redis分布式锁
 **/
@Slf4j
public class RedisLock<T>{

    private RedisTemplate<String, String> redisTemplate;
    private Random random = new Random();

    /*获取锁失败后的默认等待时间,真正的等待时间时【DEFAULT_SLEEP_TIME + DEFAULT_SLEEP_TIME的随机数】*/
    private static final int DEFAULT_SLEEP_TIME = 50;
    /*锁对象RedisKey*/
    private String lockKey;
    /*锁超时时间,防止有线程持有锁因异常情况无法释放,其他线程无限的执行等待*/
    private int lockTimeout = 10 * 60 * 1000;
    /*获取锁最大等待时间*/
    private int maxWaitTime = 15 * 1000;

    /**
     * 构造函数
     *
     * @param redisTemplate Redis模板
     * @param lockKey       锁对象RedisKey
     */
    public RedisLock(RedisTemplate<String, String> redisTemplate, String lockKey){
        this.redisTemplate = redisTemplate;
        this.lockKey = lockKey + "_lock";
    }

    /**
     * 构造函数
     *
     * @param redisTemplate Redis模板
     * @param lockKey       锁对象RedisKey
     * @param maxWaitMs     获取锁最大等待时间
     */
    public RedisLock(RedisTemplate<String, String> redisTemplate, String lockKey, int maxWaitMs){
        this(redisTemplate, lockKey);
        this.maxWaitTime = maxWaitMs;
    }

    /**
     * 构造函数
     *
     * @param redisTemplate Redis模板
     * @param lockKey       锁对象RedisKey
     * @param maxWaitMs     获取锁最大等待时间
     * @param lockTimeout   锁超时时间
     */
    public RedisLock(RedisTemplate<String, String> redisTemplate, String lockKey, int maxWaitMs, int lockTimeout){
        this(redisTemplate, lockKey, maxWaitMs);
        this.lockTimeout = lockTimeout;
    }

    /**
     * 接口将由任何需要使用RedisLock分布式锁的类实现
     */
    public interface RedisLockRunnable<T>{
        T run() throws Exception;
    }

    /**
     * 使用分布式锁执行,并返回执行结果
     *
     * @param runnable 分布式锁执行对象
     * @return 执行结果
     * @throws Exception 异常
     */
    public T execute(RedisLockRunnable<T> runnable) throws Exception{
        T obj;
        if(lock()){
            try{
                obj = runnable.run();
            }
            finally{
                try{
                    unlock();
                }
                catch(Exception e){
                    log.error("RedisLock--->unlock error, exception info : {}", e);
                }
            }
        }
        else{
            throw new RuntimeException("系统繁忙,请稍后重试");
        }
        return obj;
    }

    /**
     * 获得锁
     * 实现思路:
     * 1、使用Redis的setNX命令
     * 2、Redis缓存的key是锁的key, value是锁的到期时间(注意:这里把过期时间放在value了,同时设置其超时时间,做了2重过期保障)
     * 执行过程:
     * 1、通过setnx尝试设置某个key的值,成功(当前没有这个锁)则返回,成功获得锁
     * 2、锁已经存在则获取锁的到期时间,和当前时间比较,超时的话,则设置新的值
     * 2018-05-29 xupengji 为适应多重锁,放开lock()、unlock() 方法,方法内手动控制锁逻辑
     *
     * @return 是否获得锁成功
     * @throws InterruptedException 中断异常
     */
    public boolean lock() throws InterruptedException{
        int timeout = maxWaitTime;
        while(timeout >= 0){
            /*
            尽量保证redis里的锁过期时间大于Key过期时间
            这样只要锁设置成功,redis里面锁到期时间一般会大于系统时间
             */
            String expiresTime = String.valueOf(System.currentTimeMillis() + lockTimeout + 2000); //锁到期时间
            if(this.setNX(lockKey, expiresTime)){
                log.debug("----------加锁状态key---------" + lockKey);
                return true;
            }
            log.debug("----------加锁失败key---------" + lockKey);
            String redisExpiresTime = redisTemplate.opsForValue().get(lockKey); //redis里的锁过期时间
            /*
            判断是否为空,不为空的情况下,如果被其他线程设置了值,则第二个条件判断是过不去的
            这里值有可能会覆盖别人的锁的时间,但是因为相差了很少的时间,所以可以接受
             */
            if(redisExpiresTime != null && Long.parseLong(redisExpiresTime) < System.currentTimeMillis()){
                /*获取上一个锁到期时间,并设置现在的锁到期时间*/
                String oldValueStr = redisTemplate.opsForValue().getAndSet(lockKey, expiresTime);
                redisTemplate.expire(lockKey, lockTimeout, TimeUnit.SECONDS);
                /*
                如果这个时候,多个线程恰好都到了这里,但是只会有一个线程取到的值和上一个线程设置的值相同
                (其他的线程因为getAndSet被重新赋值了,所以不会和oldValueStr相同)
                 */
                if(oldValueStr != null && oldValueStr.equals(redisExpiresTime)){
                    return true;
                }
            }

            /*
            默认延迟时间,使用固定数+随机数,可以防止同时到达多个进程,
            每次都是以同样的频率申请锁,并发可能性更大.
             */
            long sleepMillis = random.nextInt(DEFAULT_SLEEP_TIME) + DEFAULT_SLEEP_TIME;
            if(timeout > 0){
                Thread.sleep(sleepMillis);
            }
            timeout -= sleepMillis;
        }
        return false;
    }

    /**
     * 释放锁
     * 2018-05-29 xupengji 为适应多重锁,放开lock()、unlock() 方法,方法内手动控制锁逻辑
     */
    public void unlock(){
        String redisExpiresTime = redisTemplate.opsForValue().get(lockKey); //redis里的锁过期时间
        /*
        如果锁还没到超时时间,说明是当前线程自己的锁(在锁的超时时间内,不释放这个锁别人肯定得不到)
        如果锁超时了,那可能是别人的锁,无需删除(如果是自己的锁,可能这个key一直存在,直到过期或下个获取锁的线程过来能删除掉)
         */
        if(redisExpiresTime != null
                && Long.parseLong(redisExpiresTime) >= System.currentTimeMillis()){
            redisTemplate.delete(lockKey);
            log.debug("-----开始解锁----key--------" + lockKey);
        }
    }

    /**
     * Key不存在时设置value
     *
     * @param key   Redis Key
     * @param value Redis Value
     * @return 是否设置成功
     */
    private boolean setNX(final String key, final String value){
        Boolean result = false;

        try{
            result = redisTemplate.execute((RedisCallback<Boolean>)connection -> {
                StringRedisSerializer serializer = new StringRedisSerializer();
                return connection.setNX(serializer.serialize(key), serializer.serialize(value));
            });

            if(result){
                redisTemplate.expire(key, lockTimeout, TimeUnit.SECONDS);
            }
        }
        catch(Exception e){
            log.error("RedisLock--->setNX redis error, key = {},e = ", key, e);
        }

        return result;
    }
}

 

使用方法:

RedisLock<Integer> lock = new RedisLock<>(redisTemplate, "analysisAddMember");
Integer resultInt = lock.execute(() -> service.method(object));

 

© 著作权归作者所有

疏影横斜

疏影横斜

粉丝 7
博文 14
码字总数 6250
作品 0
青岛
私信 提问
每日一博 | 基于 redis 的 spring boot 分布式锁实现

每日一博 | 基于 redis 的 spring boot 分布式锁实现 Harries Blog™2018-01-030 阅读 ACESpringAppbeanAPIbuildAOPActionbug 随着现在 分布式 架构越来越盛行,在很多场景下需要使用到 分布...

Harries Blog™
2018/01/03
0
0
并发编程-锁的发展和主流分布式锁比较总结

一、锁的发展 系统结构由传统的“单应用服务--》SOA --》微服务 --》无服务器” 的演进过程中,场景越来越复杂,由单体应用的但进程中多线程并发的内存锁,随着互联网场景越来越复杂,在复杂...

贾浩v
2017/10/24
274
0
基于 Redis 实现简单的分布式锁

本文作者:伯乐在线 -Float_Lu 。未经作者许可,禁止转载! 欢迎加入伯乐在线专栏作者。 摘要 分布式锁在很多应用场景下是非常有效的手段,比如当运行在多个机器上的不同进程需要访问同一个竞...

伯乐在线
2016/05/21
0
0
基于Redis实现简单的分布式锁

摘要 分布式锁在很多应用场景下是非常有效的手段,比如当运行在多个机器上的不同进程需要访问同一个竞争资源的时候,那么就会涉及到进程对资源的加锁和释放,这样才能保证数据的安全访问。分...

Float_Luuu
2016/05/18
2.3K
0
关于redis分布式锁的初步研究与分享

导言: 前段时间项目中用到了分布式锁,所以就对分布式锁进行了一些研究,首先当然是去看redis的分布式锁实现,这里说明一下,分布式锁的实现可以有两种比较简单的方式来实现,一种是redis的...

zhangkay
2017/12/16
0
0

没有更多内容

加载失败,请刷新页面

加载更多

面试官,Java8 JVM内存结构变了,永久代到元空间

在文章《JVM之内存结构详解》中我们描述了Java7以前的JVM内存结构,但在Java8和以后版本中JVM的内存结构慢慢发生了变化。作为面试官如果你还不知道,那么面试过程中是不是有些露怯?作为面试...

程序新视界
13分钟前
11
0
读书笔记:深入理解ES6 (八)

第八章 迭代器(Iterator)与生成器(Generator) 第1节 循环语句的问题   在循环、多重循环中,通过变量来跟踪数组索引的行为容易导致程序出错。迭代器的出现旨在消除这种复杂性,并减少循...

张森ZS
14分钟前
10
0
Elasticsearch 实战(一) - 简介

官腔 Elasticsearch,分布式,高性能,高可用,可伸缩的搜索和分析系统 基本等于没说,咱们慢慢看 1 概述 百度:我们比如说想找寻任何的信息的时候,就会上百度去搜索一下,比如说找一部自己喜...

JavaEdge
18分钟前
10
0
【jQuery基础学习】11 jQuery性能简单优化

本文转载于:专业的前端网站➦【jQuery基础学习】11 jQuery性能简单优化 关于性能优化 合适的选择器 $("#id")会直接调用底层方法,所以这是最快的。如果这样不能直接找到,也可以用find方法继...

前端老手
27分钟前
10
0
重磅发布 | 全球首个云原生应用标准定义与架构模型 OAM 正式开源

导读:2019 年 10 月 17 日,阿里巴巴合伙人、阿里云智能基础产品事业部总经理蒋江伟(花名:小邪)在 Qcon 上海重磅宣布,阿里云与微软联合推出开放应用模型 Open Application Model (OAM...

阿里云官方博客
30分钟前
11
0

没有更多内容

加载失败,请刷新页面

加载更多

返回顶部
顶部