Redis实现分布式锁Spring Boot、Redisson

Redis实现分布式锁(Spring Boot原生与Redisson实现)

在现代分布式系统中,保证共享资源的并发访问安全是至关重要的。为了避免多个客户端同时访问某个共享资源导致的数据冲突,分布式锁作为一种常见的同步机制被广泛应用。而Redis凭借其高效、可靠的特性,成为了分布式锁的理想实现方案。本文将介绍如何通过Redis实现分布式锁,包括Spring Boot原生实现、Redisson实现以及基本的分布式锁原理。

1. 分布式锁的基本原理

分布式锁(Distributed Lock)是一种在多个分布式节点之间控制资源访问的机制。分布式锁的主要目的是保证在同一时刻只有一个客户端能够访问某个共享资源,从而避免由于并发访问导致的竞态条件和数据不一致。

Redis通过它的SETNX命令为分布式锁提供了一个简单且有效的实现方式。SETNX的作用是只有在指定的key不存在时,才会设置一个新的值,并返回1表示成功。如果指定的key已经存在,SETNX命令返回0,并不会进行任何操作。

分布式锁的核心特性:

  • 互斥性:同一时刻只有一个客户端能够获取锁并执行临界区的操作。
  • 公平性:锁的获取可以按照请求的顺序进行分配。
  • 可靠性:确保即使客户端崩溃或发生异常,锁依然能够正确释放,避免死锁。
  • 高可用性:在Redis节点发生故障的情况下,分布式锁能够保证仍然有效。

2. Redis实现分布式锁的方式

2.1 基本实现方式

Redis分布式锁的基本实现方式是通过SETNX命令来设置一个key,该key的值是一个代表锁的标识。为了防止死锁,还需要设置一个过期时间,确保即使客户端崩溃,锁也能在一定时间内自动释放。

public class RedisDistributedLock {

    private static final String LOCK_KEY = "lock_key";
    private static final int LOCK_EXPIRE_TIME = 30; // 锁的有效时间(秒)

    // 获取分布式锁
    public boolean acquireLock(Jedis jedis) {
        long result = jedis.setnx(LOCK_KEY, String.valueOf(System.currentTimeMillis() + LOCK_EXPIRE_TIME * 1000));
        if (result == 1) {
            // 如果锁获取成功,设置过期时间
            jedis.expire(LOCK_KEY, LOCK_EXPIRE_TIME);
            return true;
        } else {
            String lockTimeStr = jedis.get(LOCK_KEY);
            long lockTime = Long.parseLong(lockTimeStr);
            if (lockTime < System.currentTimeMillis()) {
                // 如果锁已经过期,重新获取锁
                String oldValue = jedis.getSet(LOCK_KEY, String.valueOf(System.currentTimeMillis() + LOCK_EXPIRE_TIME * 1000));
                if (oldValue != null && oldValue.equals(lockTimeStr)) {
                    jedis.expire(LOCK_KEY, LOCK_EXPIRE_TIME);
                    return true;
                }
            }
        }
        return false; // 锁未获取成功
    }

    // 释放分布式锁
    public void releaseLock(Jedis jedis) {
        jedis.del(LOCK_KEY);
    }
}
2.2 锁的过期时间

为了避免死锁的发生,我们会在获取锁时为其设置一个过期时间。过期时间确保即使客户端在持有锁期间崩溃,其他客户端仍然能够重新获得锁。

在实现中,SETNX命令和EXPIRE命令结合使用,保证了锁的有效性和自动释放。为了防止锁过期后仍然无法被释放,我们可以使用getSet来重新设置锁的时间,从而确保锁的有效期。

3. Spring Boot中实现Redis分布式锁

3.1 配置Spring Boot Redis连接

首先,在pom.xml文件中引入Spring Boot的Redis依赖:

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

然后,在application.properties中配置Redis连接:

spring.redis.host=localhost
spring.redis.port=6379
spring.redis.password=
3.2 使用Redisson实现分布式锁

Redisson是Redis的高级客户端,它提供了更加简洁和高级的分布式锁API。在Spring Boot项目中使用Redisson实现分布式锁非常方便,接下来我们将通过Redisson来实现分布式锁。

首先,添加Redisson的依赖:

<dependency>
    <groupId>org.redisson</groupId>
    <artifactId>redisson-spring-data-26</artifactId>
    <version>3.16.3</version>
</dependency>

然后,配置Redisson客户端:

@Configuration
public class RedissonConfig {
    @Bean
    public RedissonClient redissonClient() {
        Config config = new Config();
        config.useSingleServer().setAddress("redis://localhost:6379");
        return Redisson.create(config);
    }
}

接着,在服务类中使用Redisson来获取分布式锁:

@Service
public class MyService {

    private final RedissonClient redissonClient;

