正确的redis分布式锁实现方式-Java

正确的redis分布式锁4种实现方式-Java

实现原理

redis 实现分布式锁主要用到的是 redis 的一个命令 setnx (如果key不存在就设置成功,如果key存在则设置失败) 和 setex(为key设置超时时间) 。

源码参考

方案1 - 原生 redis java客户端 Jedis 实现分布式锁

1 maven 依赖

        <dependency>
            <groupId>redis.clients</groupId>
            <artifactId>jedis</artifactId>
            <version>3.1.0</version>
        </dependency>

2 配置文件 application.properties

server.port=8089
spring.application.name=springboots-redis
spring.redis.host=127.0.0.1
spring.redis.port=6379

3 创建 Jedis 数据源

import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import redis.clients.jedis.JedisPool;
import redis.clients.jedis.JedisPoolConfig;

@Configuration
@ConfigurationProperties(prefix = "spring.redis")
@Data
public class RedisConfig {

    private String host;

    private int port;

    @Bean
    public JedisPool generateJedisPoolFactory() {
        JedisPoolConfig poolConfig = new JedisPoolConfig();
        JedisPool jedisPool = new JedisPool(poolConfig, host, port, 3000, null);
        return jedisPool;
    }

}

4 实现关键代码

    /**
     * 原生 jedis 实现分布式锁 (高版本)
     * @param lockKey
     * @param lockValue
     * @param expireSecond
     * @return
     */
    @Override
    public boolean getLockAndExpireV4(String lockKey, String lockValue, long expireSecond) {

        Jedis jedis = jedisPool.getResource();
        //高版本用法
        SetParams setParams = SetParams.setParams();
        setParams.nx().ex((int)expireSecond);
        String result = jedis.set(lockKey, lockValue,setParams);
		//低版本的用法  String result = jedis.set(key,value,"NX","EX",expireSecond);
        return result != null;
    }

方案2 - redisTemplate + JedisCommands 实现分布式锁

1 maven 依赖

        <dependency>
            <groupId>redis.clients</groupId>
            <artifactId>jedis</artifactId>
            <version>3.1.0</version>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-redis</artifactId>
            <!--  因为 springboot2.0中默认是使用 Lettuce来集成Redis服务,
            spring-boot-starter-data-redis默认只引入了 Lettuce包,并没有引入 jedis包支持。
            所以在我们需要手动引入 jedis的包,并排除掉 lettuce的包-->
            <exclusions>
                <exclusion>
                    <groupId>io.lettuce</groupId>
                    <artifactId>lettuce-core</artifactId>
                </exclusion>
            </exclusions>
        </dependency>

2 配置 redisTemplate 或者 使用spring-boot-starter-data-redis 自动装配

3 实现关键代码

/**
     * jediscommand 实现方式
     * @param lockKey
     * @param lockValue
     * @param expireSecond 锁在多少秒后释放
     * @return
     */
    @Override
    public boolean getLockAndExpire(final String lockKey, final String lockValue, long expireSecond) {

        if(StringUtils.isEmpty(lockKey) || lockValue == null){
            log.error("加锁参数错误 key:{},value:{}", lockKey,lockValue);
            return false;
        }
        Boolean execute = (Boolean) redisTemplate.execute((RedisCallback) connection -> {
            JedisCommands nativeConnection = (JedisCommands) connection.getNativeConnection();
            //返回 OK 或者 null
            SetParams setParams = SetParams.setParams();
            setParams.nx().ex((int)expireSecond);
            String lockFlag = nativeConnection.set(lockKey, lockValue, setParams);
            return lockFlag != null;
        });
        return execute;
    }

方案3 - redisTemplate 实现分布式锁

1 maven 依赖

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-redis</artifactId>
            <!--  因为 springboot2.0中默认是使用 Lettuce来集成Redis服务,
            spring-boot-starter-data-redis默认只引入了 Lettuce包,并没有引入 jedis包支持。
            所以在我们需要手动引入 jedis的包,并排除掉 lettuce的包-->
            <exclusions>
                <exclusion>
                    <groupId>io.lettuce</groupId>
                    <artifactId>lettuce-core</artifactId>
                </exclusion>
            </exclusions>
        </dependency>

2 关键实现代码, 2.1 之前此方法无法实现

    /**
     * 2.1的包开始 setIfAbsent 才支持 两个命令连用
     * 高版本直接 setnx setex 连用
     * @param lockKey
     * @param lockValue
     * @param expireSecond
     * @return
     */
    @Override
    public boolean getLockAndExpireV2(String lockKey, String lockValue, long expireSecond) {
        return redisTemplate.opsForValue().setIfAbsent(lockKey,lockKey,expireSecond, TimeUnit.SECONDS);
    }

方案4 - redisTemplate + lua 脚本实现分布式锁

redis 在2.6.0 后开始支持lua 脚本。由于redis分布式锁需要将两个命令连用, 而lua脚本刚好可以将多个命令封装起来保持原子性

1 引入maven 依赖 同方案3

1 编写lua脚本 lock.lua, 放在 resource目录下 以便引用

 -- 加锁脚本,其中KEYS[]为外部传入参数
 -- KEYS[1]表示key
 -- KEYS[2]表示value
 -- KEYS[3]表示过期时间
 if redis.call("setnx", KEYS[1], KEYS[2]) == 1 then
    redis.call("setex", KEYS[1], KEYS[3],KEYS[2])
    return 1
 else
     return 0
 end

2 关键实现代码

    /**
     * 低版本的springboot包调用方式稍有不同
     * @param lockKey
     * @param lockValue
     * @param expireSecond
     * @return
     */
    @Override
    public boolean getLockAndExpireV3(String lockKey, String lockValue, long expireSecond) {

        DefaultRedisScript<Boolean> booleanDefaultRedisScript = new DefaultRedisScript<>();

        booleanDefaultRedisScript.setScriptSource(new ResourceScriptSource(new ClassPathResource("lock.lua")));

        booleanDefaultRedisScript.setResultType(Boolean.class);
        //封装参数
        List<String> param = new ArrayList<>();
        param.add(lockKey);
        param.add(lockValue);
        param.add(String.valueOf(expireSecond));

        return redisTemplate.execute(booleanDefaultRedisScript,param);
    }

总结

  1. redis 分布式锁的实现主要是依赖于redis 的setnx 与 setex 两个命令
  2. redis 分布式锁的实现需要一条命令 实现将以上两个命令的连用,如果拆开成为两个步骤,两个步骤之间出现异常或者系统宕机会导致锁一直不能释放。
  3. spring 封装的 redistemplate 2.1 包之前 不支持两个命令连用,因此需要找到其它替代方案。
©️2020 CSDN 皮肤主题: 书香水墨 设计师:CSDN官方博客 返回首页