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

摘要:本文将介绍如何使用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分布式锁,我们可以确保在高并发环境下,某个接口只有一个线程能够执行。这对于保证数据的一致性和完整性非常重要。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

秋分的秋刀鱼

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值