首页 > 数据库 >高并发下使用Redis分布式锁确保接口执行唯一性【重点】

高并发下使用Redis分布式锁确保接口执行唯一性【重点】

时间:2024-06-07 15:33:00浏览次数:14  
标签:唯一性 Redis lockKey expireTime 线程 public 分布式

摘要:本文将介绍如何使用Redis分布式锁来确保在高并发环境下,确保某个接口只有一个线程能够执行。通过使用RedisSETNX命令,我们可以实现一个简单的分布式锁,从而避免多个线程同时访问共享资源。

一、背景

在高并发的系统中,为了保证数据的一致性和完整性,我们经常需要对某些接口进行互斥访问。例如,在电商系统中,当用户下单时,我们需要确保同一时间只有一个用户能够完成支付操作。为了实现这一目标,我们可以使用分布式锁。Redis作为一种高性能的内存数据库,非常适合用于实现分布式锁。

二、Redis分布式锁的原理

Redis分布式锁的原理是:在Redis中设置一个特定的键值对,当多个线程同时尝试获取锁时,只有一个线程能够成功设置该键值对,其他线程则无法设置成功。这样,我们就可以确保同一时间只有一个线程能够执行被锁住的接口。

三、实现Redis分布式锁的步骤

① 使用SETNX命令尝试设置锁。SETNX命令在设置键值对时,只有当键不存在时才能设置成功。如果键已经存在,则设置失败。这样,我们就可以确保同一时间只有一个线程能够成功设置锁。
② 如果设置锁成功,则执行被锁住的接口。在执行完接口后,需要释放锁,以便其他线程可以获取锁并执行接口。释放锁的方法是删除键值对。
③ 如果设置锁失败,则说明已经有其他线程正在执行被锁住的接口。此时,线程可以选择等待一段时间后再次尝试获取锁,或者直接放弃执行。

四、示例代码

4. RedisUtils工具类
public class RedisUtils {
    private final static Logger LOGGER = LoggerFactory.getLogger(RedisUtils.class);

    /**
     * 默认redis过期时间3s
     */
    private static final long expireTime = 3000L;

    private static RedisTemplate redisTemplate;

    /**
     * 加锁,自定义过期时间
     */
    public static boolean lock(final String key, Long expireTime) {
        if (key == null) {
            return false;
        }
        String value = GeneralUtils.generateUUID();
        Boolean flag = redisTemplate.opsForValue().setIfAbsent(key, value, expireTime, TimeUnit.MILLISECONDS);

        if (Boolean.TRUE.equals(flag)) {
            return true;
        } else {
            throw new BusinessException("业务繁忙,请稍后重试");
        }
    }

    /**
     * 释放锁
     */
    public static boolean unlock(final String key) {
        if (key == null) {
            return false;
        }
        return Boolean.TRUE.equals(redisTemplate.delete(key));
    }
}
4.2 自定义RedisLock注解
/**
 * 本注解基于RedisUtils封装
 * expireTime 过期时间 如果注解中不赋值,则使用默认值,单位毫秒
 * lockKey 用于指定被注解方法参数中做为锁的KEY的参数名称。如果不指定,则使用类名+方法名做为KEY
 */
@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface RedisLock {

    long expireTime() default 30000;

    String lockKey() default "";
}
4.3 RedisLockAspect切面类
@Aspect
@Component
@Slf4j
public class RedisLockAspect {

    @Pointcut("@annotation(luoxs.common.annotation.RedisLock)")
    private void anyMethod() {
    }

    @Around("anyMethod()")
    public Object around(ProceedingJoinPoint pjp) throws Throwable {
        Object result;
        Method method = ((MethodSignature) pjp.getSignature()).getMethod();

        RedisLock redisLock = method.getAnnotation(RedisLock.class);
        if (redisLock == null) {
            Method realMethod = pjp.getTarget().getClass().getDeclaredMethod(method.getName(), method.getParameterTypes());
            redisLock = realMethod.getAnnotation(RedisLock.class);
        }

        String lockKey = redisLock.lockKey();
        long expireTime = redisLock.expireTime();
        if (ObjectUtil.isEmpty(lockKey)) {
            //如果未设置key字段则使用类名+方法名做为KEY
            lockKey = method.getDeclaringClass().getName() + method.getName();
        }
        try {
            //加锁
            RedisUtils.lock(lockKey, expireTime);
            //执行
            result = pjp.proceed();
        } catch (Exception e) {
            log.error(e.getMessage(),e);
            throw e;
        } finally {
            RedisUtils.unlock(lockKey);
        }
        return result;
    }
}

4.4 新建一个TestController进行测试
@RestController
@RequestMapping("/test")
public class TestController{
	
