SpringBoot集成redis,实现分布式锁

目录

1、准备

2、配置参数

3、配置JedisPool

4、分布式锁工具类

5、分布式锁超时失效问题


 


1、准备

使用redis实现分布式锁,需要用的setnx(),所以需要集成Jedis

需要引入jar,jar最好和redis的jar版本对应上,不然会出现版本冲突,使用的时候会报异常redis.clients.jedis.Jedis.set(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;I)Ljava/lang/String;

我使用的redis版本是2.3.0,Jedis使用的是3.3.0

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

2、配置参数

spring:
    redis:
        host: localhost
        port: 6379
        password: root
        timeout: 5000
        # Redis数据库索引(默认为0)
        database: 0
        # 连接池最大连接数(使用负值表示没有限制)
        jedis:
            pool:
            # 连接池最大连接数(使用负值表示没有限制)
            max-active: 8
            # 连接池最大阻塞等待时间(使用负值表示没有限制)
            max-wait: -1
            # 连接池中的最大空闲连接
            max-idle: 8
            # 连接池中的最小空闲连接
            min-idle: 0
            # 获取连接时检测是否可用
            testOnBorrow: true

3、配置JedisPool

import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import redis.clients.jedis.JedisPool;
import redis.clients.jedis.JedisPoolConfig;

/**
 * Jedis配置项
 * @autho ConnorSong
 * @date 2021/1/21 9:55 上午
 */
@Configuration
@Slf4j
public class JedisPoolCinfigration {

    @Bean
    public JedisPoolConfig jedisPoolConfig(@Value("${spring.redis.jedis.pool.max-active}") int maxActive,
                                           @Value("${spring.redis.jedis.pool.max-idle}") int maxIdle,
                                           @Value("${spring.redis.jedis.pool.min-idle}") int minIdle,
                                           @Value("${spring.redis.jedis.pool.max-wait}") long maxWaitMillis,
                                           @Value("${spring.redis.jedis.pool.testOnBorrow}") boolean testOnBorrow) {
        JedisPoolConfig jedisPoolConfig = new JedisPoolConfig();
        jedisPoolConfig.setMaxTotal(maxActive);
        jedisPoolConfig.setMaxIdle(maxIdle);
        jedisPoolConfig.setMinIdle(minIdle);
        jedisPoolConfig.setMaxWaitMillis(maxWaitMillis);
        jedisPoolConfig.setTestOnBorrow(testOnBorrow);

        return jedisPoolConfig;
    }

    @Bean
    public JedisPool jedisPool(@Value("${spring.redis.host}") String host,
                               @Value("${spring.redis.password}") String password,
                               @Value("${spring.redis.port}") int port,
                               @Value("${spring.redis.timeout}") int timeout, JedisPoolConfig jedisPoolConfig) {

        log.info("=====创建JedisPool连接池=====");
        if (StringUtils.isNotEmpty(password)) {
            return new JedisPool(jedisPoolConfig, host, port, timeout, password);
        }

        return new JedisPool(jedisPoolConfig, host, port, timeout);

    }
}

4、分布式锁工具类

import lombok.extern.slf4j.Slf4j;
import redis.clients.jedis.Jedis;
import redis.clients.jedis.params.SetParams;

import java.util.Collections;

/**
 * jedis分布式锁工具类
 * @autho ConnorSong
 * @date 2021/1/20 6:26 下午
 */
@Slf4j
public class JedisLockUtils {

    private static final String LOCK_SUCCESS = "OK";

    private static final Long RELEASE_SUCCESS = 1L;
    /**
     * 尝试获取分布式锁
     * @param jedis Redis客户端
     * @param lockKey 锁
     * @param lockValue value
     * @param expireTime 超期时间(秒)
     * @return 是否获取成功
     */
    public static boolean tryGetLock(Jedis jedis, String lockKey, String lockValue, int expireTime) {
        log.info("----获取Jedis分布式锁----lockKey:{}", lockKey);

        try {
            //方案一,具有原子性,并且可以设置过期时间,避免拿到锁后,业务代码出现异常,无法释放锁
            String result = jedis.set(lockKey, lockValue, new SetParams().nx().ex(expireTime));
            if (LOCK_SUCCESS.equals(result)) {
                return true;
            }
            return false;
            //方案二,setnx()具有原子性,但是有后续判断,整体不具有原子性,不能设置过期时间
//            //setnx(lockkey, 当前时间+过期超时时间),如果返回 1,则获取锁成功;如果返回 0 则没有获取到锁
//            String value = new Date().getTime() + expireTime + "";
//            if(1 == jedis.setnx(lockKey, value)){
//                return true;
//            }else{
//                String oldExpireTime = jedis.get(lockKey);
//                if(Long.valueOf(oldExpireTime)< new Date().getTime()){
//                    //锁超时,可以获取锁重新设置锁
//                    //计算 newExpireTime = 当前时间+过期超时时间,然后 getset(lockkey, newExpireTime) 会返回当前 lockkey的值currentExpireTime
//                    long newExpireTime = new Date().getTime() + expireTime;
//                    String currentExpireTime = jedis.getSet(lockKey, newExpireTime + "");
//                    if(currentExpireTime.equals(oldExpireTime)){
//                        return true;
//                    }
//                }
//                return false;
//            }
        }finally {
            returnResource(jedis);
        }
    }

    /**
     * 释放分布式锁
     * @param jedis Redis客户端
     * @param lockKey 锁
     * @return 是否释放成功
     */
    public static boolean closeLock(Jedis jedis, String lockKey, String lockValue) {
        log.info("----释放Jedis分布式锁----lockKey:{}, lockValue:{}", lockKey, lockValue);
        try {
            String script = "if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1]) else return 0 end";
            Object result = jedis.eval(script, Collections.singletonList(lockKey), Collections.singletonList(lockValue));
            if (RELEASE_SUCCESS.equals(result)) {
                return true;
            }
            return false;
        }finally {
            returnResource(jedis);
        }
    }

    /**
     * 关闭资源
     * @param jedis
     */
    public static void returnResource(final Jedis jedis){
        if(null != jedis){
            jedis.close();
        }
    }


}

5、分布式锁超时失效问题

问题:在加锁和释放锁之间,如果业务代码执行时间过长,导致锁超时被释放掉。

具体情况:

  1. 第一台机器获取锁,业务未执行完,锁过期了;
  2. 第二台机器就可以获取到锁,在第二台机器未释放锁的时候,第一台机器释放锁(主动关闭锁);
  3. 那么第三台机器就可以获取到锁,也执行业务代码;
  4. 那么,以此类推,就会出现每次都会出现两台机器同时执行锁里面的业务代码

解决方案:

方案1:估算出业务代码执行时间,增加锁的过期时间,保证锁内的业务代码能够执行完毕,然后主动释放锁;

方案2:对于redis分布式锁,key是唯一,但是value不一定唯一,可以通过String value = UUID.randomUUID().toString();使value是唯一,因为文中closeLock()方法是通过lua脚本,实现多条指令的原子性执行,与del()方法不同,需要匹配到value才会执行成功,所以每台机器获取到锁设置的value都是不同的,就不会出现第一台机器主动释放锁,第二台机器未执行完,第三台机器获取到锁的情况。

总结:

推荐方案1和方案2结合使用,但是这样其实还是无法完全解决第一台机器业务未执行完,锁过期,第二台机器获取到锁的情况。因为生产环境复杂多变,不知道会因为数据库或者其他原因,致使业务长时间卡在那,导致锁过期。

  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 4
    评论
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值