Redis分布式锁

分布式锁

注意:项目代码在github上,github地址为https://github.com/nocoders/java-everything.git

工作中项目是部署在多台服务器上的,所以经常会面临解决分布式场景下数据一致性问题,这个时候可以使用分布式锁来解决。

Redis实现分布式锁有两种方案,一种是直接执行lua脚本,另一种是使用Redisson中的RedissonLocklua脚本只是简单的实现了分布式锁,Redisson则是实现了可重入锁以及对锁的时长自动延时

LUA脚本

使用lua脚本实现分布式锁的原子性删除操作。创建锁的时候,使用Redis的SETNX命令进行创建,设置key-value。如果创建失败,说明锁已经被占用了,否则创建成功。当加锁代码运行完成时,使用lua脚本对锁进行删除操作。

创建锁

SpringBoot项目中,我们可以使用StringRedisTemplatesetIfAbsent(K key, V value, long timeout, TimeUnit unit)方法创建锁。

  1. 引入依赖

     <dependency>
         <groupId>org.springframework.boot</groupId>
         <artifactId>spring-boot-starter-data-redis</artifactId>
     </dependency>
    
  2. 锁创建

    String lockValue = UUID.randomUUID().toString().replace("-", "");
    Boolean lockRes = stringRedisTemplate.opsForValue().setIfAbsent("lockKey", lockValue, 100L, TimeUnit.SECONDS);
    

删除锁

锁使用完成后,需要使用lua脚本删除锁,脚本中会先根据key获取到value,并根据value同传入的value值是否相等判断是否为同一线程设置的锁,满足条件后会将其进行删除。

  1. lua脚本

    if redis.call('get', KEYS[1]) == ARGV[1]
        then
            return redis.call('del', KEYS[1])
        else
            return 0
    end
    
  2. 使用DefaultRedisScript获取文件中的lua脚本

    DefaultRedisScript<Long> defaultRedisScript = new DefaultRedisScript();
    defaultRedisScript.setScriptSource(new ResourceScriptSource(new ClassPathResource("deleteKey.lua")));
    defaultRedisScript.setResultType(Long.class);
    
  3. 执行lua脚本,返回结果0代表执行失败,1代表执行成功

    Long result = stringRedisTemplate.execute(defaultRedisScript, Collections.singletonList("lockKey"),lockValue);
    

完整代码如下:

import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.core.io.ClassPathResource;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.redis.core.script.DefaultRedisScript;
import org.springframework.scripting.support.ResourceScriptSource;
import org.springframework.test.context.junit4.SpringRunner;

import java.util.Collections;
import java.util.UUID;
import java.util.concurrent.TimeUnit;

/**
 * setNx 加锁
 * lua脚本删除分布式锁
 *
 * @author linmeng
 * @date 2023/2/24 23:48
 */
@SpringBootTest
@RunWith(SpringRunner.class)
public class RedisLockLua {

	@Autowired
	private StringRedisTemplate stringRedisTemplate;

	@Test
	public void redisLockUnlock() {
		String lockValue = UUID.randomUUID().toString().replace("-", "");
		Boolean lockRes = stringRedisTemplate.opsForValue().setIfAbsent("lockKey", lockValue, 100L, TimeUnit.SECONDS);
		if (Boolean.TRUE.equals(lockRes)) {
			System.out.println("加锁成功");
			DefaultRedisScript<Long> defaultRedisScript = new DefaultRedisScript();
			defaultRedisScript.setScriptSource(new ResourceScriptSource(new ClassPathResource("deleteKey.lua")));
			defaultRedisScript.setResultType(Long.class);
			Long result = stringRedisTemplate.execute(defaultRedisScript, Collections.singletonList("lockKey"),lockValue);
			System.out.println("解锁是否成功:"+ (1 == result));
		}
	}
}

Redisson

Redisson底层使用的还是lua脚本,只不过在原有基础上添加了可重入以及使用定时任务自动延时的功能。可重入原理可查看这篇博客 Redis分布式锁—Redisson+RLock可重入锁实现篇, 自动续期原理可查看下面这篇博客 Redisson分布式锁自动续期源码分析

  1. 引入依赖

    <dependency>
        <groupId>org.redisson</groupId>
        <artifactId>redisson</artifactId>
        <version>3.19.3</version>
    </dependency>
    
  2. bean注入

    import org.redisson.Redisson;
    import org.redisson.api.RedissonClient;
    import org.redisson.config.Config;
    import org.springframework.context.annotation.Bean;
    import org.springframework.context.annotation.Configuration;
    
    /**
     * Redisson 配置类
     * @author linmeng
     * @date 2023/2/25 00:14
     */
    @Configuration
    public class RedissonConfig {
    	@Bean
    	public RedissonClient redissonClient() {
    		Config config = new Config();
    		config.useSingleServer()
    				.setAddress("redis://1.117.171.139:6379").setDatabase(1);
    		return Redisson.create(config);
    	}
    }
    
  3. 使用

    import org.junit.Test;
    import org.junit.runner.RunWith;
    import org.redisson.api.RedissonClient;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.boot.test.context.SpringBootTest;
    import org.springframework.test.context.junit4.SpringRunner;
    
    import java.util.concurrent.TimeUnit;
    import java.util.concurrent.locks.Lock;
    
    /**
     * Redisson 分布式锁实现
     *
     * @author linmeng
     * @date 2023/2/24 23:48
     */
    @SpringBootTest
    @RunWith(SpringRunner.class)
    public class RedisLockRedisson {
    
    	@Autowired
    	private RedissonClient redissonClient;
    
    	@Test
    	public void redisLockUnlock() {
    		Lock lock = redissonClient.getLock("lock");
    		try {
    			lock.lock();
    			System.out.println("加锁成功");
    			TimeUnit.SECONDS.sleep(5);
    			lock.lock();
    			System.out.println("加锁成功2");
    			TimeUnit.SECONDS.sleep(5);
    		} catch (Exception e) {
    		} finally {
    			lock.unlock();
    			System.out.println("解锁成功:");
    			lock.unlock();
    			System.out.println("解锁成功2:");
    
    		}
    	}
    }
    

参考链接

  1. Redis分布式锁—SETNX+Lua脚本实现篇
  2. Redis 执行 Lua 脚本抛出 StatusOutput does not support set(long) 异常
  3. SpringBoot + Redis 执行lua脚本
  4. Redis分布式锁—Redisson+RLock可重入锁实现篇
  5. [Redisson分布式锁自动续期源码分析](
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值