    @Autowired
    public MyService(RedissonClient redissonClient) {
        this.redissonClient = redissonClient;
    }

    public void executeWithLock() {
        RLock lock = redissonClient.getLock("myLock");

        try {
            lock.lock(); // 获取锁
            // 执行业务逻辑
            System.out.println("Lock acquired and business logic executed");
        } finally {
            lock.unlock(); // 释放锁
        }
    }
}
3.3 Spring Boot原生实现Redis分布式锁

在Spring Boot中,使用RedisTemplate可以直接通过原生Redis命令来实现分布式锁。通过SETNX命令,我们可以确保只有一个客户端能获得锁。

首先,我们配置RedisTemplate

@Configuration
public class RedisConfig {

    @Bean
    public RedisTemplate<String, String> redisTemplate(RedisConnectionFactory factory) {
        RedisTemplate<String, String> template = new RedisTemplate<>();
        template.setConnectionFactory(factory);
        template.setKeySerializer(new StringRedisSerializer());
        template.setValueSerializer(new StringRedisSerializer());
        return template;
    }
}

接着,通过RedisTemplate实现分布式锁:

@Service
public class RedisLockService {

    private static final String LOCK_KEY = "lock_key"; // 锁的key
    private static final int LOCK_EXPIRE_TIME = 30; // 锁的有效时间(秒)

    private final RedisTemplate<String, String> redisTemplate;

    @Autowired
    public RedisLockService(RedisTemplate<String, String> redisTemplate) {
        this.redisTemplate = redisTemplate;
    }

    // 获取分布式锁
    public boolean acquireLock() {
        long currentTime = System.currentTimeMillis();
        String lockValue = String.valueOf(currentTime + LOCK_EXPIRE_TIME * 1000); // 锁过期时间
        Boolean result = redisTemplate.opsForValue().setIfAbsent(LOCK_KEY, lockValue, LOCK_EXPIRE_TIME, TimeUnit.SECONDS);

        return Boolean.TRUE.equals(result); // 如果获取锁成功,返回true
    }

    // 释放分布式锁
    public void releaseLock() {
        String currentLockValue = redisTemplate.opsForValue().get(LOCK_KEY);
        if (currentLockValue != null) {
            long lockTime = Long.parseLong(currentLockValue);
            if (lockTime < System.currentTimeMillis()) {
                redisTemplate.delete(LOCK_KEY); // 锁超时,删除锁
            }
        }
    }
}
3.4 锁的过期时间与重试机制

为了防止死锁的发生,可以在获取锁时设置一个最大重试次数,并且加上重试间隔,以避免长时间等待。例如:

@Service
public class RedisLockServiceWithRetry {

    private static final String LOCK_KEY = "lock_key";
    private static final int LOCK_EXPIRE_TIME = 30;
    private static final int RETRY_INTERVAL = 1000; // 重试间隔(毫秒)
    private static final int MAX_RETRIES = 5; // 最大重试次数

    private final RedisTemplate<String, String> redisTemplate;

    @Autowired
    public RedisLockServiceWithRetry(RedisTemplate<String, String> redisTemplate) {
        this.redisTemplate = redisTemplate;
    }

    // 获取分布式锁
    public boolean acquireLockWithRetry() throws InterruptedException {
        int retries = 0;
        while (retries < MAX_RETRIES) {
            boolean lockAcquired = acquireLock();
            if (lockAcquired) {
                return true;
            }
            retries++;
            Thread.sleep(RETRY_INTERVAL); // 等待一段时间后重试
        }
        return false; // 达到最大重试次数仍未获取到锁
    }

    private boolean acquireLock() {
        long currentTime = System.currentTimeMillis();
        String lockValue = String.valueOf(currentTime + LOCK_EXPIRE_TIME * 1000);
        Boolean result = redisTemplate.opsForValue().setIfAbsent(LOCK_KEY, lockValue, LOCK_EXPIRE_TIME, TimeUnit.SECONDS);
        return Boolean.TRUE.equals(result);
    }

    // 释放分布式锁
    public void releaseLock() {
        String currentLockValue = redisTemplate.opsForValue().get(LOCK_KEY);
        if (currentLockValue != null) {
            long lockTime = Long.parseLong(currentLockValue);
            if (lockTime < System.currentTimeMillis()) {
                redisTemplate.delete(LOCK_KEY);
            }
        }
    }
}

4. 总结

通过Redis实现分布式锁,我们可以确保多个分布式节点之间能够安全地访问共享资源,避免了并发操作导致的竞态条件。本文介绍了两种实现方式:一种是使用Spring Boot原生的RedisTemplate,另一种是使用Redisson。在实际应用中,根据业务需求选择合适的实现方式,可以帮助我们在高并发环境下确保系统的稳定性和可靠性。

正文到此结束
评论插件初始化中...
Loading...
本文目录