RedisTemplate 实现基于 Value 操作的简易锁机制

本文涉及的产品
Redis 开源版,标准版 2GB
推荐场景:
搭建游戏排行榜
云数据库 Tair(兼容Redis),内存型 2GB
简介: RedisTemplate 实现基于 Value 操作的简易锁机制

在高并发场景下,确保操作的原子性和避免竞态条件至关重要。Redis 提供了丰富的数据结构和操作,是实现分布式锁的一个高效选择。本文将介绍如何使用 RedisTemplateopsForValue().setIfAbsent() 方法来实现一种简单的锁机制,并提供一个示例代码,展示如何在 Java 应用中利用这一机制来保护共享资源的访问。


简介

RedisTemplate.opsForValue().setIfAbsent(key, value, timeout, timeUnit) 方法能够原子性地设置一个 key-value 对,仅当该 key 不存在时才执行设置操作。这个特性非常适合用来实现锁:尝试设置一个锁标识(key),如果设置成功(即之前没有这个锁),则认为获取锁成功;如果设置失败(即锁已被其他线程占有),则获取锁失败。同时,通过设置超时时间,可以避免死锁问题。


实现原理

  1. 锁标识:选择一个唯一的 key 作为锁的标识,通常包含请求的唯一信息,如方法名或参数的 hash 值。
  2. 锁超时:通过设置 key 的过期时间来自动释放锁,防止因异常情况导致锁无法被正常释放。
  3. 原子操作setIfAbsent 方法保证了“设置”操作的原子性,这是实现锁的关键。

示例代码

下面是一个使用 Spring Data RedisRedisTemplate 实现基于 Value 操作的锁机制的简单示例:

import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.ValueOperations;
import org.springframework.stereotype.Component;

import java.util.concurrent.TimeUnit;

@Component
public class DistributedLockService {

    private final RedisTemplate<String, String> redisTemplate;

    public DistributedLockService(RedisTemplate<String, String> redisTemplate) {
        this.redisTemplate = redisTemplate;
    }

    /**
     * 尝试获取锁。
     * @param lockKey 锁的key
     * @param requestId 请求标识,用于解锁时验证
     * @param expireTime 超时时间,单位秒
     * @return 是否获取锁成功
     */
    public boolean tryLock(String lockKey, String requestId, long expireTime) {
        ValueOperations<String, String> operations = redisTemplate.opsForValue();
        Boolean isLockSuccess = operations.setIfAbsent(lockKey, requestId, expireTime, TimeUnit.SECONDS);
        return Boolean.TRUE.equals(isLockSuccess);
    }

    /**
     * 释放锁。
     * @param lockKey 锁的key
     * @param requestId 请求标识,需与加锁时一致
     * @return 是否释放锁成功
     */
    public boolean releaseLock(String lockKey, String requestId) {
        String currentValue = redisTemplate.opsForValue().get(lockKey);
        if (requestId.equals(currentValue)) {
            redisTemplate.delete(lockKey);
            return true;
        }
        return false;
    }

    // 示例使用
    public void doSomethingUnderLock(String lockKey) {
        String requestId = UUID.randomUUID().toString(); // 生成唯一请求ID
        if (tryLock(lockKey, requestId, 5)) { // 尝试获取锁,超时5秒
            try {
                // 执行受保护的代码逻辑
                System.out.println("执行业务逻辑...");
            } finally {
                // 无论是否执行成功都尝试释放锁
                releaseLock(lockKey, requestId);
            }
        } else {
            System.out.println("获取锁失败,操作被跳过。");
        }
    }
}


注意事项

  • 锁的有效时间:设置合适的锁过期时间非常重要,过长可能导致资源被锁定时间过久,影响系统响应;过短可能导致操作还未完成锁就被自动释放。
  • 锁的公平性:上述示例的锁实现是非公平的,即先请求的客户端不一定能先获得锁。在某些场景下,可能需要实现公平锁机制。
  • 异常处理:确保在所有可能的退出路径中都能释放锁,避免死锁。
  • 重入问题:上述示例不支持锁的重入,即同一个线程在未释放锁的情况下再次请求同一把锁会失败。对于需要重入锁的场景,需要额外的逻辑来跟踪锁的持有状态。

通过上述方式,我们可以有效地利用 Redis 和 RedisTemplate 来实现一个简单而有效的分布式锁机制,保护我们的关键操作免受并发访问的影响。

相关文章
|
缓存 负载均衡 前端开发
Nginx极简入门(九)Nginx实现动静分离!
前面介绍了Nginx的负载均衡,一般来说,都需要将动态资源和静态资源分开,这样可以很大程度的提升静态资源的访问速度,同时在开过程中也可以让前后端开发并行可以有效的提高开发时间,也可以有些的减少联调时间 。接下来介绍什么是动静分离以及如何使用Nginx实现动静分离。
Nginx极简入门(九)Nginx实现动静分离!
|
SQL 监控 Java
Springboot整合p6spy实现sql监控
Springboot整合p6spy实现sql监控
700 0
|
缓存 NoSQL Java
RedisTemplate操作Redis,这一篇文章就够了
redis是一款开源的Key-Value数据库,运行在内存中,由C语言编写。企业开发通常采用Redis来实现缓存。同类的产品还有memcache 、memcached 等。
2832 1
|
存储 缓存 NoSQL
redis中的分布式锁(setIfAbsent)(expire)
redis中的分布式锁(setIfAbsent)(expire)
|
安全 Java 开发者
深入解析ReentrantLock重入锁:Java多线程中的利器
深入解析ReentrantLock重入锁:Java多线程中的利器
2616 4
|
NoSQL Redis
RedisTemplate.opsForValue()用法简介并举例
RedisTemplate.opsForValue()用法简介并举例
1694 1
|
小程序 JavaScript 安全
微信小程序实现云闪付支付(代码和注释很详细)
微信小程序实现云闪付支付(代码和注释很详细)
|
关系型数据库 MySQL 数据库
MySQL数据库——多表查询(1)-多表关系(一对多、多对对多、一对一)、多表查询概述(概念、笛卡尔积、分类)
MySQL数据库——多表查询(1)-多表关系(一对多、多对对多、一对一)、多表查询概述(概念、笛卡尔积、分类)
291 0
|
存储 安全 Java
数据库轻松切换:解读Spring中的AbstractRoutingDataSource
数据库轻松切换:解读Spring中的AbstractRoutingDataSource
761 0
|
负载均衡 前端开发 Java
Feign 踩坑指南 (接口返回泛型设置属性为null)
Feign 简介 Feign 的英文表意为“假装,伪装,变形”, 是一个http请求调用的轻量级框架,可以以Java接口注解的方式调用Http请求,而不用像Java中通过封装HTTP请求报文的方式直接调用。Feign通过处理注解,将请求模板化,当实际调用的时候,传入参数,根据参数再应用到请求上,进而转化成真正的请求,这种请求相对而言比较直观。
2461 0
Feign 踩坑指南 (接口返回泛型设置属性为null)