RedisTemplate分布式锁

使用Redis作为分布式锁是非常常见的。相对于基于数据库的锁,Redis分布式锁的效率更高、更可靠、更方便。在使用Redis时,RedisTemplate是一个很好的工具类。那么本文就来详细介绍RedisTemplate分布式锁。

一、使用Jedis实现RedisTemplate

RedisTemplate是Spring Data Redis提供的一个在Java环境下访问Redis数据库的接口。在使用RedisTemplate分布式锁时,首先要实例化RedisTemplate,并将JedisConnectionFactory作为它的构造参数。

public class RedisLock {
    private static final Logger logger = LoggerFactory.getLogger(RedisLock.class);
    private RedisTemplate redisTemplate;
    private String lockKey;
    private String threadId;
    private long keepMills;
    private boolean locked = false;

    public RedisLock(RedisTemplate redisTemplate, String lockKey, long keepMills) {
        this.redisTemplate = redisTemplate;
        this.lockKey = lockKey;
        this.keepMills = keepMills;
        this.threadId = String.valueOf(Thread.currentThread().getId());
    }

    //... more code ...
}

这里要注意配置文件:在实例化JedisConnectionFactory时,需要配置Redis的IP地址、端口和密码等信息。以下是示例代码:

@Configuration
public class RedisConfig {
    @Value("${spring.redis.host}")
    private String host;
    @Value("${spring.redis.port}")
    private int port;
    @Value("${spring.redis.database}")
    private int database;
    @Value("${spring.redis.password}")
    private String password;
    @Value("${spring.redis.jedis.pool.max-active}")
    private int maxActive;
    @Value("${spring.redis.jedis.pool.max-idle}")
    private int maxIdle;
    @Value("${spring.redis.jedis.pool.max-wait}")
    private long maxWait;
    @Value("${spring.redis.timeout}")
    private long timeout;

    @Bean
    JedisConnectionFactory jedisConnectionFactory() {
        RedisStandaloneConfiguration redisStandaloneConfiguration = new RedisStandaloneConfiguration();
        redisStandaloneConfiguration.setHostName(host);
        redisStandaloneConfiguration.setPort(port);
        redisStandaloneConfiguration.setDatabase(database);
        redisStandaloneConfiguration.setPassword(RedisPassword.of(password));
        JedisClientConfiguration.JedisPoolingClientConfigurationBuilder poolingBuilder = (JedisClientConfiguration.JedisPoolingClientConfigurationBuilder) JedisClientConfiguration.builder();
        JedisPoolConfig jedisPoolConfig = new JedisPoolConfig();
        jedisPoolConfig.setMaxTotal(maxActive);
        jedisPoolConfig.setMaxIdle(maxIdle);
        jedisPoolConfig.setMaxWaitMillis(maxWait);
        poolingBuilder.poolConfig(jedisPoolConfig);
        JedisClientConfiguration jedisClientConfiguration = poolingBuilder.build();
        JedisConnectionFactory jedisConnectionFactory = new JedisConnectionFactory(redisStandaloneConfiguration, jedisClientConfiguration);
        return jedisConnectionFactory;
    }

    @Bean(name = "redisTemplate")
    public RedisTemplate redisTemplate() {
        RedisTemplate redisTemplate = new RedisTemplate();
        redisTemplate.setConnectionFactory(jedisConnectionFactory());
        redisTemplate.setKeySerializer(new StringRedisSerializer());
        redisTemplate.setValueSerializer(new GenericJackson2JsonRedisSerializer());
        redisTemplate.setHashKeySerializer(new StringRedisSerializer());
        redisTemplate.setHashValueSerializer(new GenericJackson2JsonRedisSerializer());
        redisTemplate.afterPropertiesSet();
        return redisTemplate;
    }
}

二、实现简单的Redis分布式锁

首先,RedisTemplate分布式锁需要使用SETNX命令,如果返回值为1,表示加锁成功,如果为0,说明锁已经被其他线程持有。

