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/zh-hant/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

發表回復

登錄後才能評論