实现基于Redis的分布式锁机制

大家好,我是微赚淘客系统3.0的小编,也是冬天不穿秋裤,天冷也要风度的程序猿!今天我们来讨论一下如何实现基于Redis的分布式锁机制。分布式锁在分布式系统中非常重要,它可以防止多个进程或线程同时访问共享资源,确保数据的一致性和完整性。

1. 为什么选择Redis实现分布式锁

Redis是一种高性能的键值存储系统,支持丰富的操作,具有高并发处理能力。使用Redis实现分布式锁,有以下优点:

  • 性能高效:Redis的读写性能非常高,适合实现高性能的分布式锁。
  • 原子操作:Redis提供了多种原子操作,能够确保锁的操作具有原子性。
  • 持久化:Redis支持数据持久化,能在系统故障后恢复锁的信息。

2. 基于Redis的分布式锁实现思路

基于Redis的分布式锁实现思路如下:

  1. 获取锁:使用SET命令尝试设置一个键,并使用NX选项确保键不存在时才设置。
  2. 释放锁:通过删除锁对应的键来释放锁。
  3. 锁超时:设置键的过期时间,防止死锁。

3. 使用Jedis实现Redis分布式锁

Jedis是一个简单易用的Redis客户端,下面我们通过Jedis实现一个简单的分布式锁。

3.1 引入依赖

pom.xml中添加Jedis的依赖:

<dependency>
    <groupId>redis.clients</groupId>
    <artifactId>jedis</artifactId>
    <version>4.0.0</version>
</dependency>
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.

3.2 实现分布式锁

package cn.juwatech.lock;

import redis.clients.jedis.Jedis;

public class RedisDistributedLock {
    private static final String LOCK_KEY = "distributed_lock";
    private static final int EXPIRE_TIME = 30000; // 锁过期时间,单位毫秒

    private Jedis jedis;

    public RedisDistributedLock(Jedis jedis) {
        this.jedis = jedis;
    }

    public boolean acquireLock(String requestId) {
        String result = jedis.set(LOCK_KEY, requestId, "NX", "PX", EXPIRE_TIME);
        return "OK".equals(result);
    }

    public boolean releaseLock(String requestId) {
        if (requestId.equals(jedis.get(LOCK_KEY))) {
            jedis.del(LOCK_KEY);
            return true;
        }
        return false;
    }
}
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.
  • 19.
  • 20.
  • 21.
  • 22.
  • 23.
  • 24.
  • 25.
  • 26.
  • 27.

3.3 测试分布式锁

package cn.juwatech.lock;

import redis.clients.jedis.Jedis;

public class LockTest {
    public static void main(String[] args) {
        Jedis jedis = new Jedis("localhost", 6379);
        RedisDistributedLock lock = new RedisDistributedLock(jedis);

        String requestId = "unique_request_id";
        
        if (lock.acquireLock(requestId)) {
            System.out.println("获取锁成功");
            // 执行业务逻辑
            if (lock.releaseLock(requestId)) {
                System.out.println("释放锁成功");
            } else {
                System.out.println("释放锁失败");
            }
        } else {
            System.out.println("获取锁失败");
        }

        jedis.close();
    }
}
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.
  • 19.
  • 20.
  • 21.
  • 22.
  • 23.
  • 24.
  • 25.
  • 26.

4. 优化分布式锁

4.1 防止锁误删

在上面的实现中,锁的释放是通过直接删除键来实现的,但这可能导致误删的情况。可以使用Lua脚本保证锁的释放操作是原子的。

package cn.juwatech.lock;

import redis.clients.jedis.Jedis;

public class RedisDistributedLock {
    private static final String LOCK_KEY = "distributed_lock";
    private static final int EXPIRE_TIME = 30000; // 锁过期时间,单位毫秒
    private static final String RELEASE_LOCK_SCRIPT =
            "if redis.call('get', KEYS[1]) == ARGV[1] then " +
            "return redis.call('del', KEYS[1]) " +
            "else " +
            "return 0 " +
            "end";