下面是加锁操作的代码:

public boolean lock() {
    long now = System.currentTimeMillis();
    long expired = now + keepMills + 1;
    String expiredStr = String.valueOf(expired);

    if (redisTemplate.opsForValue().setIfAbsent(lockKey, expiredStr)) {
        locked = true;
        return true;
    }

    String currentValueStr = (String) redisTemplate.opsForValue().get(lockKey);

    if (currentValueStr != null && Long.parseLong(currentValueStr) < now) {
        // lock is expired
        String oldValueStr = (String) redisTemplate.opsForValue().getAndSet(lockKey, expiredStr);
        if (oldValueStr != null && oldValueStr.equals(currentValueStr)) {
                locked = true;
                return true;
        }
    }
    return false;
}

代码中的keepMills表示锁的过期时间,单位是毫秒。代码逻辑是:首先尝试加锁;若获取锁成功,则设置locked为true,返回true;若不能获取锁,则检查锁是否过期,如果过期,使用getset方法获取旧值并将新值设置到锁的key中。如果新旧值相同,则成功获取锁,否则继续竞争锁。

接下来是解锁操作的代码:

public void unlock() {
    if (locked) {
        redisTemplate.delete(lockKey);
        locked = false;
    }
}

解锁操作比较简单,只需要将key删除即可。

三、优化Redis分布式锁

上面的Redis分布式锁实现虽然可以满足基本的使用,但仍存在一些问题,如高并发时锁可能会失效、锁的时间过长等。下面分别介绍优化方案。

1.使用Lua脚本提高并发性

为了避免高并发时锁的失效,可以使用Lua脚本来确保Redis分布式锁的原子性。

Lua脚本要比Java代码快,并且可以确保Redis分布式锁的原子性。以下是使用Lua脚本实现分布式锁的代码:

private static final String LOCK_SCRIPT =
        "if redis.call('SETNX', KEYS[1], ARGV[1]) == 1 " +
        "then " +
        "   redis.call('PEXPIRE', KEYS[1], ARGV[2]); " + 
        "   return true; " + 
        "else " +
        "   return false; " + 
        "end; ";

private static final String UNLOCK_SCRIPT =
        "if redis.call('GET', KEYS[1]) == ARGV[1] " +
        "then " +
        "   return redis.call('DEL', KEYS[1]); " +
        "else " +
        "   return 0; " +
        "end; ";

public boolean lock() {
    long now = System.currentTimeMillis();
    long expired = now + keepMills + 1;
    String expiredStr = String.valueOf(expired);

    RedisScript redisScript = new DefaultRedisScript(LOCK_SCRIPT, Boolean.class);
    boolean success = redisTemplate.execute(redisScript, Collections.singletonList(lockKey), expiredStr, String.valueOf(keepMills));

    locked = success;
    return success;
}

public void unlock() {
    RedisScript redisScript = new DefaultRedisScript(UNLOCK_SCRIPT, Long.class);
    redisTemplate.execute(redisScript, Collections.singletonList(lockKey), String.valueOf(System.currentTimeMillis()));
    locked = false;
}

代码中,LOCK_SCRIPT和UNLOCK_SCRIPT是Lua脚本,分别用于加锁和解锁操作。使用RedisTemplate执行脚本时,将脚本和参数传递给execute方法即可。

2.使用Redisson优化锁的时间

如果在分布式系统中锁的时间过长,可能会导致死锁的问题。为了避免这种情况,可以使用Redisson提供的可重入锁。

Redisson是一个Java Redis客户端,实现了分布式和可扩展的Java数据结构。Redisson的可重入锁将使用累计次数来递增释放锁的次数。这意味着任何线程都可以释放锁,而不仅仅是那个持有锁的线程。

public boolean lock() {
    RLock redissonLock = redissonClient.getLock(lockKey);
    try {
        boolean success = redissonLock.tryLock(0, keepMills, TimeUnit.MILLISECONDS);
        locked = success;
        return success;
    } catch (InterruptedException e) {
        return false;
    }
}