	@RedisLock(lockKey = "testRedisLock")
    @GetMapping("/testRedisLock")
    @ApiOperation("Redis分布式锁确保接口唯一性测试")
    public BaseResponse<Void> testRedisLock() throws InterruptedException{
    	//方便测试睡30秒
    	Thread.sleep(30000);
        return BaseResponse.ok();
    }
}
4.5 测试结果

个人比较喜欢用Postman进行测试:第一个线程调用之后,紧接着启用另一个线程进行调用
第二个线程返回了我们想要的信息,如下图所示:
在这里插入图片描述
第一个线程开始调用后30秒返回了执行的结果,如下图所示:
在这里插入图片描述

五、总结

通过使用Redis分布式锁,我们可以确保在高并发环境下,某个接口只有一个线程能够执行。这对于保证数据的一致性和完整性非常重要。

标签:唯一性,Redis,lockKey,expireTime,线程,public,分布式
From: https://blog.csdn.net/weixin_42039228/article/details/139489368

相关文章

  • 谈谈Redis缓存中MySQL的数据如何与Redis同步
    在现代应用程序中,性能和响应速度是至关重要的。为了提高数据访问速度,常常会使用缓存技术。Redis作为一种高性能的内存数据库,常被用作缓存层,而MySQL则作为持久化存储层。如何有效地将MySQL数据与Redis缓存进行同步,是一个关键问题。本文将详细探讨Redis作为缓存时,http://ww......
  • 【百万字详解Redis】集群部署
    文章目录Redis集群部署......
  • 在微服务架构模式中Redis的应用
    1. Redis概述                Redis是一种开源的内存数据库,也被称为数据结构服务器,它支持多种数据类型,如字符串、哈希表、列表、集合和有序集合。Redis具有快速、高效的特点,可以在内存中存储数据,并通过持久化机制将数据写入磁盘,保证数据的持久性。Redis还提......
  • Redis(事务、持久化、高可用 、高可扩、过期删除、内存淘汰)说明、分析、区别
    Redis高级理解Redis事务机制掌握Redis持久化机制理解Redis高可用—主从复制、哨兵模式理解Redis高可扩—RedisCluster数据分片掌握Redis过期删除策略掌握Redis内存淘汰策略1事务机制1.1场景分析以关注为例,在B站上程序员关注了A,同时A也关注了程序员,那么......
  • 巧用docker+jmeter快速实现分布式百万级并发
    分享背景碰到的问题:一个JMeter实例可能无法产生足够的负载来对你的应用程序进行压力测试~解决办法:1、修改jmeter配置文件里的内存堆2、引入jmeter分布式压测带来的问题:如果我们要做分布式负载测试–我们需要1个主机和N个从机来产生巨大的负载。每台JMeter从机都需要......
  • 浅谈Redis的三种集群策略及应用场景
    本文分享自天翼云开发者社区《浅谈Redis的三种集群策略及应用场景》,作者:段林Redis提供了三种集群策略:1.主从模式:这种模式⽐较简单,主库可以读写,并且会和从库进⾏数据同步,这种模式下,客户端直接连主库或某个从库,但是但主库或从库宕机后,客户端需要⼿动修改IP,另外,这种模式也⽐较难进......
  • 数据太多时(百万条数据),查询采用Quartz + redis方式比较快
    Quartz+redisQuartz的简介:quartz是一款开源且丰富特性的**“任务调度库”,能够集成与任何的java**应用,下到独立应用,大到电子商业系统。quartz就是基于java实现的任务调度框架,用于执行你想要执行的任何任务。1.什么是任务调度?任务调度就是我们系统中创建了N个任务,每个任......
  • Redis之事务
    事务mysql的事务mysql的事务满足ACID:原子性、一致性、隔离性和持久性。redis的事务事务是指一个完整的动作,要么全部执行,要么什么也没有做。Redis事务不是严格意义上的事务,只是用于帮助用户在一个步骤中执行多个命令。单个Redis命令的执行是原子性的,但Redis没有......
  • redis性能测试
    环境redis7.2.5主频核心数内存2.5GHz3264GB测试结论当前场景下redis单线程、多线程表现差异不大使用pipeline模式可以显著提高基准性能非pipilie下redis性能再12~13w左右pipiline下redis性能在35w左右测试记录单线程redis基准测试(只测试s......
  • hive-3.1.2分布式搭建
    hive-3.1.2分布式搭建文档谷歌浏览器下载网址:GoogleChrome–Downloadthefast,securebrowserfromGoogle华为云镜像站:https://mirrors.huaweicloud.com/home1、上传解压配置环境变量#1、解压tar-zxvfapache-hive-3.1.2-bin.tar.gz-C/usr/local/s......