springboot集成redis分布式锁

目前,大多数服务都是用了多实例,集群部署。传统的synchronized,ReentrantLock等锁,只能在单实例内部生效。集群服务的线程安全,需要通过分布式锁实现,当然如果是数据库访问的话,也可以通过数据库锁实现分布式锁(TODO: mysql集群行锁能否在分布式服务中生效?单实例写,肯定是没问题的;多实例写需要再探究下)。本篇文章主要介绍在springboot项目中使用redis实现分布式锁。

1. StringRedisTemplate实现分布式锁

1.1 pom.xml文件引入redis依赖

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

1.2 在application.yml中配置redis

spring:
  redis:
    host: 192.168.1.6
    port: 6379

1.3 新建redis锁操作类

/**
 * redis锁操作类
 */
@Repository
public class RedisLock {
    
    @Autowired
    private StringRedisTemplate stringredisTemplate;

    public RedisLock(StringRedisTemplate stringredisTemplate) {
        this.stringredisTemplate = stringredisTemplate;
    }

    /**
     * 加锁,无阻塞
     * 1. 加锁过程必须设置过期时间。如果没有设置过期时间,手动释放锁的操作出现问题,那么就发生死锁,锁永远不能被释放.
     * 2. 加锁和设置过期时间过程必须是原子操作。如果加锁后服务宕机或程序崩溃,来不及设置过期时间,同样会发生死锁.
     *   
     */
    public String tryLock(String key, long expire) {
        String token = UUID.randomUUID().toString();
        //setIfAbsent方法:当key不存在的时候,设置成功并返回true,当key存在的时候,设置失败并返回false
        //token是对应的value,expire是缓存过期时间
        Boolean isSuccess = stringredisTemplate.opsForValue().setIfAbsent(key, token, expire, TimeUnit.MILLISECONDS);
        if (isSuccess) {
            return token;
        }
        return null;
    }

    /**
     * 加锁,有阻塞
     */
    public String lock(String name, long expire, long timeout) {
        long startTime = System.currentTimeMillis();
        String token;

        do {
            token = tryLock(name, expire);
            if (token == null) {
                if ((System.currentTimeMillis() - startTime) > timeout) {
                    break;
                }
                try {
                    //try 50 per sec
                    Thread.sleep(50);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                    return null;
                }
            }
        } while (token == null);

        return token;
    }

    /**
     * 解锁操作
     * 1. 解锁必须是解除自己加上的锁.
     * 试想一个这样的场景,服务A加锁,但执行效率非常慢,导致锁失效后还未执行完,但这时候服务B已经拿到锁了,这时候服务A执行完毕了去解锁,
     * 把服务B的锁给解掉了,其他服务C、D、E...都可以拿到锁了,这就有问题了.
     * 加锁的时候我们可以设置唯一value,解锁时判断是不是自己先前的value就行了.
     *   
     */
    public boolean unlock(String key, String token) {
        //解锁时需要先取出key对应的value进行判断是否相等,这也是为什么加锁的时候需要放不重复的值作为value
        String value = stringredisTemplate.opsForValue().get(key);
        if (StringUtils.equals(value, token)) {
            stringredisTemplate.delete(key);
            return true;
        }
        return false;
    }
}

1.4 业务操作类,用上RedisLock

@Service
public class ServiceImpl implements Service {
    @Autowired
    private Mapper mapper;
    @Autowired
    private RedisLock redisLock;

    @Override
    public Map<String, Object> test() {
        Map<String, Object> resultMap = new HashMap<>();
        
        //加锁操作,在需要的地方加锁,控制锁粒度尽量小
        String token = redisLock.lock("key", 3000, 3500);
        try {
            //获取到了锁,执行正常业务
            if (token != null) {
                //执行业务
            } else {
                resultMap.put("code", "401");
                resultMap.put("msg", "其他窗口正在操作,请稍后再试");
                return resultMap;
            }
        } finally {
            //解锁
            if (token != null) {
                boolean isSuccess = redisLock.unlock("key", token);
            }
        }
        resultMap.put("code", "200");
        resultMap.put("msg", "success");
        return resultMap;
    }
}

2. Redisson实现分布式锁

2.1 pom依赖

<dependency>
	<groupId>org.redisson</groupId>
	<artifactId>redisson</artifactId>
	<version>3.11.5</version>
</dependency>

2.2 RedissonClient配置类

@Configuration
public class RedisRedLock{

    @Value("${spring.redis.sentinel.nodesRedis}")
    private String[] nodes;
    @Value("${spring.redis.sentinel.master}")
    private String master;

