• springboot利用redis实现分布式锁(redis为单机模式)


    1.pom文件添加redis支持

            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-data-redis</artifactId>
            </dependency>

    2.application.properties或者(application.yml)添加redis配置

    spring.redis.database=1
    spring.redis.host=172.xx.xx.xx
    spring.redis.password=123456
    spring.redis.port=6379

    上面的spring.redis.host替换成自己的redis服务地址,如果没有用到密码则删除spring.redis.password配置即可

    3.redis工具类

    package com.example.demo;
    
    import com.example.demo.extend.FastJsonRedisSerializer;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.beans.factory.annotation.Qualifier;
    import org.springframework.data.redis.core.RedisTemplate;
    import org.springframework.data.redis.core.script.DefaultRedisScript;
    import org.springframework.stereotype.Component;
    
    import javax.annotation.PostConstruct;
    import java.util.Collections;
    import java.util.List;
    
    @Component
    public class RedisUtil {
    
        @Autowired
        private RedisTemplate<String,String> template;
    
        @PostConstruct
        public void init(){
            template.setKeySerializer(template.getStringSerializer());
            template.setValueSerializer(template.getStringSerializer());
            template.setHashKeySerializer(template.getStringSerializer());
            template.setHashValueSerializer(template.getStringSerializer());
        }
    
        /**
         * 通过lua脚本 加锁并设置过期时间
         * @param key 锁key值
         * @param value 锁value值
         * @param expire 过期时间,单位秒
         * @return true:加锁成功,false:加锁失败
         */
        public boolean getLock(String key,String value,String expire){
            DefaultRedisScript<String> redisScript = new DefaultRedisScript<String>();
            redisScript.setResultType(String.class);
            String strScript = "";
            strScript +="    if redis.call('setNx',KEYS[1],ARGV[1])==1 then ";
            strScript +="        return redis.call('expire',KEYS[1],ARGV[2]) ";
            strScript +="    else";
            strScript +="        return 0 ";
            strScript +="    end ";
            redisScript.setScriptText(strScript);
            try{
                Object result = this.template.execute(redisScript,template.getStringSerializer(),template.getStringSerializer(), Collections.singletonList(key),value,expire);
                System.out.println("redis返回:"+result);
                return "1".equals(""+result);
            }catch (Exception e){
                //可以自己做异常处理
                return false;
            }
    
        }
    
        /**
         * 通过lua脚本释放锁
         * @param key 锁key值
         * @param value 锁value值(仅当redis里面的value值和传入的相同时才释放,避免释放其他线程的锁)
         * @return true:释放锁成功,false:释放锁失败(可能已过期或者已被释放)
         */
        public boolean releaseLock(String key,String value){
            DefaultRedisScript<String> redisScript = new DefaultRedisScript<>();
            redisScript.setResultType(String.class);
            String strScript = "";
            strScript +="if redis.call('get',KEYS[1]) == ARGV[1] then ";
            strScript +="    return redis.call('del',KEYS[1]) ";
            strScript +="else ";
            strScript +="    return 0 ";
            strScript +="end ";
            redisScript.setScriptText(strScript);
            try{
                Object result = this.template.execute(redisScript,template.getStringSerializer(),template.getStringSerializer(), Collections.singletonList(key),value);
                return "1".equals(""+result);
            }catch (Exception e){
                //可以自己做异常处理
                return false;
            }
        }
    }

    redis锁用到的是setNx命令,这个命令的意思是如果redis里面存在这个key则不再添加,如果key不存在则添加成功,当setNx设置成功之后再给这个值设置一个超期时间来防止出现极端情况(断网,服务终止)导致锁没有被及时释放的情况。

    上面的加锁和解锁都是通过lua脚本进行,redis里面lua脚本执行时是原子操作,可以保证加锁和设置超时同时成功或者失败,不会出现设置值成功 添加超时时间失败的情况

    4.使用

    在需要加锁的地方注入RedisUtil对象即可。有问题的可以留言一起探讨细节问题

    @Autowired
    private RedisLockUtil redisUtil;
    boolean lock = this.redisUtil.getLock("FORM_SUBMIT"+formId,formId,"2");
                if(!lock){
                    //未获得锁
                    throw new ServiceException("当前已经有任务在执行!");
                }
    
    //---------执行业务逻辑
    
    if(lock){
                    //释放锁不关心成功与否
                    this.redisUtil.releaseLock("FORM_SUBMIT"+formId,formId);
                }

    上面的业务逻辑最好放在try catch中执行,释放锁的代码放到finally里面执行。

    5.考虑各种情况下会不会导致bug的出现

    5.1:加锁失败

      拿不到锁业务逻辑不执行,没问题

    5.2:加锁成功,释放锁失败

      网络原因或者其他原因没有释放掉,没关系 ,超时时间过了就会自己释放,没问题

    5.3:加锁成功,业务执行时间过长,锁已经被redis自己释放

       此种情况需要根据业务的实际情况设置合理的超时时间,可能会出问题,原因在于 基于分布式的系统是无法避免类似的问题,具体可以参考如下博客的文章,

    此文章引入了 关于Redis分布式锁的安全性问题,在分布式系统专家Martin Kleppmann和Redis的作者antirez之间发生过的一场争论,内容很精彩。https://blog.csdn.net/paincupid/article/details/75094550

  • 相关阅读:
    js 回车调用后台事件
    获取下拉框选中的值:
    MVC 3.0 在各个版本IIS中的部署
    创建Windows域
    SQL Server 事务、异常和游标
    IIS配置PHP环境(快速最新版)
    js操作select下拉框
    如何清除访问远程网络时保存的密码
    免费Web服务
    Firefox不支持event解决方法
  • 原文地址:https://www.cnblogs.com/bcde/p/11132479.html
Copyright © 2020-2023  润新知