    private Jedis jedis;

    public RedisDistributedLock(Jedis jedis) {
        this.jedis = jedis;
    }

    public boolean acquireLock(String requestId) {
        String result = jedis.set(LOCK_KEY, requestId, "NX", "PX", EXPIRE_TIME);
        return "OK".equals(result);
    }

    public boolean releaseLock(String requestId) {
        Object result = jedis.eval(RELEASE_LOCK_SCRIPT, 1, LOCK_KEY, requestId);
        return 1L == result;
    }
}
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.
  • 19.
  • 20.
  • 21.
  • 22.
  • 23.
  • 24.
  • 25.
  • 26.
  • 27.
  • 28.
  • 29.
  • 30.

4.2 续租机制

为了防止锁的持有者因为某些原因未能及时释放锁,可以实现续租机制,即在锁快过期时,持有者可以续租锁,延长锁的有效期。

package cn.juwatech.lock;

import redis.clients.jedis.Jedis;

public class RedisDistributedLock {
    private static final String LOCK_KEY = "distributed_lock";
    private static final int EXPIRE_TIME = 30000; // 锁过期时间,单位毫秒
    private static final String RENEW_LOCK_SCRIPT =
            "if redis.call('get', KEYS[1]) == ARGV[1] then " +
            "return redis.call('pexpire', KEYS[1], ARGV[2]) " +
            "else " +
            "return 0 " +
            "end";

    private Jedis jedis;

    public RedisDistributedLock(Jedis jedis) {
        this.jedis = jedis;
    }

    public boolean acquireLock(String requestId) {
        String result = jedis.set(LOCK_KEY, requestId, "NX", "PX", EXPIRE_TIME);
        return "OK".equals(result);
    }

    public boolean releaseLock(String requestId) {
        Object result = jedis.eval(RELEASE_LOCK_SCRIPT, 1, LOCK_KEY, requestId);
        return 1L == result;
    }

    public boolean renewLock(String requestId) {
        Object result = jedis.eval(RENEW_LOCK_SCRIPT, 1, LOCK_KEY, requestId, String.valueOf(EXPIRE_TIME));
        return 1L == result;
    }
}
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.
  • 19.
  • 20.
  • 21.
  • 22.
  • 23.
  • 24.
  • 25.
  • 26.
  • 27.
  • 28.
  • 29.
  • 30.
  • 31.
  • 32.
  • 33.
  • 34.
  • 35.

5. 集成Spring Boot

在实际项目中,可以将上述代码集成到Spring Boot项目中,通过Spring管理Jedis实例。

5.1 配置Redis

application.properties中配置Redis连接信息:

spring.redis.host=localhost
spring.redis.port=6379
  • 1.
  • 2.

5.2 配置类

package cn.juwatech.config;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import redis.clients.jedis.Jedis;

@Configuration
public class RedisConfig {

    @Bean
    public Jedis jedis() {
        return new Jedis("localhost", 6379);
    }
}
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.

5.3 使用Redis分布式锁

package cn.juwatech.service;

import cn.juwatech.lock.RedisDistributedLock;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import redis.clients.jedis.Jedis;

@Service
public class LockService {

    @Autowired
    private Jedis jedis;

    public void performTaskWithLock() {
        RedisDistributedLock lock = new RedisDistributedLock(jedis);
        String requestId = "unique_request_id";
        
        if (lock.acquireLock(requestId)) {
            try {
                System.out.println("获取锁成功");
                // 执行业务逻辑
            } finally {
                if (lock.releaseLock(requestId)) {
                    System.out.println("释放锁成功");
                } else {
                    System.out.println("释放锁失败");
                }
            }
        } else {
            System.out.println("获取锁失败");
        }
    }
}
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.
  • 19.
  • 20.
  • 21.
  • 22.
  • 23.
  • 24.
  • 25.
  • 26.
  • 27.
  • 28.
  • 29.
  • 30.
  • 31.
  • 32.
  • 33.