public void unlock() {
    if (locked) {
        RLock redissonLock = redissonClient.getLock(lockKey);
        redissonLock.unlock();
    }
}

代码中的RLock表示Redisson的可重入锁。在加锁时,调用tryLock方法,如果获取锁成功,则设置locked为true,返回true。在解锁时,直接调用unlock方法即可。

四、总结

本文详细介绍了RedisTemplate分布式锁的实现原理和优化方式。使用RedisTemplate分布式锁可以帮助我们解决在分布式环境下数据一致性的问题,是分布式系统中比较常见和可靠的锁实现方式。

原创文章,作者:小蓝,如若转载,请注明出处:https://www.506064.com/n/304242.html

(0)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
小蓝小蓝
上一篇 2025-01-01 11:05
下一篇 2025-01-01 11:05

相关推荐

  • KeyDB Java:完美的分布式高速缓存方案

    本文将从以下几个方面对KeyDB Java进行详细阐述:KeyDB Java的特点、安装和配置、使用示例、性能测试。 一、KeyDB Java的特点 KeyDB Java是KeyD…

    编程 2025-04-29
  • Java Hmily分布式事务解决方案

    分布式系统是现在互联网公司架构中的必备项,但随着业务的不断扩展,分布式事务的问题也日益凸显。为了解决分布式事务问题,Java Hmily分布式事务解决方案应运而生。本文将对Java…

    编程 2025-04-28
  • JL Transaction – 实现分布式事务管理的利器

    本文将为大家介绍JL Transaction,这是一款可以实现分布式事务管理的开源事务框架,它可以帮助企业在分布式环境下有效地解决事务的一致性问题,从而保障系统的稳定性和可靠性。 …

    编程 2025-04-28
  • 使用RPC研发云实现分布式服务交互

    本文将基于RPC研发云,阐述分布式服务交互实现的过程和实现方式。 一、RPC研发云简介 RPC研发云是一种基于分布式架构的服务框架,在处理不同语言之间的通信上变得越来越流行。通过使…

    编程 2025-04-28
  • 分布式文件系统数据分布算法

    数据分布算法是分布式文件系统中的重要技术之一,它能够实现将文件分散存储于各个节点上,提高系统的可靠性和性能。在这篇文章中,我们将从多个方面对分布式文件系统数据分布算法进行详细的阐述…

    编程 2025-04-27
  • 使用RedisTemplate设置缓存过期时间

    RedisTemplate 是 Spring Data Redis 为了方便开发者操作 Redis 数据库而提供的一个模板类。在使用 RedisTemplate 操作 Redis …

    编程 2025-04-24
  • 使用Spring Cloud Redis实现分布式缓存管理

    一、背景介绍 在分布式互联网应用中,缓存技术扮演着非常重要的角色。缓存技术能够有效减轻数据库的访问压力,提高应用的访问速度。在分布式应用中,如何统一管理分布式缓存成为了一项挑战。本…

    编程 2025-04-24
  • 使用Kubernetes(K8s)搭建分布式系统

    一、Kubernetes概述 Kubernetes是一个用于自动部署、扩展和管理容器化应用程序的开源平台。其提供了高可用性、自我修复能力和易于扩展的特征,使得大规模、高度可用的分布…

    编程 2025-04-24
  • 分布式锁的实现与应用——以Redisson为例

    分布式锁是保障在分布式系统中多个节点之间资源互斥的重要手段,而Redisson是Redis官方推荐的Java客户端,不仅提供基于Java语言对Redis的操作接口,还提供了分布式锁…

    编程 2025-04-23
  • 详解SpringBoot分布式锁

    一、为什么需要分布式锁? 在分布式系统中,多个节点需要对同一资源进行并发访问和操作。如果没有分布式锁,很容易出现资源竞争问题,引发数据错误或系统崩溃的风险。 例如,假设有两个客户端…

    编程 2025-04-23

发表回复

登录后才能评论