    /**
     * 单个服务
     * @return
     */
    @Bean
    public  RedissonClient redissonClientSign(){
        Config config = new Config();
        config.useSingleServer()
                .setIdleConnectionTimeout(10000)//如果当前连接池里的连接数量超过了最小空闲连接数,而同时有连接空闲时间超过了该数值,那么这些连接将会自动被关闭,并从连接池里去掉。时间单位是毫秒。
                .setConnectTimeout(30000)//同任何节点建立连接时的等待超时。时间单位是毫秒。
                .setTimeout(3000)//等待节点回复命令的时间。该时间从命令发送成功时开始计时。
                .setPingTimeout(30000)
                .setReconnectionTimeout(3000)//当与某个节点的连接断开时,等待与其重新建立
                .setPassword("root123456abc").setDatabase(0);
        return  Redisson.create(config);
    }

    /**
     * 哨兵模式
     * @return
     */
    @Bean
    public  RedissonClient redissonClient(){
        Config config = new Config();
        config.useSentinelServers().setMasterName(master)
                .setFailedSlaveReconnectionInterval(5000)
                .addSentinelAddress(nodes)
                .setMasterConnectionPoolSize(500)//设置对于master节点的连接池中连接数最大为500
                .setSlaveConnectionPoolSize(500)//设置对于slave节点的连接池中连接数最大为500
                .setIdleConnectionTimeout(10000)//如果当前连接池里的连接数量超过了最小空闲连接数,而同时有连接空闲时间超过了该数值,那么这些连接将会自动被关闭,并从连接池里去掉。时间单位是毫秒。
                .setConnectTimeout(30000)//同任何节点建立连接时的等待超时。时间单位是毫秒。
                .setTimeout(3000)//等待节点回复命令的时间。该时间从命令发送成功时开始计时。
                .setPingTimeout(30000)
                .setReconnectionTimeout(3000)//当与某个节点的连接断开时,等待与其重新建立
                .setPassword("root123456abc").setDatabase(0);
        return  Redisson.create(config);
    }
}

2.3 锁工具接口以及实现类

public interface RedLockUtils {

    void lock(String lockKey);
    void lock(String lockKey, long expireTime);
    boolean tryLockTimeout(String lockKey, long waitTime, long expireTime) throws InterruptedException;
    void unLock(String lockKey);
}
@Component
public class RedLockUtilsImpl implements RedLockUtils{

    @Autowired
    private RedissonClient redissonClient;

    /**
     * 可重入!线程不主动解锁将会永远存在! 慎用
     */
    public void lock(String lockKey){
        RLock lock1 = redissonClient.getLock(lockKey);
        redissonClient.getRedLock(lock1).lock();
    }

    public void lock(String lockKey, long expireTime) {
        RLock lock1 = redissonClient.getLock(lockKey);
        redissonClient.getRedLock(lock1).lock(expireTime, TimeUnit.MILLISECONDS);
    }

    public boolean tryLockTimeout(String lockKey,long waitTime, long expireTime) throws InterruptedException {
        RLock lock1 = redissonClient.getLock(lockKey);
        return redissonClient.getRedLock(lock1).tryLock(waitTime, expireTime, TimeUnit.MILLISECONDS);
    }
    
    public void unLock(String lockKey) {
        RLock lock1 = redissonClient.getLock(lockKey);
        redissonClient.getRedLock(lock1).unlock();
    }
}

2.4 业务类使用示例

@Service
public class ServiceImpl implements Service {

    @Autowired
    private RedLockUtils redLockUtils;

    public void test() {
        String lockKey = "key";
        boolean isLock = false;
        try {
            //获得锁 注意锁的力度,只需要锁定需要防止并发的业务,锁的力度越低性能越好!
            isLock = redLockUtils.tryLockTimeout(lockKey, 5000, 10000);
            //超时未获得锁
            if(!isLock ){
                logger.warn("操作太频繁,请稍后重试");
                return;
            }
            //执行业务(需要锁定的部分)
            
        } catch (Exception e) {
            logger.error("error:", e);
        }finally {
            //解锁
            if(isLock) {
                redLockUtils.unLock(lockKey);
            }
        }
    }
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
回答: 在Spring Boot中集成Redis分布式锁可以使用Redisson来实现。Redisson是一个基于Redis的分布式对象和服务框架,内部已经实现了Redis分布式锁,使用起来更加方便和稳定。通过Redisson,可以使用RedLock算法来实现分布式锁,确保锁的正确性和可靠性。在Redis集群环境下,可能存在锁失效或死锁的问题,但使用Redisson的分布式锁可以解决这个问题。此外,分布式锁是为了解决分布式系统中控制共享资源访问的问题,因为在分布式系统中,多线程、多进程分布在不同机器上,传统的并发控制锁策略失效。因此,通过集成Redis分布式锁,可以有效地控制共享资源的访问。 #### 引用[.reference_title] - *1* *2* [springboot集成redis 分布式锁(redistemplate,lua,redisson)](https://blog.csdn.net/jun2571/article/details/130382023)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v91^control_2,239^v3^insert_chatgpt"}} ] [.reference_item] - *3* [Springboot集成Redis——实现分布式锁](https://blog.csdn.net/tang_seven/article/details/126769580)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v91^control_2,239^v3^insert_chatgpt"}} ] [.reference_item] [ .reference_list ]
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值